diff --git a/core/src/main/java/google/registry/batch/AsyncTaskEnqueuer.java b/core/src/main/java/google/registry/batch/AsyncTaskEnqueuer.java
index f0c567c28..9b2f151be 100644
--- a/core/src/main/java/google/registry/batch/AsyncTaskEnqueuer.java
+++ b/core/src/main/java/google/registry/batch/AsyncTaskEnqueuer.java
@@ -35,15 +35,12 @@ public final class AsyncTaskEnqueuer {
/** The HTTP parameter names used by async flows. */
public static final String PARAM_RESOURCE_KEY = "resourceKey";
- public static final String PARAM_HOST_KEY = "hostKey";
public static final String PARAM_REQUESTED_TIME = "requestedTime";
public static final String PARAM_RESAVE_TIMES = "resaveTimes";
/** The task queue names used by async flows. */
public static final String QUEUE_ASYNC_ACTIONS = "async-actions";
- public static final String QUEUE_ASYNC_HOST_RENAME = "async-host-rename-pull";
-
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Duration MAX_ASYNC_ETA = Duration.standardDays(30);
diff --git a/core/src/main/java/google/registry/batch/AsyncTaskMetrics.java b/core/src/main/java/google/registry/batch/AsyncTaskMetrics.java
deleted file mode 100644
index 019110a75..000000000
--- a/core/src/main/java/google/registry/batch/AsyncTaskMetrics.java
+++ /dev/null
@@ -1,165 +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.monitoring.metrics.EventMetric.DEFAULT_FITTER;
-import static google.registry.batch.AsyncTaskMetrics.OperationType.CONTACT_AND_HOST_DELETE;
-import static google.registry.batch.AsyncTaskMetrics.OperationType.DNS_REFRESH;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.flogger.FluentLogger;
-import com.google.monitoring.metrics.DistributionFitter;
-import com.google.monitoring.metrics.EventMetric;
-import com.google.monitoring.metrics.FibonacciFitter;
-import com.google.monitoring.metrics.IncrementableMetric;
-import com.google.monitoring.metrics.LabelDescriptor;
-import com.google.monitoring.metrics.MetricRegistryImpl;
-import google.registry.util.Clock;
-import javax.inject.Inject;
-import org.joda.time.DateTime;
-import org.joda.time.Duration;
-
-/**
- * Instrumentation for async flows (contact/host deletion and DNS refreshes).
- *
- * @see AsyncTaskEnqueuer
- */
-public class AsyncTaskMetrics {
-
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- private final Clock clock;
-
- @Inject
- public AsyncTaskMetrics(Clock clock) {
- this.clock = clock;
- }
-
- /**
- * A Fibonacci fitter used for bucketing the batch count.
- *
- *
We use a Fibonacci filter because it provides better resolution at the low end than an
- * exponential fitter, which is important because most batch sizes are likely to be very low,
- * despite going up to 1,000 on the high end. Also, the precision is better, as batch size is
- * inherently an integer, whereas an exponential fitter with an exponent base less than 2 would
- * have unintuitive boundaries.
- */
- private static final DistributionFitter FITTER_BATCH_SIZE =
- FibonacciFitter.create(maxLeaseCount());
-
- private static final ImmutableSet LABEL_DESCRIPTORS =
- ImmutableSet.of(
- LabelDescriptor.create("operation_type", "The type of async flow operation."),
- LabelDescriptor.create("result", "The result of the async flow operation."));
-
- @VisibleForTesting
- static final IncrementableMetric asyncFlowOperationCounts =
- MetricRegistryImpl.getDefault()
- .newIncrementableMetric(
- "/async_flows/operations",
- "Count of Async Flow Operations",
- "count",
- LABEL_DESCRIPTORS);
-
- @VisibleForTesting
- static final EventMetric asyncFlowOperationProcessingTime =
- MetricRegistryImpl.getDefault()
- .newEventMetric(
- "/async_flows/processing_time",
- "Async Flow Processing Time",
- "milliseconds",
- LABEL_DESCRIPTORS,
- DEFAULT_FITTER);
-
- @VisibleForTesting
- static final EventMetric asyncFlowBatchSize =
- MetricRegistryImpl.getDefault()
- .newEventMetric(
- "/async_flows/batch_size",
- "Async Operation Batch Size",
- "batch size",
- ImmutableSet.of(
- LabelDescriptor.create("operation_type", "The type of async flow operation.")),
- FITTER_BATCH_SIZE);
-
- /** The type of asynchronous operation. */
- public enum OperationType {
- CONTACT_DELETE("contactDelete"),
- HOST_DELETE("hostDelete"),
- CONTACT_AND_HOST_DELETE("contactAndHostDelete"),
- DNS_REFRESH("dnsRefresh");
-
- private final String metricLabelValue;
-
- OperationType(String metricLabelValue) {
- this.metricLabelValue = metricLabelValue;
- }
-
- String getMetricLabelValue() {
- return metricLabelValue;
- }
- }
-
- /** The result of an asynchronous operation. */
- public enum OperationResult {
- /** The operation processed correctly and the result was success. */
- SUCCESS("success"),
-
- /** The operation processed correctly and the result was failure. */
- FAILURE("failure"),
-
- /** The operation did not process correctly due to some unexpected error. */
- ERROR("error"),
-
- /** The operation was skipped because the request is now stale. */
- STALE("stale");
-
- private final String metricLabelValue;
-
- OperationResult(String metricLabelValue) {
- this.metricLabelValue = metricLabelValue;
- }
-
- String getMetricLabelValue() {
- return metricLabelValue;
- }
- }
-
- public void recordAsyncFlowResult(
- OperationType operationType, OperationResult operationResult, DateTime whenEnqueued) {
- asyncFlowOperationCounts.increment(
- operationType.getMetricLabelValue(), operationResult.getMetricLabelValue());
- long processingMillis = new Duration(whenEnqueued, clock.nowUtc()).getMillis();
- asyncFlowOperationProcessingTime.record(
- processingMillis,
- operationType.getMetricLabelValue(),
- operationResult.getMetricLabelValue());
- logger.atInfo().log(
- "Asynchronous %s operation took %d ms to process, yielding result: %s.",
- operationType.getMetricLabelValue(),
- processingMillis,
- operationResult.getMetricLabelValue());
- }
-
- public void recordContactHostDeletionBatchSize(long batchSize) {
- asyncFlowBatchSize.record(batchSize, CONTACT_AND_HOST_DELETE.getMetricLabelValue());
- }
-
- public void recordDnsRefreshBatchSize(long batchSize) {
- asyncFlowBatchSize.record(batchSize, DNS_REFRESH.getMetricLabelValue());
- }
-}
diff --git a/core/src/main/java/google/registry/dns/DnsModule.java b/core/src/main/java/google/registry/dns/DnsModule.java
index 4a06206c2..4b182eb2a 100644
--- a/core/src/main/java/google/registry/dns/DnsModule.java
+++ b/core/src/main/java/google/registry/dns/DnsModule.java
@@ -16,6 +16,7 @@ package google.registry.dns;
import static google.registry.dns.DnsConstants.DNS_PUBLISH_PUSH_QUEUE_NAME;
import static google.registry.dns.DnsConstants.DNS_PULL_QUEUE_NAME;
+import static google.registry.dns.RefreshDnsOnHostRenameAction.PARAM_HOST_KEY;
import static google.registry.request.RequestParameters.extractEnumParameter;
import static google.registry.request.RequestParameters.extractIntParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter;
@@ -61,7 +62,7 @@ public abstract class DnsModule {
*/
@Provides
static HashFunction provideHashFunction() {
- return Hashing.murmur3_32();
+ return Hashing.murmur3_32_fixed();
}
@Provides
@@ -118,6 +119,12 @@ public abstract class DnsModule {
return extractSetOfParameters(req, PARAM_HOSTS);
}
+ @Provides
+ @Parameter(PARAM_HOST_KEY)
+ static String provideResourceKey(HttpServletRequest req) {
+ return extractRequiredParameter(req, PARAM_HOST_KEY);
+ }
+
@Provides
@Parameter("domainOrHostName")
static String provideName(HttpServletRequest req) {
diff --git a/core/src/main/java/google/registry/dns/RefreshDnsOnHostRenameAction.java b/core/src/main/java/google/registry/dns/RefreshDnsOnHostRenameAction.java
new file mode 100644
index 000000000..7e27281a0
--- /dev/null
+++ b/core/src/main/java/google/registry/dns/RefreshDnsOnHostRenameAction.java
@@ -0,0 +1,90 @@
+// Copyright 2022 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.dns;
+
+import static google.registry.dns.RefreshDnsOnHostRenameAction.PATH;
+import static google.registry.model.EppResourceUtils.getLinkedDomainKeys;
+import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+
+import com.google.common.net.MediaType;
+import google.registry.model.EppResourceUtils;
+import google.registry.model.domain.Domain;
+import google.registry.model.host.Host;
+import google.registry.persistence.VKey;
+import google.registry.request.Action;
+import google.registry.request.Action.Service;
+import google.registry.request.Parameter;
+import google.registry.request.Response;
+import google.registry.request.auth.Auth;
+import javax.inject.Inject;
+import org.joda.time.DateTime;
+
+@Action(
+ service = Service.BACKEND,
+ path = PATH,
+ method = Action.Method.POST,
+ auth = Auth.AUTH_INTERNAL_OR_ADMIN)
+public class RefreshDnsOnHostRenameAction implements Runnable {
+
+ public static final String QUEUE_HOST_RENAME = "async-host-rename";
+ public static final String PARAM_HOST_KEY = "hostKey";
+ public static final String PATH = "/_dr/task/refreshDnsOnHostRename";
+
+ private final VKey hostKey;
+ private final Response response;
+ private final DnsQueue dnsQueue;
+
+ @Inject
+ RefreshDnsOnHostRenameAction(
+ @Parameter(PARAM_HOST_KEY) String hostKey, Response response, DnsQueue dnsQueue) {
+ this.hostKey = VKey.createEppVKeyFromString(hostKey);
+ this.response = response;
+ this.dnsQueue = dnsQueue;
+ }
+
+ @Override
+ public void run() {
+ tm().transact(
+ () -> {
+ DateTime now = tm().getTransactionTime();
+ Host host = tm().loadByKeyIfPresent(hostKey).orElse(null);
+ boolean hostValid = true;
+ String failureMessage = null;
+ if (host == null) {
+ hostValid = false;
+ failureMessage = String.format("Host to refresh does not exist: %s", hostKey);
+ } else if (EppResourceUtils.isDeleted(host, now)) {
+ hostValid = false;
+ failureMessage =
+ String.format("Host to refresh is already deleted: %s", host.getHostName());
+ } else {
+ getLinkedDomainKeys(
+ host.createVKey(), host.getUpdateTimestamp().getTimestamp(), null)
+ .stream()
+ .map(domainKey -> tm().loadByKey(domainKey))
+ .filter(Domain::shouldPublishToDns)
+ .forEach(domain -> dnsQueue.addDomainRefreshTask(domain.getDomainName()));
+ }
+
+ if (!hostValid) {
+ // Set the response status code to be 204 so to not retry.
+ response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
+ response.setStatus(SC_NO_CONTENT);
+ response.setPayload(failureMessage);
+ }
+ });
+ }
+}
diff --git a/core/src/main/java/google/registry/env/common/backend/WEB-INF/web.xml b/core/src/main/java/google/registry/env/common/backend/WEB-INF/web.xml
index 8ffdfa4a4..573a57d06 100644
--- a/core/src/main/java/google/registry/env/common/backend/WEB-INF/web.xml
+++ b/core/src/main/java/google/registry/env/common/backend/WEB-INF/web.xml
@@ -244,12 +244,6 @@
/_dr/task/resaveEntity
-
-
- backend-servlet
- /_dr/task/dnsRefreshForHostRename
-
-
backend-servlet
diff --git a/core/src/main/java/google/registry/env/common/default/WEB-INF/queue.xml b/core/src/main/java/google/registry/env/common/default/WEB-INF/queue.xml
index 6fc9c9ab1..6700c2420 100644
--- a/core/src/main/java/google/registry/env/common/default/WEB-INF/queue.xml
+++ b/core/src/main/java/google/registry/env/common/default/WEB-INF/queue.xml
@@ -46,6 +46,12 @@
+
+
+ async-host-rename
+ 1/s
+
+
beam-reporting
diff --git a/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java b/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java
index 938fba9fb..4e55cb6ce 100644
--- a/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java
+++ b/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java
@@ -27,7 +27,6 @@ import static google.registry.model.transfer.TransferStatus.SERVER_CANCELLED;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
-import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -79,7 +78,6 @@ public final class ContactDeleteFlow implements TransactionalFlow {
@Inject @Superuser boolean isSuperuser;
@Inject Optional authInfo;
@Inject ContactHistory.Builder historyBuilder;
- @Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject EppResponse.Builder responseBuilder;
@Inject
diff --git a/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java b/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java
index 1b766f0ce..0b5160eef 100644
--- a/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java
+++ b/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java
@@ -24,7 +24,6 @@ import static google.registry.model.eppoutput.Result.Code.SUCCESS;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
-import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
@@ -79,7 +78,6 @@ public final class HostDeleteFlow implements TransactionalFlow {
@Inject Trid trid;
@Inject @Superuser boolean isSuperuser;
@Inject HostHistory.Builder historyBuilder;
- @Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject EppResponse.Builder responseBuilder;
@Inject
diff --git a/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java b/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java
index 665ad7a9d..abf303faa 100644
--- a/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java
+++ b/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java
@@ -16,6 +16,8 @@ package google.registry.flows.host;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.Sets.union;
+import static google.registry.dns.RefreshDnsOnHostRenameAction.PARAM_HOST_KEY;
+import static google.registry.dns.RefreshDnsOnHostRenameAction.QUEUE_HOST_RENAME;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@@ -30,9 +32,12 @@ import static google.registry.model.reporting.HistoryEntry.Type.HOST_UPDATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
+import com.google.cloud.tasks.v2.Task;
+import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.dns.DnsQueue;
+import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.flows.EppException;
import google.registry.flows.EppException.ObjectAlreadyExistsException;
import google.registry.flows.EppException.ParameterValueRangeErrorException;
@@ -59,6 +64,8 @@ import google.registry.model.host.HostCommand.Update.Change;
import google.registry.model.host.HostHistory;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.persistence.VKey;
+import google.registry.request.Action.Service;
+import google.registry.util.CloudTasksUtils;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
@@ -107,9 +114,8 @@ public final class HostUpdateFlow implements TransactionalFlow {
* requires special checking, since you must be able to clear the status off the object with an
* update.
*/
- private static final ImmutableSet DISALLOWED_STATUSES = ImmutableSet.of(
- StatusValue.PENDING_DELETE,
- StatusValue.SERVER_UPDATE_PROHIBITED);
+ private static final ImmutableSet DISALLOWED_STATUSES =
+ ImmutableSet.of(StatusValue.PENDING_DELETE, StatusValue.SERVER_UPDATE_PROHIBITED);
@Inject ResourceCommand resourceCommand;
@Inject ExtensionManager extensionManager;
@@ -120,7 +126,10 @@ public final class HostUpdateFlow implements TransactionalFlow {
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject DnsQueue dnsQueue;
@Inject EppResponse.Builder responseBuilder;
- @Inject HostUpdateFlow() {}
+ @Inject CloudTasksUtils cloudTasksUtils;
+
+ @Inject
+ HostUpdateFlow() {}
@Override
public EppResponse run() throws EppException {
@@ -268,7 +277,12 @@ public final class HostUpdateFlow implements TransactionalFlow {
}
// We must also enqueue updates for all domains that use this host as their nameserver so
// that their NS records can be updated to point at the new name.
- // TODO(jianglai): implement a SQL based solution.
+ Task task =
+ cloudTasksUtils.createPostTask(
+ RefreshDnsOnHostRenameAction.PATH,
+ Service.BACKEND.toString(),
+ ImmutableMultimap.of(PARAM_HOST_KEY, existingHost.createVKey().stringify()));
+ cloudTasksUtils.enqueue(QUEUE_HOST_RENAME, task);
}
}
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 dca1f8170..be7ca5859 100644
--- a/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java
+++ b/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java
@@ -34,6 +34,7 @@ import google.registry.dns.DnsModule;
import google.registry.dns.PublishDnsUpdatesAction;
import google.registry.dns.ReadDnsQueueAction;
import google.registry.dns.RefreshDnsAction;
+import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.dns.writer.VoidDnsWriterModule;
import google.registry.dns.writer.clouddns.CloudDnsWriterModule;
import google.registry.dns.writer.dnsupdate.DnsUpdateConfigModule;
@@ -153,6 +154,8 @@ interface BackendRequestComponent {
RefreshDnsAction refreshDnsAction();
+ RefreshDnsOnHostRenameAction refreshDnsOnHostRenameAction();
+
RelockDomainAction relockDomainAction();
ResaveAllEppResourcesPipelineAction resaveAllEppResourcesPipelineAction();
diff --git a/core/src/test/java/google/registry/batch/AsyncTaskMetricsTest.java b/core/src/test/java/google/registry/batch/AsyncTaskMetricsTest.java
deleted file mode 100644
index d4af63ffe..000000000
--- a/core/src/test/java/google/registry/batch/AsyncTaskMetricsTest.java
+++ /dev/null
@@ -1,47 +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.monitoring.metrics.contrib.DistributionMetricSubject.assertThat;
-import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat;
-import static google.registry.batch.AsyncTaskMetrics.OperationResult.SUCCESS;
-import static google.registry.batch.AsyncTaskMetrics.OperationType.CONTACT_AND_HOST_DELETE;
-
-import com.google.common.collect.ImmutableSet;
-import google.registry.testing.FakeClock;
-import org.junit.jupiter.api.Test;
-
-/** Unit tests for {@link AsyncTaskMetrics}. */
-class AsyncTaskMetricsTest {
-
- private final FakeClock clock = new FakeClock();
- private final AsyncTaskMetrics asyncTaskMetrics = new AsyncTaskMetrics(clock);
-
- @Test
- void testRecordAsyncFlowResult_calculatesDurationMillisCorrectly() {
- asyncTaskMetrics.recordAsyncFlowResult(
- CONTACT_AND_HOST_DELETE,
- SUCCESS,
- clock.nowUtc().minusMinutes(10).minusSeconds(5).minusMillis(566));
- assertThat(AsyncTaskMetrics.asyncFlowOperationCounts)
- .hasValueForLabels(1, "contactAndHostDelete", "success")
- .and()
- .hasNoOtherValues();
- assertThat(AsyncTaskMetrics.asyncFlowOperationProcessingTime)
- .hasDataSetForLabels(ImmutableSet.of(605566.0), "contactAndHostDelete", "success")
- .and()
- .hasNoOtherValues();
- }
-}
diff --git a/core/src/test/java/google/registry/dns/RefreshDnsOnHostRenameActionTest.java b/core/src/test/java/google/registry/dns/RefreshDnsOnHostRenameActionTest.java
new file mode 100644
index 000000000..8233d66e3
--- /dev/null
+++ b/core/src/test/java/google/registry/dns/RefreshDnsOnHostRenameActionTest.java
@@ -0,0 +1,110 @@
+// Copyright 2022 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.dns;
+
+import static com.google.common.truth.Truth.assertThat;
+import static google.registry.testing.DatabaseHelper.createTld;
+import static google.registry.testing.DatabaseHelper.newDomain;
+import static google.registry.testing.DatabaseHelper.persistActiveHost;
+import static google.registry.testing.DatabaseHelper.persistDeletedHost;
+import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted;
+import static google.registry.testing.DatabaseHelper.persistResource;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.common.collect.ImmutableSet;
+import google.registry.model.eppcommon.StatusValue;
+import google.registry.model.host.Host;
+import google.registry.persistence.transaction.JpaTestExtensions;
+import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
+import google.registry.testing.FakeClock;
+import google.registry.testing.FakeResponse;
+import org.joda.time.DateTime;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+/** Unit tests for {@link RefreshDnsOnHostRenameAction}. */
+public class RefreshDnsOnHostRenameActionTest {
+
+ private final FakeClock clock = new FakeClock(DateTime.parse("2015-01-15T11:22:33Z"));
+ private final DnsQueue dnsQueue = mock(DnsQueue.class);
+ private final FakeResponse response = new FakeResponse();
+
+ @RegisterExtension
+ final JpaIntegrationTestExtension jpa =
+ new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
+
+ private RefreshDnsOnHostRenameAction action;
+
+ private void createAction(String hostKey) {
+ action = new RefreshDnsOnHostRenameAction(hostKey, response, dnsQueue);
+ }
+
+ private void assertDnsTasksEnqueued(String... domains) {
+ for (String domain : domains) {
+ verify(dnsQueue).addDomainRefreshTask(domain);
+ }
+ verifyNoMoreInteractions(dnsQueue);
+ }
+
+ @BeforeEach
+ void beforeEach() {
+ createTld("tld");
+ }
+
+ @Test
+ void testSuccess() {
+ Host host = persistActiveHost("ns1.example.tld");
+ persistResource(newDomain("example.tld", host));
+ persistResource(newDomain("otherexample.tld", host));
+ persistResource(newDomain("untouched.tld", persistActiveHost("ns2.example.tld")));
+ persistResource(
+ newDomain("suspended.tld", host)
+ .asBuilder()
+ .setStatusValues(ImmutableSet.of(StatusValue.CLIENT_HOLD))
+ .build());
+ persistDomainAsDeleted(newDomain("deleted.tld", host), clock.nowUtc().minusDays(1));
+ createAction(host.createVKey().stringify());
+ action.run();
+ assertDnsTasksEnqueued("example.tld", "otherexample.tld");
+ assertThat(response.getStatus()).isEqualTo(SC_OK);
+ }
+
+ @Test
+ void testFailure_nonexistentHost() {
+ createAction("kind:Host@sql:rO0ABXQABGJsYWg");
+ action.run();
+ assertDnsTasksEnqueued();
+ assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
+ assertThat(response.getPayload())
+ .isEqualTo("Host to refresh does not exist: VKey(sql:blah)");
+ }
+
+ @Test
+ void testFailure_deletedHost() {
+ Host host = persistDeletedHost("ns1.example.tld", clock.nowUtc().minusDays(1));
+ persistResource(newDomain("example.tld", host));
+ createAction(host.createVKey().stringify());
+ action.run();
+ assertDnsTasksEnqueued();
+ assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
+ assertThat(response.getPayload())
+ .isEqualTo("Host to refresh is already deleted: ns1.example.tld");
+ }
+}
diff --git a/core/src/test/java/google/registry/flows/EppTestComponent.java b/core/src/test/java/google/registry/flows/EppTestComponent.java
index beeda8fba..b60039c7a 100644
--- a/core/src/test/java/google/registry/flows/EppTestComponent.java
+++ b/core/src/test/java/google/registry/flows/EppTestComponent.java
@@ -36,6 +36,7 @@ import google.registry.testing.FakeSleeper;
import google.registry.tmch.TmchCertificateAuthority;
import google.registry.tmch.TmchXmlSignature;
import google.registry.util.Clock;
+import google.registry.util.CloudTasksUtils;
import google.registry.util.Sleeper;
import javax.inject.Singleton;
@@ -89,6 +90,11 @@ public interface EppTestComponent {
return asyncTaskEnqueuer;
}
+ @Provides
+ CloudTasksUtils provideCloudTasksUtils() {
+ return cloudTasksHelper.getTestCloudTasksUtils();
+ }
+
@Provides
Clock provideClock() {
return clock;
diff --git a/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java
index a60349f5d..a6ff7362f 100644
--- a/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java
+++ b/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java
@@ -372,18 +372,14 @@ class DomainTransferRequestFlowTest
assertThat(transferApprovedPollMessage.getEventTime()).isEqualTo(implicitTransferTime);
assertThat(autorenewPollMessage.getEventTime()).isEqualTo(expectedExpirationTime);
assertThat(
- transferApprovedPollMessage
- .getResponseData()
- .stream()
+ transferApprovedPollMessage.getResponseData().stream()
.filter(TransferResponse.class::isInstance)
.map(TransferResponse.class::cast)
.collect(onlyElement())
.getTransferStatus())
.isEqualTo(TransferStatus.SERVER_APPROVED);
PendingActionNotificationResponse panData =
- transferApprovedPollMessage
- .getResponseData()
- .stream()
+ transferApprovedPollMessage.getResponseData().stream()
.filter(PendingActionNotificationResponse.class::isInstance)
.map(PendingActionNotificationResponse.class::cast)
.collect(onlyElement());
@@ -394,30 +390,24 @@ class DomainTransferRequestFlowTest
// transfer pending message, and a transfer approved message (both OneTime messages).
assertThat(getPollMessages("TheRegistrar", implicitTransferTime)).hasSize(2);
PollMessage losingTransferPendingPollMessage =
- getPollMessages("TheRegistrar", clock.nowUtc())
- .stream()
+ getPollMessages("TheRegistrar", clock.nowUtc()).stream()
.filter(pollMessage -> TransferStatus.PENDING.getMessage().equals(pollMessage.getMsg()))
.collect(onlyElement());
PollMessage losingTransferApprovedPollMessage =
- getPollMessages("TheRegistrar", implicitTransferTime)
- .stream()
+ getPollMessages("TheRegistrar", implicitTransferTime).stream()
.filter(Predicates.not(Predicates.equalTo(losingTransferPendingPollMessage)))
.collect(onlyElement());
assertThat(losingTransferPendingPollMessage.getEventTime()).isEqualTo(clock.nowUtc());
assertThat(losingTransferApprovedPollMessage.getEventTime()).isEqualTo(implicitTransferTime);
assertThat(
- losingTransferPendingPollMessage
- .getResponseData()
- .stream()
+ losingTransferPendingPollMessage.getResponseData().stream()
.filter(TransferResponse.class::isInstance)
.map(TransferResponse.class::cast)
.collect(onlyElement())
.getTransferStatus())
.isEqualTo(TransferStatus.PENDING);
assertThat(
- losingTransferApprovedPollMessage
- .getResponseData()
- .stream()
+ losingTransferApprovedPollMessage.getResponseData().stream()
.filter(TransferResponse.class::isInstance)
.map(TransferResponse.class::cast)
.collect(onlyElement())
diff --git a/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java b/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java
index 35daa495d..4432a16dc 100644
--- a/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java
+++ b/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java
@@ -16,6 +16,8 @@ package google.registry.flows.host;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.truth.Truth.assertThat;
+import static google.registry.dns.RefreshDnsOnHostRenameAction.PARAM_HOST_KEY;
+import static google.registry.dns.RefreshDnsOnHostRenameAction.QUEUE_HOST_RENAME;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.createTld;
@@ -38,10 +40,12 @@ import static google.registry.testing.TaskQueueHelper.assertNoDnsTasksEnqueued;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
+import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.flows.EppException;
import google.registry.flows.EppRequestSource;
import google.registry.flows.FlowUtils.NotLoggedInException;
@@ -75,6 +79,7 @@ import google.registry.model.tld.Registry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
+import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
@@ -199,12 +204,13 @@ class HostUpdateFlowTest extends ResourceFlowTestCase {
Host renamedHost = doSuccessfulTest();
assertThat(renamedHost.isSubordinate()).isTrue();
// Task enqueued to change the NS record of the referencing domain.
- // TODO(jianglai): add assertion on host rename refresh based on SQL impementation.
- // assertTasksEnqueued(
- // QUEUE_ASYNC_HOST_RENAME,
- // new TaskMatcher()
- // .param(PARAM_HOST_KEY, renamedHost.createVKey().stringify())
- // .param("requestedTime", clock.nowUtc().toString()));
+ cloudTasksHelper.assertTasksEnqueued(
+ QUEUE_HOST_RENAME,
+ new TaskMatcher()
+ .url(RefreshDnsOnHostRenameAction.PATH)
+ .method(HttpMethod.POST)
+ .service("backend")
+ .param(PARAM_HOST_KEY, renamedHost.createVKey().stringify()));
}
@Test
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 e2b2e43c9..c1046c396 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
@@ -24,6 +24,7 @@ PATH CLASS
/_dr/task/rdeReport RdeReportAction POST n INTERNAL,API APP ADMIN
/_dr/task/rdeStaging RdeStagingAction GET,POST n INTERNAL,API APP ADMIN
/_dr/task/rdeUpload RdeUploadAction POST n INTERNAL,API APP ADMIN
+/_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction POST n INTERNAL,API APP ADMIN
/_dr/task/relockDomain RelockDomainAction POST y INTERNAL,API APP ADMIN
/_dr/task/resaveAllEppResourcesPipeline ResaveAllEppResourcesPipelineAction GET n INTERNAL,API APP ADMIN
/_dr/task/resaveEntity ResaveEntityAction POST n INTERNAL,API APP ADMIN