diff --git a/java/google/registry/batch/DeleteLoadTestDataAction.java b/java/google/registry/batch/DeleteLoadTestDataAction.java new file mode 100644 index 000000000..c3862c2e5 --- /dev/null +++ b/java/google/registry/batch/DeleteLoadTestDataAction.java @@ -0,0 +1,148 @@ +// Copyright 2018 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.common.base.Preconditions.checkState; +import static google.registry.config.RegistryEnvironment.PRODUCTION; +import static google.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN; +import static google.registry.mapreduce.inputs.EppResourceInputs.createEntityInput; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.request.Action.Method.POST; + +import com.google.appengine.tools.mapreduce.Mapper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.googlecode.objectify.Key; +import google.registry.config.RegistryEnvironment; +import google.registry.mapreduce.MapreduceRunner; +import google.registry.model.EppResource; +import google.registry.model.contact.ContactResource; +import google.registry.model.host.HostResource; +import google.registry.model.index.EppResourceIndex; +import google.registry.model.index.ForeignKeyIndex; +import google.registry.request.Action; +import google.registry.request.Parameter; +import google.registry.request.Response; +import google.registry.request.auth.Auth; +import google.registry.util.FormattingLogger; +import google.registry.util.PipelineUtils; +import java.util.List; +import javax.inject.Inject; + +/** + * Hard deletes load-test ContactResources, HostResources, their subordinate history entries, and + * the associated ForeignKey and EppResourceIndex entities. + * + *

This only deletes contacts and hosts, NOT domains. To delete domains, use + * {@link DeleteLoadTestDataAction} and pass it the TLD(s) that the load test domains were created + * on. Note that DeleteLoadTestDataAction is safe enough to run in production whereas this mapreduce + * is not, but this one does not need to be runnable in production because load testing isn't run + * against production. + */ +@Action(path = "/_dr/task/deleteLoadTestData", method = POST, auth = Auth.AUTH_INTERNAL_ONLY) +public class DeleteLoadTestDataAction implements Runnable { + + private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); + + /** + * The registrars for which to wipe out all contacts/hosts. + * + *

