Remove non-batch async contact/host deletion actions

They have been superseded by DeleteContactsAndHostsAction.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=134424453
This commit is contained in:
mcilwain 2016-09-27 10:38:38 -07:00 committed by Ben McIlwain
parent 6c610d49fe
commit 1dcc5e5cc6
13 changed files with 2 additions and 1150 deletions

View file

@ -219,11 +219,6 @@ public final class ProductionRegistryConfigExample implements RegistryConfig {
return "TheRegistrar"; return "TheRegistrar";
} }
@Override
public Duration getAsyncDeleteFlowMapreduceDelay() {
return Duration.standardSeconds(90);
}
@Override @Override
public Duration getAsyncFlowFailureBackoff() { public Duration getAsyncFlowFailureBackoff() {
return Duration.standardMinutes(10); return Duration.standardMinutes(10);

View file

@ -211,27 +211,6 @@ public interface RegistryConfig {
*/ */
public String getCheckApiServletRegistrarClientId(); public String getCheckApiServletRegistrarClientId();
/**
* Returns the delay before executing async delete flow mapreduces.
*
* <p>This delay should be sufficiently longer than a transaction, to solve the following problem:
* <ul>
* <li>a domain mutation flow starts a transaction
* <li>the domain flow non-transactionally reads a resource and sees that it's not in
* PENDING_DELETE
* <li>the domain flow creates a new reference to this resource
* <li>a contact/host delete flow runs and marks the resource PENDING_DELETE and commits
* <li>the domain flow commits
* </ul>
*
* <p>Although we try not to add references to a PENDING_DELETE resource, strictly speaking that
* is ok as long as the mapreduce eventually sees the new reference (and therefore asynchronously
* fails the delete). Without this delay, the mapreduce might have started before the domain flow
* committed, and could potentially miss the reference.
*/
// TODO(b/26140521): Remove this configuration option along with non-batched async operations.
public Duration getAsyncDeleteFlowMapreduceDelay();
/** /**
* Returns the amount of time to back off following an async flow task failure. * Returns the amount of time to back off following an async flow task failure.
* *

View file

@ -167,11 +167,6 @@ public class TestRegistryConfig implements RegistryConfig {
return "TheRegistrar"; return "TheRegistrar";
} }
@Override
public Duration getAsyncDeleteFlowMapreduceDelay() {
return Duration.standardSeconds(90);
}
@Override @Override
public Duration getAsyncFlowFailureBackoff() { public Duration getAsyncFlowFailureBackoff() {
return Duration.standardMinutes(10); return Duration.standardMinutes(10);

View file

@ -246,20 +246,6 @@
<url-pattern>/_dr/task/deleteProberData</url-pattern> <url-pattern>/_dr/task/deleteProberData</url-pattern>
</servlet-mapping> </servlet-mapping>
<!-- Deletes the specified contact resource if it is not referenced by any domains. -->
<!-- TODO(b/26140521): Delete this mapping once non-batched async operations are deleted. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/deleteContactResource</url-pattern>
</servlet-mapping>
<!-- Deletes the specified host resource if it is not referenced by any domains. -->
<!-- TODO(b/26140521): Delete this mapping once non-batched async operations are deleted. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/deleteHostResource</url-pattern>
</servlet-mapping>
<!-- <!--
Deletes contacts and hosts enqueued for asynchronous deletion if they are Deletes contacts and hosts enqueued for asynchronous deletion if they are
not referenced by any domain. not referenced by any domain.

View file

@ -14,11 +14,7 @@
package google.registry.flows.async; package google.registry.flows.async;
import static google.registry.flows.async.DeleteContactsAndHostsAction.QUEUE_ASYNC_DELETE; import static google.registry.flows.async.DeleteContactsAndHostsAction.QUEUE_ASYNC_DELETE;
import static google.registry.flows.async.DeleteEppResourceAction.PARAM_IS_SUPERUSER;
import static google.registry.flows.async.DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID;
import static google.registry.flows.async.DeleteEppResourceAction.PARAM_RESOURCE_KEY;
import static google.registry.flows.async.DnsRefreshForHostRenameAction.PARAM_HOST_KEY; import static google.registry.flows.async.DnsRefreshForHostRenameAction.PARAM_HOST_KEY;
import static google.registry.request.RequestParameters.extractBooleanParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter; import static google.registry.request.RequestParameters.extractRequiredParameter;
import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.Queue;
@ -39,27 +35,7 @@ public final class AsyncFlowsModule {
return QueueFactory.getQueue(QUEUE_ASYNC_DELETE); return QueueFactory.getQueue(QUEUE_ASYNC_DELETE);
} }
@Provides //TODO(b/26140521): Delete this method once non-batched DNS host refresh mapreduce is deleted.
@Parameter(PARAM_IS_SUPERUSER)
//TODO(b/26140521): Delete this method once non-batched async operations are deleted.
static boolean provideIsSuperuser(HttpServletRequest req) {
return extractBooleanParameter(req, PARAM_IS_SUPERUSER);
}
@Provides
@Parameter(PARAM_REQUESTING_CLIENT_ID)
//TODO(b/26140521): Delete this method once non-batched async operations are deleted.
static String provideRequestingClientId(HttpServletRequest req) {
return extractRequiredParameter(req, PARAM_REQUESTING_CLIENT_ID);
}
@Provides
@Parameter(PARAM_RESOURCE_KEY)
//TODO(b/26140521): Delete this method once non-batched async operations are deleted.
static String provideResourceKey(HttpServletRequest req) {
return extractRequiredParameter(req, PARAM_RESOURCE_KEY);
}
@Provides @Provides
@Parameter(PARAM_HOST_KEY) @Parameter(PARAM_HOST_KEY)
static String provideHostKey(HttpServletRequest req) { static String provideHostKey(HttpServletRequest req) {

View file

@ -1,81 +0,0 @@
// Copyright 2016 The Domain Registry 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.flows.async;
import static google.registry.flows.ResourceFlowUtils.handlePendingTransferOnDelete;
import com.googlecode.objectify.Key;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.Type;
import google.registry.request.Action;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* A mapreduce to delete the specified ContactResource, but ONLY if it is not referred to by any
* existing DomainBase entity.
*/
@Action(path = "/_dr/task/deleteContactResource")
// TODO(b/26140521): Delete this class once non-batched async operations are deleted.
public class DeleteContactResourceAction extends DeleteEppResourceAction<ContactResource> {
@Inject
public DeleteContactResourceAction() {
super(
new DeleteContactResourceMapper(),
new DeleteContactResourceReducer());
}
/** An async deletion mapper for {@link ContactResource}. */
public static class DeleteContactResourceMapper extends DeleteEppResourceMapper<ContactResource> {
private static final long serialVersionUID = -5904009575877950342L;
@Override
protected boolean isLinked(
DomainBase domain, Key<ContactResource> targetResourceKey) {
return domain.getReferencedContacts().contains(targetResourceKey);
}
}
/** An async deletion reducer for {@link ContactResource}. */
public static class DeleteContactResourceReducer
extends DeleteEppResourceReducer<ContactResource> {
private static final long serialVersionUID = -7633644054441045215L;
@Override
protected Type getHistoryType(boolean successfulDelete) {
return successfulDelete
? HistoryEntry.Type.CONTACT_DELETE
: HistoryEntry.Type.CONTACT_DELETE_FAILURE;
}
@Override
protected void performDeleteTasks(
ContactResource targetResource,
ContactResource deletedResource,
DateTime deletionTime,
HistoryEntry historyEntryForDelete) {
handlePendingTransferOnDelete(
targetResource,
deletedResource,
deletionTime,
historyEntryForDelete);
}
}
}

View file

@ -1,264 +0,0 @@
// Copyright 2016 The Domain Registry 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.flows.async;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.flows.ResourceFlowUtils.prepareDeletedResourceAsBuilder;
import static google.registry.flows.ResourceFlowUtils.updateForeignKeyIndexDeletionTime;
import static google.registry.model.EppResourceUtils.isActive;
import static google.registry.model.EppResourceUtils.isDeleted;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.PipelineUtils.createJobPath;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.appengine.tools.mapreduce.Reducer;
import com.google.appengine.tools.mapreduce.ReducerInput;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Work;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.mapreduce.inputs.EppResourceInputs;
import google.registry.mapreduce.inputs.NullInput;
import google.registry.model.EppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.util.Clock;
import google.registry.util.FormattingLogger;
import google.registry.util.TypeUtils.TypeInstantiator;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* A mapreduce to delete the specified EPP resource, but ONLY if it is not referred to by any
* existing DomainBase entity.
*/
// TODO(b/26140521): Delete this class once non-batched async operations are deleted.
public abstract class DeleteEppResourceAction<T extends EppResource> implements Runnable {
/** The HTTP parameter name used to specify the websafe key of the resource to delete. */
public static final String PARAM_RESOURCE_KEY = "resourceKey";
public static final String PARAM_REQUESTING_CLIENT_ID = "requestingClientId";
public static final String PARAM_IS_SUPERUSER = "superuser";
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
@Inject @Parameter(PARAM_RESOURCE_KEY) String resourceKeyString;
@Inject @Parameter(PARAM_REQUESTING_CLIENT_ID) String requestingClientId;
@Inject @Parameter(PARAM_IS_SUPERUSER) boolean isSuperuser;
@Inject Clock clock;
@Inject MapreduceRunner mrRunner;
@Inject Response response;
DeleteEppResourceMapper<T> mapper;
DeleteEppResourceReducer<T> reducer;
protected DeleteEppResourceAction(
DeleteEppResourceMapper<T> mapper,
DeleteEppResourceReducer<T> reducer) {
this.mapper = mapper;
this.reducer = reducer;
}
@Override
public void run() {
Key<T> resourceKey = null;
T resource;
try {
resourceKey = Key.create(resourceKeyString);
resource = checkArgumentNotNull(ofy().load().key(resourceKey).now());
} catch (IllegalArgumentException e) {
throw new BadRequestException(resourceKey == null
? "Could not parse key string: " + resourceKeyString
: "Could not load resource for key: " + resourceKey);
}
checkArgument(
resource.getClass().equals(new TypeInstantiator<T>(getClass()){}.getExactType()),
String.format("Cannot delete a %s via this action.", resource.getClass().getSimpleName()));
checkState(
!isDeleted(resource, clock.nowUtc()),
"Resource %s is already deleted.", resource.getForeignKey());
checkState(
resource.getStatusValues().contains(StatusValue.PENDING_DELETE),
"Resource %s is not set as PENDING_DELETE", resource.getForeignKey());
mapper.setTargetResource(resourceKey);
reducer.setClient(requestingClientId, isSuperuser);
logger.infofmt("Executing Delete EPP resource mapreduce for %s", resourceKey);
try {
response.sendJavaScriptRedirect(createJobPath(mrRunner
.setJobName("Check for EPP resource references and then delete")
.setModuleName("backend")
.runMapreduce(
mapper,
reducer,
ImmutableList.of(
// Add an extra shard that maps over a null domain. See the mapper code for why.
new NullInput<DomainBase>(),
EppResourceInputs.createEntityInput(DomainBase.class)))));
} catch (Throwable t) {
logger.severefmt(
t, "Error while kicking off DeleteEppResource MR for %s", resource.getForeignKey());
}
}
/**
* 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 abstract static class DeleteEppResourceMapper<T extends EppResource>
extends Mapper<DomainBase, Key<T>, Boolean> {
private static final long serialVersionUID = -7355145176854995813L;
private DateTime targetResourceUpdateTimestamp;
private Key<T> targetEppResourceKey;
private void setTargetResource(Key<T> targetEppResourceKey) {
this.targetEppResourceKey = targetEppResourceKey;
this.targetResourceUpdateTimestamp =
ofy().load().key(targetEppResourceKey).now().getUpdateAutoTimestamp().getTimestamp();
}
/** Determine whether the target resource is a linked resource on the domain. */
protected abstract boolean isLinked(DomainBase domain, Key<T> targetResourceKey);
@Override
public void map(DomainBase domain) {
// The reducer only runs if at least one value is emitted. We add a null input to the
// mapreduce and always emit 'false' for it to force the reducer to run. We can then emit
// 'true' for linked domains and not emit anything for unlinked domains, which speeds up the
// reducer since it will only receive true keys, of which there will be few (usually none).
if (domain == null) {
emit(targetEppResourceKey, false);
return;
}
if (isActive(domain, targetResourceUpdateTimestamp)
&& isLinked(domain, targetEppResourceKey)) {
emit(targetEppResourceKey, true);
}
}
}
/**
* 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 abstract static class DeleteEppResourceReducer<T extends EppResource>
extends Reducer<Key<T>, Boolean, Void> {
private static final long serialVersionUID = 875017002097945151L;
private String requestingClientId;
private boolean isSuperuser;
private void setClient(String requestingClientId, boolean isSuperuser) {
this.requestingClientId = requestingClientId;
this.isSuperuser = isSuperuser;
}
/**
* Determine the proper history entry type for the delete operation, as a function of
* whether or not the delete was successful.
*/
protected abstract HistoryEntry.Type getHistoryType(boolean successfulDelete);
/** Perform any type-specific tasks on the resource to be deleted (and/or its dependencies). */
protected abstract void performDeleteTasks(
T targetResource,
T deletedResource,
DateTime deletionTime,
HistoryEntry historyEntryForDelete);
@Override
public void reduce(final Key<T> key, final ReducerInput<Boolean> values) {
final boolean hasNoActiveReferences = !Iterators.contains(values, true);
logger.infofmt("Processing delete request for %s", key.toString());
String pollMessageText = ofy().transactNew(new Work<String>() {
@Override
@SuppressWarnings("unchecked")
public String run() {
DateTime now = ofy().getTransactionTime();
T targetResource = (T) ofy().load().key(key).now().cloneProjectedAtTime(now);
String resourceName = targetResource.getForeignKey();
// Double-check that the resource is still active and in PENDING_DELETE within the
// transaction.
checkState(
!isDeleted(targetResource, now),
"Resource %s is already deleted.", resourceName);
checkState(
targetResource.getStatusValues().contains(StatusValue.PENDING_DELETE),
"Resource %s is not in PENDING_DELETE.", resourceName);
targetResource = (T) targetResource.asBuilder()
.removeStatusValue(StatusValue.PENDING_DELETE)
.build();
boolean requestedByCurrentOwner =
targetResource.getCurrentSponsorClientId().equals(requestingClientId);
boolean deleteAllowed = hasNoActiveReferences && (requestedByCurrentOwner || isSuperuser);
String resourceTypeName =
targetResource.getClass().getAnnotation(ExternalMessagingName.class).value();
HistoryEntry.Type historyType = getHistoryType(deleteAllowed);
String pollMessageText = deleteAllowed
? String.format("Deleted %s %s.", resourceTypeName, resourceName)
: String.format(
"Can't delete %s %s because %s.",
resourceTypeName,
resourceName,
requestedByCurrentOwner
? "it is referenced by a domain"
: "it was transferred prior to deletion");
HistoryEntry historyEntry = new HistoryEntry.Builder()
.setClientId(requestingClientId)
.setModificationTime(now)
.setType(historyType)
.setParent(key)
.build();
PollMessage.OneTime deleteResultMessage = new PollMessage.OneTime.Builder()
.setClientId(requestingClientId)
.setMsg(pollMessageText)
.setParent(historyEntry)
.setEventTime(now)
.build();
if (deleteAllowed) {
T deletedResource = prepareDeletedResourceAsBuilder(targetResource, now).build();
performDeleteTasks(targetResource, deletedResource, now, historyEntry);
updateForeignKeyIndexDeletionTime(deletedResource);
ofy().save().<Object>entities(deletedResource, historyEntry, deleteResultMessage);
} else {
ofy().save().<Object>entities(targetResource, historyEntry, deleteResultMessage);
}
return pollMessageText;
}
});
logger.infofmt(pollMessageText);
}
}
}

View file

@ -1,82 +0,0 @@
// Copyright 2016 The Domain Registry 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.flows.async;
import static google.registry.model.ofy.ObjectifyService.ofy;
import com.googlecode.objectify.Key;
import google.registry.dns.DnsQueue;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.Type;
import google.registry.request.Action;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* A mapreduce to delete the specified HostResource, but ONLY if it is not referred to by any
* existing DomainBase entity.
*/
@Action(path = "/_dr/task/deleteHostResource")
// TODO(b/26140521): Delete this class once non-batched async operations are deleted.
public class DeleteHostResourceAction extends DeleteEppResourceAction<HostResource> {
@Inject
public DeleteHostResourceAction() {
super(
new DeleteHostResourceMapper(),
new DeleteHostResourceReducer());
}
/** An async deletion mapper for {@link HostResource}. */
public static class DeleteHostResourceMapper extends DeleteEppResourceMapper<HostResource> {
private static final long serialVersionUID = 1941092742903217194L;
@Override
protected boolean isLinked(DomainBase domain, Key<HostResource> targetResourceKey) {
return domain.getNameservers().contains(targetResourceKey);
}
}
/** An async deletion reducer for {@link HostResource}. */
public static class DeleteHostResourceReducer extends DeleteEppResourceReducer<HostResource> {
private static final long serialVersionUID = 555457935288867324L;
@Override
protected Type getHistoryType(boolean successfulDelete) {
return successfulDelete
? HistoryEntry.Type.HOST_DELETE
: HistoryEntry.Type.HOST_DELETE_FAILURE;
}
@Override
protected void performDeleteTasks(
HostResource targetResource,
HostResource deletedResource,
DateTime deletionTime,
HistoryEntry historyEntryForDelete) {
if (targetResource.getSuperordinateDomain() != null) {
DnsQueue.create().addHostRefreshTask(targetResource.getFullyQualifiedHostName());
ofy().save().entity(
ofy().load().key(targetResource.getSuperordinateDomain()).now().asBuilder()
.removeSubordinateHost(targetResource.getFullyQualifiedHostName())
.build());
}
}
}
}

View file

@ -39,6 +39,7 @@ import org.joda.time.DateTime;
/** /**
* Enqueues DNS refreshes for applicable domains following a host rename. * Enqueues DNS refreshes for applicable domains following a host rename.
*/ */
//TODO(b/26140521): Delete this once non-batched DNS host refresh mapreduce is deleted.
@Action(path = "/_dr/task/dnsRefreshForHostRename") @Action(path = "/_dr/task/dnsRefreshForHostRename")
public class DnsRefreshForHostRenameAction implements Runnable { public class DnsRefreshForHostRenameAction implements Runnable {

View file

@ -42,9 +42,7 @@ import google.registry.export.UpdateSnapshotViewAction;
import google.registry.export.sheet.SheetModule; import google.registry.export.sheet.SheetModule;
import google.registry.export.sheet.SyncRegistrarsSheetAction; import google.registry.export.sheet.SyncRegistrarsSheetAction;
import google.registry.flows.async.AsyncFlowsModule; import google.registry.flows.async.AsyncFlowsModule;
import google.registry.flows.async.DeleteContactResourceAction;
import google.registry.flows.async.DeleteContactsAndHostsAction; import google.registry.flows.async.DeleteContactsAndHostsAction;
import google.registry.flows.async.DeleteHostResourceAction;
import google.registry.flows.async.DnsRefreshForHostRenameAction; import google.registry.flows.async.DnsRefreshForHostRenameAction;
import google.registry.mapreduce.MapreduceModule; import google.registry.mapreduce.MapreduceModule;
import google.registry.monitoring.whitebox.MetricsExportAction; import google.registry.monitoring.whitebox.MetricsExportAction;
@ -91,10 +89,7 @@ interface BackendRequestComponent {
BrdaCopyAction brdaCopyAction(); BrdaCopyAction brdaCopyAction();
CommitLogCheckpointAction commitLogCheckpointAction(); CommitLogCheckpointAction commitLogCheckpointAction();
CommitLogFanoutAction commitLogFanoutAction(); CommitLogFanoutAction commitLogFanoutAction();
// TODO(b/26140521): Remove separate contact/host actions here.
DeleteContactResourceAction deleteContactResourceAction();
DeleteContactsAndHostsAction deleteContactsAndHostsAction(); DeleteContactsAndHostsAction deleteContactsAndHostsAction();
DeleteHostResourceAction deleteHostResourceAction();
DeleteOldCommitLogsAction deleteOldCommitLogsAction(); DeleteOldCommitLogsAction deleteOldCommitLogsAction();
DeleteProberDataAction deleteProberDataAction(); DeleteProberDataAction deleteProberDataAction();
DnsRefreshForHostRenameAction dnsRefreshForHostRenameAction(); DnsRefreshForHostRenameAction dnsRefreshForHostRenameAction();

View file

@ -1,288 +0,0 @@
// Copyright 2016 The Domain Registry 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.flows.async;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
import static google.registry.testing.DatastoreHelper.assertNoBillingEvents;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.getPollMessages;
import static google.registry.testing.DatastoreHelper.newContactResource;
import static google.registry.testing.DatastoreHelper.persistActiveContact;
import static google.registry.testing.DatastoreHelper.persistContactWithPendingTransfer;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.googlecode.objectify.Key;
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.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.poll.PendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.request.HttpException.BadRequestException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link DeleteContactResourceAction}. */
@RunWith(JUnit4.class)
public class DeleteContactResourceActionTest
extends DeleteEppResourceActionTestCase<DeleteContactResourceAction> {
ContactResource contactUnused;
@Before
public void setup() throws Exception {
setupDeleteEppResourceAction(new DeleteContactResourceAction());
contactUnused = persistActiveContact("blah1235");
}
@Test
public void testSuccess_contact_referencedByActiveDomain_doesNotGetDeleted() throws Exception {
contactUsed = persistResource(
contactUsed.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build());
runMapreduceWithKeyParam(Key.create(contactUsed).getString());
contactUsed = loadByForeignKey(ContactResource.class, "blah1234", now);
assertAboutContacts().that(contactUsed).doesNotHaveStatusValue(StatusValue.PENDING_DELETE)
.and().hasDeletionTime(END_OF_TIME);
domain = loadByForeignKey(DomainResource.class, "example.tld", now);
assertThat(domain.getReferencedContacts()).contains(Key.create(contactUsed));
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(contactUsed, HistoryEntry.Type.CONTACT_DELETE_FAILURE);
assertPollMessageFor(
historyEntry,
"TheRegistrar",
"Can't delete contact blah1234 because it is referenced by a domain.");
}
@Test
public void testSuccess_contact_notReferenced_getsDeleted() throws Exception {
contactUnused = persistResource(
contactUnused.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(StatusValue.PENDING_DELETE)
.build());
assertAboutContacts().that(contactUnused).hasNonNullLocalizedPostalInfo()
.and().hasNonNullInternationalizedPostalInfo()
.and().hasNonNullEmailAddress()
.and().hasNonNullVoiceNumber()
.and().hasNonNullFaxNumber();
Key<ContactResource> key = Key.create(contactUnused);
runMapreduceWithKeyParam(key.getString());
assertThat(loadByForeignKey(ContactResource.class, "blah1235", now)).isNull();
ContactResource contactAfterDeletion = ofy().load().key(key).now();
assertAboutContacts().that(contactAfterDeletion).hasDeletionTime(now)
// 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(HistoryEntry.Type.CONTACT_DELETE);
assertAboutContacts().that(contactAfterDeletion).hasNullLocalizedPostalInfo()
.and().hasNullInternationalizedPostalInfo()
.and().hasNullEmailAddress()
.and().hasNullVoiceNumber()
.and().hasNullFaxNumber();
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(contactAfterDeletion, HistoryEntry.Type.CONTACT_DELETE);
assertPollMessageFor(historyEntry, "TheRegistrar", "Deleted contact blah1235.");
}
@Test
public void testSuccess_contactWithPendingTransfer_getsDeleted() throws Exception {
ContactResource contact = persistContactWithPendingTransfer(
persistActiveContact("sh8013").asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build(),
transferRequestTime,
transferExpirationTime,
clock.nowUtc());
runMapreduceWithKeyParam(Key.create(contact).getString());
// Check that the contact is deleted as of now.
assertThat(loadByForeignKey(ContactResource.class, "sh8013", now)).isNull();
// Check that it's still there (it wasn't deleted yesterday) and that it has history.
assertAboutContacts()
.that(loadByForeignKey(ContactResource.class, "sh8013", now.minusDays(1)))
.hasOneHistoryEntryEachOfTypes(
HistoryEntry.Type.CONTACT_TRANSFER_REQUEST,
HistoryEntry.Type.CONTACT_DELETE);
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()));
System.out.println(gainingPollMessage);
assertThat(gainingPollMessage.getEventTime()).isEqualTo(clock.nowUtc());
assertThat(
Iterables.getOnlyElement(FluentIterable.from(gainingPollMessage.getResponseData())
.filter(TransferResponse.class))
.getTransferStatus())
.isEqualTo(TransferStatus.SERVER_CANCELLED);
PendingActionNotificationResponse panData = Iterables.getOnlyElement(FluentIterable
.from(gainingPollMessage.getResponseData())
.filter(PendingActionNotificationResponse.class));
assertThat(panData.getTrid())
.isEqualTo(Trid.create("transferClient-trid", "transferServer-trid"));
assertThat(panData.getActionResult()).isFalse();
}
@Test
public void testSuccess_contact_referencedByDeleteDomain_getsDeleted() throws Exception {
contactUsed = persistResource(
contactUsed.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build());
domain = persistResource(
domain.asBuilder()
.setDeletionTime(now.minusDays(3))
.build());
runMapreduceWithKeyParam(Key.create(contactUsed).getString());
assertThat(loadByForeignKey(ContactResource.class, "blah1234", now)).isNull();
ContactResource contactBeforeDeletion =
loadByForeignKey(ContactResource.class, "blah1234", now.minusDays(1));
assertAboutContacts().that(contactBeforeDeletion).hasDeletionTime(now)
.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(HistoryEntry.Type.CONTACT_DELETE);
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(contactBeforeDeletion, HistoryEntry.Type.CONTACT_DELETE);
assertPollMessageFor(historyEntry, "TheRegistrar", "Deleted contact blah1234.");
}
@Test
public void testFailure_notPendingDelete() throws Exception {
thrown.expect(IllegalStateException.class, "Resource blah1235 is not set as PENDING_DELETE");
runMapreduceWithKeyParam(Key.create(contactUnused).getString());
assertThat(
loadByForeignKey(ContactResource.class, "blah1235", now)).isEqualTo(contactUnused);
}
@Test
public void testSuccess_notRequestedByOwner_doesNotGetDeleted() throws Exception {
contactUnused = persistResource(
contactUnused.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build());
Key<ContactResource> key = Key.create(contactUnused);
runMapreduceWithParams(key.getString(), "OtherRegistrar", false);
contactUnused = loadByForeignKey(ContactResource.class, "blah1235", now);
assertAboutContacts().that(contactUnused).doesNotHaveStatusValue(StatusValue.PENDING_DELETE)
.and().hasDeletionTime(END_OF_TIME);
domain = loadByForeignKey(DomainResource.class, "example.tld", now);
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(contactUnused, HistoryEntry.Type.CONTACT_DELETE_FAILURE);
assertPollMessageFor(
historyEntry,
"OtherRegistrar",
"Can't delete contact blah1235 because it was transferred prior to deletion.");
}
@Test
public void testSuccess_notRequestedByOwner_isSuperuser_getsDeleted() throws Exception {
contactUnused = persistResource(
contactUnused.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(StatusValue.PENDING_DELETE)
.build());
Key<ContactResource> key = Key.create(contactUnused);
runMapreduceWithParams(key.getString(), "OtherRegistrar", true);
assertThat(loadByForeignKey(ContactResource.class, "blah1235", now)).isNull();
ContactResource contactAfterDeletion = ofy().load().key(key).now();
assertAboutContacts().that(contactAfterDeletion).hasDeletionTime(now)
// 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(HistoryEntry.Type.CONTACT_DELETE);
assertAboutContacts().that(contactAfterDeletion).hasNullLocalizedPostalInfo()
.and().hasNullInternationalizedPostalInfo()
.and().hasNullEmailAddress()
.and().hasNullVoiceNumber()
.and().hasNullFaxNumber();
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(contactAfterDeletion, HistoryEntry.Type.CONTACT_DELETE);
assertPollMessageFor(historyEntry, "OtherRegistrar", "Deleted contact blah1235.");
}
@Test
public void testFailure_targetResourceDoesntExist() throws Exception {
createTld("tld");
ContactResource notPersisted = newContactResource("somecontact");
thrown.expect(
BadRequestException.class,
"Could not load resource for key: Key<?>(ContactResource(\"7-ROID\"))");
runMapreduceWithKeyParam(Key.create(notPersisted).getString());
}
@Test
public void testFailure_contactAlreadyDeleted() throws Exception {
ContactResource contactDeleted = persistResource(
newContactResource("blah1236").asBuilder()
.setCreationTimeForTest(clock.nowUtc().minusDays(2))
.setDeletionTime(clock.nowUtc().minusDays(1))
.build());
thrown.expect(
IllegalStateException.class,
"Resource blah1236 is already deleted.");
runMapreduceWithKeyParam(Key.create(contactDeleted).getString());
}
}

View file

@ -1,139 +0,0 @@
// Copyright 2016 The Domain Registry 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.flows.async;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.getOnlyPollMessageForHistoryEntry;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveContact;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistResource;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainResource;
import google.registry.model.host.HostResource;
import google.registry.model.ofy.Ofy;
import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.OneTime;
import google.registry.model.registry.Registry;
import google.registry.model.reporting.HistoryEntry;
import google.registry.request.HttpException.BadRequestException;
import google.registry.testing.ExceptionRule;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.InjectRule;
import google.registry.testing.mapreduce.MapreduceTestCase;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Rule;
import org.junit.Test;
/** Unit tests for {@link DeleteEppResourceAction}. */
public abstract class DeleteEppResourceActionTestCase<T extends DeleteEppResourceAction<?>>
extends MapreduceTestCase<T> {
@Rule
public final ExceptionRule thrown = new ExceptionRule();
@Rule
public final InjectRule inject = new InjectRule();
DateTime now = DateTime.now(DateTimeZone.UTC);
FakeClock clock = new FakeClock(now);
final DateTime transferRequestTime = now.minusDays(3);
final DateTime transferExpirationTime =
transferRequestTime.plus(Registry.DEFAULT_TRANSFER_GRACE_PERIOD);
ContactResource contactUsed;
HostResource hostUsed;
DomainResource domain;
public void setupDeleteEppResourceAction(T deleteEppResourceAction) throws Exception {
action = deleteEppResourceAction;
action.mrRunner = new MapreduceRunner(Optional.<Integer>of(5), Optional.<Integer>absent());
action.response = new FakeResponse();
action.clock = clock;
inject.setStaticField(Ofy.class, "clock", clock);
createTld("tld");
contactUsed = persistActiveContact("blah1234");
hostUsed = persistActiveHost("ns1.example.tld");
domain = persistResource(
newDomainResource("example.tld", contactUsed).asBuilder()
.setNameservers(ImmutableSet.of(Key.create(hostUsed)))
.build());
}
void runMapreduce() throws Exception {
clock.advanceOneMilli();
action.run();
executeTasksUntilEmpty("mapreduce");
ofy().clearSessionCache();
now = clock.nowUtc();
}
void runMapreduceWithParams(
String resourceKeyString,
String requestingClientId,
boolean isSuperuser) throws Exception {
action.resourceKeyString = resourceKeyString;
action.requestingClientId = requestingClientId;
action.isSuperuser = isSuperuser;
runMapreduce();
}
void runMapreduceWithKeyParam(String resourceKeyString) throws Exception {
runMapreduceWithParams(resourceKeyString, "TheRegistrar", false);
}
/**
* Helper method to check that one poll message exists with a given history entry, resource,
* client id, and message.
*/
void assertPollMessageFor(
HistoryEntry historyEntry,
String clientId,
String msg) {
PollMessage.OneTime pollMessage = (OneTime) getOnlyPollMessageForHistoryEntry(historyEntry);
assertThat(msg).isEqualTo(pollMessage.getMsg());
assertThat(now).isEqualTo(pollMessage.getEventTime());
assertThat(clientId).isEqualTo(pollMessage.getClientId());
assertThat(pollMessage.getClientId()).isEqualTo(clientId);
}
@Test
public void testFailure_domainKeyPassed() throws Exception {
DomainResource domain = persistActiveDomain("fail.tld");
thrown.expect(
IllegalArgumentException.class, "Cannot delete a DomainResource via this action.");
runMapreduceWithKeyParam(Key.create(domain).getString());
assertThat(loadByForeignKey(DomainResource.class, "fail.tld", now)).isEqualTo(domain);
}
@Test
public void testFailure_badKeyPassed() throws Exception {
createTld("tld");
thrown.expect(BadRequestException.class, "Could not parse key string: a bad key");
runMapreduceWithKeyParam("a bad key");
}
}

View file

@ -1,221 +0,0 @@
// Copyright 2016 The Domain Registry 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.flows.async;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.testing.DatastoreHelper.assertNoBillingEvents;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.newHostResource;
import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.HostResourceSubject.assertAboutHosts;
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry;
import google.registry.request.HttpException.BadRequestException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link DeleteHostResourceAction}. */
@RunWith(JUnit4.class)
public class DeleteHostResourceActionTest
extends DeleteEppResourceActionTestCase<DeleteHostResourceAction> {
HostResource hostUnused;
@Before
public void setup() throws Exception {
setupDeleteEppResourceAction(new DeleteHostResourceAction());
hostUnused = persistActiveHost("ns2.example.tld");
}
@Test
public void testSuccess_host_referencedByActiveDomain_doesNotGetDeleted() throws Exception {
hostUsed = persistResource(
hostUsed.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build());
runMapreduceWithKeyParam(Key.create(hostUsed).getString());
hostUsed = loadByForeignKey(HostResource.class, "ns1.example.tld", now);
assertAboutHosts().that(hostUsed).doesNotHaveStatusValue(StatusValue.PENDING_DELETE)
.and().hasDeletionTime(END_OF_TIME);
domain = loadByForeignKey(DomainResource.class, "example.tld", now);
assertThat(domain.getNameservers()).contains(Key.create(hostUsed));
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(hostUsed, HistoryEntry.Type.HOST_DELETE_FAILURE);
assertPollMessageFor(
historyEntry,
"TheRegistrar",
"Can't delete host ns1.example.tld because it is referenced by a domain.");
}
@Test
public void testSuccess_host_notReferenced_getsDeleted() throws Exception {
hostUnused = persistResource(
hostUnused.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build());
runMapreduceWithKeyParam(Key.create(hostUnused).getString());
assertThat(loadByForeignKey(HostResource.class, "ns2.example.tld", now)).isNull();
HostResource hostBeforeDeletion =
loadByForeignKey(HostResource.class, "ns2.example.tld", now.minusDays(1));
assertAboutHosts().that(hostBeforeDeletion).hasDeletionTime(now)
.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(HistoryEntry.Type.HOST_DELETE);
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(hostBeforeDeletion, HistoryEntry.Type.HOST_DELETE);
assertPollMessageFor(historyEntry, "TheRegistrar", "Deleted host ns2.example.tld.");
}
@Test
public void testSuccess_host_referencedByDeletedDomain_getsDeleted() throws Exception {
hostUsed = persistResource(
hostUsed.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build());
domain = persistResource(
domain.asBuilder()
.setDeletionTime(now.minusDays(3))
.build());
runMapreduceWithKeyParam(Key.create(hostUsed).getString());
assertThat(loadByForeignKey(HostResource.class, "ns1.example.tld", now)).isNull();
HostResource hostBeforeDeletion =
loadByForeignKey(HostResource.class, "ns1.example.tld", now.minusDays(1));
assertAboutHosts().that(hostBeforeDeletion).hasDeletionTime(now)
.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(HistoryEntry.Type.HOST_DELETE);
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(hostBeforeDeletion, HistoryEntry.Type.HOST_DELETE);
assertPollMessageFor(historyEntry, "TheRegistrar", "Deleted host ns1.example.tld.");
}
@Test
public void testSuccess_subordinateHost_getsDeleted() throws Exception {
domain = persistResource(
newDomainResource("example.tld").asBuilder()
.setSubordinateHosts(ImmutableSet.of("ns2.example.tld"))
.build());
persistResource(
hostUnused.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.setSuperordinateDomain(Key.create(domain))
.build());
runMapreduceWithKeyParam(Key.create(hostUnused).getString());
// Check that the host is deleted as of now.
assertThat(loadByForeignKey(HostResource.class, "ns2.example.tld", clock.nowUtc()))
.isNull();
assertNoBillingEvents();
assertThat(loadByForeignKey(DomainResource.class, "example.tld", clock.nowUtc())
.getSubordinateHosts())
.isEmpty();
assertDnsTasksEnqueued("ns2.example.tld");
HostResource hostBeforeDeletion =
loadByForeignKey(HostResource.class, "ns2.example.tld", now.minusDays(1));
assertAboutHosts().that(hostBeforeDeletion).hasDeletionTime(now)
.and().hasExactlyStatusValues(StatusValue.OK)
.and().hasOnlyOneHistoryEntryWhich().hasType(HistoryEntry.Type.HOST_DELETE);
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(hostBeforeDeletion, HistoryEntry.Type.HOST_DELETE);
assertPollMessageFor(historyEntry, "TheRegistrar", "Deleted host ns2.example.tld.");
}
@Test
public void testFailure_notPendingDelete() throws Exception {
thrown.expect(
IllegalStateException.class, "Resource ns2.example.tld is not set as PENDING_DELETE");
runMapreduceWithKeyParam(Key.create(hostUnused).getString());
assertThat(
loadByForeignKey(HostResource.class, "ns2.example.tld", now)).isEqualTo(hostUnused);
}
@Test
public void testSuccess_notRequestedByOwner_doesNotGetDeleted() throws Exception {
hostUnused = persistResource(
hostUnused.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build());
Key<HostResource> key = Key.create(hostUnused);
runMapreduceWithParams(key.getString(), "OtherRegistrar", false);
hostUnused = loadByForeignKey(HostResource.class, "ns2.example.tld", now);
assertAboutHosts().that(hostUnused).doesNotHaveStatusValue(StatusValue.PENDING_DELETE)
.and().hasDeletionTime(END_OF_TIME);
domain = loadByForeignKey(DomainResource.class, "example.tld", now);
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(hostUnused, HistoryEntry.Type.HOST_DELETE_FAILURE);
assertPollMessageFor(
historyEntry,
"OtherRegistrar",
"Can't delete host ns2.example.tld because it was transferred prior to deletion.");
}
@Test
public void testSuccess_notRequestedByOwner_isSuperuser_getsDeleted() throws Exception {
hostUnused = persistResource(
hostUnused.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.build());
Key<HostResource> key = Key.create(hostUnused);
runMapreduceWithParams(key.getString(), "OtherRegistrar", true);
assertThat(loadByForeignKey(HostResource.class, "ns2.example.tld", now)).isNull();
HostResource hostBeforeDeletion =
loadByForeignKey(HostResource.class, "ns2.example.tld", now.minusDays(1));
assertAboutHosts().that(hostBeforeDeletion).hasDeletionTime(now)
.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(HistoryEntry.Type.HOST_DELETE);
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(hostBeforeDeletion, HistoryEntry.Type.HOST_DELETE);
assertPollMessageFor(historyEntry, "OtherRegistrar", "Deleted host ns2.example.tld.");
}
@Test
public void testFailure_targetResourceDoesntExist() throws Exception {
createTld("tld");
HostResource notPersisted = newHostResource("ns1.example.tld");
thrown.expect(
BadRequestException.class,
"Could not load resource for key: Key<?>(HostResource(\"7-ROID\"))");
runMapreduceWithKeyParam(Key.create(notPersisted).getString());
}
@Test
public void testFailure_hostAlreadyDeleted() throws Exception {
HostResource hostDeleted = persistResource(
newHostResource("ns3.example.tld").asBuilder()
.setCreationTimeForTest(clock.nowUtc().minusDays(2))
.setDeletionTime(clock.nowUtc().minusDays(1))
.build());
thrown.expect(
IllegalStateException.class,
"Resource ns3.example.tld is already deleted.");
runMapreduceWithKeyParam(Key.create(hostDeleted).getString());
}
}