diff --git a/core/src/main/java/google/registry/batch/WipeOutCloudSqlAction.java b/core/src/main/java/google/registry/batch/WipeOutCloudSqlAction.java
deleted file mode 100644
index a3e9baacf..000000000
--- a/core/src/main/java/google/registry/batch/WipeOutCloudSqlAction.java
+++ /dev/null
@@ -1,161 +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.batch;
-
-import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
-import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
-import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.flogger.FluentLogger;
-import google.registry.config.RegistryEnvironment;
-import google.registry.persistence.PersistenceModule.SchemaManagerConnection;
-import google.registry.request.Action;
-import google.registry.request.Response;
-import google.registry.request.auth.Auth;
-import google.registry.util.Retrier;
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.function.Supplier;
-import javax.inject.Inject;
-
-/**
- * Wipes out all Cloud SQL data in a Nomulus GCP environment.
- *
- *
This class is created for the QA environment, where migration testing with production data
- * will happen. A regularly scheduled wipeout is a prerequisite to using production data there.
- */
-@Action(
- service = Action.Service.BACKEND,
- path = "/_dr/task/wipeOutCloudSql",
- auth = Auth.AUTH_API_ADMIN)
-public class WipeOutCloudSqlAction implements Runnable {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- private static final ImmutableSet FORBIDDEN_ENVIRONMENTS =
- ImmutableSet.of(RegistryEnvironment.PRODUCTION, RegistryEnvironment.SANDBOX);
-
- private final Supplier connectionSupplier;
- private final Response response;
- private final Retrier retrier;
-
- @Inject
- WipeOutCloudSqlAction(
- @SchemaManagerConnection Supplier connectionSupplier,
- Response response,
- Retrier retrier) {
- this.connectionSupplier = connectionSupplier;
- this.response = response;
- this.retrier = retrier;
- }
-
- @Override
- public void run() {
- response.setContentType(PLAIN_TEXT_UTF_8);
-
- if (FORBIDDEN_ENVIRONMENTS.contains(RegistryEnvironment.get())) {
- response.setStatus(SC_FORBIDDEN);
- response.setPayload("Wipeout is not allowed in " + RegistryEnvironment.get());
- return;
- }
-
- try {
- retrier.callWithRetry(
- () -> {
- try (Connection conn = connectionSupplier.get()) {
- dropAllTables(conn, listTables(conn));
- dropAllSequences(conn, listSequences(conn));
- }
- return null;
- },
- e -> !(e instanceof SQLException));
- response.setStatus(SC_OK);
- response.setPayload("Wiped out Cloud SQL in " + RegistryEnvironment.get());
- } catch (RuntimeException e) {
- logger.atSevere().withCause(e).log("Failed to wipe out Cloud SQL data.");
- response.setStatus(SC_INTERNAL_SERVER_ERROR);
- response.setPayload("Failed to wipe out Cloud SQL in " + RegistryEnvironment.get());
- }
- }
-
- /** Returns a list of all tables in the public schema of a Postgresql database. */
- static ImmutableList listTables(Connection connection) throws SQLException {
- try (ResultSet resultSet =
- connection.getMetaData().getTables(null, null, null, new String[] {"TABLE"})) {
- ImmutableList.Builder tables = new ImmutableList.Builder<>();
- while (resultSet.next()) {
- String schema = resultSet.getString("TABLE_SCHEM");
- if (schema == null || !schema.equalsIgnoreCase("public")) {
- continue;
- }
- String tableName = resultSet.getString("TABLE_NAME");
- tables.add("public.\"" + tableName + "\"");
- }
- return tables.build();
- }
- }
-
- static void dropAllTables(Connection conn, ImmutableList tables) throws SQLException {
- if (tables.isEmpty()) {
- return;
- }
-
- try (Statement statement = conn.createStatement()) {
- for (String table : tables) {
- statement.addBatch(String.format("DROP TABLE IF EXISTS %s CASCADE;", table));
- }
- for (int code : statement.executeBatch()) {
- if (code == Statement.EXECUTE_FAILED) {
- throw new RuntimeException("Failed to drop some tables. Please check.");
- }
- }
- }
- }
-
- /** Returns a list of all sequences in a Postgresql database. */
- static ImmutableList listSequences(Connection conn) throws SQLException {
- try (Statement statement = conn.createStatement();
- ResultSet resultSet =
- statement.executeQuery("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';")) {
- ImmutableList.Builder sequences = new ImmutableList.Builder<>();
- while (resultSet.next()) {
- sequences.add('\"' + resultSet.getString(1) + '\"');
- }
- return sequences.build();
- }
- }
-
- static void dropAllSequences(Connection conn, ImmutableList sequences)
- throws SQLException {
- if (sequences.isEmpty()) {
- return;
- }
-
- try (Statement statement = conn.createStatement()) {
- for (String sequence : sequences) {
- statement.addBatch(String.format("DROP SEQUENCE IF EXISTS %s CASCADE;", sequence));
- }
- for (int code : statement.executeBatch()) {
- if (code == Statement.EXECUTE_FAILED) {
- throw new RuntimeException("Failed to drop some sequences. Please check.");
- }
- }
- }
- }
-}
diff --git a/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java b/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java
index 5aba5714c..0f65a5d0d 100644
--- a/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java
+++ b/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java
@@ -26,7 +26,6 @@ import google.registry.batch.RelockDomainAction;
import google.registry.batch.ResaveAllEppResourcesPipelineAction;
import google.registry.batch.ResaveEntityAction;
import google.registry.batch.SendExpiringCertificateNotificationEmailAction;
-import google.registry.batch.WipeOutCloudSqlAction;
import google.registry.batch.WipeOutContactHistoryPiiAction;
import google.registry.cron.CronModule;
import google.registry.cron.TldFanoutAction;
@@ -178,8 +177,6 @@ interface BackendRequestComponent {
UpdateRegistrarRdapBaseUrlsAction updateRegistrarRdapBaseUrlsAction();
- WipeOutCloudSqlAction wipeOutCloudSqlAction();
-
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
@Subcomponent.Builder
diff --git a/core/src/main/resources/google/registry/batch/delete_expired_domain.xml b/core/src/main/resources/google/registry/batch/delete_expired_domain.xml
new file mode 100644
index 000000000..8714f04b6
--- /dev/null
+++ b/core/src/main/resources/google/registry/batch/delete_expired_domain.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ %DOMAIN%
+
+
+
+
+ Non-renewing domain has reached expiration date.
+ false
+
+
+ ABC-12345
+
+
diff --git a/core/src/test/java/google/registry/batch/WipeOutCloudSqlActionTest.java b/core/src/test/java/google/registry/batch/WipeOutCloudSqlActionTest.java
deleted file mode 100644
index 509944871..000000000
--- a/core/src/test/java/google/registry/batch/WipeOutCloudSqlActionTest.java
+++ /dev/null
@@ -1,117 +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.batch;
-
-import static com.google.common.truth.Truth.assertThat;
-import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
-import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import google.registry.config.RegistryEnvironment;
-import google.registry.testing.FakeClock;
-import google.registry.testing.FakeResponse;
-import google.registry.testing.FakeSleeper;
-import google.registry.util.Retrier;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-/** Unit tests for {@link WipeOutCloudSqlAction}. */
-@ExtendWith(MockitoExtension.class)
-public class WipeOutCloudSqlActionTest {
-
- @Mock private Statement stmt;
- @Mock private Connection conn;
- @Mock private DatabaseMetaData metaData;
- @Mock private ResultSet resultSet;
-
- private FakeResponse response = new FakeResponse();
- private Retrier retrier = new Retrier(new FakeSleeper(new FakeClock()), 2);
-
- @BeforeEach
- void beforeEach() throws Exception {
- lenient().when(conn.createStatement()).thenReturn(stmt);
- lenient().when(conn.getMetaData()).thenReturn(metaData);
- lenient()
- .when(
- metaData.getTables(
- nullable(String.class),
- nullable(String.class),
- nullable(String.class),
- nullable(String[].class)))
- .thenReturn(resultSet);
- lenient().when(stmt.executeQuery(anyString())).thenReturn(resultSet);
- lenient().when(resultSet.next()).thenReturn(false);
- }
-
- @Test
- void run_projectAllowed() throws Exception {
- WipeOutCloudSqlAction action = new WipeOutCloudSqlAction(() -> conn, response, retrier);
- action.run();
- assertThat(response.getStatus()).isEqualTo(SC_OK);
- verify(stmt, times(1)).executeQuery(anyString());
- verify(stmt, times(1)).close();
- verifyNoMoreInteractions(stmt);
- }
-
- @Test
- void run_projectNotAllowed() {
- try {
- RegistryEnvironment.SANDBOX.setup();
- WipeOutCloudSqlAction action = new WipeOutCloudSqlAction(() -> conn, response, retrier);
- action.run();
- assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
- verifyNoInteractions(stmt);
- } finally {
- RegistryEnvironment.UNITTEST.setup();
- }
- }
-
- @Test
- void run_nonRetrieableFailure() throws Exception {
- doThrow(new SQLException()).when(conn).getMetaData();
- WipeOutCloudSqlAction action = new WipeOutCloudSqlAction(() -> conn, response, retrier);
- action.run();
- assertThat(response.getStatus()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
- verifyNoInteractions(stmt);
- }
-
- @Test
- void run_retrieableFailure() throws Exception {
- when(conn.getMetaData()).thenThrow(new RuntimeException()).thenReturn(metaData);
- WipeOutCloudSqlAction action = new WipeOutCloudSqlAction(() -> conn, response, retrier);
- action.run();
- assertThat(response.getStatus()).isEqualTo(SC_OK);
- verify(stmt, times(1)).executeQuery(anyString());
- verify(stmt, times(1)).close();
- verifyNoMoreInteractions(stmt);
- }
-}
diff --git a/core/src/test/java/google/registry/batch/WipeOutCloudSqlIntegrationTest.java b/core/src/test/java/google/registry/batch/WipeOutCloudSqlIntegrationTest.java
deleted file mode 100644
index a3d785db5..000000000
--- a/core/src/test/java/google/registry/batch/WipeOutCloudSqlIntegrationTest.java
+++ /dev/null
@@ -1,89 +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.batch;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.common.collect.ImmutableList;
-import google.registry.persistence.NomulusPostgreSql;
-import java.sql.Connection;
-import java.sql.Statement;
-import java.util.Properties;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.testcontainers.containers.PostgreSQLContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-
-/** Tests the database wipeout mechanism used by {@link WipeOutCloudSqlAction}. */
-@Testcontainers
-public class WipeOutCloudSqlIntegrationTest {
-
- @Container
- PostgreSQLContainer container = new PostgreSQLContainer(NomulusPostgreSql.getDockerTag());
-
- private Connection getJdbcConnection() throws Exception {
- Properties properties = new Properties();
- properties.setProperty("user", container.getUsername());
- properties.setProperty("password", container.getPassword());
- return container.getJdbcDriverInstance().connect(container.getJdbcUrl(), properties);
- }
-
- @BeforeEach
- void beforeEach() throws Exception {
- try (Connection conn = getJdbcConnection();
- Statement statement = conn.createStatement()) {
- statement.addBatch("CREATE TABLE public.\"Domain\" (value int);");
- statement.addBatch("CREATE SEQUENCE public.\"Domain_seq\"");
- statement.executeBatch();
- }
- }
-
- @Test
- void listTables() throws Exception {
- try (Connection conn = getJdbcConnection()) {
- ImmutableList tables = WipeOutCloudSqlAction.listTables(conn);
- assertThat(tables).containsExactly("public.\"Domain\"");
- }
- }
-
- @Test
- void dropAllTables() throws Exception {
- try (Connection conn = getJdbcConnection()) {
- ImmutableList tables = WipeOutCloudSqlAction.listTables(conn);
- assertThat(tables).isNotEmpty();
- WipeOutCloudSqlAction.dropAllTables(conn, tables);
- assertThat(WipeOutCloudSqlAction.listTables(conn)).isEmpty();
- }
- }
-
- @Test
- void listAllSequences() throws Exception {
- try (Connection conn = getJdbcConnection()) {
- ImmutableList sequences = WipeOutCloudSqlAction.listSequences(conn);
- assertThat(sequences).containsExactly("\"Domain_seq\"");
- }
- }
-
- @Test
- void dropAllSequences() throws Exception {
- try (Connection conn = getJdbcConnection()) {
- ImmutableList sequences = WipeOutCloudSqlAction.listSequences(conn);
- assertThat(sequences).isNotEmpty();
- WipeOutCloudSqlAction.dropAllSequences(conn, sequences);
- assertThat(WipeOutCloudSqlAction.listSequences(conn)).isEmpty();
- }
- }
-}
diff --git a/core/src/test/resources/google/registry/module/backend/backend_routing.txt b/core/src/test/resources/google/registry/module/backend/backend_routing.txt
index a6b820400..6c0344bfc 100644
--- a/core/src/test/resources/google/registry/module/backend/backend_routing.txt
+++ b/core/src/test/resources/google/registry/module/backend/backend_routing.txt
@@ -35,5 +35,4 @@ PATH CLASS
/_dr/task/tmchDnl TmchDnlAction POST y API APP ADMIN
/_dr/task/tmchSmdrl TmchSmdrlAction POST y API APP ADMIN
/_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y API APP ADMIN
-/_dr/task/wipeOutCloudSql WipeOutCloudSqlAction GET n API APP ADMIN
/_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n API APP ADMIN