This is hard-coded because it's too dangerous to specify as a request parameter. By putting + * it in code it always has to go through code review. + */ + private static final ImmutableSet LOAD_TEST_REGISTRARS = ImmutableSet.of("proxy"); + + @Inject + @Parameter(PARAM_DRY_RUN) + boolean isDryRun; + + @Inject MapreduceRunner mrRunner; + @Inject Response response; + @Inject RegistryEnvironment registryEnvironment; + + @Inject + DeleteLoadTestDataAction() {} + + @Override + public void run() { + // This mapreduce doesn't guarantee that foreign key relations are preserved, so isn't safe to + // run on production. On other environments, data is fully wiped out occasionally anyway, so + // having some broken data that isn't referred to isn't the end of the world. + checkState( + registryEnvironment != PRODUCTION, "This mapreduce is not safe to run on PRODUCTION."); + + response.sendJavaScriptRedirect( + PipelineUtils.createJobPath( + mrRunner + .setJobName("Delete load test data") + .setModuleName("backend") + .runMapOnly( + new DeleteLoadTestDataMapper(isDryRun), + ImmutableList.of( + createEntityInput(ContactResource.class), + createEntityInput(HostResource.class))))); + } + + /** Provides the map method that runs for each existing contact and host entity. */ + public static class DeleteLoadTestDataMapper extends Mapper { + + private static final long serialVersionUID = -3817710674062432694L; + + private final boolean isDryRun; + + public DeleteLoadTestDataMapper(boolean isDryRun) { + this.isDryRun = isDryRun; + } + + @Override + public final void map(EppResource resource) { + if (LOAD_TEST_REGISTRARS.contains(resource.getPersistedCurrentSponsorClientId())) { + deleteResource(resource); + getContext() + .incrementCounter( + String.format("deleted %s entities", resource.getClass().getSimpleName())); + } else { + getContext().incrementCounter("skipped, not load test data"); + } + } + + private void deleteResource(EppResource resource) { + final Key eppIndex = + Key.create(EppResourceIndex.create(Key.create(resource))); + final Key> fki = ForeignKeyIndex.createKey(resource); + int numEntitiesDeleted = + ofy() + .transact( + () -> { + // This ancestor query selects all descendant entities. + List> resourceAndDependentKeys = + ofy().load().ancestor(resource).keys().list(); + ImmutableSet> allKeys = + new ImmutableSet.Builder>() + .add(fki) + .add(eppIndex) + .addAll(resourceAndDependentKeys) + .build(); + if (isDryRun) { + logger.infofmt("Would hard-delete the following entities: %s", allKeys); + } else { + ofy().deleteWithoutBackup().keys(allKeys); + } + return allKeys.size(); + }); + getContext().incrementCounter("total entities deleted", numEntitiesDeleted); + } + } +} diff --git a/java/google/registry/env/common/backend/WEB-INF/web.xml b/java/google/registry/env/common/backend/WEB-INF/web.xml index 99e856f1b..2744cf42d 100644 --- a/java/google/registry/env/common/backend/WEB-INF/web.xml +++ b/java/google/registry/env/common/backend/WEB-INF/web.xml @@ -286,6 +286,12 @@ /_dr/task/deleteProberData + + + backend-servlet + /_dr/task/deleteLoadTestData + + backend-servlet diff --git a/java/google/registry/module/backend/BackendRequestComponent.java b/java/google/registry/module/backend/BackendRequestComponent.java index f08db9641..3b2f25858 100644 --- a/java/google/registry/module/backend/BackendRequestComponent.java +++ b/java/google/registry/module/backend/BackendRequestComponent.java @@ -22,6 +22,7 @@ import google.registry.backup.DeleteOldCommitLogsAction; import google.registry.backup.ExportCommitLogDiffAction; import google.registry.batch.BatchModule; import google.registry.batch.DeleteContactsAndHostsAction; +import google.registry.batch.DeleteLoadTestDataAction; import google.registry.batch.DeleteProberDataAction; import google.registry.batch.ExpandRecurringBillingEventsAction; import google.registry.batch.MapreduceEntityCleanupAction; @@ -117,6 +118,7 @@ interface BackendRequestComponent { CommitLogFanoutAction commitLogFanoutAction(); CopyDetailReportsAction copyDetailReportAction(); DeleteContactsAndHostsAction deleteContactsAndHostsAction(); + DeleteLoadTestDataAction deleteLoadTestDataAction(); DeleteOldCommitLogsAction deleteOldCommitLogsAction(); DeleteProberDataAction deleteProberDataAction(); ExpandRecurringBillingEventsAction expandRecurringBillingEventsAction(); diff --git a/javatests/google/registry/module/backend/testdata/backend_routing.txt b/javatests/google/registry/module/backend/testdata/backend_routing.txt index 244ee8d23..472afd13d 100644 --- a/javatests/google/registry/module/backend/testdata/backend_routing.txt +++ b/javatests/google/registry/module/backend/testdata/backend_routing.txt @@ -8,6 +8,7 @@ PATH CLASS METHOD /_dr/task/checkSnapshot CheckSnapshotAction POST,GET y INTERNAL APP IGNORED /_dr/task/copyDetailReports CopyDetailReportsAction POST n INTERNAL,API APP ADMIN /_dr/task/deleteContactsAndHosts DeleteContactsAndHostsAction GET n INTERNAL APP IGNORED +/_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n INTERNAL APP IGNORED /_dr/task/deleteOldCommitLogs DeleteOldCommitLogsAction GET n INTERNAL APP IGNORED /_dr/task/deleteProberData DeleteProberDataAction POST n INTERNAL APP IGNORED /_dr/task/expandRecurringBillingEvents ExpandRecurringBillingEventsAction GET n INTERNAL APP IGNORED