1
0
mirror of https://github.com/google/nomulus synced 2026-07-11 02:22:30 +00:00

Compare commits

...

33 Commits

Author SHA1 Message Date
Lai Jiang e1f29a8103 Add routing for ReadDnsRefreshRequestsAction (#1990)
It looks like we forgot this crucial part to actually add the necessary
routing the new action...

Also fixes a linter warning.
2023-04-12 15:17:21 -04:00
Pavlo Tkach 055a52f67e Trim cloud scheduler config url value before submitting (#1988) 2023-04-10 19:05:32 -04:00
sarahcaseybot d17678959c Add tool commands to modify TTLs on a TLD (#1985)
* Add tool commands to modify TTLs on a TLD

* Small changes

* Add an example to the parameter description
2023-04-10 14:43:56 -04:00
Lai Jiang 79ba1b94c4 Add SQL-based DNS refresh processing mechanism (#1971) 2023-04-07 17:31:28 -04:00
gbrodman 33a771b13e Add Java code for storing and using IDN tables per-TLD (#1977)
This includes changes to make sure that we use the proper per-TLD IDN
tables as well as setting/updating/removing them via the Create/Update
TLD commands.
2023-04-06 17:33:23 -04:00
gbrodman bd65c6eee6 Allow a credit of 0 when deleting a domain during a grace period (#1984)
There can be situations (anchor tenants, test tokens, other ways of
getting a domain to cost $0) where we may want to delete a domain during
the add grace period but the credit applied is 0. We should not fail on
those cases.

See b/277115241 for an example.
2023-04-06 15:58:53 -04:00
Ben McIlwain 20c673840e Add a new Unconfusable Latin table (#1981)
This new table has just been approved by ICANN. It is the same as our existing
Extended Latin table, except with the removal of some lesser-used characters
with diacritic marks that are confusable variants.

The filenames for the IDN tables are made explicit to improve code readability.

And this reverses the removal of G with stroke from the existing Extended Latin
table (see PR #1938), so that that table continues to accurately reflect the
state of our previously launched TLDs.

This is the full list of removed characters:

U+00E1                         # LATIN SMALL LETTER A WITH ACUTE
U+0101                         # LATIN SMALL LETTER A WITH MACRON
U+01CE                         # LATIN SMALL LETTER A WITH CARON
U+010B                         # LATIN SMALL LETTER C WITH DOT ABOVE
U+01E7                         # LATIN SMALL LETTER G WITH CARON
U+0123                         # LATIN SMALL LETTER G WITH CEDILLA
U+01E5                         # LATIN SMALL LETTER G WITH STROKE
U+0131                         # LATIN SMALL LETTER DOTLESS I
U+00ED                         # LATIN SMALL LETTER I WITH ACUTE
U+00EF                         # LATIN SMALL LETTER I WITH DIAERESIS
U+01D0                         # LATIN SMALL LETTER I WITH CARON
U+0144                         # LATIN SMALL LETTER N WITH ACUTE
U+014B                         # LATIN SMALL LETTER ENG
U+00F3                         # LATIN SMALL LETTER O WITH ACUTE
U+014D                         # LATIN SMALL LETTER O WITH MACRON
U+01D2                         # LATIN SMALL LETTER O WITH CARON
U+0157                         # LATIN SMALL LETTER R WITH CEDILLA
U+0163                         # LATIN SMALL LETTER T WITH CEDILLA
U+00FA                         # LATIN SMALL LETTER U WITH ACUTE
U+00FC                         # LATIN SMALL LETTER U WITH DIAERESIS
U+01D4                         # LATIN SMALL LETTER U WITH CARON
U+1E83                         # LATIN SMALL LETTER W WITH ACUTE
U+1E81                         # LATIN SMALL LETTER W WITH GRAVE
U+1E85                         # LATIN SMALL LETTER W WITH DIAERESIS
U+1EF3                         # LATIN SMALL LETTER Y WITH GRAVE
U+017C                         # LATIN SMALL LETTER Z WITH DOT ABOVE
2023-04-06 15:49:36 -04:00
Lai Jiang 11c60b8c8f Temporarily disable contact history wipeout (#1982)
Makes the next run at the first Monday of December, which should give us
plenty of time to fix the issue with it wiping out PII in the most recent
contact history.

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/1982)
<!-- Reviewable:end -->
2023-04-06 13:41:51 -04:00
Lai Jiang e330fd1c66 Remove cron.xml from sandbox (#1979)
It is somehow missed in #1965.
2023-04-06 11:30:07 -04:00
Pavlo Tkach 57c17042b6 Transaction manager to not retry inner transactions (#1974) 2023-04-05 16:46:36 -04:00
sarahcaseybot 8623fce119 Check for default tokens in the renew flow (#1978)
* Check for default tokens in the renew flow

* Remove extra check

* Add allowed action
2023-04-05 12:25:09 -04:00
Lai Jiang 7243575433 Remove unused GAE dependencies from NordnUploadAction (#1980) 2023-04-04 16:53:35 -04:00
sarahcaseybot 8eab43d371 Check allowedEppActions when validating tokens (#1972)
* Check allowedEppActions when validating tokens

* Reflect failed tokens in the fee check

* Add tests for domainCheckFlow

* Add hyphens to fee class name

* Add clarifying comment to catch block

* Add specific exception types
2023-04-04 14:29:50 -04:00
sarahcaseybot 34d329c158 Add tool changes to modify allowedEppActions on allocation tokens (#1970)
* Add tool changes to modify allowedEppActions on allocation tokens

* Change enum value error message

* Remove unnecessary variable

* Prevent UNKNOWN command

* Check command name instead of string
2023-03-31 14:37:19 -04:00
Pavlo Tkach 425ecdcd87 Add disable_runner_v2 to pipeline options (#1976) 2023-03-30 17:10:37 -04:00
gbrodman 77ee124374 Add SQL change for per-TLD IDN tables (#1975) 2023-03-28 17:03:22 -04:00
Lai Jiang b9742adc0b Delete cron.xml (#1965)
We've successfully migrated to using Cloud Scheduler.
2023-03-23 14:29:06 -04:00
sarahcaseybot d4cd25c4ae Add pricing logic for allocation tokens in domain renew (#1961)
* Add pricing logic for allocation tokens in domain renew

* Add clarifying comment

* Several fixes

* Add test for renewalPriceBehavior not changing
2023-03-23 14:00:36 -04:00
sarahcaseybot 8b7e938ed6 Add TTL configs to Registry object (#1968)
* Add TTL configs to Registry object

* Change A and AAAA records TTL field name
2023-03-22 13:56:11 -04:00
Pavlo Tkach c216c874b4 Remove app engine deps from Lock flyway change (#1911) 2023-03-20 12:25:12 -04:00
Pavlo Tkach 0ab9471c8d Make cloud scheduler deployment part of gradle deploy (alpha, qa and crash only) (#1969) 2023-03-20 11:10:00 -04:00
sarahcaseybot d482754f66 Implement default tokens for the fee extension in domain check flow (#1950)
* Implement default tokens for the fee extension in domain check

* Add test for expired token

* Add test for alloc token and default token

* Fix formatting

* Always check for default tokens

* Change transaction time to passed in DateTime
2023-03-17 15:41:17 -04:00
sarahcaseybot fe086b43f5 Add TTL columns to the Tld table (#1964)
* Add TTL columns to Tld table

* Change A and AAAA records column name
2023-03-17 11:54:14 -04:00
Lai Jiang 95f1bca3fb Remove Nordn pull queue code (#1966)
The SQL-based flow is verified to work on production.
2023-03-16 17:37:48 -04:00
sarahcaseybot 178a2323d9 Add allowedEppActions to AllocationToken Java classes (#1958)
* Add allowedEppActions field to AllocationToken Java class and converter

* Add getter and setter
2023-03-16 15:45:34 -04:00
Lai Jiang a44aa1378f Create a DnsRefreshRequest entity backed by the corresponding table (#1941)
Also adds a DnsUtils class to deal with adding, polling, and removing
DNS refresh requests (only adding is implemented for now). The class
also takes care of choosing which mechanism to use (pull queue vs. SQL)
based on the current time and the database migration schedule map.
2023-03-16 13:02:20 -04:00
Pavlo Tkach d0f625f70e angular version update 15.1.0 -> 15.2.2 (#1967) 2023-03-16 11:56:38 -04:00
gbrodman fb59874234 Allow for multiple service accounts in authentication (#1963)
When submitting tasks to Cloud Tasks, we will use the built-in OIDC
authentication which runs under the default service account (not the
cloud scheduler service account). We want either to work for app-level
auth.
2023-03-15 10:20:58 -04:00
gbrodman b6083e227f Move CloudTasksUtils to core/ project (#1956)
This does nothing for now, but in the future this will allow us to refer
to the RegistryConfig and/or Service objects from the core project. This
will be necessary when changing CloudTasksUtils to not use the AppEngine
built-in connection (it will need to use a standard HTTP request
instead).
2023-03-14 15:15:05 -04:00
Lai Jiang 5805b6859e Rename process_time column in DnsRefreshRequest (#1962)
Make it explicit that this is the last process time, not a scheduled
future process time.
2023-03-14 14:03:12 -04:00
Pavlo Tkach 3108e8a871 Use builder image as a base for schema-deployer and schema-verifier (#1955) 2023-03-13 15:37:02 -04:00
Pavlo Tkach ec142caf9c Expand ID Token Auth verifier to catch all exceptions (#1960) 2023-03-13 12:12:47 -04:00
Pavlo Tkach e60ad58098 Restore resaveAllEppResourcesPipeline as a cloud task (#1953) 2023-03-13 10:44:25 -04:00
169 changed files with 9578 additions and 5716 deletions
+1
View File
@@ -103,6 +103,7 @@ explodeWar.doLast {
file("${it.explodedAppDirectory}/WEB-INF/lib/tools.jar").setWritable(true)
}
appengineDeployAll.finalizedBy ':cloudSchedulerDeployer'
rootProject.deploy.dependsOn appengineDeployAll
rootProject.stage.dependsOn appengineStage
tasks['war'].dependsOn ':console-webapp:buildConsoleWebappProd'
+15
View File
@@ -558,6 +558,21 @@ task coreDev {
javadocDependentTasks.each { tasks.javadoc.dependsOn(it) }
// Runs the script, which deploys cloud scheduler tasks based on the config
task cloudSchedulerDeployer {
doLast {
def env = environment
if (!prodOrSandboxEnv) {
exec {
commandLine 'go', 'run',
"${rootDir}/release/builder/cloudSchedulerDeployer.go",
"${rootDir}/core/src/main/java/google/registry/env/${env}/default/WEB-INF/cloud-scheduler-tasks.xml",
"domain-registry-${env}"
}
}
}
}
// disable javadoc in subprojects, these will break because they don't have
// the correct classpath (see above).
gradle.taskGraph.whenReady { graph ->
+2165 -1713
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -13,24 +13,24 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^15.1.0",
"@angular/cdk": "^15.0.4",
"@angular/common": "^15.1.0",
"@angular/compiler": "^15.1.0",
"@angular/core": "^15.1.0",
"@angular/forms": "^15.1.0",
"@angular/material": "^15.0.4",
"@angular/platform-browser": "^15.1.0",
"@angular/platform-browser-dynamic": "^15.1.0",
"@angular/router": "^15.1.0",
"@angular/animations": "^15.2.2",
"@angular/cdk": "^15.2.2",
"@angular/common": "^15.2.2",
"@angular/compiler": "^15.2.2",
"@angular/core": "^15.2.2",
"@angular/forms": "^15.2.2",
"@angular/material": "^15.2.2",
"@angular/platform-browser": "^15.2.2",
"@angular/platform-browser-dynamic": "^15.2.2",
"@angular/router": "^15.2.2",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.1.0",
"@angular/cli": "~15.1.0",
"@angular/compiler-cli": "^15.1.0",
"@angular-devkit/build-angular": "^15.2.4",
"@angular/cli": "~15.2.4",
"@angular/compiler-cli": "^15.2.2",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.11.18",
"concurrently": "^7.6.0",
+1
View File
@@ -1028,6 +1028,7 @@ test {
// TODO(weiminyu): Remove dependency on sqlIntegrationTest
}.dependsOn(fragileTest, outcastTest, standardTest, registryToolIntegrationTest, sqlIntegrationTest)
// When we override tests, we also break the cleanTest command.
cleanTest.dependsOn(cleanFragileTest, cleanOutcastTest, cleanStandardTest,
cleanRegistryToolIntegrationTest, cleanSqlIntegrationTest)
@@ -25,7 +25,6 @@ import com.google.common.flogger.FluentLogger;
import google.registry.model.EppResource;
import google.registry.persistence.VKey;
import google.registry.request.Action.Service;
import google.registry.util.CloudTasksUtils;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
@@ -86,6 +85,6 @@ public final class AsyncTaskEnqueuer {
cloudTasksUtils.enqueue(
QUEUE_ASYNC_ACTIONS,
cloudTasksUtils.createPostTaskWithDelay(
ResaveEntityAction.PATH, Service.BACKEND.toString(), params, etaDuration));
ResaveEntityAction.PATH, Service.BACKEND, params, etaDuration));
}
}
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.util;
package google.registry.batch;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
@@ -36,12 +36,18 @@ import com.google.common.net.MediaType;
import com.google.common.net.UrlEscapers;
import com.google.protobuf.ByteString;
import com.google.protobuf.util.Timestamps;
import google.registry.config.RegistryConfig.Config;
import google.registry.request.Action.Service;
import google.registry.util.Clock;
import google.registry.util.CollectionUtils;
import google.registry.util.Retrier;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Optional;
import java.util.Random;
import java.util.function.Supplier;
import javax.inject.Inject;
import org.joda.time.Duration;
/** Utilities for dealing with Cloud Tasks. */
@@ -57,11 +63,12 @@ public class CloudTasksUtils implements Serializable {
private final String locationId;
private final SerializableCloudTasksClient client;
@Inject
public CloudTasksUtils(
Retrier retrier,
Clock clock,
String projectId,
String locationId,
@Config("projectId") String projectId,
@Config("locationId") String locationId,
SerializableCloudTasksClient client) {
this.retrier = retrier;
this.clock = clock;
@@ -108,7 +115,7 @@ public class CloudTasksUtils implements Serializable {
* the worker service</a>
*/
private Task createTask(
String path, HttpMethod method, String service, Multimap<String, String> params) {
String path, HttpMethod method, Service service, Multimap<String, String> params) {
checkArgument(
path != null && !path.isEmpty() && path.charAt(0) == '/',
"The path must start with a '/'.");
@@ -119,7 +126,8 @@ public class CloudTasksUtils implements Serializable {
AppEngineHttpRequest.Builder requestBuilder =
AppEngineHttpRequest.newBuilder()
.setHttpMethod(method)
.setAppEngineRouting(AppEngineRouting.newBuilder().setService(service).build());
.setAppEngineRouting(
AppEngineRouting.newBuilder().setService(service.toString()).build());
if (!CollectionUtils.isNullOrEmpty(params)) {
Escaper escaper = UrlEscapers.urlPathSegmentEscaper();
@@ -165,7 +173,7 @@ public class CloudTasksUtils implements Serializable {
private Task createTaskWithJitter(
String path,
HttpMethod method,
String service,
Service service,
Multimap<String, String> params,
Optional<Integer> jitterSeconds) {
if (!jitterSeconds.isPresent() || jitterSeconds.get() <= 0) {
@@ -199,7 +207,7 @@ public class CloudTasksUtils implements Serializable {
private Task createTaskWithDelay(
String path,
HttpMethod method,
String service,
Service service,
Multimap<String, String> params,
Duration delay) {
if (delay.isEqual(Duration.ZERO)) {
@@ -211,11 +219,11 @@ public class CloudTasksUtils implements Serializable {
.build();
}
public Task createPostTask(String path, String service, Multimap<String, String> params) {
public Task createPostTask(String path, Service service, Multimap<String, String> params) {
return createTask(path, HttpMethod.POST, service, params);
}
public Task createGetTask(String path, String service, Multimap<String, String> params) {
public Task createGetTask(String path, Service service, Multimap<String, String> params) {
return createTask(path, HttpMethod.GET, service, params);
}
@@ -224,7 +232,7 @@ public class CloudTasksUtils implements Serializable {
*/
public Task createPostTaskWithJitter(
String path,
String service,
Service service,
Multimap<String, String> params,
Optional<Integer> jitterSeconds) {
return createTaskWithJitter(path, HttpMethod.POST, service, params, jitterSeconds);
@@ -235,7 +243,7 @@ public class CloudTasksUtils implements Serializable {
*/
public Task createGetTaskWithJitter(
String path,
String service,
Service service,
Multimap<String, String> params,
Optional<Integer> jitterSeconds) {
return createTaskWithJitter(path, HttpMethod.GET, service, params, jitterSeconds);
@@ -243,13 +251,13 @@ public class CloudTasksUtils implements Serializable {
/** Create a {@link Task} via HTTP.POST that will be delayed for {@code delay}. */
public Task createPostTaskWithDelay(
String path, String service, Multimap<String, String> params, Duration delay) {
String path, Service service, Multimap<String, String> params, Duration delay) {
return createTaskWithDelay(path, HttpMethod.POST, service, params, delay);
}
/** Create a {@link Task} via HTTP.GET that will be delayed for {@code delay}. */
public Task createGetTaskWithDelay(
String path, String service, Multimap<String, String> params, Duration delay) {
String path, Service service, Multimap<String, String> params, Duration delay) {
return createTaskWithDelay(path, HttpMethod.GET, service, params, delay);
}
@@ -32,7 +32,7 @@ import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.EppResourceUtils;
import google.registry.model.domain.Domain;
@@ -98,7 +98,7 @@ public class DeleteProberDataAction implements Runnable {
/** Number of domains to retrieve and delete per SQL transaction. */
private static final int BATCH_SIZE = 1000;
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject
@Parameter(PARAM_DRY_RUN)
@@ -264,6 +264,6 @@ public class DeleteProberDataAction implements Runnable {
// messages, or auto-renews because those will all be hard-deleted the next time the job runs
// anyway.
tm().putAll(ImmutableList.of(deletedDomain, historyEntry));
dnsQueue.addDomainRefreshTask(deletedDomain.getDomainName());
dnsUtils.requestDomainDnsRefresh(deletedDomain.getDomainName());
}
}
@@ -36,6 +36,7 @@ import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.flows.custom.CustomLogicFactoryModule;
import google.registry.flows.custom.CustomLogicModule;
import google.registry.flows.domain.DomainPricingLogic;
import google.registry.flows.domain.DomainPricingLogic.AllocationTokenInvalidForPremiumNameException;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent.Cancellation;
import google.registry.model.billing.BillingEvent.Flag;
@@ -53,6 +54,7 @@ import google.registry.util.Clock;
import google.registry.util.SystemClock;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Optional;
import java.util.Set;
import javax.inject.Singleton;
import org.apache.beam.sdk.Pipeline;
@@ -383,24 +385,31 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
// It is OK to always create a OneTime, even though the domain might be deleted or transferred
// later during autorenew grace period, as a cancellation will always be written out in those
// instances.
OneTime oneTime =
new OneTime.Builder()
.setBillingTime(billingTime)
.setRegistrarId(recurring.getRegistrarId())
// Determine the cost for a one-year renewal.
.setCost(
domainPricingLogic
.getRenewPrice(tld, recurring.getTargetId(), eventTime, 1, recurring)
.getRenewCost())
.setEventTime(eventTime)
.setFlags(union(recurring.getFlags(), Flag.SYNTHETIC))
.setDomainHistory(historyEntry)
.setPeriodYears(1)
.setReason(recurring.getReason())
.setSyntheticCreationTime(endTime)
.setCancellationMatchingBillingEvent(recurring)
.setTargetId(recurring.getTargetId())
.build();
OneTime oneTime = null;
try {
oneTime =
new OneTime.Builder()
.setBillingTime(billingTime)
.setRegistrarId(recurring.getRegistrarId())
// Determine the cost for a one-year renewal.
.setCost(
domainPricingLogic
.getRenewPrice(
tld, recurring.getTargetId(), eventTime, 1, recurring, Optional.empty())
.getRenewCost())
.setEventTime(eventTime)
.setFlags(union(recurring.getFlags(), Flag.SYNTHETIC))
.setDomainHistory(historyEntry)
.setPeriodYears(1)
.setReason(recurring.getReason())
.setSyntheticCreationTime(endTime)
.setCancellationMatchingBillingEvent(recurring)
.setTargetId(recurring.getTargetId())
.build();
} catch (AllocationTokenInvalidForPremiumNameException e) {
// This should not be reached since we are not using an allocation token
return;
}
results.add(oneTime);
}
results.add(
@@ -26,6 +26,7 @@ import com.google.auto.value.AutoValue;
import com.google.cloud.storage.BlobId;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
import google.registry.gcs.GcsUtils;
import google.registry.keyring.api.PgpHelper;
import google.registry.model.common.Cursor;
@@ -46,7 +47,6 @@ import google.registry.rde.RdeUtil;
import google.registry.request.Action.Service;
import google.registry.request.RequestParameters;
import google.registry.tldconfig.idn.IdnTableEnum;
import google.registry.util.CloudTasksUtils;
import google.registry.xjc.rdeheader.XjcRdeHeader;
import google.registry.xjc.rdeheader.XjcRdeHeaderElement;
import google.registry.xml.ValidationMode;
@@ -306,7 +306,7 @@ public class RdeIO {
RDE_UPLOAD_QUEUE,
cloudTasksUtils.createPostTaskWithDelay(
RdeUploadAction.PATH,
Service.BACKEND.getServiceId(),
Service.BACKEND,
ImmutableMultimap.of(
RequestParameters.PARAM_TLD,
key.tld(),
@@ -318,7 +318,7 @@ public class RdeIO {
BRDA_QUEUE,
cloudTasksUtils.createPostTaskWithDelay(
BrdaCopyAction.PATH,
Service.BACKEND.getServiceId(),
Service.BACKEND,
ImmutableMultimap.of(
RequestParameters.PARAM_TLD,
key.tld(),
@@ -38,6 +38,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
import dagger.BindsInstance;
import dagger.Component;
import google.registry.batch.CloudTasksUtils;
import google.registry.beam.common.RegistryJpaIO;
import google.registry.beam.common.RegistryPipelineOptions;
import google.registry.config.CloudTasksUtilsModule;
@@ -62,7 +63,6 @@ import google.registry.rde.DepositFragment;
import google.registry.rde.PendingDeposit;
import google.registry.rde.PendingDeposit.PendingDepositCoder;
import google.registry.rde.RdeMarshaller;
import google.registry.util.CloudTasksUtils;
import google.registry.util.UtilsModule;
import google.registry.xml.ValidationMode;
import java.io.ByteArrayInputStream;
@@ -19,18 +19,15 @@ import com.google.cloud.tasks.v2.CloudTasksClient;
import com.google.cloud.tasks.v2.CloudTasksSettings;
import dagger.Module;
import dagger.Provides;
import google.registry.batch.CloudTasksUtils;
import google.registry.batch.CloudTasksUtils.GcpCloudTasksClient;
import google.registry.batch.CloudTasksUtils.SerializableCloudTasksClient;
import google.registry.config.CredentialModule.DefaultCredential;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import google.registry.util.CloudTasksUtils.GcpCloudTasksClient;
import google.registry.util.CloudTasksUtils.SerializableCloudTasksClient;
import google.registry.util.GoogleCredentialsBundle;
import google.registry.util.Retrier;
import java.io.IOException;
import java.io.Serializable;
import java.util.function.Supplier;
import javax.inject.Singleton;
/**
* A {@link Module} that provides {@link CloudTasksUtils}.
@@ -41,17 +38,6 @@ import javax.inject.Singleton;
@Module
public abstract class CloudTasksUtilsModule {
@Singleton
@Provides
public static CloudTasksUtils provideCloudTasksUtils(
@Config("projectId") String projectId,
@Config("locationId") String locationId,
SerializableCloudTasksClient client,
Retrier retrier,
Clock clock) {
return new CloudTasksUtils(retrier, clock, projectId, locationId, client);
}
// Provides a supplier instead of using a Dagger @Provider because the latter is not serializable.
@Provides
public static Supplier<CloudTasksClient> provideCloudTasksClientSupplier(
@@ -32,6 +32,8 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import dagger.Module;
import dagger.Provides;
import google.registry.dns.ReadDnsRefreshRequestsAction;
import google.registry.model.common.DnsRefreshRequest;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.util.YamlUtils;
import java.lang.annotation.Documented;
@@ -107,9 +109,9 @@ public final class RegistryConfig {
}
@Provides
@Config("cloudSchedulerServiceAccountEmail")
public static String provideCloudSchedulerServiceAccountEmail(RegistryConfigSettings config) {
return config.gcpProject.cloudSchedulerServiceAccountEmail;
@Config("serviceAccountEmails")
public static ImmutableList<String> provideServiceAccountEmails(RegistryConfigSettings config) {
return ImmutableList.copyOf(config.gcpProject.serviceAccountEmails);
}
@Provides
@@ -294,9 +296,9 @@ public final class RegistryConfig {
/**
* The maximum number of domain and host updates to batch together to send to
* PublishDnsUpdatesAction, to avoid exceeding AppEngine's limits.
* PublishDnsUpdatesAction, to avoid exceeding HTTP request timeout limits.
*
* @see google.registry.dns.ReadDnsQueueAction
* @see google.registry.dns.ReadDnsRefreshRequestsAction
*/
@Provides
@Config("dnsTldUpdateBatchSize")
@@ -327,23 +329,30 @@ public final class RegistryConfig {
}
/**
* The requested maximum duration for ReadDnsQueueAction.
* The requested maximum duration for {@link ReadDnsRefreshRequestsAction}.
*
* <p>ReadDnsQueueAction reads update tasks from the dns-pull queue. It will continue reading
* tasks until either the queue is empty, or this duration has passed.
* <p>{@link ReadDnsRefreshRequestsAction} reads refresh requests from {@link DnsRefreshRequest}
* It will continue reading requests until either no requests exist that matche the condition,
* or this duration has passed.
*
* <p>This time is the maximum duration between the first and last attempt to lease tasks from
* the dns-pull queue. The actual running time might be slightly longer, as we process the
* tasks.
* <p>This time is the maximum duration between the first and last attempt to read requests from
* {@link DnsRefreshRequest}. The actual running time might be slightly longer, as we process
* the requests.
*
* <p>This value should be less than the cron-job repeat rate for ReadDnsQueueAction, to make
* sure we don't have multiple ReadDnsActions leasing tasks simultaneously.
* <p>The requests that are read will not be read again by any action until after this period
* has passed, so concurrent runs (or runs that are very close to each other) of {@link
* ReadDnsRefreshRequestsAction} will not keep reading the same requests with the earliest
* request time.
*
* @see google.registry.dns.ReadDnsQueueAction
* <p>Still, this value should ideally be less than the cloud scheduler job repeat rate for
* {@link ReadDnsRefreshRequestsAction}, to not waste resources on multiple actions running at
* the same time.
*
* <p>see google.registry.dns.ReadDnsRefreshRequestsAction
*/
@Provides
@Config("readDnsQueueActionRuntime")
public static Duration provideReadDnsQueueRuntime() {
@Config("readDnsRefreshRequestsActionRuntime")
public static Duration provideReadDnsRefreshRequestsRuntime() {
return Duration.standardSeconds(45);
}
@@ -930,7 +939,7 @@ public final class RegistryConfig {
* <p>Note that this uses {@code @Named} instead of {@code @Config} so that it can be used from
* the low-level util package, which cannot have a dependency on the config package.
*
* @see google.registry.util.CloudTasksUtils
* @see google.registry.batch.CloudTasksUtils
*/
@Provides
@Named("transientFailureRetries")
@@ -54,7 +54,7 @@ public class RegistryConfigSettings {
public String backendServiceUrl;
public String toolsServiceUrl;
public String pubapiServiceUrl;
public String cloudSchedulerServiceAccountEmail;
public List<String> serviceAccountEmails;
}
/** Configuration options for OAuth settings for authenticating users. */
@@ -22,8 +22,11 @@ gcpProject:
backendServiceUrl: https://localhost
toolsServiceUrl: https://localhost
pubapiServiceUrl: https://localhost
# Service account used by Cloud Scheduler to send authenticated requests.
cloudSchedulerServiceAccountEmail: cloud-scheduler-email@email.com
# Service accounts eligible for authorization (e.g. default service account,
# account used by Cloud Scheduler) to send authenticated requests.
serviceAccountEmails:
- default-service-account-email@email.com
- cloud-scheduler-email@email.com
gSuite:
# Publicly accessible domain name of the running G Suite instance.
@@ -38,6 +38,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.Parameter;
@@ -45,7 +46,6 @@ import google.registry.request.ParameterMap;
import google.registry.request.RequestParameters;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.CloudTasksUtils;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;
@@ -158,6 +158,6 @@ public final class TldFanoutAction implements Runnable {
params.put(RequestParameters.PARAM_TLD, tld);
}
return cloudTasksUtils.createPostTaskWithJitter(
endpoint, Service.BACKEND.toString(), params, jitterSeconds);
endpoint, Service.BACKEND, params, jitterSeconds);
}
}
@@ -34,5 +34,8 @@ public class DnsConstants {
public static final String DNS_TARGET_CREATE_TIME_PARAM = "Create-Time";
/** The possible values of the {@code DNS_TARGET_TYPE_PARAM} parameter. */
public enum TargetType { DOMAIN, HOST, ZONE }
public enum TargetType {
DOMAIN,
HOST
}
}
@@ -19,6 +19,8 @@ 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.extractOptionalIntParameter;
import static google.registry.request.RequestParameters.extractOptionalParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter;
import static google.registry.request.RequestParameters.extractSetOfParameters;
@@ -33,6 +35,7 @@ import google.registry.dns.DnsConstants.TargetType;
import google.registry.dns.writer.DnsWriterZone;
import google.registry.request.Parameter;
import google.registry.request.RequestParameters;
import java.util.Optional;
import java.util.Set;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
@@ -48,7 +51,10 @@ public abstract class DnsModule {
public static final String PARAM_DOMAINS = "domains";
public static final String PARAM_HOSTS = "hosts";
public static final String PARAM_PUBLISH_TASK_ENQUEUED = "enqueued";
public static final String PARAM_REFRESH_REQUEST_CREATED = "itemsCreated";
public static final String PARAM_REFRESH_REQUEST_TIME = "requestTime";
// This parameter cannot be named "jitterSeconds", which will be read by TldFanoutAction and not
// be passed down to actions.
public static final String PARAM_DNS_JITTER_SECONDS = "dnsJitterSeconds";
@Binds
@DnsWriterZone
@@ -83,10 +89,13 @@ public abstract class DnsModule {
return DateTime.parse(extractRequiredParameter(req, PARAM_PUBLISH_TASK_ENQUEUED));
}
// TODO: Retire the old header after DNS pull queue migration.
@Provides
@Parameter(PARAM_REFRESH_REQUEST_CREATED)
@Parameter(PARAM_REFRESH_REQUEST_TIME)
static DateTime provideItemsCreateTime(HttpServletRequest req) {
return DateTime.parse(extractRequiredParameter(req, PARAM_REFRESH_REQUEST_CREATED));
return DateTime.parse(
extractOptionalParameter(req, "itemsCreated")
.orElse(extractRequiredParameter(req, PARAM_REFRESH_REQUEST_TIME)));
}
@Provides
@@ -125,6 +134,12 @@ public abstract class DnsModule {
return extractRequiredParameter(req, PARAM_HOST_KEY);
}
@Provides
@Parameter(PARAM_DNS_JITTER_SECONDS)
static Optional<Integer> provideJitterSeconds(HttpServletRequest req) {
return extractOptionalIntParameter(req, PARAM_DNS_JITTER_SECONDS);
}
@Provides
@Parameter("domainOrHostName")
static String provideName(HttpServletRequest req) {
@@ -37,6 +37,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.InternetDomainName;
import com.google.common.util.concurrent.RateLimiter;
import google.registry.config.RegistryConfig;
import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.tld.Registries;
import google.registry.util.Clock;
@@ -54,13 +55,13 @@ import org.joda.time.Duration;
* <p>This includes a {@link RateLimiter} to limit the {@link Queue#leaseTasks} call rate to 9 QPS,
* to stay under the 10 QPS limit for this function.
*
* <p>Note that overlapping calls to {@link ReadDnsQueueAction} (the only place where
* {@link DnsQueue#leaseTasks} is used) will have different rate limiters, so they could exceed the
* allowed rate. This should be rare though - because {@link DnsQueue#leaseTasks} is only used in
* {@link ReadDnsQueueAction}, which is run as a cron job with running time shorter than the cron
* repeat time - meaning there should never be two instances running at once.
* <p>Note that overlapping calls to {@link ReadDnsQueueAction} (the only place where {@link
* DnsQueue#leaseTasks} is used) will have different rate limiters, so they could exceed the allowed
* rate. This should be rare though - because {@link DnsQueue#leaseTasks} is only used in {@link
* ReadDnsQueueAction}, which is run as a cron job with running time shorter than the cron repeat
* time - meaning there should never be two instances running at once.
*
* @see google.registry.config.RegistryConfig.ConfigModule#provideReadDnsQueueRuntime
* @see RegistryConfig.ConfigModule#provideReadDnsRefreshRequestsRuntime()
*/
public class DnsQueue {
@@ -77,11 +78,15 @@ public class DnsQueue {
private static final RateLimiter rateLimiter = RateLimiter.create(9);
@Inject
public DnsQueue(@Named(DNS_PULL_QUEUE_NAME) Queue queue, Clock clock) {
DnsQueue(@Named(DNS_PULL_QUEUE_NAME) Queue queue, Clock clock) {
this.queue = queue;
this.clock = clock;
}
Clock getClock() {
return clock;
}
@VisibleForTesting
public static DnsQueue createForTesting(Clock clock) {
return new DnsQueue(getQueue(DNS_PULL_QUEUE_NAME), clock);
@@ -108,7 +113,7 @@ public class DnsQueue {
}
/** Adds a task to the queue to refresh the DNS information for the specified subordinate host. */
public TaskHandle addHostRefreshTask(String hostName) {
TaskHandle addHostRefreshTask(String hostName) {
Optional<InternetDomainName> tld = Registries.findTldForName(InternetDomainName.from(hostName));
checkArgument(
tld.isPresent(), String.format("%s is not a subordinate host to a known tld", hostName));
@@ -116,12 +121,12 @@ public class DnsQueue {
}
/** Enqueues a task to refresh DNS for the specified domain now. */
public TaskHandle addDomainRefreshTask(String domainName) {
TaskHandle addDomainRefreshTask(String domainName) {
return addDomainRefreshTask(domainName, Duration.ZERO);
}
/** Enqueues a task to refresh DNS for the specified domain at some point in the future. */
public TaskHandle addDomainRefreshTask(String domainName, Duration countdown) {
TaskHandle addDomainRefreshTask(String domainName, Duration countdown) {
return addToQueue(
TargetType.DOMAIN,
domainName,
@@ -129,11 +134,6 @@ public class DnsQueue {
countdown);
}
/** Adds a task to the queue to refresh the DNS information for the specified zone. */
public TaskHandle addZoneRefreshTask(String zoneName) {
return addToQueue(TargetType.ZONE, zoneName, zoneName, Duration.ZERO);
}
/**
* Returns the maximum number of tasks that can be leased with {@link #leaseTasks}.
*
@@ -0,0 +1,133 @@
// Copyright 2023 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.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
import com.google.common.net.InternetDomainName;
import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.common.DnsRefreshRequest;
import google.registry.model.tld.Registries;
import java.util.Collection;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/** Utility class to handle DNS refresh requests. */
// TODO: Make this a static util function once we are done with the DNS pull queue migration.
public class DnsUtils {
private final DnsQueue dnsQueue;
@Inject
DnsUtils(DnsQueue dnsQueue) {
this.dnsQueue = dnsQueue;
}
private void requestDnsRefresh(String name, TargetType type, Duration delay) {
// Throws an IllegalArgumentException if the name is not under a managed TLD -- we only update
// DNS for names that are under our management.
String tld = Registries.findTldForNameOrThrow(InternetDomainName.from(name)).toString();
if (usePullQueue()) {
if (TargetType.HOST.equals(type)) {
dnsQueue.addHostRefreshTask(name);
} else {
dnsQueue.addDomainRefreshTask(name, delay);
}
} else {
tm().transact(
() ->
tm().insert(
new DnsRefreshRequest(
type, name, tld, tm().getTransactionTime().plus(delay))));
}
}
public void requestDomainDnsRefresh(String domainName, Duration delay) {
requestDnsRefresh(domainName, TargetType.DOMAIN, delay);
}
public void requestDomainDnsRefresh(String domainName) {
requestDomainDnsRefresh(domainName, Duration.ZERO);
}
public void requestHostDnsRefresh(String hostName) {
requestDnsRefresh(hostName, TargetType.HOST, Duration.ZERO);
}
/**
* Returns pending DNS update requests that need further processing up to batch size, in ascending
* order of their request time, and updates their processing time to now.
*
* <p>The criteria to pick the requests to include are:
*
* <ul>
* <li>They are for the given TLD.
* <li>Their request time is not in the future.
* <li>The last time they were processed is before the cooldown period.
* </ul>
*/
public ImmutableList<DnsRefreshRequest> readAndUpdateRequestsWithLatestProcessTime(
String tld, Duration cooldown, int batchSize) {
return tm().transact(
() -> {
DateTime transactionTime = tm().getTransactionTime();
ImmutableList<DnsRefreshRequest> requests =
tm().query(
"FROM DnsRefreshRequest WHERE tld = :tld "
+ "AND requestTime <= :now AND lastProcessTime < :cutoffTime "
+ "ORDER BY requestTime ASC, id ASC",
DnsRefreshRequest.class)
.setParameter("tld", tld)
.setParameter("now", transactionTime)
.setParameter("cutoffTime", transactionTime.minus(cooldown))
.setMaxResults(batchSize)
.getResultStream()
// Note that the process time is when the request was last read, batched and
// queued up for publishing, not when it is actually published by the DNS
// writer. This timestamp acts as a cooldown so the same request will not be
// retried too frequently. See DnsRefreshRequest.getLastProcessTime for a
// detailed explaination.
.map(e -> e.updateProcessTime(transactionTime))
.collect(toImmutableList());
tm().updateAll(requests);
return requests;
});
}
/**
* Removes the requests that have been processed.
*
* <p>Note that if a request entity has already been deleted, the method still succeeds without
* error because all we care about is that it no longer exists after the method runs.
*/
public void deleteRequests(Collection<DnsRefreshRequest> requests) {
tm().transact(
() ->
tm().delete(
requests.stream()
.map(DnsRefreshRequest::createVKey)
.collect(toImmutableList())));
}
private boolean usePullQueue() {
return !DatabaseMigrationStateSchedule.getValueAtTime(dnsQueue.getClock().nowUtc())
.equals(MigrationState.DNS_SQL);
}
}
@@ -22,7 +22,7 @@ import static google.registry.dns.DnsModule.PARAM_HOSTS;
import static google.registry.dns.DnsModule.PARAM_LOCK_INDEX;
import static google.registry.dns.DnsModule.PARAM_NUM_PUBLISH_LOCKS;
import static google.registry.dns.DnsModule.PARAM_PUBLISH_TASK_ENQUEUED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_CREATED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_TIME;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.RequestParameters.PARAM_TLD;
@@ -35,6 +35,7 @@ import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.InternetDomainName;
import dagger.Lazy;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.dns.DnsMetrics.ActionStatus;
import google.registry.dns.DnsMetrics.CommitStatus;
@@ -54,7 +55,6 @@ import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.lock.LockHandler;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import google.registry.util.DomainNameUtils;
import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService;
@@ -85,7 +85,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final DnsQueue dnsQueue;
private final DnsUtils dnsUtils;
private final DnsWriterProxy dnsWriterProxy;
private final DnsMetrics dnsMetrics;
private final Duration timeout;
@@ -124,7 +124,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
public PublishDnsUpdatesAction(
@Parameter(PARAM_DNS_WRITER) String dnsWriter,
@Parameter(PARAM_PUBLISH_TASK_ENQUEUED) DateTime enqueuedTime,
@Parameter(PARAM_REFRESH_REQUEST_CREATED) DateTime itemsCreateTime,
@Parameter(PARAM_REFRESH_REQUEST_TIME) DateTime itemsCreateTime,
@Parameter(PARAM_LOCK_INDEX) int lockIndex,
@Parameter(PARAM_NUM_PUBLISH_LOCKS) int numPublishLocks,
@Parameter(PARAM_DOMAINS) Set<String> domains,
@@ -139,7 +139,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress,
@Header(APP_ENGINE_RETRY_HEADER) Optional<Integer> appEngineRetryCount,
@Header(CLOUD_TASKS_RETRY_HEADER) Optional<Integer> cloudTasksRetryCount,
DnsQueue dnsQueue,
DnsUtils dnsUtils,
DnsWriterProxy dnsWriterProxy,
DnsMetrics dnsMetrics,
LockHandler lockHandler,
@@ -147,7 +147,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
CloudTasksUtils cloudTasksUtils,
SendEmailService sendEmailService,
Response response) {
this.dnsQueue = dnsQueue;
this.dnsUtils = dnsUtils;
this.dnsWriterProxy = dnsWriterProxy;
this.dnsMetrics = dnsMetrics;
this.timeout = timeout;
@@ -339,14 +339,14 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
DNS_PUBLISH_PUSH_QUEUE_NAME,
cloudTasksUtils.createPostTask(
PATH,
Service.BACKEND.toString(),
Service.BACKEND,
ImmutableMultimap.<String, String>builder()
.put(PARAM_TLD, tld)
.put(PARAM_DNS_WRITER, dnsWriter)
.put(PARAM_LOCK_INDEX, Integer.toString(lockIndex))
.put(PARAM_NUM_PUBLISH_LOCKS, Integer.toString(numPublishLocks))
.put(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.put(PARAM_REFRESH_REQUEST_CREATED, itemsCreateTime.toString())
.put(PARAM_REFRESH_REQUEST_TIME, itemsCreateTime.toString())
.put(PARAM_DOMAINS, Joiner.on(",").join(domains))
.put(PARAM_HOSTS, Joiner.on(",").join(hosts))
.build()));
@@ -356,10 +356,10 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
private void requeueBatch() {
logger.atInfo().log("Requeueing batch for retry.");
for (String domain : nullToEmpty(domains)) {
dnsQueue.addDomainRefreshTask(domain);
dnsUtils.requestDomainDnsRefresh(domain);
}
for (String host : nullToEmpty(hosts)) {
dnsQueue.addHostRefreshTask(host);
dnsUtils.requestHostDnsRefresh(host);
}
}
@@ -26,7 +26,7 @@ import static google.registry.dns.DnsModule.PARAM_HOSTS;
import static google.registry.dns.DnsModule.PARAM_LOCK_INDEX;
import static google.registry.dns.DnsModule.PARAM_NUM_PUBLISH_LOCKS;
import static google.registry.dns.DnsModule.PARAM_PUBLISH_TASK_ENQUEUED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_CREATED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_TIME;
import static google.registry.request.RequestParameters.PARAM_TLD;
import static google.registry.util.DomainNameUtils.getSecondLevelDomain;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -44,6 +44,7 @@ import com.google.common.collect.Ordering;
import com.google.common.flogger.FluentLogger;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.tld.Registries;
@@ -53,7 +54,6 @@ import google.registry.request.Action.Service;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Comparator;
@@ -109,7 +109,7 @@ public final class ReadDnsQueueAction implements Runnable {
@Inject
ReadDnsQueueAction(
@Config("dnsTldUpdateBatchSize") int tldUpdateBatchSize,
@Config("readDnsQueueActionRuntime") Duration requestedMaximumDuration,
@Config("readDnsRefreshRequestsActionRuntime") Duration requestedMaximumDuration,
@Parameter(PARAM_JITTER_SECONDS) Optional<Integer> jitterSeconds,
Clock clock,
DnsQueue dnsQueue,
@@ -372,14 +372,14 @@ public final class ReadDnsQueueAction implements Runnable {
Task task =
cloudTasksUtils.createPostTaskWithJitter(
PublishDnsUpdatesAction.PATH,
Service.BACKEND.toString(),
Service.BACKEND,
ImmutableMultimap.<String, String>builder()
.put(PARAM_TLD, tld)
.put(PARAM_DNS_WRITER, dnsWriter)
.put(PARAM_LOCK_INDEX, Integer.toString(lockIndex))
.put(PARAM_NUM_PUBLISH_LOCKS, Integer.toString(numPublishLocks))
.put(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.put(PARAM_REFRESH_REQUEST_CREATED, earliestCreateTime.toString())
.put(PARAM_REFRESH_REQUEST_TIME, earliestCreateTime.toString())
.put(
PARAM_DOMAINS,
chunk.stream()
@@ -0,0 +1,206 @@
// Copyright 2023 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.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static google.registry.dns.DnsConstants.DNS_PUBLISH_PUSH_QUEUE_NAME;
import static google.registry.dns.DnsModule.PARAM_DNS_JITTER_SECONDS;
import static google.registry.dns.DnsModule.PARAM_DNS_WRITER;
import static google.registry.dns.DnsModule.PARAM_DOMAINS;
import static google.registry.dns.DnsModule.PARAM_HOSTS;
import static google.registry.dns.DnsModule.PARAM_LOCK_INDEX;
import static google.registry.dns.DnsModule.PARAM_NUM_PUBLISH_LOCKS;
import static google.registry.dns.DnsModule.PARAM_PUBLISH_TASK_ENQUEUED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_TIME;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.RequestParameters.PARAM_TLD;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DomainNameUtils.getSecondLevelDomain;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.cloud.tasks.v2.Task;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.common.DnsRefreshRequest;
import google.registry.model.tld.Registry;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import java.util.Collection;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* Action for fanning out DNS refresh tasks by TLD, using data taken from {@link DnsRefreshRequest}
* table.
*/
@Action(
service = Service.BACKEND,
path = "/_dr/task/readDnsRefreshRequests",
automaticallyPrintOk = true,
method = POST,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
public final class ReadDnsRefreshRequestsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final int tldUpdateBatchSize;
private final Duration requestedMaximumDuration;
private final Optional<Integer> jitterSeconds;
private final String tld;
private final Clock clock;
private final DnsUtils dnsUtils;
private final HashFunction hashFunction;
private final CloudTasksUtils cloudTasksUtils;
@Inject
ReadDnsRefreshRequestsAction(
@Config("dnsTldUpdateBatchSize") int tldUpdateBatchSize,
@Config("readDnsRefreshRequestsActionRuntime") Duration requestedMaximumDuration,
@Parameter(PARAM_DNS_JITTER_SECONDS) Optional<Integer> jitterSeconds,
@Parameter(PARAM_TLD) String tld,
Clock clock,
DnsUtils dnsUtils,
HashFunction hashFunction,
CloudTasksUtils cloudTasksUtils) {
this.tldUpdateBatchSize = tldUpdateBatchSize;
this.requestedMaximumDuration = requestedMaximumDuration;
this.jitterSeconds = jitterSeconds;
this.tld = tld;
this.clock = clock;
this.dnsUtils = dnsUtils;
this.hashFunction = hashFunction;
this.cloudTasksUtils = cloudTasksUtils;
}
/**
* Reads requests up to the maximum requested runtime, and enqueues update batches from the these
* requests.
*/
@Override
public void run() {
if (Registry.get(tld).getDnsPaused()) {
logger.atInfo().log("The queue updated is paused for TLD: %s.", tld);
return;
}
DateTime requestedEndTime = clock.nowUtc().plus(requestedMaximumDuration);
// See getLockIndex(), requests are evenly distributed to [1, numDnsPublishLocks], so each
// bucket would be roughly the size of tldUpdateBatchSize.
int processBatchSize = tldUpdateBatchSize * Registry.get(tld).getNumDnsPublishLocks();
while (requestedEndTime.isAfter(clock.nowUtc())) {
ImmutableList<DnsRefreshRequest> requests =
dnsUtils.readAndUpdateRequestsWithLatestProcessTime(
tld, requestedMaximumDuration, processBatchSize);
logger.atInfo().log("Read %d DNS update requests for TLD %s.", requests.size(), tld);
if (!requests.isEmpty()) {
processRequests(requests);
}
if (requests.size() < processBatchSize) {
return;
}
}
}
/**
* Subdivides {@link DnsRefreshRequest} into buckets by lock index, enqueue a Cloud Tasks task per
* bucket, and then delete the requests in each bucket.
*/
void processRequests(Collection<DnsRefreshRequest> requests) {
int numPublishLocks = Registry.get(tld).getNumDnsPublishLocks();
requests.stream()
.collect(
toImmutableSetMultimap(
request -> getLockIndex(numPublishLocks, request), request -> request))
.asMap()
.forEach(
(lockIndex, bucketedRequests) -> {
try {
enqueueUpdates(lockIndex, numPublishLocks, bucketedRequests);
dnsUtils.deleteRequests(bucketedRequests);
logger.atInfo().log(
"Processed %d DNS update requests for TLD %s.", bucketedRequests.size(), tld);
} catch (Exception e) {
// Log but continue to process the next bucket. The failed tasks will NOT be
// deleted and will be retried after the cooldown period has passed.
logger.atSevere().withCause(e).log(
"Error processing DNS update requests: %s", bucketedRequests);
}
});
}
/**
* Returns the lock index for a given {@link DnsRefreshRequest}.
*
* <p>We hash the second level domain for all records, to group in-bailiwick hosts (the only ones
* we refresh DNS for) with their superordinate domains. We use consistent hashing to determine
* the lock index because it gives us [0,N) bucketing properties out of the box, then add 1 to
* make indexes within [1,N].
*/
int getLockIndex(int numPublishLocks, DnsRefreshRequest request) {
String domain = getSecondLevelDomain(request.getName(), tld);
return Hashing.consistentHash(hashFunction.hashString(domain, UTF_8), numPublishLocks) + 1;
}
/** Creates DNS refresh tasks for all writers for the tld within a lock index. */
void enqueueUpdates(int lockIndex, int numPublishLocks, Collection<DnsRefreshRequest> requests) {
ImmutableList.Builder<String> domainsBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<String> hostsBuilder = new ImmutableList.Builder<>();
DateTime earliestRequestTime = END_OF_TIME;
for (DnsRefreshRequest request : requests) {
if (request.getRequestTime().isBefore(earliestRequestTime)) {
earliestRequestTime = request.getRequestTime();
}
String name = request.getName();
if (request.getType().equals(TargetType.DOMAIN)) {
domainsBuilder.add(name);
} else {
hostsBuilder.add(name);
}
}
ImmutableList<String> domains = domainsBuilder.build();
ImmutableList<String> hosts = hostsBuilder.build();
for (String dnsWriter : Registry.get(tld).getDnsWriters()) {
Task task =
cloudTasksUtils.createPostTaskWithJitter(
PublishDnsUpdatesAction.PATH,
Service.BACKEND,
ImmutableMultimap.<String, String>builder()
.put(PARAM_TLD, tld)
.put(PARAM_DNS_WRITER, dnsWriter)
.put(PARAM_LOCK_INDEX, Integer.toString(lockIndex))
.put(PARAM_NUM_PUBLISH_LOCKS, Integer.toString(numPublishLocks))
.put(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.put(PARAM_REFRESH_REQUEST_TIME, earliestRequestTime.toString())
.put(PARAM_DOMAINS, Joiner.on(',').join(domains))
.put(PARAM_HOSTS, Joiner.on(',').join(hosts))
.build(),
jitterSeconds);
cloudTasksUtils.enqueue(DNS_PUBLISH_PUSH_QUEUE_NAME, task);
logger.atInfo().log(
"Enqueued DNS update request for (TLD %s, lock %d) with %d domains and %d hosts.",
tld, lockIndex, domains.size(), hosts.size());
}
}
}
@@ -39,7 +39,7 @@ import javax.inject.Inject;
public final class RefreshDnsAction implements Runnable {
private final Clock clock;
private final DnsQueue dnsQueue;
private final DnsUtils dnsUtils;
private final String domainOrHostName;
private final TargetType type;
@@ -48,11 +48,11 @@ public final class RefreshDnsAction implements Runnable {
@Parameter("domainOrHostName") String domainOrHostName,
@Parameter("type") TargetType type,
Clock clock,
DnsQueue dnsQueue) {
DnsUtils dnsUtils) {
this.domainOrHostName = domainOrHostName;
this.type = type;
this.clock = clock;
this.dnsQueue = dnsQueue;
this.dnsUtils = dnsUtils;
}
@Override
@@ -63,11 +63,11 @@ public final class RefreshDnsAction implements Runnable {
switch (type) {
case DOMAIN:
loadAndVerifyExistence(Domain.class, domainOrHostName);
dnsQueue.addDomainRefreshTask(domainOrHostName);
dnsUtils.requestDomainDnsRefresh(domainOrHostName);
break;
case HOST:
verifyHostIsSubordinate(loadAndVerifyExistence(Host.class, domainOrHostName));
dnsQueue.addHostRefreshTask(domainOrHostName);
dnsUtils.requestHostDnsRefresh(domainOrHostName);
break;
default:
throw new BadRequestException("Unsupported type: " + type);
@@ -45,14 +45,14 @@ public class RefreshDnsOnHostRenameAction implements Runnable {
private final VKey<Host> hostKey;
private final Response response;
private final DnsQueue dnsQueue;
private final DnsUtils dnsUtils;
@Inject
RefreshDnsOnHostRenameAction(
@Parameter(PARAM_HOST_KEY) String hostKey, Response response, DnsQueue dnsQueue) {
@Parameter(PARAM_HOST_KEY) String hostKey, Response response, DnsUtils dnsUtils) {
this.hostKey = VKey.createEppVKeyFromString(hostKey);
this.response = response;
this.dnsQueue = dnsQueue;
this.dnsUtils = dnsUtils;
}
@Override
@@ -76,7 +76,7 @@ public class RefreshDnsOnHostRenameAction implements Runnable {
.stream()
.map(domainKey -> tm().loadByKey(domainKey))
.filter(Domain::shouldPublishToDns)
.forEach(domain -> dnsQueue.addDomainRefreshTask(domain.getDomainName()));
.forEach(domain -> dnsUtils.requestDomainDnsRefresh(domain.getDomainName()));
}
if (!hostValid) {
@@ -137,4 +137,14 @@
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url>
<![CDATA[/_dr/cron/fanout?queue=dns-refresh&forEachRealTld&ForEachTestTld&endpoint=/_dr/task/readDnsRefreshRequests&dnsJitterSeconds=45]]></url>
<name>readDnsRefreshRequests</name>
<description>
Enqueue a ReadDnsRefreshRequestAction for each TLD.
</description>
<schedule>*/1 * * * *</schedule>
</task>
</taskentries>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
</cronentries>
@@ -157,6 +157,12 @@
<url-pattern>/_dr/cron/readDnsQueue</url-pattern>
</servlet-mapping>
<!-- Reads the DNS refresh requests and kick off the appropriate tasks to update zone. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/readDnsRefreshRequests</url-pattern>
</servlet-mapping>
<!-- Publishes DNS updates. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
@@ -6,6 +6,13 @@
<mode>pull</mode>
</queue>
<!-- Queue for reading DNS update requests and batching them off to the dns-publish queue. -->
<queue>
<name>dns-refresh</name>
<rate>100/s</rate>
</queue>
<!-- Queue for publishing DNS updates in batches. -->
<queue>
<name>dns-publish</name>
<rate>100/s</rate>
@@ -18,6 +25,7 @@
</retry-parameters>
</queue>
<!-- Queue for uploading RDE deposits to the escrow provider. -->
<queue>
<name>rde-upload</name>
<rate>10/m</rate>
@@ -28,6 +36,7 @@
</retry-parameters>
</queue>
<!-- Queue for uploading RDE reports to ICANN. -->
<queue>
<name>rde-report</name>
<rate>1/s</rate>
@@ -37,6 +46,7 @@
</retry-parameters>
</queue>
<!-- Queue for copying BRDA deposits to GCS. -->
<queue>
<name>brda</name>
<rate>1/m</rate>
@@ -74,7 +84,7 @@
</retry-parameters>
</queue>
<!-- Queue for tasks to produce LORDN CSV reports, either by the query or queue method. -->
<!-- Queue for tasks to produce LORDN CSV reports, populated by a Cloud Scheduler fanout job. -->
<queue>
<name>nordn</name>
<rate>1/s</rate>
@@ -84,18 +94,6 @@
</retry-parameters>
</queue>
<!-- Queue for LORDN Claims CSV rows to be periodically queried and then uploaded in batches. -->
<queue>
<name>lordn-claims</name>
<mode>pull</mode>
</queue>
<!-- Queue for LORDN Sunrise CSV rows to be periodically queried and then uploaded in batches. -->
<queue>
<name>lordn-sunrise</name>
<mode>pull</mode>
</queue>
<!-- Queue for tasks that sync data to Google Spreadsheets. -->
<queue>
<name>sheet</name>
@@ -139,6 +139,16 @@
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url>
<![CDATA[/_dr/cron/fanout?queue=dns-refresh&forEachRealTld&ForEachTestTld&endpoint=/_dr/task/readDnsRefreshRequests&dnsJitterSeconds=45]]></url>
<name>readDnsRefreshRequests</name>
<description>
Enqueue a ReadDnsRefreshRequestAction for each TLD.
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<name>deleteExpiredDomains</name>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
</cronentries>
@@ -90,7 +90,18 @@
<schedule>0 */1 * * *</schedule>
</task>
<!-- TODO: @ptkach add resaveAllEppResourcesPipelineAction https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml;l=105 -->
<task>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<name>resaveAllEppResourcesPipeline</name>
<description>
This job resaves all our resources, projected in time to "now".
</description>
<!--
Deviation from cron tasks schedule: 1st monday of month 09:00 is replaced
with 1st of the month 09:00
-->
<schedule>0 9 1 * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/updateRegistrarRdapBaseUrls]]></url>
@@ -162,30 +173,6 @@
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=sunrise&pullQueue]]></url>
<name>nordnUploadSunrisePullQueue</name>
<description>
This job uploads LORDN Sunrise CSV files for each TLD to MarksDB using
pull queue. It should be run at most every three hours, or at absolute
minimum every 26 hours.
</description>
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=claims&pullQueue]]></url>
<name>nordnUploadClaimsPullQueue</name>
<description>
This job uploads LORDN Claims CSV files for each TLD to MarksDB using pull
queue. It should be run at most every three hours, or at absolute minimum
every 26 hours.
</description>
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
<name>deleteProberData</name>
@@ -223,6 +210,16 @@
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url>
<![CDATA[/_dr/cron/fanout?queue=dns-refresh&forEachRealTld&ForEachTestTld&endpoint=/_dr/task/readDnsRefreshRequests&dnsJitterSeconds=45]]></url>
<name>readDnsRefreshRequests</name>
<description>
Enqueue a ReadDnsRefreshRequestAction for each TLD.
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingStaging&runInEmpty]]></url>
<name>icannReportingStaging</name>
@@ -283,6 +280,6 @@
This job runs weekly to wipe out PII fields of ContactHistory entities
that have been in the database for a certain period of time.
</description>
<schedule>0 15 * * 1</schedule>
<schedule>0 15 * 12 1</schedule>
</task>
</taskentries>
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
</cronentries>
@@ -21,6 +21,16 @@
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url>
<![CDATA[/_dr/cron/fanout?queue=dns-refresh&forEachRealTld&ForEachTestTld&endpoint=/_dr/task/readDnsRefreshRequests&dnsJitterSeconds=45]]></url>
<name>readDnsRefreshRequests</name>
<description>
Enqueue a ReadDnsRefreshRequestAction for each TLD.
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<name>syncRegistrarsSheet</name>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
</cronentries>
@@ -11,9 +11,6 @@
<!--
This only needs to run once per day, but we launch additional jobs in case the
cursor is lagging behind, so it'll catch up to the current date eventually.
See <a href="../../../production/default/WEB-INF/cron.xml">production config</a> for an
explanation of job starting times.
-->
<schedule>7 */12 * * *</schedule>
</task>
@@ -95,6 +92,19 @@
<schedule>0 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<name>resaveAllEppResourcesPipeline</name>
<description>
This job resaves all our resources, projected in time to "now".
</description>
<!--
Deviation from cron tasks schedule: 1st monday of month 09:00 is replaced
with 1st of the month 09:00
-->
<schedule>0 9 1 * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<name>deleteExpiredDomains</name>
@@ -132,8 +142,6 @@
<schedule>0 5 * * *</schedule>
</task>
<!-- TODO: @ptkach add resaveAllEppResourcesPipelineAction https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cron.xml;l=89 -->
<task>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<name>readDnsQueue</name>
@@ -144,6 +152,16 @@
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url>
<![CDATA[/_dr/cron/fanout?queue=dns-refresh&forEachRealTld&ForEachTestTld&endpoint=/_dr/task/readDnsRefreshRequests&dnsJitterSeconds=45]]></url>
<name>readDnsRefreshRequests</name>
<description>
Enqueue a ReadDnsRefreshRequestAction for each TLD.
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
<name>wipeOutContactHistoryPii</name>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
</cronentries>
@@ -52,6 +52,11 @@ import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponsePar
import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.domain.token.AllocationTokenDomainCheckResults;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForCommandException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException;
import google.registry.model.EppResource;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.billing.BillingEvent;
@@ -271,20 +276,56 @@ public final class DomainCheckFlow implements Flow {
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
for (String domainName : getDomainNamesToCheckForFee(feeCheckItem, domainNames.keySet())) {
Optional<AllocationToken> defaultToken =
DomainFlowUtils.checkForDefaultToken(
Registry.get(InternetDomainName.from(domainName).parent().toString()),
domainName,
feeCheckItem.getCommandName(),
registrarId,
now);
FeeCheckResponseExtensionItem.Builder<?> builder = feeCheckItem.createResponseBuilder();
Optional<Domain> domain = Optional.ofNullable(domainObjs.get(domainName));
handleFeeRequest(
feeCheckItem,
builder,
domainNames.get(domainName),
domain,
feeCheck.getCurrency(),
now,
pricingLogic,
allocationToken,
availableDomains.contains(domainName),
recurrences.getOrDefault(domainName, null));
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
try {
if (allocationToken.isPresent()) {
AllocationTokenFlowUtils.validateToken(
InternetDomainName.from(domainName),
allocationToken.get(),
feeCheckItem.getCommandName(),
registrarId,
now);
}
handleFeeRequest(
feeCheckItem,
builder,
domainNames.get(domainName),
domain,
feeCheck.getCurrency(),
now,
pricingLogic,
allocationToken.isPresent() ? allocationToken : defaultToken,
availableDomains.contains(domainName),
recurrences.getOrDefault(domainName, null));
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
} catch (AllocationTokenNotValidForCommandException
| AllocationTokenNotValidForDomainException
| AllocationTokenNotValidForRegistrarException
| AllocationTokenNotValidForTldException
| AllocationTokenNotInPromotionException e) {
// Allocation token is either not an active token or it is not valid for the EPP command,
// registrar, domain, or TLD.
Registry registry = Registry.get(InternetDomainName.from(domainName).parent().toString());
responseItems.add(
builder
.setDomainNameIfSupported(domainName)
.setPeriod(feeCheckItem.getPeriod())
.setCommand(
feeCheckItem.getCommandName(),
feeCheckItem.getPhase(),
feeCheckItem.getSubphase())
.setCurrencyIfSupported(registry.getCurrency())
.setClass("token-not-supported")
.build());
}
}
}
return ImmutableList.of(feeCheck.createResponse(responseItems.build()));
@@ -15,8 +15,6 @@
package google.registry.flows.domain;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
@@ -54,7 +52,6 @@ import static google.registry.model.tld.Registry.TldState.QUIET_PERIOD;
import static google.registry.model.tld.Registry.TldState.START_DATE_SUNRISE;
import static google.registry.model.tld.label.ReservationType.NAME_COLLISION;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
@@ -62,9 +59,8 @@ import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.ExtensionManager;
@@ -86,8 +82,6 @@ import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.DomainCommand.Create;
@@ -95,6 +89,7 @@ import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.launch.LaunchCreateExtension;
import google.registry.model.domain.metadata.MetadataExtension;
@@ -123,10 +118,7 @@ import google.registry.model.tld.Registry.TldType;
import google.registry.model.tld.label.ReservationType;
import google.registry.model.tmch.ClaimsList;
import google.registry.model.tmch.ClaimsListDao;
import google.registry.persistence.VKey;
import google.registry.tmch.LordnTaskUtils;
import google.registry.tmch.LordnTaskUtils.LordnPhase;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -235,7 +227,8 @@ public final class DomainCreateFlow implements TransactionalFlow {
@Inject DomainCreateFlowCustomLogic flowCustomLogic;
@Inject DomainFlowTmchUtils tmchUtils;
@Inject DomainPricingLogic pricingLogic;
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject DomainCreateFlow() {}
@Override
@@ -273,6 +266,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
validateLaunchCreateNotice(launchCreate.get().getNotice(), domainLabel, isSuperuser, now);
}
boolean isSunriseCreate = hasSignedMarks && (tldState == START_DATE_SUNRISE);
// TODO(sarahbot@): Add check for valid EPP actions on the token
Optional<AllocationToken> allocationToken =
allocationTokenFlowUtils.verifyAllocationTokenCreateIfPresent(
command,
@@ -281,8 +275,10 @@ public final class DomainCreateFlow implements TransactionalFlow {
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
boolean defaultTokenUsed = false;
if (!allocationToken.isPresent() && !registry.getDefaultPromoTokens().isEmpty()) {
allocationToken = checkForDefaultToken(registry, command);
if (!allocationToken.isPresent()) {
allocationToken =
DomainFlowUtils.checkForDefaultToken(
registry, command.getDomainName(), CommandName.CREATE, registrarId, now);
if (allocationToken.isPresent()) {
defaultTokenUsed = true;
}
@@ -405,12 +401,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
.setLordnPhase(
!DatabaseMigrationStateSchedule.getValueAtTime(tm().getTransactionTime())
.equals(MigrationState.NORDN_SQL)
? LordnPhase.NONE
: hasSignedMarks
? LordnPhase.SUNRISE
: hasClaimsNotice ? LordnPhase.CLAIMS : LordnPhase.NONE);
hasSignedMarks
? LordnPhase.SUNRISE
: hasClaimsNotice ? LordnPhase.CLAIMS : LordnPhase.NONE);
Domain domain = domainBuilder.build();
if (allocationToken.isPresent()
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
@@ -433,8 +426,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
allocationTokenFlowUtils.redeemToken(
allocationToken.get(), domainHistory.getHistoryEntryId()));
}
enqueueTasks(domain, hasSignedMarks, hasClaimsNotice);
if (domain.shouldPublishToDns()) {
dnsUtils.requestDomainDnsRefresh(domain.getDomainName());
}
EntityChanges entityChanges =
flowCustomLogic.beforeSave(
DomainCreateFlowCustomLogic.BeforeSaveParameters.newBuilder()
@@ -458,36 +452,6 @@ public final class DomainCreateFlow implements TransactionalFlow {
.build();
}
private Optional<AllocationToken> checkForDefaultToken(
Registry registry, DomainCommand.Create command) throws EppException {
Map<VKey<AllocationToken>, Optional<AllocationToken>> tokens =
AllocationToken.getAll(registry.getDefaultPromoTokens());
ImmutableList<Optional<AllocationToken>> tokenList =
registry.getDefaultPromoTokens().stream()
.map(tokens::get)
.filter(Optional::isPresent)
.collect(toImmutableList());
checkState(
!isNullOrEmpty(tokenList),
"Failure while loading default TLD promotions from the database");
// Check if any of the tokens are valid for this domain registration
for (Optional<AllocationToken> token : tokenList) {
try {
AllocationTokenFlowUtils.validateToken(
InternetDomainName.from(command.getDomainName()),
token.get(),
registrarId,
tm().getTransactionTime());
} catch (AssociationProhibitsOperationException e) {
// Allocation token was not valid for this registration, continue to check the next token in
// the list
continue;
}
// Only use the first valid token in the list
return token;
}
return Optional.empty();
}
/**
* Verifies that signed marks are only sent during sunrise.
*
@@ -707,17 +671,6 @@ public final class DomainCreateFlow implements TransactionalFlow {
.build();
}
private void enqueueTasks(Domain newDomain, boolean hasSignedMarks, boolean hasClaimsNotice) {
if (newDomain.shouldPublishToDns()) {
dnsQueue.addDomainRefreshTask(newDomain.getDomainName());
}
if (!DatabaseMigrationStateSchedule.getValueAtTime(tm().getTransactionTime())
.equals(MigrationState.NORDN_SQL)
&& (hasClaimsNotice || hasSignedMarks)) {
LordnTaskUtils.enqueueDomainTask(newDomain);
}
}
/**
* Determines the {@link RenewalPriceBehavior} and the renewal price that needs be stored in the
* {@link Recurring} billing events.
@@ -44,7 +44,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.ExtensionManager;
@@ -129,7 +129,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
@Inject @TargetId String targetId;
@Inject @Superuser boolean isSuperuser;
@Inject DomainHistory.Builder historyBuilder;
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject Trid trid;
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject EppResponse.Builder responseBuilder;
@@ -260,7 +260,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
// If there's a pending transfer, the gaining client's autorenew billing
// event and poll message will already have been deleted in
// ResourceDeleteFlow since it's listed in serverApproveEntities.
dnsQueue.addDomainRefreshTask(existingDomain.getDomainName());
dnsUtils.requestDomainDnsRefresh(existingDomain.getDomainName());
entitiesToSave.add(newDomain, domainHistory);
EntityChanges entityChanges =
@@ -39,6 +39,7 @@ import static google.registry.model.tld.label.ReservationType.RESERVED_FOR_ANCHO
import static google.registry.model.tld.label.ReservationType.RESERVED_FOR_SPECIFIC_USE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
@@ -63,6 +64,7 @@ import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.net.InternetDomainName;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.EppException.AuthorizationErrorException;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.ObjectDoesNotExistException;
@@ -72,6 +74,7 @@ import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
import google.registry.flows.EppException.RequiredParameterMissingException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.flows.EppException.UnimplementedOptionException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.EppResource;
import google.registry.model.billing.BillingEvent;
@@ -94,6 +97,7 @@ import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.fee.FeeQueryResponseExtensionItem;
import google.registry.model.domain.fee.FeeTransformCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
@@ -172,8 +176,7 @@ public class DomainFlowUtils {
CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9').or(CharMatcher.anyOf("-.")));
/** Default validator used to determine if an IDN name can be provisioned on a TLD. */
private static final IdnLabelValidator IDN_LABEL_VALIDATOR =
IdnLabelValidator.createDefaultIdnLabelValidator();
private static final IdnLabelValidator IDN_LABEL_VALIDATOR = new IdnLabelValidator();
/** The maximum number of DS records allowed on a domain. */
private static final int MAX_DS_RECORDS_PER_DOMAIN = 8;
@@ -695,7 +698,8 @@ public class DomainFlowUtils {
builder.setAvailIfSupported(true);
fees =
pricingLogic
.getRenewPrice(registry, domainNameString, now, years, recurringBillingEvent)
.getRenewPrice(
registry, domainNameString, now, years, recurringBillingEvent, allocationToken)
.getFees();
break;
case RESTORE:
@@ -1191,6 +1195,49 @@ public class DomainFlowUtils {
.getResultList();
}
/**
* Checks if there is a valid default token to be used for a domain create command.
*
* <p>If there is more than one valid default token for the registration, only the first valid
* token found on the TLD's default token list will be returned.
*/
public static Optional<AllocationToken> checkForDefaultToken(
Registry registry,
String domainName,
CommandName commandName,
String registrarId,
DateTime now)
throws EppException {
if (isNullOrEmpty(registry.getDefaultPromoTokens())) {
return Optional.empty();
}
Map<VKey<AllocationToken>, Optional<AllocationToken>> tokens =
AllocationToken.getAll(registry.getDefaultPromoTokens());
ImmutableList<Optional<AllocationToken>> tokenList =
registry.getDefaultPromoTokens().stream()
.map(tokens::get)
.filter(Optional::isPresent)
.collect(toImmutableList());
checkState(
!isNullOrEmpty(tokenList),
"Failure while loading default TLD promotions from the database");
// Check if any of the tokens are valid for this domain registration
for (Optional<AllocationToken> token : tokenList) {
try {
AllocationTokenFlowUtils.validateToken(
InternetDomainName.from(domainName), token.get(), commandName, registrarId, now);
} catch (AssociationProhibitsOperationException | StatusProhibitsOperationException e) {
// Allocation token was not valid for this registration, continue to check the next token in
// the list
continue;
}
// Only use the first valid token in the list
return token;
}
// No valid default token found
return Optional.empty();
}
/** Resource linked to this domain does not exist. */
static class LinkedResourcesDoNotExistException extends ObjectDoesNotExistException {
public LinkedResourcesDoNotExistException(Class<?> type, ImmutableSet<String> resourceIds) {
@@ -34,6 +34,7 @@ import google.registry.model.domain.fee.BaseFee;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenBehavior;
import google.registry.model.pricing.PremiumPricingEngine.DomainPrices;
import google.registry.model.tld.Registry;
import java.math.RoundingMode;
@@ -112,21 +113,22 @@ public final class DomainPricingLogic {
String domainName,
DateTime dateTime,
int years,
@Nullable Recurring recurringBillingEvent) {
@Nullable Recurring recurringBillingEvent,
Optional<AllocationToken> allocationToken)
throws AllocationTokenInvalidForPremiumNameException {
checkArgument(years > 0, "Number of years must be positive");
Money renewCost;
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
boolean isRenewCostPremiumPrice;
// recurring billing event is null if the domain is still available. Billing events are created
// in the process of domain creation.
if (recurringBillingEvent == null) {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
renewCost = domainPrices.getRenewCost().multipliedBy(years);
renewCost = getDomainRenewCostWithDiscount(domainPrices, years, allocationToken);
isRenewCostPremiumPrice = domainPrices.isPremium();
} else {
switch (recurringBillingEvent.getRenewalPriceBehavior()) {
case DEFAULT:
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
renewCost = domainPrices.getRenewCost().multipliedBy(years);
renewCost = getDomainRenewCostWithDiscount(domainPrices, years, allocationToken);
isRenewCostPremiumPrice = domainPrices.isPremium();
break;
// if the renewal price behavior is specified, then the renewal price should be the same
@@ -136,6 +138,7 @@ public final class DomainPricingLogic {
recurringBillingEvent.getRenewalPrice(),
"Unexpected behavior: renewal price cannot be null when renewal behavior is"
+ " SPECIFIED");
// Don't apply allocation token to renewal price when SPECIFIED
renewCost = recurringBillingEvent.getRenewalPrice().get().multipliedBy(years);
isRenewCostPremiumPrice = false;
break;
@@ -143,9 +146,11 @@ public final class DomainPricingLogic {
// at standard price of domains at the time, even if the domain is premium
case NONPREMIUM:
renewCost =
Registry.get(getTldFromDomainName(domainName))
.getStandardRenewCost(dateTime)
.multipliedBy(years);
getDomainCostWithDiscount(
false,
years,
allocationToken,
Registry.get(getTldFromDomainName(domainName)).getStandardRenewCost(dateTime));
isRenewCostPremiumPrice = false;
break;
default:
@@ -202,7 +207,7 @@ public final class DomainPricingLogic {
@Nullable Recurring recurringBillingEvent)
throws EppException {
FeesAndCredits renewPrice =
getRenewPrice(registry, domainName, dateTime, 1, recurringBillingEvent);
getRenewPrice(registry, domainName, dateTime, 1, recurringBillingEvent, Optional.empty());
return customLogic.customizeTransferPrice(
TransferPriceParameters.newBuilder()
.setFeesAndCredits(
@@ -242,25 +247,40 @@ public final class DomainPricingLogic {
private Money getDomainCreateCostWithDiscount(
DomainPrices domainPrices, int years, Optional<AllocationToken> allocationToken)
throws EppException {
return getDomainCostWithDiscount(
domainPrices.isPremium(), years, allocationToken, domainPrices.getCreateCost());
}
/** Returns the domain renew cost with allocation-token-related discounts applied. */
private Money getDomainRenewCostWithDiscount(
DomainPrices domainPrices, int years, Optional<AllocationToken> allocationToken)
throws AllocationTokenInvalidForPremiumNameException {
return getDomainCostWithDiscount(
domainPrices.isPremium(), years, allocationToken, domainPrices.getRenewCost());
}
private Money getDomainCostWithDiscount(
boolean isPremium, int years, Optional<AllocationToken> allocationToken, Money oneYearCost)
throws AllocationTokenInvalidForPremiumNameException {
if (allocationToken.isPresent()
&& allocationToken.get().getDiscountFraction() != 0.0
&& domainPrices.isPremium()
&& isPremium
&& !allocationToken.get().shouldDiscountPremiums()) {
throw new AllocationTokenInvalidForPremiumNameException();
}
Money oneYearCreateCost = domainPrices.getCreateCost();
Money totalDomainCreateCost = oneYearCreateCost.multipliedBy(years);
Money totalDomainFlowCost = oneYearCost.multipliedBy(years);
// Apply the allocation token discount, if applicable.
if (allocationToken.isPresent()) {
if (allocationToken.isPresent()
&& allocationToken.get().getTokenBehavior().equals(TokenBehavior.DEFAULT)) {
int discountedYears = Math.min(years, allocationToken.get().getDiscountYears());
Money discount =
oneYearCreateCost.multipliedBy(
oneYearCost.multipliedBy(
discountedYears * allocationToken.get().getDiscountFraction(),
RoundingMode.HALF_EVEN);
totalDomainCreateCost = totalDomainCreateCost.minus(discount);
totalDomainFlowCost = totalDomainFlowCost.minus(discount);
}
return totalDomainCreateCost;
return totalDomainFlowCost;
}
/** An allocation token was provided that is invalid for premium domains. */
@@ -66,6 +66,7 @@ import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.fee.FeeRenewCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.metadata.MetadataExtension;
@@ -172,13 +173,25 @@ public final class DomainRenewFlow implements TransactionalFlow {
Renew command = (Renew) resourceCommand;
// Loads the target resource if it exists
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
String tld = existingDomain.getTld();
Registry registry = Registry.get(tld);
Optional<AllocationToken> allocationToken =
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain,
Registry.get(existingDomain.getTld()),
registry,
registrarId,
now,
CommandName.RENEW,
eppInput.getSingleExtension(AllocationTokenExtension.class));
boolean defaultTokenUsed = false;
if (!allocationToken.isPresent()) {
allocationToken =
DomainFlowUtils.checkForDefaultToken(
registry, existingDomain.getDomainName(), CommandName.RENEW, registrarId, now);
if (allocationToken.isPresent()) {
defaultTokenUsed = true;
}
}
verifyRenewAllowed(authInfo, existingDomain, command, allocationToken);
// If client passed an applicable static token this updates the domain
@@ -198,8 +211,9 @@ public final class DomainRenewFlow implements TransactionalFlow {
targetId,
now,
years,
existingRecurringBillingEvent);
validateFeeChallenge(feeRenew, feesAndCredits, false);
existingRecurringBillingEvent,
allocationToken);
validateFeeChallenge(feeRenew, feesAndCredits, defaultTokenUsed);
flowCustomLogic.afterValidation(
AfterValidationParameters.newBuilder()
.setExistingDomain(existingDomain)
@@ -208,7 +222,6 @@ public final class DomainRenewFlow implements TransactionalFlow {
.build());
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder.setRevisionId(domainHistoryId.getRevisionId());
String tld = existingDomain.getTld();
// Bill for this explicit renew itself.
BillingEvent.OneTime explicitRenewEvent =
createRenewBillingEvent(
@@ -241,7 +254,6 @@ public final class DomainRenewFlow implements TransactionalFlow {
GracePeriod.forBillingEvent(
GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent))
.build();
Registry registry = Registry.get(existingDomain.getTld());
DomainHistory domainHistory =
buildDomainHistory(
newDomain, now, command.getPeriod(), registry.getRenewGracePeriodLength());
@@ -34,7 +34,7 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.flows.EppException;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
@@ -122,7 +122,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
@Inject @TargetId String targetId;
@Inject @Superuser boolean isSuperuser;
@Inject DomainHistory.Builder historyBuilder;
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject EppResponse.Builder responseBuilder;
@Inject DomainPricingLogic pricingLogic;
@Inject DomainRestoreRequestFlow() {}
@@ -186,7 +186,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
entitiesToSave.add(newDomain, domainHistory, autorenewEvent, autorenewPollMessage);
tm().putAll(entitiesToSave.build());
tm().delete(existingDomain.getDeletePollMessage());
dnsQueue.addDomainRefreshTask(existingDomain.getDomainName());
dnsUtils.requestDomainDnsRefresh(existingDomain.getDomainName());
return responseBuilder
.setExtensions(createResponseExtensions(feesAndCredits, feeUpdate, isExpired))
.build();
@@ -53,6 +53,7 @@ import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.token.AllocationTokenExtension;
@@ -135,6 +136,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
Registry.get(existingDomain.getTld()),
registrarId,
now,
CommandName.TRANSFER,
eppInput.getSingleExtension(AllocationTokenExtension.class));
verifyOptionalAuthInfo(authInfo, existingDomain);
verifyHasPendingTransfer(existingDomain);
@@ -57,6 +57,7 @@ import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Transfer;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.metadata.MetadataExtension;
@@ -173,6 +174,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
Registry.get(existingDomain.getTld()),
gainingClientId,
now,
CommandName.TRANSFER,
eppInput.getSingleExtension(AllocationTokenExtension.class));
Optional<DomainTransferRequestSuperuserExtension> superuserExtension =
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
@@ -49,7 +49,7 @@ import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.net.InternetDomainName;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -156,7 +156,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
@Inject @Superuser boolean isSuperuser;
@Inject Trid trid;
@Inject DomainHistory.Builder historyBuilder;
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject EppResponse.Builder responseBuilder;
@Inject DomainUpdateFlowCustomLogic flowCustomLogic;
@Inject DomainPricingLogic pricingLogic;
@@ -183,7 +183,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
historyBuilder.setType(DOMAIN_UPDATE).setDomain(newDomain).build();
validateNewState(newDomain);
if (requiresDnsUpdate(existingDomain, newDomain)) {
dnsQueue.addDomainRefreshTask(targetId);
dnsUtils.requestDomainDnsRefresh(targetId);
}
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(newDomain, domainHistory);
@@ -30,6 +30,7 @@ import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenBehavior;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
@@ -78,7 +79,7 @@ public class AllocationTokenFlowUtils {
ImmutableMap.Builder<InternetDomainName, String> resultsBuilder = new ImmutableMap.Builder<>();
for (InternetDomainName domainName : domainNames) {
try {
validateToken(domainName, tokenEntity, registrarId, now);
validateToken(domainName, tokenEntity, CommandName.CREATE, registrarId, now);
validDomainNames.add(domainName);
} catch (EppException e) {
resultsBuilder.put(domainName, e.getMessage());
@@ -109,11 +110,19 @@ public class AllocationTokenFlowUtils {
* @throws EppException if the token is invalid in any way
*/
public static void validateToken(
InternetDomainName domainName, AllocationToken token, String registrarId, DateTime now)
InternetDomainName domainName,
AllocationToken token,
CommandName commandName,
String registrarId,
DateTime now)
throws EppException {
// Only tokens with default behavior require validation
if (TokenBehavior.DEFAULT.equals(token.getTokenBehavior())) {
if (!token.getAllowedEppActions().isEmpty()
&& !token.getAllowedEppActions().contains(commandName)) {
throw new AllocationTokenNotValidForCommandException();
}
if (!token.getAllowedRegistrarIds().isEmpty()
&& !token.getAllowedRegistrarIds().contains(registrarId)) {
throw new AllocationTokenNotValidForRegistrarException();
@@ -173,7 +182,12 @@ public class AllocationTokenFlowUtils {
return Optional.empty();
}
AllocationToken tokenEntity = loadToken(extension.get().getAllocationToken());
validateToken(InternetDomainName.from(command.getDomainName()), tokenEntity, registrarId, now);
validateToken(
InternetDomainName.from(command.getDomainName()),
tokenEntity,
CommandName.CREATE,
registrarId,
now);
return Optional.of(
tokenCustomLogic.validateToken(command, tokenEntity, registry, registrarId, now));
}
@@ -184,6 +198,7 @@ public class AllocationTokenFlowUtils {
Registry registry,
String registrarId,
DateTime now,
CommandName commandName,
Optional<AllocationTokenExtension> extension)
throws EppException {
if (!extension.isPresent()) {
@@ -191,7 +206,11 @@ public class AllocationTokenFlowUtils {
}
AllocationToken tokenEntity = loadToken(extension.get().getAllocationToken());
validateToken(
InternetDomainName.from(existingDomain.getDomainName()), tokenEntity, registrarId, now);
InternetDomainName.from(existingDomain.getDomainName()),
tokenEntity,
commandName,
registrarId,
now);
return Optional.of(
tokenCustomLogic.validateToken(existingDomain, tokenEntity, registry, registrarId, now));
}
@@ -280,6 +299,14 @@ public class AllocationTokenFlowUtils {
}
}
/** The allocation token is not valid for this EPP command. */
public static class AllocationTokenNotValidForCommandException
extends AssociationProhibitsOperationException {
AllocationTokenNotValidForCommandException() {
super("Allocation token not valid for the EPP command");
}
}
/** The allocation token is invalid. */
public static class InvalidAllocationTokenException extends AuthorizationErrorException {
InvalidAllocationTokenException() {
@@ -28,7 +28,7 @@ import static google.registry.util.CollectionUtils.isNullOrEmpty;
import com.google.common.collect.ImmutableSet;
import google.registry.config.RegistryConfig.Config;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.flows.EppException;
import google.registry.flows.EppException.ParameterValueRangeErrorException;
import google.registry.flows.EppException.RequiredParameterMissingException;
@@ -85,7 +85,7 @@ public final class HostCreateFlow implements TransactionalFlow {
@Inject @RegistrarId String registrarId;
@Inject @TargetId String targetId;
@Inject HostHistory.Builder historyBuilder;
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject EppResponse.Builder responseBuilder;
@Inject
@@ -138,7 +138,7 @@ public final class HostCreateFlow implements TransactionalFlow {
.build());
// Only update DNS if this is a subordinate host. External hosts have no glue to write, so
// they are only written as NS records from the referencing domain.
dnsQueue.addHostRefreshTask(targetId);
dnsUtils.requestHostDnsRefresh(targetId);
}
tm().insertAll(entitiesToSave);
return responseBuilder.setResData(HostCreateData.create(targetId, now)).build();
@@ -24,7 +24,7 @@ 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.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -71,7 +71,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
StatusValue.PENDING_DELETE,
StatusValue.SERVER_DELETE_PROHIBITED);
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject ExtensionManager extensionManager;
@Inject @RegistrarId String registrarId;
@Inject @TargetId String targetId;
@@ -104,7 +104,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
}
Host newHost = existingHost.asBuilder().setStatusValues(null).setDeletionTime(now).build();
if (existingHost.isSubordinate()) {
dnsQueue.addHostRefreshTask(existingHost.getHostName());
dnsUtils.requestHostDnsRefresh(existingHost.getHostName());
tm().update(
tm().loadByKey(existingHost.getSuperordinateDomain())
.asBuilder()
@@ -36,7 +36,8 @@ 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.batch.CloudTasksUtils;
import google.registry.dns.DnsUtils;
import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.flows.EppException;
import google.registry.flows.EppException.ObjectAlreadyExistsException;
@@ -65,7 +66,6 @@ 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;
@@ -124,7 +124,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
@Inject @Superuser boolean isSuperuser;
@Inject HostHistory.Builder historyBuilder;
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject EppResponse.Builder responseBuilder;
@Inject CloudTasksUtils cloudTasksUtils;
@@ -266,21 +266,21 @@ public final class HostUpdateFlow implements TransactionalFlow {
// Only update DNS for subordinate hosts. External hosts have no glue to write, so they
// are only written as NS records from the referencing domain.
if (existingHost.isSubordinate()) {
dnsQueue.addHostRefreshTask(existingHost.getHostName());
dnsUtils.requestHostDnsRefresh(existingHost.getHostName());
}
// In case of a rename, there are many updates we need to queue up.
if (((Update) resourceCommand).getInnerChange().getHostName() != null) {
// If the renamed host is also subordinate, then we must enqueue an update to write the new
// glue.
if (newHost.isSubordinate()) {
dnsQueue.addHostRefreshTask(newHost.getHostName());
dnsUtils.requestHostDnsRefresh(newHost.getHostName());
}
// 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.
Task task =
cloudTasksUtils.createPostTask(
RefreshDnsOnHostRenameAction.PATH,
Service.BACKEND.toString(),
Service.BACKEND,
ImmutableMultimap.of(PARAM_HOST_KEY, existingHost.createVKey().stringify()));
cloudTasksUtils.enqueue(QUEUE_HOST_RENAME, task);
}
@@ -68,6 +68,7 @@ U+011F # LATIN SMALL LETTER G WITH BREVE
U+01E7 # LATIN SMALL LETTER G WITH CARON
U+0121 # LATIN SMALL LETTER G WITH DOT ABOVE
U+0123 # LATIN SMALL LETTER G WITH CEDILLA
U+01E5 # LATIN SMALL LETTER G WITH STROKE
U+0068 # LATIN SMALL LETTER H
U+0127 # LATIN SMALL LETTER H WITH STROKE
U+0069 # LATIN SMALL LETTER I
@@ -28,13 +28,13 @@ import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Iterators;
import com.google.common.flogger.FluentLogger;
import com.google.protobuf.Timestamp;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryEnvironment;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.security.XsrfTokenManager;
import google.registry.util.CloudTasksUtils;
import java.time.Instant;
import java.util.Arrays;
import java.util.Iterator;
@@ -337,7 +337,7 @@ public class LoadTestAction implements Runnable {
cloudTasksUtils
.createPostTask(
"/_dr/epptool",
Service.TOOLS.toString(),
Service.TOOLS,
ImmutableMultimap.of(
"clientId",
registrarId,
@@ -90,7 +90,10 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
SEQUENCE_BASED_ALLOCATE_ID(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY),
/** Use SQL-based Nordn upload flow instead of the pull queue-based one. */
NORDN_SQL(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
NORDN_SQL(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY),
/** Use SQL-based DNS update flow instead of the pull queue-based one. */
DNS_SQL(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
private final PrimaryDatabase primaryDatabase;
private final boolean isReadOnly;
@@ -171,7 +174,11 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
MigrationState.SQL_PRIMARY)
.putAll(MigrationState.SQL_ONLY, MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
.putAll(MigrationState.SEQUENCE_BASED_ALLOCATE_ID, MigrationState.NORDN_SQL)
.putAll(MigrationState.NORDN_SQL, MigrationState.SEQUENCE_BASED_ALLOCATE_ID);
.putAll(
MigrationState.NORDN_SQL,
MigrationState.SEQUENCE_BASED_ALLOCATE_ID,
MigrationState.DNS_SQL)
.putAll(MigrationState.DNS_SQL, MigrationState.NORDN_SQL);
// In addition, we can always transition from a state to itself (useful when updating the map).
Arrays.stream(MigrationState.values()).forEach(state -> builder.put(state, state));
@@ -0,0 +1,139 @@
// Copyright 2023 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.model.common;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import google.registry.dns.DnsConstants.TargetType;
import google.registry.dns.PublishDnsUpdatesAction;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import org.joda.time.DateTime;
@Entity
@Table(indexes = {@Index(columnList = "requestTime"), @Index(columnList = "lastProcessTime")})
public class DnsRefreshRequest extends ImmutableObject {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
@SuppressWarnings("unused")
protected long id;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private TargetType type;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String tld;
@Column(nullable = false)
private DateTime requestTime;
@Column(nullable = false)
private DateTime lastProcessTime;
public TargetType getType() {
return type;
}
public String getName() {
return name;
}
public String getTld() {
return tld;
}
public DateTime getRequestTime() {
return requestTime;
}
/**
* The time at which the entity was last processed.
*
* <p>Note that "processed" means that it was read, not necessarily that the DNS request was
* processed successfully. The subsequent steps to bundle requests together and enqueue them in a
* Cloud Tasks queue for {@link PublishDnsUpdatesAction} to process can still fail.
*
* <p>This value allows us to control if a row is just recently read and should be skipped, should
* there are concurrent reads that all attempt to read the rows with oldest {@link #requestTime},
* or another read that comes too early after the previous read.
*/
public DateTime getLastProcessTime() {
return lastProcessTime;
}
@Override
public VKey<DnsRefreshRequest> createVKey() {
return VKey.create(DnsRefreshRequest.class, id);
}
protected DnsRefreshRequest() {}
private DnsRefreshRequest(
@Nullable Long id,
TargetType type,
String name,
String tld,
DateTime requestTime,
DateTime lastProcessTime) {
checkNotNull(type, "Target type cannot be null");
checkNotNull(name, "Domain/host name cannot be null");
checkNotNull(tld, "TLD cannot be null");
checkNotNull(requestTime, "Request time cannot be null");
checkNotNull(lastProcessTime, "Last process time cannot be null");
if (id != null) {
this.id = id;
}
this.type = type;
this.name = name;
this.tld = tld;
this.requestTime = requestTime;
this.lastProcessTime = lastProcessTime;
}
public DnsRefreshRequest(TargetType type, String name, String tld, DateTime requestTime) {
this(null, type, name, tld, requestTime, START_OF_TIME);
}
public DnsRefreshRequest updateProcessTime(DateTime processTime) {
checkArgument(
processTime.isAfter(getRequestTime()),
"Process time %s must be later than request time %s",
processTime,
getRequestTime());
checkArgument(
processTime.isAfter(getLastProcessTime()),
"New process time %s must be later than the old one %s",
processTime,
getLastProcessTime());
return new DnsRefreshRequest(id, getType(), getName(), getTld(), getRequestTime(), processTime);
}
}
@@ -34,7 +34,7 @@ public class Credit extends BaseFee {
BigDecimal cost, FeeType type, String description) {
Credit instance = new Credit();
instance.cost = checkNotNull(cost);
checkArgument(instance.cost.signum() < 0);
checkArgument(instance.cost.signum() <= 0, "A credit cannot have a positive cost");
instance.type = checkNotNull(type);
instance.description = description;
return instance;
@@ -14,6 +14,8 @@
package google.registry.model.domain.fee;
import static com.google.common.base.Preconditions.checkArgument;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.Period;
import java.util.Optional;
@@ -37,7 +39,19 @@ public abstract class FeeQueryCommandExtensionItem extends ImmutableObject {
RENEW,
TRANSFER,
RESTORE,
UPDATE
UPDATE;
public static CommandName parseKnownCommand(String string) {
try {
CommandName command = valueOf(string);
checkArgument(!command.equals(UNKNOWN));
return command;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Invalid EPP action name. Valid actions are CREATE, RENEW, TRANSFER, RESTORE, and"
+ " UPDATE");
}
}
}
/** The default validity period (if not specified) is 1 year for all operations. */
@@ -45,6 +45,7 @@ import google.registry.model.CreateAutoTimestamp;
import google.registry.model.UpdateAutoTimestampEntity;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.persistence.VKey;
import google.registry.persistence.WithVKey;
@@ -213,6 +214,9 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
TimedTransitionProperty<TokenStatus> tokenStatusTransitions =
TimedTransitionProperty.withInitialValue(NOT_STARTED);
/** Allowed EPP actions for this token, or null if all actions are allowed. */
@Nullable Set<CommandName> allowedEppActions;
public String getToken() {
return token;
}
@@ -263,6 +267,10 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
return tokenStatusTransitions;
}
public ImmutableSet<CommandName> getAllowedEppActions() {
return nullToEmptyImmutableCopy(allowedEppActions);
}
public RenewalPriceBehavior getRenewalPriceBehavior() {
return renewalPriceBehavior;
}
@@ -450,6 +458,11 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
return this;
}
public Builder setAllowedEppActions(Set<CommandName> allowedEppActions) {
getInstance().allowedEppActions = forceEmptyToNull(allowedEppActions);
return this;
}
public Builder setRenewalPriceBehavior(RenewalPriceBehavior renewalPriceBehavior) {
getInstance().renewalPriceBehavior = renewalPriceBehavior;
return this;
@@ -52,6 +52,7 @@ import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.ReservedList;
import google.registry.persistence.VKey;
import google.registry.persistence.converter.JodaMoneyType;
import google.registry.tldconfig.idn.IdnTableEnum;
import google.registry.util.Idn;
import java.util.List;
import java.util.Map;
@@ -275,6 +276,26 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
}
}
/**
* The time to live for DNS A and AAAA records.
*
* <p>When this field is null, the "dnsDefaultATtl" value from the config file will be used.
*/
Duration dnsAPlusAaaaTtl;
/**
* The time to live for DNS NS records.
*
* <p>When this field is null, the "dnsDefaultNsTtl" value from the config file will be used.
*/
Duration dnsNsTtl;
/**
* The time to live for DNS DS records.
*
* <p>When this field is null, the "dnsDefaultDsTtl" value from the config file will be used.
*/
Duration dnsDsTtl;
/**
* The unicode-aware representation of the TLD associated with this {@link Registry}.
*
@@ -467,6 +488,9 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
*/
List<VKey<AllocationToken>> defaultPromoTokens;
/** A set of allowed {@link IdnTableEnum}s for this TLD, or empty if we should use the default. */
Set<IdnTableEnum> idnTables;
public String getTldStr() {
return tldStr;
}
@@ -647,6 +671,21 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
return numDnsPublishLocks;
}
/** Returns the time to live for A and AAAA records. */
public Duration getDnsAPlusAaaaTtl() {
return dnsAPlusAaaaTtl;
}
/** Returns the time to live for NS records. */
public Duration getDnsNsTtl() {
return dnsNsTtl;
}
/** Returns the time to live for DS records. */
public Duration getDnsDsTtl() {
return dnsDsTtl;
}
public ImmutableSet<String> getAllowedRegistrantContactIds() {
return nullToEmptyImmutableCopy(allowedRegistrantContactIds);
}
@@ -659,6 +698,10 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
return nullToEmptyImmutableCopy(defaultPromoTokens);
}
public ImmutableSet<IdnTableEnum> getIdnTables() {
return nullToEmptyImmutableCopy(idnTables);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -737,6 +780,21 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
return this;
}
public Builder setDnsAPlusAaaaTtl(Duration dnsAPlusAaaaTtl) {
getInstance().dnsAPlusAaaaTtl = dnsAPlusAaaaTtl;
return this;
}
public Builder setDnsNsTtl(Duration dnsNsTtl) {
getInstance().dnsNsTtl = dnsNsTtl;
return this;
}
public Builder setDnsDsTtl(Duration dnsDsTtl) {
getInstance().dnsDsTtl = dnsDsTtl;
return this;
}
public Builder setAddGracePeriodLength(Duration addGracePeriodLength) {
checkArgument(
addGracePeriodLength.isLongerThan(Duration.ZERO),
@@ -942,6 +1000,11 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
return this;
}
public Builder setIdnTables(ImmutableSet<IdnTableEnum> idnTables) {
getInstance().idnTables = idnTables;
return this;
}
@Override
public Registry build() {
final Registry instance = getInstance();
@@ -33,6 +33,7 @@ import google.registry.cron.TldFanoutAction;
import google.registry.dns.DnsModule;
import google.registry.dns.PublishDnsUpdatesAction;
import google.registry.dns.ReadDnsQueueAction;
import google.registry.dns.ReadDnsRefreshRequestsAction;
import google.registry.dns.RefreshDnsAction;
import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.dns.writer.VoidDnsWriterModule;
@@ -144,6 +145,8 @@ interface BackendRequestComponent {
ReadDnsQueueAction readDnsQueueAction();
ReadDnsRefreshRequestsAction readDnsRefreshRequestsAction();
RdeReportAction rdeReportAction();
RdeStagingAction rdeStagingAction();
@@ -0,0 +1,34 @@
// Copyright 2023 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.persistence.converter;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import javax.persistence.Converter;
@Converter(autoApply = true)
public class CommandNameSetConverter
extends StringSetConverterBase<FeeQueryCommandExtensionItem.CommandName> {
@Override
String toString(CommandName element) {
return element.name();
}
@Override
CommandName fromString(String value) {
return FeeQueryCommandExtensionItem.CommandName.valueOf(value);
}
}
@@ -0,0 +1,34 @@
// Copyright 2023 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.persistence.converter;
import google.registry.tldconfig.idn.IdnTableEnum;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/** JPA {@link AttributeConverter} for storing/retrieving {@link IdnTableEnum}s. */
@Converter(autoApply = true)
public class IdnTableEnumSetConverter extends StringSetConverterBase<IdnTableEnum> {
@Override
String toString(IdnTableEnum element) {
return element.name();
}
@Override
IdnTableEnum fromString(String value) {
return IdnTableEnum.valueOf(value);
}
}
@@ -154,6 +154,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public <T> T transact(Supplier<T> work) {
// This prevents inner transaction from retrying, thus avoiding a cascade retry effect.
if (inTransaction()) {
return transactNoRetry(work);
}
return retrier.callWithRetry(() -> transactNoRetry(work), JpaRetries::isFailedTxnRetriable);
}
@@ -38,6 +38,7 @@ import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpProgressMonitor;
import dagger.Lazy;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.keyring.api.KeyModule.Key;
@@ -56,7 +57,6 @@ import google.registry.request.RequestParameters;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import google.registry.util.Retrier;
import google.registry.util.TeeOutputStream;
import java.io.ByteArrayInputStream;
@@ -131,8 +131,7 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
prefix.ifPresent(s -> params.put(RdeModule.PARAM_PREFIX, s));
cloudTasksUtils.enqueue(
RDE_REPORT_QUEUE,
cloudTasksUtils.createPostTask(
RdeReportAction.PATH, Service.BACKEND.getServiceId(), params));
cloudTasksUtils.createPostTask(RdeReportAction.PATH, Service.BACKEND, params));
}
@Override
@@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.persistence.PersistenceModule;
@@ -37,7 +38,6 @@ import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import java.io.IOException;
import javax.inject.Inject;
import org.joda.time.Duration;
@@ -140,7 +140,7 @@ public class GenerateInvoicesAction implements Runnable {
ReportingModule.BEAM_QUEUE,
cloudTasksUtils.createPostTaskWithDelay(
PublishInvoicesAction.PATH,
Service.BACKEND.toString(),
Service.BACKEND,
ImmutableMultimap.of(
ReportingModule.PARAM_JOB_ID,
jobId,
@@ -26,6 +26,7 @@ import com.google.api.services.dataflow.model.Job;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.reporting.ReportingModule;
import google.registry.request.Action;
@@ -33,7 +34,6 @@ import google.registry.request.Action.Service;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.CloudTasksUtils;
import java.io.IOException;
import javax.inject.Inject;
import org.joda.time.YearMonth;
@@ -127,7 +127,7 @@ public class PublishInvoicesAction implements Runnable {
BillingModule.CRON_QUEUE,
cloudTasksUtils.createPostTask(
CopyDetailReportsAction.PATH,
Service.BACKEND.toString(),
Service.BACKEND,
ImmutableMultimap.of(PARAM_YEAR_MONTH, yearMonth.toString())));
}
}
@@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.bigquery.BigqueryJobFailureException;
import google.registry.config.RegistryConfig.Config;
import google.registry.reporting.icann.IcannReportingModule.ReportType;
@@ -34,7 +35,6 @@ import google.registry.request.Action.Service;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.CloudTasksUtils;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import google.registry.util.SendEmailService;
@@ -123,7 +123,7 @@ public final class IcannReportingStagingAction implements Runnable {
CRON_QUEUE,
cloudTasksUtils.createPostTaskWithDelay(
IcannReportingUploadAction.PATH,
Service.BACKEND.toString(),
Service.BACKEND,
null,
Duration.standardMinutes(2)));
return null;
@@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.keyring.api.KeyModule.Key;
@@ -37,7 +38,6 @@ import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import java.io.IOException;
import javax.inject.Inject;
import org.joda.time.Duration;
@@ -135,7 +135,7 @@ public class GenerateSpec11ReportAction implements Runnable {
ReportingModule.BEAM_QUEUE,
cloudTasksUtils.createPostTaskWithDelay(
PublishSpec11ReportAction.PATH,
Service.BACKEND.toString(),
Service.BACKEND,
ImmutableMultimap.of(
ReportingModule.PARAM_JOB_ID,
jobId,
@@ -55,7 +55,7 @@ public abstract class IdTokenAuthenticationBase implements AuthenticationMechani
JsonWebSignature token;
try {
token = tokenVerifier.verify(rawIdToken);
} catch (TokenVerifier.VerificationException e) {
} catch (Exception e) {
logger.atInfo().withCause(e).log("Error when verifying access token");
return AuthResult.NOT_AUTHENTICATED;
}
@@ -18,6 +18,7 @@ import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static google.registry.request.auth.AuthLevel.APP;
import com.google.auth.oauth2.TokenVerifier;
import com.google.common.collect.ImmutableList;
import google.registry.config.RegistryConfig.Config;
import google.registry.request.auth.AuthModule.ServiceAccount;
import javax.inject.Inject;
@@ -30,16 +31,16 @@ import javax.servlet.http.HttpServletRequest;
*/
public class ServiceAccountAuthenticationMechanism extends IdTokenAuthenticationBase {
private final String cloudSchedulerEmailPrefix;
private static final String BEARER_PREFIX = "Bearer ";
private final ImmutableList<String> serviceAccountEmails;
@Inject
public ServiceAccountAuthenticationMechanism(
@ServiceAccount TokenVerifier tokenVerifier,
@Config("cloudSchedulerServiceAccountEmail") String cloudSchedulerEmailPrefix) {
@Config("serviceAccountEmails") ImmutableList<String> serviceAccountEmails) {
super(tokenVerifier);
this.cloudSchedulerEmailPrefix = cloudSchedulerEmailPrefix;
this.serviceAccountEmails = serviceAccountEmails;
}
@Override
@@ -53,7 +54,7 @@ public class ServiceAccountAuthenticationMechanism extends IdTokenAuthentication
@Override
AuthResult authResultFromEmail(String emailAddress) {
if (emailAddress.equals(cloudSchedulerEmailPrefix)) {
if (serviceAccountEmails.stream().anyMatch(e -> e.equals(emailAddress))) {
return AuthResult.create(APP);
} else {
return AuthResult.NOT_AUTHENTICATED;
@@ -17,8 +17,8 @@ package google.registry.tldconfig.idn;
import static google.registry.tldconfig.idn.IdnTableEnum.EXTENDED_LATIN;
import static google.registry.tldconfig.idn.IdnTableEnum.JA;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.model.tld.Registry;
import google.registry.util.Idn;
import java.util.Optional;
@@ -26,23 +26,8 @@ import java.util.Optional;
public final class IdnLabelValidator {
/** Most TLDs will use this generic list of IDN tables. */
private static final ImmutableList<IdnTableEnum> DEFAULT_IDN_TABLES =
ImmutableList.of(EXTENDED_LATIN, JA);
private static final ImmutableMap<String, ImmutableList<IdnTableEnum>>
DEFAULT_IDN_TABLE_LISTS_PER_TLD =
ImmutableMap.of("xn--q9jyb4c", ImmutableList.of(EXTENDED_LATIN, JA));
/** Some TLDs have their own IDN tables, configured here. */
private ImmutableMap<String, ImmutableList<IdnTableEnum>> idnTableListsPerTld;
IdnLabelValidator(ImmutableMap<String, ImmutableList<IdnTableEnum>> indTableListsPerTld) {
this.idnTableListsPerTld = indTableListsPerTld;
}
public static IdnLabelValidator createDefaultIdnLabelValidator() {
return new IdnLabelValidator(DEFAULT_IDN_TABLE_LISTS_PER_TLD);
}
private static final ImmutableSet<IdnTableEnum> DEFAULT_IDN_TABLES =
ImmutableSet.of(EXTENDED_LATIN, JA);
/**
* Returns name of first matching {@link IdnTable} if domain label is valid for the given TLD.
@@ -50,10 +35,13 @@ public final class IdnLabelValidator {
* <p>A label is valid if it is considered valid by at least one configured IDN table for that
* TLD. If no match is found, an absent value is returned.
*/
public Optional<String> findValidIdnTableForTld(String label, String tld) {
public Optional<String> findValidIdnTableForTld(String label, String tldStr) {
String unicodeString = Idn.toUnicode(label);
for (IdnTableEnum idnTable :
Optional.ofNullable(idnTableListsPerTld.get(tld)).orElse(DEFAULT_IDN_TABLES)) {
Registry tld = Registry.get(tldStr); // uses the cache
ImmutableSet<IdnTableEnum> idnTablesForTld = tld.getIdnTables();
ImmutableSet<IdnTableEnum> idnTables =
idnTablesForTld.isEmpty() ? DEFAULT_IDN_TABLES : idnTablesForTld;
for (IdnTableEnum idnTable : idnTables) {
if (idnTable.getTable().isValidLabel(unicodeString)) {
return Optional.of(idnTable.getTable().getName());
}
@@ -24,23 +24,48 @@ import java.net.URL;
/** Wrapper enum that loads all {@link IdnTable} resources into memory. */
public enum IdnTableEnum {
EXTENDED_LATIN,
JA;
/**
* Extended Latin, as used on our existing TLD launches prior to 2023.
*
* <p>As of 2023 this table is no longer conformant with ICANN's IDN policies for new launches, so
* it is retained solely for legacy compatibility with already-launched TLDs.
*/
EXTENDED_LATIN("extended_latin.txt"),
/**
* Extended Latin, but with confusable characters removed.
*
* <p>This is compatible with ICANN's requirements as of 2023, and is used for the Dads and Grads
* TLDs and all subsequent TLD launches. Note that confusable characters consist of various
* letters with diacritic marks on them, e.g. U+00EF (LATIN SMALL LETTER I WITH DIAERESIS) is not
* allowed because it is confusable with the standard i.
*/
UNCONFUSABLE_LATIN("unconfusable_latin.txt"),
/**
* Japanese, as used on our existing TLD launches prior to 2023.
*
* <p>As of 2023 this table is no longer conformant with ICANN's IDN policies for new launches, so
* it is retained solely for legacy compatibility with already-launched TLDs.
*/
JA("japanese.txt");
private final IdnTable table;
IdnTableEnum() {
this.table = load(Ascii.toLowerCase(name()));
IdnTableEnum(String filename) {
this.table = load(Ascii.toLowerCase(name()), filename);
}
public IdnTable getTable() {
return table;
}
private static IdnTable load(String name) {
private static IdnTable load(String tableName, String filename) {
try {
URL resource = Resources.getResource(IdnTableEnum.class, name + ".txt");
return IdnTable.createFrom(name, readLines(resource, UTF_8), LanguageValidator.get(name));
URL resource = Resources.getResource(IdnTableEnum.class, filename);
return IdnTable.createFrom(
tableName, readLines(resource, UTF_8), LanguageValidator.get(tableName));
} catch (IOException e) {
throw new RuntimeException(e); // should never happen
}
@@ -49,6 +49,7 @@ U+011F # LATIN SMALL LETTER G WITH BREVE
U+01E7 # LATIN SMALL LETTER G WITH CARON
U+0121 # LATIN SMALL LETTER G WITH DOT ABOVE
U+0123 # LATIN SMALL LETTER G WITH CEDILLA
U+01E5 # LATIN SMALL LETTER G WITH STROKE
U+0068 # LATIN SMALL LETTER H
U+0127 # LATIN SMALL LETTER H WITH STROKE
U+0069 # LATIN SMALL LETTER I
@@ -0,0 +1,124 @@
# Registry: Charleston Road Registry Inc.
# Script: Latn
# Version: 2.0
# Effective Date: 2023-04-04
# URL: https://www.iana.org/domains/idn-tables/tables/google_latn_2.0.txt
# Policy: https://www.registry.google/about/policies/domainabuse/
# Contact Name: CRR Tech
# Email address: crr-tech@google.com
# Telephone: +1 (650) 253-0000
#
# Code points requiring context rules
#
# Code point Description of rule/Reference
#
# U+002D Label must neither start nor end with U+002D. Label
# HYPHEN-MINUS must not have U+002D in both third and fourth
# position. RFC 5891 (sec 4.2.3.1)
#
U+002D # HYPHEN-MINUS
U+0030 # DIGIT ZERO
U+0031 # DIGIT ONE
U+0032 # DIGIT TWO
U+0033 # DIGIT THREE
U+0034 # DIGIT FOUR
U+0035 # DIGIT FIVE
U+0036 # DIGIT SIX
U+0037 # DIGIT SEVEN
U+0038 # DIGIT EIGHT
U+0039 # DIGIT NINE
U+0061 # LATIN SMALL LETTER A
U+00E0 # LATIN SMALL LETTER A WITH GRAVE
U+0103 # LATIN SMALL LETTER A WITH BREVE
U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
U+00E3 # LATIN SMALL LETTER A WITH TILDE
U+0105 # LATIN SMALL LETTER A WITH OGONEK
U+00E6 # LATIN SMALL LETTER AE
U+0062 # LATIN SMALL LETTER B
U+0063 # LATIN SMALL LETTER C
U+0107 # LATIN SMALL LETTER C WITH ACUTE
U+010D # LATIN SMALL LETTER C WITH CARON
U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
U+0064 # LATIN SMALL LETTER D
U+010F # LATIN SMALL LETTER D WITH CARON
U+0111 # LATIN SMALL LETTER D WITH STROKE
U+00F0 # LATIN SMALL LETTER ETH
U+0065 # LATIN SMALL LETTER E
U+00E9 # LATIN SMALL LETTER E WITH ACUTE
U+00E8 # LATIN SMALL LETTER E WITH GRAVE
U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
U+011B # LATIN SMALL LETTER E WITH CARON
U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
U+0119 # LATIN SMALL LETTER E WITH OGONEK
U+0113 # LATIN SMALL LETTER E WITH MACRON
U+0117 # LATIN SMALL LETTER E WITH DOT ABOVE
U+0259 # LATIN SMALL LETTER SCHWA
U+0066 # LATIN SMALL LETTER F
U+0067 # LATIN SMALL LETTER G
U+011F # LATIN SMALL LETTER G WITH BREVE
U+0121 # LATIN SMALL LETTER G WITH DOT ABOVE
U+0068 # LATIN SMALL LETTER H
U+0127 # LATIN SMALL LETTER H WITH STROKE
U+0069 # LATIN SMALL LETTER I
U+00EC # LATIN SMALL LETTER I WITH GRAVE
U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
U+012F # LATIN SMALL LETTER I WITH OGONEK
U+012B # LATIN SMALL LETTER I WITH MACRON
U+006A # LATIN SMALL LETTER J
U+006B # LATIN SMALL LETTER K
U+01E9 # LATIN SMALL LETTER K WITH CARON
U+0137 # LATIN SMALL LETTER K WITH CEDILLA
U+006C # LATIN SMALL LETTER L
U+013A # LATIN SMALL LETTER L WITH ACUTE
U+013E # LATIN SMALL LETTER L WITH CARON
U+013C # LATIN SMALL LETTER L WITH CEDILLA
U+0142 # LATIN SMALL LETTER L WITH STROKE
U+006D # LATIN SMALL LETTER M
U+006E # LATIN SMALL LETTER N
U+0148 # LATIN SMALL LETTER N WITH CARON
U+00F1 # LATIN SMALL LETTER N WITH TILDE
U+0146 # LATIN SMALL LETTER N WITH CEDILLA
U+006F # LATIN SMALL LETTER O
U+00F2 # LATIN SMALL LETTER O WITH GRAVE
U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
U+0151 # LATIN SMALL LETTER O WITH DOUBLE ACUTE
U+00F5 # LATIN SMALL LETTER O WITH TILDE
U+00F8 # LATIN SMALL LETTER O WITH STROKE
U+0153 # LATIN SMALL LIGATURE OE
U+0070 # LATIN SMALL LETTER P
U+0071 # LATIN SMALL LETTER Q
U+0072 # LATIN SMALL LETTER R
U+0155 # LATIN SMALL LETTER R WITH ACUTE
U+0159 # LATIN SMALL LETTER R WITH CARON
U+0073 # LATIN SMALL LETTER S
U+015B # LATIN SMALL LETTER S WITH ACUTE
U+0161 # LATIN SMALL LETTER S WITH CARON
U+015F # LATIN SMALL LETTER S WITH CEDILLA
U+0074 # LATIN SMALL LETTER T
U+0165 # LATIN SMALL LETTER T WITH CARON
U+0167 # LATIN SMALL LETTER T WITH STROKE
U+0075 # LATIN SMALL LETTER U
U+00F9 # LATIN SMALL LETTER U WITH GRAVE
U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
U+016F # LATIN SMALL LETTER U WITH RING ABOVE
U+0171 # LATIN SMALL LETTER U WITH DOUBLE ACUTE
U+0173 # LATIN SMALL LETTER U WITH OGONEK
U+016B # LATIN SMALL LETTER U WITH MACRON
U+0076 # LATIN SMALL LETTER V
U+0077 # LATIN SMALL LETTER W
U+0175 # LATIN SMALL LETTER W WITH CIRCUMFLEX
U+0078 # LATIN SMALL LETTER X
U+0079 # LATIN SMALL LETTER Y
U+00FD # LATIN SMALL LETTER Y WITH ACUTE
U+0177 # LATIN SMALL LETTER Y WITH CIRCUMFLEX
U+00FF # LATIN SMALL LETTER Y WITH DIAERESIS
U+007A # LATIN SMALL LETTER Z
U+017A # LATIN SMALL LETTER Z WITH ACUTE
U+017E # LATIN SMALL LETTER Z WITH CARON
U+0292 # LATIN SMALL LETTER EZH
U+01EF # LATIN SMALL LETTER EZH WITH CARON
U+00FE # LATIN SMALL LETTER THORN
@@ -14,21 +14,15 @@
package google.registry.tmch;
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.taskqueue.TaskOptions.Method;
import com.google.common.base.Joiner;
import google.registry.model.domain.Domain;
import google.registry.model.registrar.Registrar;
import java.util.Optional;
import org.joda.time.DateTime;
/**
* Helper methods for creating tasks containing CSV line data in the lordn-sunrise and lordn-claims
* queues based on {@link Domain} changes.
* Helper methods for creating tasks containing CSV line data based on {@link Domain#getLordnPhase}.
*
* <p>Note that, per the <a href="https://tools.ietf.org/html/draft-ietf-regext-tmch-func-spec-04">
* TMCH RFC</a>, while the application-datetime data is optional (which we never send because there
@@ -36,65 +30,32 @@ import org.joda.time.DateTime;
*/
public final class LordnTaskUtils {
public static final String QUEUE_SUNRISE = "lordn-sunrise";
public static final String QUEUE_CLAIMS = "lordn-claims";
public static final String COLUMNS_CLAIMS =
"roid,domain-name,notice-id,registrar-id,"
+ "registration-datetime,ack-datetime,application-datetime";
public static final String COLUMNS_SUNRISE =
"roid,domain-name,SMD-id,registrar-id," + "registration-datetime,application-datetime";
/** Enqueues a task in the LORDN queue representing a line of CSV for LORDN export. */
public static void enqueueDomainTask(Domain domain) {
tm().assertInTransaction();
// This method needs to use transactionTime as the Domain's creationTime because CreationTime
// isn't yet populated when this method is called during the resource flow.
String tld = domain.getTld();
if (domain.getLaunchNotice() == null) {
getQueue(QUEUE_SUNRISE)
.add(
TaskOptions.Builder.withTag(tld)
.method(Method.PULL)
.payload(getCsvLineForSunriseDomain(domain, tm().getTransactionTime())));
} else {
getQueue(QUEUE_CLAIMS)
.add(
TaskOptions.Builder.withTag(tld)
.method(Method.PULL)
.payload(getCsvLineForClaimsDomain(domain, tm().getTransactionTime())));
}
}
/** Returns the corresponding CSV LORDN line for a sunrise domain. */
public static String getCsvLineForSunriseDomain(Domain domain) {
return getCsvLineForSunriseDomain(domain, domain.getCreationTime());
}
// TODO: Merge into the function above after pull queue migration.
private static String getCsvLineForSunriseDomain(Domain domain, DateTime transactionTime) {
return Joiner.on(',')
.join(
domain.getRepoId(),
domain.getDomainName(),
domain.getSmdId(),
getIanaIdentifier(domain.getCreationRegistrarId()),
transactionTime); // Used as creation time.
domain.getCreationTime()); // Used as creation time.
}
/** Returns the corresponding CSV LORDN line for a claims domain. */
public static String getCsvLineForClaimsDomain(Domain domain) {
return getCsvLineForClaimsDomain(domain, domain.getCreationTime());
}
// TODO: Merge into the function above after pull queue migration.
private static String getCsvLineForClaimsDomain(Domain domain, DateTime transactionTime) {
return Joiner.on(',')
.join(
domain.getRepoId(),
domain.getDomainName(),
domain.getLaunchNotice().getNoticeId().getTcnId(),
getIanaIdentifier(domain.getCreationRegistrarId()),
transactionTime, // Used as creation time.
domain.getCreationTime(), // Used as creation time.
domain.getLaunchNotice().getAcceptedTime());
}
@@ -14,9 +14,7 @@
package google.registry.tmch;
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.net.HttpHeaders.LOCATION;
import static com.google.common.net.MediaType.CSV_UTF_8;
import static google.registry.persistence.transaction.QueryComposer.Comparator.EQ;
@@ -27,25 +25,15 @@ import static google.registry.tmch.LordnTaskUtils.COLUMNS_SUNRISE;
import static google.registry.tmch.LordnTaskUtils.getCsvLineForClaimsDomain;
import static google.registry.tmch.LordnTaskUtils.getCsvLineForSunriseDomain;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
import com.google.api.client.http.HttpMethods;
import com.google.appengine.api.taskqueue.LeaseOptions;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.TaskHandle;
import com.google.appengine.api.taskqueue.TransientFailureException;
import com.google.apphosting.api.DeadlineExceededException;
import com.google.cloud.tasks.v2.Task;
import com.google.common.base.Ascii;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.domain.Domain;
import google.registry.request.Action;
@@ -57,7 +45,6 @@ import google.registry.request.UrlConnectionUtils;
import google.registry.request.auth.Auth;
import google.registry.tmch.LordnTaskUtils.LordnPhase;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import google.registry.util.Retrier;
import google.registry.util.UrlConnectionException;
import java.io.IOException;
@@ -66,11 +53,8 @@ import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
@@ -90,12 +74,8 @@ public final class NordnUploadAction implements Runnable {
static final String PATH = "/_dr/task/nordnUpload";
static final String LORDN_PHASE_PARAM = "lordnPhase";
// TODO: Delete after migrating off of pull queue.
static final String PULL_QUEUE_PARAM = "pullQueue";
private static final int BATCH_SIZE = 1000;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Duration LEASE_PERIOD = Duration.standardHours(1);
/**
* A unique (enough) id that is outputted in log lines to make it clear which log lines are
@@ -122,10 +102,6 @@ public final class NordnUploadAction implements Runnable {
@Parameter(RequestParameters.PARAM_TLD)
String tld;
@Inject
@Parameter(PULL_QUEUE_PARAM)
Optional<Boolean> usePullQueue;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject
@@ -146,136 +122,59 @@ public final class NordnUploadAction implements Runnable {
@Override
public void run() {
if (usePullQueue.orElse(false)) {
try {
processLordnTasks();
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
} else {
checkArgument(
phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS),
"Invalid phase specified to NordnUploadAction: %s.",
phase);
tm().transact(
() -> {
// Note here that we load all domains pending Nordn in one batch, which should not
// be a problem for the rate of domain registration that we see. If we anticipate
// a peak in claims during TLD launch (sunrise is NOT first-come-first-serve, so
// there should be no expectation of a peak during it), we can consider temporarily
// increasing the frequency of Nordn upload to reduce the size of each batch.
//
// We did not further divide the domains into smaller batches because the
// read-upload-write operation per small batch needs to be inside a single
// transaction to prevent race conditions, and running several uploads in rapid
// sucession will likely overwhelm the MarksDB upload server, which recommands a
// maximum upload frequency of every 3 hours.
//
// See:
// https://datatracker.ietf.org/doc/html/draft-ietf-regext-tmch-func-spec-01#section-5.2.3.3
List<Domain> domains =
tm().createQueryComposer(Domain.class)
.where("lordnPhase", EQ, LordnPhase.valueOf(Ascii.toUpperCase(phase)))
.where("tld", EQ, tld)
.orderBy("creationTime")
.list();
if (domains.isEmpty()) {
return;
}
StringBuilder csv = new StringBuilder();
ImmutableList.Builder<Domain> newDomains = new ImmutableList.Builder<>();
domains.forEach(
domain -> {
if (phase.equals(PARAM_LORDN_PHASE_SUNRISE)) {
csv.append(getCsvLineForSunriseDomain(domain)).append('\n');
} else {
csv.append(getCsvLineForClaimsDomain(domain)).append('\n');
}
Domain newDomain = domain.asBuilder().setLordnPhase(LordnPhase.NONE).build();
newDomains.add(newDomain);
});
String columns =
phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
String header =
String.format("1,%s,%d\n%s\n", clock.nowUtc(), domains.size(), columns);
try {
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), header + csv);
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
tm().updateAll(newDomains.build());
});
}
}
/**
* Converts a list of queue tasks, each containing a row of CSV data, into a single newline-
* delimited String.
*/
static String convertTasksToCsv(List<TaskHandle> tasks, DateTime now, String columns) {
// Use a Set for deduping purposes, so we can be idempotent in case tasks happened to be
// enqueued multiple times for a given domain create.
ImmutableSortedSet.Builder<String> builder =
new ImmutableSortedSet.Builder<>(Ordering.natural());
for (TaskHandle task : checkNotNull(tasks)) {
String payload = new String(task.getPayload(), UTF_8);
if (!Strings.isNullOrEmpty(payload)) {
builder.add(payload + '\n');
}
}
ImmutableSortedSet<String> csvLines = builder.build();
String header = String.format("1,%s,%d\n%s\n", now, csvLines.size(), columns);
return header + Joiner.on("").join(csvLines);
}
/** Leases and returns all tasks from the queue with the specified tag tld, in batches. */
List<TaskHandle> loadAllTasks(Queue queue, String tld) {
ImmutableList.Builder<TaskHandle> allTasks = new ImmutableList.Builder<>();
while (true) {
List<TaskHandle> tasks =
retrier.callWithRetry(
() ->
queue.leaseTasks(
LeaseOptions.Builder.withTag(tld)
.leasePeriod(LEASE_PERIOD.getMillis(), TimeUnit.MILLISECONDS)
.countLimit(BATCH_SIZE)),
TransientFailureException.class,
DeadlineExceededException.class);
if (tasks.isEmpty()) {
return allTasks.build();
}
allTasks.addAll(tasks);
}
}
private void processLordnTasks() throws IOException, GeneralSecurityException {
checkArgument(
phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS),
"Invalid phase specified to NordnUploadAction: %s.",
phase);
DateTime now = clock.nowUtc();
Queue queue =
getQueue(
phase.equals(PARAM_LORDN_PHASE_SUNRISE)
? LordnTaskUtils.QUEUE_SUNRISE
: LordnTaskUtils.QUEUE_CLAIMS);
String columns = phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
List<TaskHandle> tasks = loadAllTasks(queue, tld);
// Note: This upload/task deletion isn't done atomically (it's not clear how one would do so
// anyway). As a result, it is possible that the upload might succeed yet the deletion of
// enqueued tasks might fail. If so, this would result in the same lines being uploaded to NORDN
// across multiple uploads. This is probably OK; all that we really cannot have is a missing
// line.
if (!tasks.isEmpty()) {
String csvData = convertTasksToCsv(tasks, now, columns);
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), csvData);
Lists.partition(tasks, BATCH_SIZE)
.forEach(
batch ->
retrier.callWithRetry(
() -> queue.deleteTask(batch), TransientFailureException.class));
}
tm().transact(
() -> {
// Note here that we load all domains pending Nordn in one batch, which should not
// be a problem for the rate of domain registration that we see. If we anticipate
// a peak in claims during TLD launch (sunrise is NOT first-come-first-serve, so
// there should be no expectation of a peak during it), we can consider temporarily
// increasing the frequency of Nordn upload to reduce the size of each batch.
//
// We did not further divide the domains into smaller batches because the
// read-upload-write operation per small batch needs to be inside a single
// transaction to prevent race conditions, and running several uploads in rapid
// sucession will likely overwhelm the MarksDB upload server, which recommands a
// maximum upload frequency of every 3 hours.
//
// See:
// https://datatracker.ietf.org/doc/html/draft-ietf-regext-tmch-func-spec-01#section-5.2.3.3
List<Domain> domains =
tm().createQueryComposer(Domain.class)
.where("lordnPhase", EQ, LordnPhase.valueOf(Ascii.toUpperCase(phase)))
.where("tld", EQ, tld)
.orderBy("creationTime")
.list();
if (domains.isEmpty()) {
return;
}
StringBuilder csv = new StringBuilder();
ImmutableList.Builder<Domain> newDomains = new ImmutableList.Builder<>();
domains.forEach(
domain -> {
if (phase.equals(PARAM_LORDN_PHASE_SUNRISE)) {
csv.append(getCsvLineForSunriseDomain(domain)).append('\n');
} else {
csv.append(getCsvLineForClaimsDomain(domain)).append('\n');
}
Domain newDomain = domain.asBuilder().setLordnPhase(LordnPhase.NONE).build();
newDomains.add(newDomain);
});
String columns =
phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
String header =
String.format("1,%s,%d\n%s\n", clock.nowUtc(), domains.size(), columns);
try {
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), header + csv);
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
tm().updateAll(newDomains.build());
});
}
/**
@@ -333,7 +232,7 @@ public final class NordnUploadAction implements Runnable {
// The actionLogId is used to uniquely associate the verify task back to the upload task.
return cloudTasksUtils.createPostTaskWithDelay(
NordnVerifyAction.PATH,
Service.BACKEND.toString(),
Service.BACKEND,
ImmutableMultimap.<String, String>builder()
.put(NordnVerifyAction.NORDN_URL_PARAM, url.toString())
.put(NordnVerifyAction.NORDN_LOG_ID_PARAM, actionLogId)
@@ -16,7 +16,6 @@ package google.registry.tmch;
import static com.google.common.io.Resources.asByteSource;
import static com.google.common.io.Resources.getResource;
import static google.registry.request.RequestParameters.extractOptionalBooleanParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter;
import dagger.Module;
@@ -26,7 +25,6 @@ import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.bouncycastle.openpgp.PGPPublicKey;
@@ -66,10 +64,4 @@ public final class TmchModule {
static String provideNordnLogId(HttpServletRequest req) {
return extractRequiredParameter(req, NordnVerifyAction.NORDN_LOG_ID_PARAM);
}
@Provides
@Parameter(NordnUploadAction.PULL_QUEUE_PARAM)
static Optional<Boolean> provideUsePullQueue(HttpServletRequest req) {
return extractOptionalBooleanParameter(req, NordnUploadAction.PULL_QUEUE_PARAM);
}
}
@@ -15,6 +15,7 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.tools.UpdateOrDeleteAllocationTokensCommand.getTokenKeys;
import static google.registry.util.CollectionUtils.findDuplicates;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
@@ -34,9 +35,11 @@ import google.registry.model.tld.Registry.TldState;
import google.registry.model.tld.Registry.TldType;
import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao;
import google.registry.tldconfig.idn.IdnTableEnum;
import google.registry.tools.params.OptionalStringParameter;
import google.registry.tools.params.TransitionListParameter.BillingCostTransitions;
import google.registry.tools.params.TransitionListParameter.TldStateTransitions;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -234,6 +237,24 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
)
Integer numDnsPublishShards;
@Nullable
@Parameter(
names = {"--dns_a_plus_aaaa_ttl"},
description = "The time to live for DNS A and AAAA records (Ex: PT240S)")
Duration dnsAPlusAaaaTtl;
@Nullable
@Parameter(
names = {"--dns_ns_ttl"},
description = "The time to live for DNS NS records (Ex: PT240S)")
Duration dnsNsTtl;
@Nullable
@Parameter(
names = {"--dns_ds_ttl"},
description = "The time to live for DNS DS records (Ex: PT240S)")
Duration dnsDsTtl;
@Nullable
@Parameter(
names = "--default_tokens",
@@ -244,6 +265,15 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
+ " present default tokens.")
List<String> defaultTokens;
@Nullable
@Parameter(
names = "--idn_tables",
description =
"A comma-separated list of the IDN tables to use for this TLD. Specify an empty list to"
+ " remove any previously-set tables and to use the default. All elements must be"
+ " IdnTableEnum values")
List<String> idnTables;
/** Returns the existing registry (for update) or null (for creates). */
@Nullable
abstract Registry getOldRegistry(String tld);
@@ -352,6 +382,9 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
Optional.ofNullable(lordnUsername).ifPresent(u -> builder.setLordnUsername(u.orElse(null)));
Optional.ofNullable(claimsPeriodEnd).ifPresent(builder::setClaimsPeriodEnd);
Optional.ofNullable(numDnsPublishShards).ifPresent(builder::setNumDnsPublishLocks);
Optional.ofNullable(dnsAPlusAaaaTtl).ifPresent(builder::setDnsAPlusAaaaTtl);
Optional.ofNullable(dnsNsTtl).ifPresent(builder::setDnsNsTtl);
Optional.ofNullable(dnsDsTtl).ifPresent(builder::setDnsDsTtl);
if (premiumListName != null) {
if (premiumListName.isPresent()) {
@@ -392,6 +425,23 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
builder.setDefaultPromoTokens(getTokenKeys(defaultTokens, null));
}
}
if (idnTables != null) {
if (idnTables.equals(ImmutableList.of(""))) {
builder.setIdnTables(ImmutableSet.of());
} else {
ImmutableSet<String> upperCaseIdnTables =
idnTables.stream().map(String::toUpperCase).collect(toImmutableSet());
ImmutableSet<String> validIdnStringValues =
Arrays.stream(IdnTableEnum.values()).map(Enum::name).collect(toImmutableSet());
checkArgument(
validIdnStringValues.containsAll(upperCaseIdnTables),
"IDN tables %s contained invalid value(s). Possible values: %s",
upperCaseIdnTables,
validIdnStringValues);
builder.setIdnTables(
upperCaseIdnTables.stream().map(IdnTableEnum::valueOf).collect(toImmutableSet()));
}
}
// Update the Registry object.
setCommandSpecificProperties(builder);
stageEntityChange(oldRegistry, builder.build());
@@ -23,6 +23,7 @@ import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STAT
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.batch.CloudTasksUtils;
import google.registry.batch.RelockDomainAction;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.billing.BillingEvent;
@@ -34,7 +35,6 @@ import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Registry;
import google.registry.model.tld.RegistryLockDao;
import google.registry.request.Action.Service;
import google.registry.util.CloudTasksUtils;
import google.registry.util.StringGenerator;
import java.util.Optional;
import javax.annotation.Nullable;
@@ -223,7 +223,7 @@ public final class DomainLockUtils {
QUEUE_ASYNC_ACTIONS,
cloudTasksUtils.createPostTaskWithDelay(
RelockDomainAction.PATH,
Service.BACKEND.toString(),
Service.BACKEND,
ImmutableMultimap.of(
RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM,
String.valueOf(lockRevisionId),
@@ -39,6 +39,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.common.io.Files;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
@@ -114,6 +115,11 @@ class GenerateAllocationTokensCommand implements Command {
description = "Comma-separated list of allowed TLDs, or null if all are allowed")
private List<String> allowedTlds;
@Parameter(
names = {"--allowed_epp_actions"},
description = "Comma-separated list of allowed EPP actions, or null if all are allowed")
private List<String> allowedEppActions;
@Parameter(
names = {"--discount_fraction"},
description =
@@ -207,7 +213,13 @@ class GenerateAllocationTokensCommand implements Command {
.setTokenType(tokenType == null ? SINGLE_USE : tokenType)
.setAllowedRegistrarIds(
ImmutableSet.copyOf(nullToEmpty(allowedClientIds)))
.setAllowedTlds(ImmutableSet.copyOf(nullToEmpty(allowedTlds)));
.setAllowedTlds(ImmutableSet.copyOf(nullToEmpty(allowedTlds)))
.setAllowedEppActions(
isNullOrEmpty(allowedEppActions)
? ImmutableSet.of()
: allowedEppActions.stream()
.map(CommandName::parseKnownCommand)
.collect(toImmutableSet()));
Optional.ofNullable(discountFraction).ifPresent(token::setDiscountFraction);
Optional.ofNullable(discountPremiums).ifPresent(token::setDiscountPremiums);
Optional.ofNullable(discountYears).ifPresent(token::setDiscountYears);
@@ -255,6 +267,10 @@ class GenerateAllocationTokensCommand implements Command {
!ImmutableList.of("").equals(allowedTlds),
"Either omit --allowed_tlds if all TLDs are allowed, or include a comma-separated list");
if (ImmutableList.of("").equals(allowedEppActions)) {
allowedEppActions = ImmutableList.of();
}
if (!isNullOrEmpty(tokenStatusTransitions)) {
// Don't allow package tokens to be created with a scheduled end time since this could allow
// future domains to be attributed to the package and never be billed. Package promotion
@@ -28,11 +28,11 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMultimap;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.rde.RdeMode;
import google.registry.rde.RdeStagingAction;
import google.registry.request.Action.Service;
import google.registry.tools.params.DateTimeParameter;
import google.registry.util.CloudTasksUtils;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -122,7 +122,7 @@ final class GenerateEscrowDepositCommand implements Command {
cloudTasksUtils.enqueue(
RDE_REPORT_QUEUE,
cloudTasksUtils.createPostTask(
RdeStagingAction.PATH, Service.BACKEND.toString(), paramsBuilder.build()));
RdeStagingAction.PATH, Service.BACKEND, paramsBuilder.build()));
}
}
@@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
@@ -63,6 +64,13 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
+ "existing list.")
private List<String> allowedTlds;
@Parameter(
names = {"--allowed_epp_actions"},
description =
"Comma-separated list of allowed EPP actions. Use an empty string to clear the existing"
+ " list.")
private List<String> allowedEppActions;
@Parameter(
names = {"--discount_fraction"},
description =
@@ -128,6 +136,9 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
if (ImmutableList.of("").equals(allowedTlds)) {
allowedTlds = ImmutableList.of();
}
if (ImmutableList.of("").equals(allowedEppActions)) {
allowedEppActions = ImmutableList.of();
}
if (tokenStatusTransitions != null
&& (tokenStatusTransitions.containsValue(TokenStatus.ENDED)
@@ -184,6 +195,14 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
.ifPresent(clientIds -> builder.setAllowedRegistrarIds(ImmutableSet.copyOf(clientIds)));
Optional.ofNullable(allowedTlds)
.ifPresent(tlds -> builder.setAllowedTlds(ImmutableSet.copyOf(tlds)));
Optional.ofNullable(allowedEppActions)
.ifPresent(
eppActions -> {
builder.setAllowedEppActions(
eppActions.stream()
.map(CommandName::parseKnownCommand)
.collect(toImmutableSet()));
});
Optional.ofNullable(discountFraction).ifPresent(builder::setDiscountFraction);
Optional.ofNullable(discountPremiums).ifPresent(builder::setDiscountPremiums);
Optional.ofNullable(discountYears).ifPresent(builder::setDiscountYears);
@@ -21,7 +21,7 @@ import static google.registry.request.RequestParameters.PARAM_TLDS;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
@@ -66,7 +66,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
@Parameter("smearMinutes")
int smearMinutes;
@Inject DnsQueue dnsQueue;
@Inject DnsUtils dnsUtils;
@Inject Clock clock;
@Inject Random random;
@@ -91,7 +91,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
domainName -> {
try {
// Smear the task execution time over the next N minutes.
dnsQueue.addDomainRefreshTask(
dnsUtils.requestDomainDnsRefresh(
domainName, Duration.standardMinutes(random.nextInt(smearMinutes)));
} catch (Throwable t) {
logger.atSevere().withCause(t).log(
@@ -36,6 +36,7 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryEnvironment;
import google.registry.export.sheet.SyncRegistrarsSheetAction;
import google.registry.flows.certs.CertificateChecker;
@@ -58,7 +59,6 @@ import google.registry.ui.forms.FormException;
import google.registry.ui.forms.FormFieldException;
import google.registry.ui.server.RegistrarFormFields;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.CloudTasksUtils;
import google.registry.util.CollectionUtils;
import google.registry.util.DiffUtils;
import java.util.HashSet;
@@ -643,7 +643,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
cloudTasksUtils.enqueue(
SyncRegistrarsSheetAction.QUEUE,
cloudTasksUtils.createGetTask(
SyncRegistrarsSheetAction.PATH, Service.BACKEND.toString(), ImmutableMultimap.of()));
SyncRegistrarsSheetAction.PATH, Service.BACKEND, ImmutableMultimap.of()));
}
String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get()));
sendEmailUtils.sendEmail(
@@ -43,6 +43,7 @@
<class>google.registry.model.billing.BillingEvent$Recurring</class>
<class>google.registry.model.common.Cursor</class>
<class>google.registry.model.common.DatabaseMigrationStateSchedule</class>
<class>google.registry.model.common.DnsRefreshRequest</class>
<class>google.registry.model.console.User</class>
<class>google.registry.model.contact.ContactHistory</class>
<class>google.registry.model.contact.Contact</class>
@@ -84,11 +85,13 @@
<class>google.registry.persistence.converter.BillingEventFlagSetConverter</class>
<class>google.registry.persistence.converter.BloomFilterConverter</class>
<class>google.registry.persistence.converter.CidrAddressBlockListConverter</class>
<class>google.registry.persistence.converter.CommandNameSetConverter</class>
<class>google.registry.persistence.converter.CurrencyToBillingConverter</class>
<class>google.registry.persistence.converter.CurrencyUnitConverter</class>
<class>google.registry.persistence.converter.DatabaseMigrationScheduleTransitionConverter</class>
<class>google.registry.persistence.converter.DateTimeConverter</class>
<class>google.registry.persistence.converter.DurationConverter</class>
<class>google.registry.persistence.converter.IdnTableEnumSetConverter</class>
<class>google.registry.persistence.converter.InetAddressSetConverter</class>
<class>google.registry.persistence.converter.LocalDateConverter</class>
<class>google.registry.persistence.converter.PostalInfoChoiceListConverter</class>
@@ -30,7 +30,6 @@ import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.FakeClock;
import google.registry.util.CapturingLogHandler;
import google.registry.util.CloudTasksUtils;
import google.registry.util.JdkLoggerConfig;
import java.util.logging.Level;
import org.joda.time.DateTime;
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.util;
package google.registry.batch;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -27,9 +27,11 @@ import com.google.cloud.tasks.v2.Task;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.LinkedListMultimap;
import google.registry.batch.CloudTasksUtils.SerializableCloudTasksClient;
import google.registry.request.Action.Service;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeSleeper;
import google.registry.util.CloudTasksUtils.SerializableCloudTasksClient;
import google.registry.util.Retrier;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Optional;
@@ -59,22 +61,22 @@ public class CloudTasksUtilsTest {
@Test
void testSuccess_createGetTasks() {
Task task = cloudTasksUtils.createGetTask("/the/path", "myservice", params);
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, params);
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
}
@Test
void testSuccess_createPostTasks() {
Task task = cloudTasksUtils.createPostTask("/the/path", "myservice", params);
Task task = cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, params);
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
.isEqualTo("application/x-www-form-urlencoded");
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
@@ -84,42 +86,43 @@ public class CloudTasksUtilsTest {
@Test
void testSuccess_createGetTasks_withNullParams() {
Task task = cloudTasksUtils.createGetTask("/the/path", "myservice", null);
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, null);
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
}
@Test
void testSuccess_createPostTasks_withNullParams() {
Task task = cloudTasksUtils.createPostTask("/the/path", "myservice", null);
Task task = cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, null);
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8)).isEmpty();
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
}
@Test
void testSuccess_createGetTasks_withEmptyParams() {
Task task = cloudTasksUtils.createGetTask("/the/path", "myservice", ImmutableMultimap.of());
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, ImmutableMultimap.of());
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
}
@Test
void testSuccess_createPostTasks_withEmptyParams() {
Task task = cloudTasksUtils.createPostTask("/the/path", "myservice", ImmutableMultimap.of());
Task task =
cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, ImmutableMultimap.of());
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8)).isEmpty();
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
}
@@ -128,12 +131,13 @@ public class CloudTasksUtilsTest {
@Test
void testSuccess_createGetTasks_withJitterSeconds() {
Task task =
cloudTasksUtils.createGetTaskWithJitter("/the/path", "myservice", params, Optional.of(100));
cloudTasksUtils.createGetTaskWithJitter(
"/the/path", Service.BACKEND, params, Optional.of(100));
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
Instant scheduleTime = Instant.ofEpochSecond(task.getScheduleTime().getSeconds());
Instant lowerBoundTime = Instant.ofEpochMilli(clock.nowUtc().getMillis());
@@ -147,11 +151,12 @@ public class CloudTasksUtilsTest {
@Test
void testSuccess_createPostTasks_withJitterSeconds() {
Task task =
cloudTasksUtils.createPostTaskWithJitter("/the/path", "myservice", params, Optional.of(1));
cloudTasksUtils.createPostTaskWithJitter(
"/the/path", Service.BACKEND, params, Optional.of(1));
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
.isEqualTo("application/x-www-form-urlencoded");
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
@@ -170,11 +175,11 @@ public class CloudTasksUtilsTest {
void testSuccess_createPostTasks_withEmptyJitterSeconds() {
Task task =
cloudTasksUtils.createPostTaskWithJitter(
"/the/path", "myservice", params, Optional.empty());
"/the/path", Service.BACKEND, params, Optional.empty());
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
.isEqualTo("application/x-www-form-urlencoded");
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
@@ -185,23 +190,25 @@ public class CloudTasksUtilsTest {
@Test
void testSuccess_createGetTasks_withEmptyJitterSeconds() {
Task task =
cloudTasksUtils.createGetTaskWithJitter("/the/path", "myservice", params, Optional.empty());
cloudTasksUtils.createGetTaskWithJitter(
"/the/path", Service.BACKEND, params, Optional.empty());
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
}
@Test
void testSuccess_createPostTasks_withZeroJitterSeconds() {
Task task =
cloudTasksUtils.createPostTaskWithJitter("/the/path", "myservice", params, Optional.of(0));
cloudTasksUtils.createPostTaskWithJitter(
"/the/path", Service.BACKEND, params, Optional.of(0));
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
.isEqualTo("application/x-www-form-urlencoded");
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
@@ -212,12 +219,13 @@ public class CloudTasksUtilsTest {
@Test
void testSuccess_createGetTasks_withZeroJitterSeconds() {
Task task =
cloudTasksUtils.createGetTaskWithJitter("/the/path", "myservice", params, Optional.of(0));
cloudTasksUtils.createGetTaskWithJitter(
"/the/path", Service.BACKEND, params, Optional.of(0));
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
}
@@ -225,12 +233,12 @@ public class CloudTasksUtilsTest {
void testSuccess_createGetTasks_withDelay() {
Task task =
cloudTasksUtils.createGetTaskWithDelay(
"/the/path", "myservice", params, Duration.standardMinutes(10));
"/the/path", Service.BACKEND, params, Duration.standardMinutes(10));
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(Instant.ofEpochSecond(task.getScheduleTime().getSeconds()))
.isEqualTo(Instant.ofEpochMilli(clock.nowUtc().plusMinutes(10).getMillis()));
}
@@ -239,11 +247,11 @@ public class CloudTasksUtilsTest {
void testSuccess_createPostTasks_withDelay() {
Task task =
cloudTasksUtils.createPostTaskWithDelay(
"/the/path", "myservice", params, Duration.standardMinutes(10));
"/the/path", Service.BACKEND, params, Duration.standardMinutes(10));
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
.isEqualTo("application/x-www-form-urlencoded");
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
@@ -260,7 +268,7 @@ public class CloudTasksUtilsTest {
IllegalArgumentException.class,
() ->
cloudTasksUtils.createGetTaskWithDelay(
"/the/path", "myservice", params, Duration.standardMinutes(-10)));
"/the/path", Service.BACKEND, params, Duration.standardMinutes(-10)));
assertThat(thrown).hasMessageThat().isEqualTo("Negative duration is not supported.");
}
@@ -271,18 +279,19 @@ public class CloudTasksUtilsTest {
IllegalArgumentException.class,
() ->
cloudTasksUtils.createGetTaskWithDelay(
"/the/path", "myservice", params, Duration.standardMinutes(-10)));
"/the/path", Service.BACKEND, params, Duration.standardMinutes(-10)));
assertThat(thrown).hasMessageThat().isEqualTo("Negative duration is not supported.");
}
@Test
void testSuccess_createPostTasks_withZeroDelay() {
Task task =
cloudTasksUtils.createPostTaskWithDelay("/the/path", "myservice", params, Duration.ZERO);
cloudTasksUtils.createPostTaskWithDelay(
"/the/path", Service.BACKEND, params, Duration.ZERO);
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
.isEqualTo("application/x-www-form-urlencoded");
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
@@ -293,12 +302,12 @@ public class CloudTasksUtilsTest {
@Test
void testSuccess_createGetTasks_withZeroDelay() {
Task task =
cloudTasksUtils.createGetTaskWithDelay("/the/path", "myservice", params, Duration.ZERO);
cloudTasksUtils.createGetTaskWithDelay("/the/path", Service.BACKEND, params, Duration.ZERO);
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
.isEqualTo("myservice");
.isEqualTo(Service.BACKEND.toString());
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
}
@@ -306,26 +315,26 @@ public class CloudTasksUtilsTest {
void testFailure_illegalPath() {
assertThrows(
IllegalArgumentException.class,
() -> cloudTasksUtils.createPostTask("the/path", "myservice", params));
() -> cloudTasksUtils.createPostTask("the/path", Service.BACKEND, params));
assertThrows(
IllegalArgumentException.class,
() -> cloudTasksUtils.createPostTask(null, "myservice", params));
() -> cloudTasksUtils.createPostTask(null, Service.BACKEND, params));
assertThrows(
IllegalArgumentException.class,
() -> cloudTasksUtils.createPostTask("", "myservice", params));
() -> cloudTasksUtils.createPostTask("", Service.BACKEND, params));
}
@Test
void testSuccess_enqueueTask() {
Task task = cloudTasksUtils.createGetTask("/the/path", "myservice", params);
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, params);
cloudTasksUtils.enqueue("test-queue", task);
verify(mockClient).enqueue("project", "location", "test-queue", task);
}
@Test
void testSuccess_enqueueTasks_varargs() {
Task task1 = cloudTasksUtils.createGetTask("/the/path", "myservice", params);
Task task2 = cloudTasksUtils.createGetTask("/other/path", "yourservice", params);
Task task1 = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, params);
Task task2 = cloudTasksUtils.createGetTask("/other/path", Service.TOOLS, params);
cloudTasksUtils.enqueue("test-queue", task1, task2);
verify(mockClient).enqueue("project", "location", "test-queue", task1);
verify(mockClient).enqueue("project", "location", "test-queue", task2);
@@ -333,8 +342,8 @@ public class CloudTasksUtilsTest {
@Test
void testSuccess_enqueueTasks_iterable() {
Task task1 = cloudTasksUtils.createGetTask("/the/path", "myservice", params);
Task task2 = cloudTasksUtils.createGetTask("/other/path", "yourservice", params);
Task task1 = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, params);
Task task2 = cloudTasksUtils.createGetTask("/other/path", Service.TOOLS, params);
cloudTasksUtils.enqueue("test-queue", ImmutableList.of(task1, task2));
verify(mockClient).enqueue("project", "location", "test-queue", task1);
verify(mockClient).enqueue("project", "location", "test-queue", task2);
@@ -27,14 +27,14 @@ import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableSet;
import google.registry.config.RegistryEnvironment;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
@@ -47,9 +47,8 @@ import google.registry.model.tld.Registry.TldType;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.DnsUtilsHelper;
import google.registry.testing.SystemPropertyExtension;
import google.registry.testing.TaskQueueExtension;
import java.util.Optional;
import java.util.Set;
import org.joda.money.Money;
@@ -68,13 +67,14 @@ class DeleteProberDataActionTest {
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
@RegisterExtension TaskQueueExtension taskQueue = new TaskQueueExtension();
@RegisterExtension
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
private DeleteProberDataAction action;
private final DnsUtils dnsUtils = mock(DnsUtils.class);
private final DnsUtilsHelper dnsUtilsHelper = new DnsUtilsHelper(dnsUtils);
@BeforeEach
void beforeEach() {
// Entities in these two should not be touched.
@@ -99,7 +99,7 @@ class DeleteProberDataActionTest {
private void resetAction() {
action = new DeleteProberDataAction();
action.dnsQueue = DnsQueue.createForTesting(new FakeClock());
action.dnsUtils = dnsUtils;
action.isDryRun = false;
action.tlds = ImmutableSet.of();
action.registryAdminRegistrarId = "TheRegistrar";
@@ -201,7 +201,7 @@ class DeleteProberDataActionTest {
DateTime timeAfterDeletion = DateTime.now(UTC);
assertThat(loadByForeignKey(Domain.class, "blah.ib-any.test", timeAfterDeletion)).isEmpty();
assertThat(loadByEntity(domain).getDeletionTime()).isLessThan(timeAfterDeletion);
assertDnsTasksEnqueued("blah.ib-any.test");
dnsUtilsHelper.assertDomainDnsRequests("blah.ib-any.test");
}
@Test
@@ -218,7 +218,7 @@ class DeleteProberDataActionTest {
action.run();
assertThat(loadByForeignKey(Domain.class, "blah.ib-any.test", timeAfterDeletion)).isEmpty();
assertThat(loadByEntity(domain).getDeletionTime()).isLessThan(timeAfterDeletion);
assertDnsTasksEnqueued("blah.ib-any.test");
dnsUtilsHelper.assertDomainDnsRequests("blah.ib-any.test");
}
@Test
@@ -0,0 +1,293 @@
// Copyright 2023 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.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.common.DnsRefreshRequest;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import java.util.Comparator;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link DnsUtils}. */
public class DnsUtilsTest {
private static final String tld = "tld";
private static final String domainName = "test.tld";
private static final String hostName = "ns1.test.tld";
private final DnsQueue dnsQueue = mock(DnsQueue.class);
private final DnsUtils dnsUtils = new DnsUtils(dnsQueue);
private final FakeClock clock = new FakeClock(DateTime.parse("2020-02-02T01:23:45Z"));
@RegisterExtension
JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@BeforeAll
static void beforeAll() {
DatabaseMigrationStateSchedule.useUncachedForTest();
}
@BeforeEach
void beforeEach() {
createTld(tld);
when(dnsQueue.getClock()).thenReturn(clock);
}
@Test
void testSuccess_hostRefresh_pullQueue() {
dnsUtils.requestHostDnsRefresh(hostName);
verify(dnsQueue).addHostRefreshTask(hostName);
assertThat(loadAllOf(DnsRefreshRequest.class)).isEmpty();
}
@Test
void testSuccess_domainRefresh_pullQueue() {
dnsUtils.requestDomainDnsRefresh(domainName);
verify(dnsQueue).addDomainRefreshTask(domainName, Duration.ZERO);
assertThat(loadAllOf(DnsRefreshRequest.class)).isEmpty();
}
@Test
void testSuccess_domainRefreshWithDelay_pullQueue() {
dnsUtils.requestDomainDnsRefresh(domainName, Duration.standardMinutes(3));
verify(dnsQueue).addDomainRefreshTask(domainName, Duration.standardMinutes(3));
assertThat(loadAllOf(DnsRefreshRequest.class)).isEmpty();
}
@Test
void testFailure_hostRefresh_unmanagedHost() {
String unmanagedHostName = "ns1.another.example";
Assertions.assertThrows(
IllegalArgumentException.class, () -> dnsUtils.requestHostDnsRefresh(unmanagedHostName));
verify(dnsQueue, never()).addHostRefreshTask(anyString());
assertThat(loadAllOf(DnsRefreshRequest.class)).isEmpty();
}
@Test
void testFailure_domainRefresh_unmanagedDomain() {
String unmanagedDomainName = "another.example";
Assertions.assertThrows(
IllegalArgumentException.class,
() -> dnsUtils.requestDomainDnsRefresh(unmanagedDomainName));
verify(dnsQueue, never()).addDomainRefreshTask(anyString(), any(Duration.class));
assertThat(loadAllOf(DnsRefreshRequest.class)).isEmpty();
}
@Test
void testSuccess_hostRefresh() {
useDnsSql();
dnsUtils.requestHostDnsRefresh(hostName);
verify(dnsQueue, never()).addHostRefreshTask(anyString());
DnsRefreshRequest request = Iterables.getOnlyElement(loadAllOf(DnsRefreshRequest.class));
assertRequest(request, TargetType.HOST, hostName, tld, clock.nowUtc());
}
@Test
void testSuccess_domainRefresh() {
useDnsSql();
dnsUtils.requestDomainDnsRefresh(domainName);
verify(dnsQueue, never()).addDomainRefreshTask(anyString(), any(Duration.class));
DnsRefreshRequest request = Iterables.getOnlyElement(loadAllOf(DnsRefreshRequest.class));
assertRequest(request, TargetType.DOMAIN, domainName, tld, clock.nowUtc());
}
@Test
void testSuccess_domainRefreshWithDelay() {
useDnsSql();
dnsUtils.requestDomainDnsRefresh(domainName, Duration.standardMinutes(3));
verify(dnsQueue, never()).addDomainRefreshTask(anyString(), any(Duration.class));
DnsRefreshRequest request = Iterables.getOnlyElement(loadAllOf(DnsRefreshRequest.class));
assertRequest(request, TargetType.DOMAIN, domainName, tld, clock.nowUtc().plusMinutes(3));
}
@Test
void testSuccess_ProcessRequests() {
ImmutableList<DnsRefreshRequest> requests = processRequests();
DateTime processtime = clock.nowUtc();
assertThat(requests.size()).isEqualTo(4);
assertRequest(
requests.get(0),
TargetType.DOMAIN,
"test2.tld",
"tld",
clock.nowUtc().minusMinutes(4),
processtime);
assertRequest(
requests.get(1),
TargetType.DOMAIN,
"test1.tld",
"tld",
clock.nowUtc().minusMinutes(3),
processtime);
assertRequest(
requests.get(2),
TargetType.HOST,
"ns1.test2.tld",
"tld",
clock.nowUtc().minusMinutes(1),
processtime);
assertRequest(
requests.get(3),
TargetType.DOMAIN,
"test5.tld",
"tld",
clock.nowUtc().minusMinutes(1),
processtime);
requests = loadAllOf(DnsRefreshRequest.class);
assertThat(requests.size()).isEqualTo(7);
// The four processed records should have updated process time in SQL as well.
assertThat(requests.stream().filter(e -> e.getLastProcessTime().equals(processtime)).count())
.isEqualTo(4);
clock.advanceOneMilli();
// Requests within cooldown period not included.
requests =
dnsUtils.readAndUpdateRequestsWithLatestProcessTime("tld", Duration.standardMinutes(1), 4);
assertThat(requests.size()).isEqualTo(1);
assertRequest(
requests.get(0),
TargetType.DOMAIN,
"test6.tld",
"tld",
clock.nowUtc().minusMinutes(1).minusMillis(1),
clock.nowUtc());
}
@Test
void testSuccess_deleteRequests() {
dnsUtils.deleteRequests(processRequests());
ImmutableList<DnsRefreshRequest> remainingRequests =
loadAllOf(DnsRefreshRequest.class).stream()
.sorted(Comparator.comparing(DnsRefreshRequest::getRequestTime))
.collect(toImmutableList());
assertThat(remainingRequests.size()).isEqualTo(3);
assertRequest(
remainingRequests.get(0),
TargetType.DOMAIN,
"something.example",
"example",
clock.nowUtc().minusMinutes(2));
assertRequest(
remainingRequests.get(1),
TargetType.DOMAIN,
"test6.tld",
"tld",
clock.nowUtc().minusMinutes(1));
assertRequest(
remainingRequests.get(2),
TargetType.DOMAIN,
"test4.tld",
"tld",
clock.nowUtc().plusMinutes(1));
tm().transact(() -> tm().delete(remainingRequests.get(2)));
assertThat(loadAllOf(DnsRefreshRequest.class).size()).isEqualTo(2);
// Should not throw even though one of the request is already deleted.
dnsUtils.deleteRequests(remainingRequests);
assertThat(loadAllOf(DnsRefreshRequest.class).size()).isEqualTo(0);
}
private ImmutableList<DnsRefreshRequest> processRequests() {
useDnsSql();
createTld("example");
// Domain Included.
dnsUtils.requestDomainDnsRefresh("test1.tld", Duration.standardMinutes(1));
// This one should be returned before test1.tld, even though it's added later, because of
// the delay specified in test1.tld.
dnsUtils.requestDomainDnsRefresh("test2.tld");
// Not included because the TLD is not under management.
dnsUtils.requestDomainDnsRefresh("something.example", Duration.standardMinutes(2));
clock.advanceBy(Duration.standardMinutes(3));
// Host included.
dnsUtils.requestHostDnsRefresh("ns1.test2.tld");
// Not included because the request time is in the future
dnsUtils.requestDomainDnsRefresh("test4.tld", Duration.standardMinutes(2));
// Included after the previous one. Same request time, order by insertion order (i.e. ID);
dnsUtils.requestDomainDnsRefresh("test5.tld");
// Not included because batch size is exceeded;
dnsUtils.requestDomainDnsRefresh("test6.tld");
clock.advanceBy(Duration.standardMinutes(1));
return dnsUtils.readAndUpdateRequestsWithLatestProcessTime(
"tld", Duration.standardMinutes(1), 4);
}
private static void assertRequest(
DnsRefreshRequest request, TargetType type, String name, String tld, DateTime requestTime) {
assertRequest(request, type, name, tld, requestTime, START_OF_TIME);
}
private static void assertRequest(
DnsRefreshRequest request,
TargetType type,
String name,
String tld,
DateTime requestTime,
DateTime processTime) {
assertThat(request.getType()).isEqualTo(type);
assertThat(request.getName()).isEqualTo(name);
assertThat(request.getTld()).isEqualTo(tld);
assertThat(request.getRequestTime()).isEqualTo(requestTime);
assertThat(request.getLastProcessTime()).isEqualTo(processTime);
}
private void useDnsSql() {
DateTime currentTime = clock.nowUtc();
clock.setTo(START_OF_TIME);
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
.put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
.put(START_OF_TIME.plusMillis(7), MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
.put(START_OF_TIME.plusMillis(8), MigrationState.NORDN_SQL)
.put(START_OF_TIME.plusMillis(9), MigrationState.DNS_SQL)
.build()));
clock.setTo(currentTime);
}
}
@@ -22,7 +22,7 @@ import static google.registry.dns.DnsModule.PARAM_HOSTS;
import static google.registry.dns.DnsModule.PARAM_LOCK_INDEX;
import static google.registry.dns.DnsModule.PARAM_NUM_PUBLISH_LOCKS;
import static google.registry.dns.DnsModule.PARAM_PUBLISH_TASK_ENQUEUED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_CREATED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_TIME;
import static google.registry.dns.PublishDnsUpdatesAction.RETRIES_BEFORE_PERMANENT_FAILURE;
import static google.registry.request.RequestParameters.PARAM_TLD;
import static google.registry.testing.DatabaseHelper.createTld;
@@ -54,6 +54,7 @@ import google.registry.request.HttpException.ServiceUnavailableException;
import google.registry.request.lock.LockHandler;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DnsUtilsHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeLockHandler;
import google.registry.testing.FakeResponse;
@@ -82,7 +83,8 @@ public class PublishDnsUpdatesActionTest {
private final FakeLockHandler lockHandler = new FakeLockHandler(true);
private final DnsWriter dnsWriter = mock(DnsWriter.class);
private final DnsMetrics dnsMetrics = mock(DnsMetrics.class);
private final DnsQueue dnsQueue = mock(DnsQueue.class);
private final DnsUtils dnsUtils = mock(DnsUtils.class);
private final DnsUtilsHelper dnsUtilsHelper = new DnsUtilsHelper(dnsUtils);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private PublishDnsUpdatesAction action;
private InternetAddress outgoingRegistry;
@@ -162,7 +164,7 @@ public class PublishDnsUpdatesActionTest {
outgoingRegistry,
Optional.ofNullable(retryCount),
Optional.empty(),
dnsQueue,
dnsUtils,
new DnsWriterProxy(ImmutableMap.of("correctWriter", dnsWriter)),
dnsMetrics,
lockHandler,
@@ -196,7 +198,7 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertNoMoreDnsRequests();
assertThat(response.getStatus()).isEqualTo(SC_OK);
}
@@ -223,7 +225,7 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertNoMoreDnsRequests();
assertThat(response.getStatus()).isEqualTo(SC_OK);
}
@@ -276,7 +278,7 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -300,7 +302,7 @@ public class PublishDnsUpdatesActionTest {
.param(PARAM_LOCK_INDEX, "1")
.param(PARAM_NUM_PUBLISH_LOCKS, "1")
.param(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.param(PARAM_REFRESH_REQUEST_CREATED, clock.nowUtc().minusHours(2).toString())
.param(PARAM_REFRESH_REQUEST_TIME, clock.nowUtc().minusHours(2).toString())
.param(PARAM_DOMAINS, "example1.xn--q9jyb4c,example2.xn--q9jyb4c")
.param(PARAM_HOSTS, "")
.header("content-type", "application/x-www-form-urlencoded"),
@@ -311,7 +313,7 @@ public class PublishDnsUpdatesActionTest {
.param(PARAM_LOCK_INDEX, "1")
.param(PARAM_NUM_PUBLISH_LOCKS, "1")
.param(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.param(PARAM_REFRESH_REQUEST_CREATED, clock.nowUtc().minusHours(2).toString())
.param(PARAM_REFRESH_REQUEST_TIME, clock.nowUtc().minusHours(2).toString())
.param(PARAM_DOMAINS, "example3.xn--q9jyb4c,example4.xn--q9jyb4c")
.param(PARAM_HOSTS, "ns1.example.xn--q9jyb4c")
.header("content-type", "application/x-www-form-urlencoded"));
@@ -339,7 +341,7 @@ public class PublishDnsUpdatesActionTest {
.param(PARAM_LOCK_INDEX, "1")
.param(PARAM_NUM_PUBLISH_LOCKS, "1")
.param(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.param(PARAM_REFRESH_REQUEST_CREATED, clock.nowUtc().minusHours(2).toString())
.param(PARAM_REFRESH_REQUEST_TIME, clock.nowUtc().minusHours(2).toString())
.param(PARAM_DOMAINS, "example1.xn--q9jyb4c,example2.xn--q9jyb4c")
.param(PARAM_HOSTS, "")
.header("content-type", "application/x-www-form-urlencoded"),
@@ -350,7 +352,7 @@ public class PublishDnsUpdatesActionTest {
.param(PARAM_LOCK_INDEX, "1")
.param(PARAM_NUM_PUBLISH_LOCKS, "1")
.param(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.param(PARAM_REFRESH_REQUEST_CREATED, clock.nowUtc().minusHours(2).toString())
.param(PARAM_REFRESH_REQUEST_TIME, clock.nowUtc().minusHours(2).toString())
.param(PARAM_DOMAINS, "example3.xn--q9jyb4c,example4.xn--q9jyb4c,example5.xn--q9jyb4c")
.param(PARAM_HOSTS, "ns1.example.xn--q9jyb4c")
.header("content-type", "application/x-www-form-urlencoded"));
@@ -376,7 +378,7 @@ public class PublishDnsUpdatesActionTest {
.param(PARAM_LOCK_INDEX, "1")
.param(PARAM_NUM_PUBLISH_LOCKS, "1")
.param(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.param(PARAM_REFRESH_REQUEST_CREATED, clock.nowUtc().minusHours(2).toString())
.param(PARAM_REFRESH_REQUEST_TIME, clock.nowUtc().minusHours(2).toString())
.param(PARAM_DOMAINS, "example1.xn--q9jyb4c")
.param(PARAM_HOSTS, "")
.header("content-type", "application/x-www-form-urlencoded"),
@@ -387,7 +389,7 @@ public class PublishDnsUpdatesActionTest {
.param(PARAM_LOCK_INDEX, "1")
.param(PARAM_NUM_PUBLISH_LOCKS, "1")
.param(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.param(PARAM_REFRESH_REQUEST_CREATED, clock.nowUtc().minusHours(2).toString())
.param(PARAM_REFRESH_REQUEST_TIME, clock.nowUtc().minusHours(2).toString())
.param(PARAM_DOMAINS, "")
.param(PARAM_HOSTS, "ns1.example.xn--q9jyb4c")
.header("content-type", "application/x-www-form-urlencoded"));
@@ -496,7 +498,7 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -526,7 +528,7 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -554,7 +556,7 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -580,9 +582,9 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verify(dnsQueue).addDomainRefreshTask("example.com");
verify(dnsQueue).addHostRefreshTask("ns1.example.com");
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertDomainDnsRequests("example.com");
dnsUtilsHelper.assertHostDnsRequests("ns1.example.com");
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -608,9 +610,9 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verify(dnsQueue).addDomainRefreshTask("example.com");
verify(dnsQueue).addHostRefreshTask("ns1.example.com");
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertDomainDnsRequests("example.com");
dnsUtilsHelper.assertHostDnsRequests("ns1.example.com");
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -632,11 +634,11 @@ public class PublishDnsUpdatesActionTest {
Duration.standardHours(2),
Duration.standardHours(1));
verifyNoMoreInteractions(dnsMetrics);
verify(dnsQueue).addDomainRefreshTask("example.com");
verify(dnsQueue).addDomainRefreshTask("example2.com");
verify(dnsQueue).addHostRefreshTask("ns1.example.com");
verify(dnsQueue).addHostRefreshTask("ns2.example.com");
verify(dnsQueue).addHostRefreshTask("ns1.example2.com");
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertDomainDnsRequests("example.com");
dnsUtilsHelper.assertDomainDnsRequests("example2.com");
dnsUtilsHelper.assertHostDnsRequests("ns1.example.com");
dnsUtilsHelper.assertHostDnsRequests("ns2.example.com");
dnsUtilsHelper.assertHostDnsRequests("ns1.example2.com");
dnsUtilsHelper.assertNoMoreDnsRequests();
}
}
@@ -144,7 +144,7 @@ public class ReadDnsQueueActionTest {
.url(PublishDnsUpdatesAction.PATH)
.param("tld", tldToDnsWriter.getKey())
.param("dnsWriter", tldToDnsWriter.getValue())
.param("itemsCreated", "3000-01-01T00:00:00.000Z")
.param("requestTime", "3000-01-01T00:00:00.000Z")
.param("enqueued", "3000-01-01T01:00:00.000Z")
// Single-lock TLDs should use lock 1 of 1 by default
.param("lockIndex", "1")
@@ -245,7 +245,7 @@ public class ReadDnsQueueActionTest {
DNS_PUBLISH_PUSH_QUEUE_NAME,
new TaskMatcher()
.param("enqueued", "3000-02-05T01:00:00.000Z")
.param("itemsCreated", "3000-02-03T00:00:00.000Z")
.param("requestTime", "3000-02-03T00:00:00.000Z")
.param("tld", "com")
.param("dnsWriter", "comWriter")
.param("domains", "domain1.com,domain2.com,domain3.com")
@@ -388,21 +388,6 @@ public class ReadDnsQueueActionTest {
ImmutableMultimap.of("com", "comWriter", "example", "exampleWriter"));
}
@RetryingTest(4)
void testSuccess_zone_getsIgnored() {
dnsQueue.addHostRefreshTask("ns1.domain.com");
dnsQueue.addDomainRefreshTask("domain.net");
dnsQueue.addZoneRefreshTask("example");
run();
TaskQueueHelper.assertNoTasksEnqueued(DNS_PULL_QUEUE_NAME);
cloudTasksHelper.assertTasksEnqueued(
DNS_PUBLISH_PUSH_QUEUE_NAME,
new TaskMatcher().url(PublishDnsUpdatesAction.PATH).param("domains", "domain.net"),
new TaskMatcher().url(PublishDnsUpdatesAction.PATH).param("hosts", "ns1.domain.com"));
}
private static String makeCommaSeparatedRange(int from, int to, String format) {
return IntStream.range(from, to)
.mapToObj(i -> String.format(format, i))
@@ -499,7 +484,7 @@ public class ReadDnsQueueActionTest {
.url(PublishDnsUpdatesAction.PATH)
.param("tld", "multilock.uk")
.param("dnsWriter", "multilockWriter")
.param("itemsCreated", "3000-01-01T00:00:00.000Z")
.param("requestTime", "3000-01-01T00:00:00.000Z")
.param("enqueued", "3000-01-01T01:00:00.000Z")
.param("domains", "hello.multilock.uk")
.param("hosts", "ns1.abc.hello.multilock.uk,ns2.hello.multilock.uk")
@@ -508,7 +493,7 @@ public class ReadDnsQueueActionTest {
.url(PublishDnsUpdatesAction.PATH)
.param("tld", "multilock.uk")
.param("dnsWriter", "multilockWriter")
.param("itemsCreated", "3000-01-01T00:00:00.000Z")
.param("requestTime", "3000-01-01T00:00:00.000Z")
.param("enqueued", "3000-01-01T01:00:00.000Z")
.param("domains", "another.multilock.uk")
.param("hosts", "ns3.def.another.multilock.uk,ns4.another.multilock.uk")
@@ -0,0 +1,296 @@
// 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.dns;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import static google.registry.model.common.DatabaseMigrationStateSchedule.set;
import static google.registry.model.common.DatabaseMigrationStateSchedule.useUncachedForTest;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyCollection;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Ordering;
import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.common.DnsRefreshRequest;
import google.registry.model.common.DnsRefreshRequestTest;
import google.registry.model.tld.Registry;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.FakeClock;
import java.util.Collection;
import java.util.Optional;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
/** Unit tests for {@link DnsRefreshRequestTest}. */
public class ReadDnsRefreshRequestsActionTest {
private final FakeClock clock = new FakeClock(DateTime.parse("2020-02-02T01:23:45Z"));
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper(clock);
private final DnsUtils dnsUtils = new DnsUtils(null);
private final Optional<Integer> jitterSeconds = Optional.of(5);
@RegisterExtension
JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
private final ReadDnsRefreshRequestsAction action =
spy(
new ReadDnsRefreshRequestsAction(
2,
Duration.standardSeconds(10),
jitterSeconds,
"tld",
clock,
dnsUtils,
null,
cloudTasksHelper.getTestCloudTasksUtils()));
private ImmutableList<DnsRefreshRequest> requests;
@BeforeAll
static void beforeAll() {
useUncachedForTest();
}
@BeforeEach
void beforeEach() {
useDnsSql();
persistResource(
createTld("tld")
.asBuilder()
.setDnsWriters(ImmutableSet.of("FooWriter", "BarWriter"))
.setNumDnsPublishLocks(2)
.build());
requests =
new ImmutableList.Builder<DnsRefreshRequest>()
.add(new DnsRefreshRequest(TargetType.DOMAIN, "domain.tld", "tld", clock.nowUtc()))
.add(
new DnsRefreshRequest(
TargetType.HOST, "ns1.domain.tld", "tld", clock.nowUtc().minusMinutes(1)))
.add(
new DnsRefreshRequest(
TargetType.DOMAIN, "future.tld", "tld", clock.nowUtc().plusMinutes(1)))
.build();
clock.advanceBy(Duration.standardMinutes(5));
persistResources(requests);
requests = loadAllOf(DnsRefreshRequest.class);
}
@Test
void testSuccess_runAction_pausedTld() {
persistResource(createTld("tld").asBuilder().setDnsPaused(true).build());
action.run();
verify(action, never()).enqueueUpdates(anyInt(), anyInt(), anyCollection());
verify(action, never()).processRequests(anyCollection());
}
@Test
void testSuccess_runAction_requestTimeInTheFuture() {
clock.setTo(DateTime.parse("2000-01-01T00:00:00Z"));
action.run();
verify(action, never()).enqueueUpdates(anyInt(), anyInt(), anyCollection());
verify(action, never()).processRequests(anyCollection());
}
@Test
void testSuccess_runAction_oneBatch() {
// The read batch size is 2 * 2 = 4. All requests will be in the same bucket, even though the
// bucket size should be roughly 2. But this is as expected because getLockIndex() only evenly
// distributes tasks in a statistical sense.
doReturn(2).when(action).getLockIndex(anyInt(), any(DnsRefreshRequest.class));
action.run();
@SuppressWarnings("unchecked")
ArgumentCaptor<Collection<DnsRefreshRequest>> captor =
ArgumentCaptor.forClass(Collection.class);
verify(action, times(1)).enqueueUpdates(eq(2), eq(2), captor.capture());
assertThat(captor.getValue().size()).isEqualTo(3);
verify(action, times(1)).processRequests(captor.capture());
assertThat(captor.getValue().size()).isEqualTo(3);
assertThat(loadAllOf(DnsRefreshRequest.class).isEmpty()).isTrue();
}
@Test
void testSuccess_runAction_twoBatches() {
// Make the read batch size 2 * 1 = 2.
persistResource(Registry.get("tld").asBuilder().setNumDnsPublishLocks(1).build());
doReturn(1).when(action).getLockIndex(anyInt(), any(DnsRefreshRequest.class));
doAnswer(
invocation -> {
@SuppressWarnings("unchecked")
ImmutableList<DnsRefreshRequest> ans =
(ImmutableList<DnsRefreshRequest>) invocation.callRealMethod();
// The next read should not time out as we only increment by one millisecond, whereas
// the timeout is set to 10 seconds.
clock.advanceOneMilli();
return ans;
})
.when(action)
.processRequests(anyCollection());
action.run();
@SuppressWarnings("unchecked")
ArgumentCaptor<Collection<DnsRefreshRequest>> captor =
ArgumentCaptor.forClass(Collection.class);
verify(action, times(2)).enqueueUpdates(eq(1), eq(1), captor.capture());
assertThat(captor.getAllValues().get(0).size()).isEqualTo(2);
assertThat(captor.getAllValues().get(1).size()).isEqualTo(1);
verify(action, times(2)).processRequests(captor.capture());
assertThat(captor.getAllValues().get(0).size()).isEqualTo(2);
assertThat(captor.getAllValues().get(1).size()).isEqualTo(1);
assertThat(loadAllOf(DnsRefreshRequest.class).isEmpty()).isTrue();
}
@Test
void testSuccess_runAction_timeOutAfterFirstRead() {
// Make the process batch size 2 * 1 = 2.
persistResource(Registry.get("tld").asBuilder().setNumDnsPublishLocks(1).build());
// Both requests in the first batch will be bucketed to the same bucket.
doReturn(1).when(action).getLockIndex(anyInt(), any(DnsRefreshRequest.class));
doAnswer(
invocation -> {
@SuppressWarnings("unchecked")
ImmutableList<DnsRefreshRequest> ans =
(ImmutableList<DnsRefreshRequest>) invocation.callRealMethod();
// After this function is called once, the loop in run() should top when it checks
// if the current time is before the request end time.
clock.advanceBy(Duration.standardHours(1));
return ans;
})
.when(action)
.processRequests(anyCollection());
action.run();
verify(action, times(1)).enqueueUpdates(anyInt(), anyInt(), anyCollection());
verify(action, times(1)).processRequests(anyCollection());
// The third request is left untouched because it is not read;
ImmutableList<DnsRefreshRequest> remainingRequests = loadAllOf(DnsRefreshRequest.class);
assertThat(remainingRequests.size()).isEqualTo(1);
assertThat(remainingRequests.get(0).getLastProcessTime()).isEqualTo(START_OF_TIME);
}
@Test
void testSuccess_processTasks() {
doReturn(2)
.doReturn(1)
.doReturn(2)
.when(action)
.getLockIndex(eq(2), any(DnsRefreshRequest.class));
action.processRequests(requests);
verify(action).enqueueUpdates(2, 2, ImmutableSet.of(requests.get(0), requests.get(2)));
verify(action).enqueueUpdates(1, 2, ImmutableSet.of(requests.get(1)));
assertThat(loadAllOf(DnsRefreshRequest.class)).isEmpty();
}
@Test
void testSuccess_processTasks_enqueueFailed_tasksNotDeleted() {
doReturn(2)
.doReturn(1)
.doReturn(2)
.when(action)
.getLockIndex(eq(2), any(DnsRefreshRequest.class));
doThrow(new RuntimeException("Something went wrong!"))
.when(action)
.enqueueUpdates(eq(2), eq(2), anyCollection());
action.processRequests(requests);
verify(action).enqueueUpdates(2, 2, ImmutableSet.of(requests.get(0), requests.get(2)));
verify(action).enqueueUpdates(1, 2, ImmutableSet.of(requests.get(1)));
assertThat(loadAllOf(DnsRefreshRequest.class).size()).isEqualTo(2);
}
@Test
void testSuccess_enqueueTasks() {
action.enqueueUpdates(2, 3, requests);
cloudTasksHelper.assertTasksEnqueued(
"dns-publish",
new TaskMatcher()
.url("/_dr/task/publishDnsUpdates")
.service("BACKEND")
.param("tld", "tld")
.param("dnsWriter", "FooWriter")
.param("lockIndex", "2")
.param("numPublishLocks", "3")
.param("enqueued", clock.nowUtc().toString())
.param("requestTime", clock.nowUtc().minusMinutes(6).toString())
.param("domains", "domain.tld,future.tld")
.param("hosts", "ns1.domain.tld"),
new TaskMatcher()
.url("/_dr/task/publishDnsUpdates")
.service("BACKEND")
.param("tld", "tld")
.param("dnsWriter", "BarWriter")
.param("lockIndex", "2")
.param("numPublishLocks", "3")
.param("enqueued", clock.nowUtc().toString())
.param("requestTime", clock.nowUtc().minusMinutes(6).toString())
.param("domains", "domain.tld,future.tld")
.param("hosts", "ns1.domain.tld"));
cloudTasksHelper
.getTestTasksFor("dns-publish")
.forEach(
task -> {
DateTime scheduledTime =
new DateTime(task.getScheduleTime().getSeconds() * 1000, DateTimeZone.UTC);
assertThat(new Duration(clock.nowUtc(), scheduledTime))
.isAtMost(Duration.standardSeconds(jitterSeconds.get()));
});
}
private void useDnsSql() {
DateTime currentTime = clock.nowUtc();
clock.setTo(START_OF_TIME);
tm().transact(
() ->
set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
.put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
.put(START_OF_TIME.plusMillis(7), MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
.put(START_OF_TIME.plusMillis(8), MigrationState.NORDN_SQL)
.put(START_OF_TIME.plusMillis(9), MigrationState.DNS_SQL)
.build()));
clock.setTo(currentTime);
}
}
@@ -21,8 +21,6 @@ import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistActiveSubordinateHost;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.domain.Domain;
@@ -30,6 +28,7 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotFoundException;
import google.registry.testing.DnsUtilsHelper;
import google.registry.testing.FakeClock;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -42,11 +41,12 @@ public class RefreshDnsActionTest {
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final DnsQueue dnsQueue = mock(DnsQueue.class);
private final DnsUtils dnsUtils = mock(DnsUtils.class);
private final DnsUtilsHelper dnsUtilsHelper = new DnsUtilsHelper(dnsUtils);
private final FakeClock clock = new FakeClock();
private void run(TargetType type, String name) {
new RefreshDnsAction(name, type, clock, dnsQueue).run();
new RefreshDnsAction(name, type, clock, dnsUtils).run();
}
@BeforeEach
@@ -59,8 +59,8 @@ public class RefreshDnsActionTest {
Domain domain = persistActiveDomain("example.xn--q9jyb4c");
persistActiveSubordinateHost("ns1.example.xn--q9jyb4c", domain);
run(TargetType.HOST, "ns1.example.xn--q9jyb4c");
verify(dnsQueue).addHostRefreshTask("ns1.example.xn--q9jyb4c");
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertHostDnsRequests("ns1.example.xn--q9jyb4c");
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -74,7 +74,7 @@ public class RefreshDnsActionTest {
try {
run(TargetType.HOST, "ns1.example.xn--q9jyb4c");
} finally {
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertNoMoreDnsRequests();
}
});
assertThat(thrown)
@@ -86,8 +86,8 @@ public class RefreshDnsActionTest {
void testSuccess_domain() {
persistActiveDomain("example.xn--q9jyb4c");
run(TargetType.DOMAIN, "example.xn--q9jyb4c");
verify(dnsQueue).addDomainRefreshTask("example.xn--q9jyb4c");
verifyNoMoreInteractions(dnsQueue);
dnsUtilsHelper.assertDomainDnsRequests("example.xn--q9jyb4c");
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -24,14 +24,13 @@ 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.DnsUtilsHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import org.joda.time.DateTime;
@@ -43,7 +42,8 @@ import org.junit.jupiter.api.extension.RegisterExtension;
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 DnsUtils dnsUtils = mock(DnsUtils.class);
private final DnsUtilsHelper dnsUtilsHelper = new DnsUtilsHelper(dnsUtils);
private final FakeResponse response = new FakeResponse();
@RegisterExtension
@@ -53,14 +53,7 @@ public class RefreshDnsOnHostRenameActionTest {
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);
action = new RefreshDnsOnHostRenameAction(hostKey, response, dnsUtils);
}
@BeforeEach
@@ -82,7 +75,7 @@ public class RefreshDnsOnHostRenameActionTest {
persistDomainAsDeleted(newDomain("deleted.tld", host), clock.nowUtc().minusDays(1));
createAction(host.createVKey().stringify());
action.run();
assertDnsTasksEnqueued("example.tld", "otherexample.tld");
dnsUtilsHelper.assertDomainDnsRequests("example.tld", "otherexample.tld");
assertThat(response.getStatus()).isEqualTo(SC_OK);
}
@@ -90,7 +83,7 @@ public class RefreshDnsOnHostRenameActionTest {
void testFailure_nonexistentHost() {
createAction("kind:Host@sql:rO0ABXQABGJsYWg");
action.run();
assertDnsTasksEnqueued();
dnsUtilsHelper.assertNoMoreDnsRequests();
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
assertThat(response.getPayload())
.isEqualTo("Host to refresh does not exist: VKey<Host>(sql:blah)");
@@ -102,7 +95,7 @@ public class RefreshDnsOnHostRenameActionTest {
persistResource(newDomain("example.tld", host));
createAction(host.createVKey().stringify());
action.run();
assertDnsTasksEnqueued();
dnsUtilsHelper.assertNoMoreDnsRequests();
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
assertThat(response.getPayload())
.isEqualTo("Host to refresh is already deleted: ns1.example.tld");
@@ -20,9 +20,11 @@ import dagger.Provides;
import dagger.Subcomponent;
import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.batch.AsyncTaskEnqueuerTest;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
import google.registry.dns.DnsQueue;
import google.registry.dns.DnsUtils;
import google.registry.flows.custom.CustomLogicFactory;
import google.registry.flows.custom.TestCustomLogicFactory;
import google.registry.flows.domain.DomainFlowTmchUtils;
@@ -30,13 +32,13 @@ import google.registry.monitoring.whitebox.EppMetric;
import google.registry.request.RequestScope;
import google.registry.request.lock.LockHandler;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DnsUtilsHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeLockHandler;
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;
@@ -59,11 +61,16 @@ public interface EppTestComponent {
private FakeLockHandler lockHandler;
private Sleeper sleeper;
private CloudTasksHelper cloudTasksHelper;
private DnsUtilsHelper dnsUtilsHelper;
public CloudTasksHelper getCloudTasksHelper() {
return cloudTasksHelper;
}
public DnsUtilsHelper getDnsUtilsHelper() {
return dnsUtilsHelper;
}
public EppMetric.Builder getMetricBuilder() {
return metricBuilder;
}
@@ -82,6 +89,7 @@ public interface EppTestComponent {
instance.metricBuilder = EppMetric.builderForRequest(clock);
instance.lockHandler = new FakeLockHandler(true);
instance.cloudTasksHelper = cloudTasksHelper;
instance.dnsUtilsHelper = new DnsUtilsHelper();
return instance;
}
@@ -95,6 +103,11 @@ public interface EppTestComponent {
return cloudTasksHelper.getTestCloudTasksUtils();
}
@Provides
DnsUtils provideDnsUtils() {
return dnsUtilsHelper.getDnsUtils();
}
@Provides
Clock provideClock() {
return clock;
@@ -45,6 +45,7 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DnsUtilsHelper;
import google.registry.testing.EppLoader;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeHttpSession;
@@ -84,6 +85,7 @@ public abstract class FlowTestCase<F extends Flow> {
protected TransportCredentials credentials = new PasswordOnlyTransportCredentials();
protected EppRequestSource eppRequestSource = EppRequestSource.UNIT_TEST;
protected CloudTasksHelper cloudTasksHelper;
protected DnsUtilsHelper dnsUtilsHelper;
private EppMetric.Builder eppMetricBuilder;
@@ -216,6 +218,7 @@ public abstract class FlowTestCase<F extends Flow> {
FakesAndMocksModule fakesAndMocksModule = FakesAndMocksModule.create(clock);
cloudTasksHelper = fakesAndMocksModule.getCloudTasksHelper();
dnsUtilsHelper = fakesAndMocksModule.getDnsUtilsHelper();
// Run the flow.
return DaggerEppTestComponent.builder()
.fakesAndMocksModule(fakesAndMocksModule)
@@ -17,6 +17,7 @@ package google.registry.flows.domain;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DEFAULT;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.eppoutput.CheckData.DomainCheck.create;
@@ -38,6 +39,7 @@ import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
@@ -73,6 +75,7 @@ import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.eppcommon.StatusValue;
@@ -350,6 +353,28 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_allocationtoken_fee_response.xml"));
}
@Test
void testSuccess_allocationTokenPromotion_doesNotUseValidDefaultToken_singleYear()
throws Exception {
setUpDefaultToken();
createTld("example");
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setDiscountFraction(0.5)
.setDiscountYears(2)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
.put(clock.nowUtc().minusDays(1), TokenStatus.VALID)
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build())
.build());
setEppInput("domain_check_allocationtoken_fee.xml");
runFlowAssertResponse(loadFile("domain_check_allocationtoken_fee_response.xml"));
}
@Test
void testSuccess_allocationTokenPromotion_multiYearAndPremiums() throws Exception {
createTld("example");
@@ -760,6 +785,32 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_response_v06.xml"));
}
private void setUpDefaultToken() {
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
}
@Test
void testFeeExtension_defaultToken_v06() throws Exception {
setUpDefaultToken();
persistActiveDomain("example1.tld");
setEppInput("domain_check_fee_v06.xml", ImmutableMap.of("CURRENCY", "USD"));
runFlowAssertResponse(loadFile("domain_check_fee_response_default_token_v06.xml"));
}
@Test
void testFeeExtension_multipleReservations() throws Exception {
persistResource(
@@ -780,6 +831,14 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_response_v11.xml"));
}
@Test
void testFeeExtension_defaultToken_v11() throws Exception {
setUpDefaultToken();
persistActiveDomain("example1.tld");
setEppInput("domain_check_fee_v11.xml", ImmutableMap.of("CURRENCY", "USD"));
runFlowAssertResponse(loadFile("domain_check_fee_response_default_token_v11.xml"));
}
@Test
void testFeeExtension_v12() throws Exception {
persistActiveDomain("example1.tld");
@@ -787,6 +846,14 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_response_v12.xml"));
}
@Test
void testFeeExtension_defaultToken_v12() throws Exception {
setUpDefaultToken();
persistActiveDomain("example1.tld");
setEppInput("domain_check_fee_v12.xml", ImmutableMap.of("CURRENCY", "USD"));
runFlowAssertResponse(loadFile("domain_check_fee_response_default_token_v12.xml"));
}
@Test
void testSuccess_thirtyDomains_restoreFees() throws Exception {
// Note that 30 is more than 25, which is the maximum # of entity groups you can enlist in a
@@ -810,6 +877,27 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_multiple_commands_response_v06.xml"));
}
@Test
void testFeeExtension_multipleCommands_tokenNotValidForSome_v06() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE, CommandName.TRANSFER))
.build());
setEppInput("domain_check_fee_multiple_commands_allocationtoken_v06.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_multiple_commands_allocationtoken_response_v06.xml"));
}
@Test
void testFeeExtension_multipleCommands_defaultTokenOnlyOnCreate_v06() throws Exception {
setUpDefaultToken();
setEppInput("domain_check_fee_multiple_commands_v06.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_multiple_commands_default_token_response_v06.xml"));
}
// Version 11 cannot have multiple commands.
@Test
@@ -818,6 +906,93 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_multiple_commands_response_v12.xml"));
}
@Test
void testFeeExtension_multipleCommands_tokenNotValidForSome_v12() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE, CommandName.TRANSFER))
.build());
setEppInput("domain_check_fee_multiple_commands_allocationtoken_v12.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_multiple_commands_allocationtoken_response_v12.xml"));
}
@Test
void testFeeExtension_multipleCommands_defaultTokenOnlyOnCreate_v12() throws Exception {
setUpDefaultToken();
setEppInput("domain_check_fee_multiple_commands_v12.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_multiple_commands_default_token_response_v12.xml"));
}
void testFeeExtension_defaultToken_notValidForAllLabels_v06() throws Exception {
createTld("example");
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountPremiums(false)
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("example")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
setEppInput("domain_check_fee_default_token_multiple_names_v06.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_default_token_multiple_names_response_v06.xml"));
}
void testFeeExtension_defaultToken_notValidForAllLabels_v11() throws Exception {
createTld("example");
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountPremiums(false)
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("example")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
setEppInput("domain_check_fee_default_token_multiple_names_v11.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_default_token_multiple_names_response_v11.xml"));
}
void testFeeExtension_defaultToken_notValidForAllLabels_v12() throws Exception {
createTld("example");
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountPremiums(false)
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("example")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
setEppInput("domain_check_fee_default_token_multiple_names_v12.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_default_token_multiple_names_response_v12.xml"));
}
/** Test the same as {@link #testFeeExtension_multipleCommands_v06} with premium labels. */
@Test
void testFeeExtension_premiumLabels_v06() throws Exception {
@@ -54,12 +54,6 @@ import static google.registry.testing.DatabaseHelper.persistReservedList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DomainSubject.assertAboutDomains;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
import static google.registry.testing.TaskQueueHelper.assertNoDnsTasksEnqueued;
import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued;
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
import static google.registry.tmch.LordnTaskUtils.QUEUE_CLAIMS;
import static google.registry.tmch.LordnTaskUtils.QUEUE_SUNRISE;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.JPY;
@@ -155,8 +149,6 @@ import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
@@ -183,7 +175,6 @@ import google.registry.monitoring.whitebox.EppMetric;
import google.registry.persistence.VKey;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.TaskQueueExtension;
import google.registry.testing.TaskQueueHelper.TaskMatcher;
import google.registry.tmch.LordnTaskUtils.LordnPhase;
import google.registry.tmch.SmdrlCsvParser;
import google.registry.tmch.TmchData;
@@ -195,12 +186,9 @@ import javax.annotation.Nullable;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junitpioneer.jupiter.cartesian.CartesianTest;
import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
@@ -230,11 +218,6 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
clock.setTo(DateTime.parse("1999-04-03T22:00:00.0Z").minus(Duration.millis(1)));
}
@BeforeAll
static void beforeAll() {
DatabaseMigrationStateSchedule.useUncachedForTest();
}
@BeforeEach
void initCreateTest() throws Exception {
createTld("tld");
@@ -393,7 +376,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
GracePeriod.create(
GracePeriodStatus.ADD, domain.getRepoId(), billingTime, "TheRegistrar", null),
createBillingEvent));
assertDnsTasksEnqueued(getUniqueIdFromCommand());
dnsUtilsHelper.assertDomainDnsRequests(getUniqueIdFromCommand());
}
private void assertNoLordn() throws Exception {
@@ -404,30 +387,16 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.hasLaunchNotice(null)
.and()
.hasLordnPhase(LordnPhase.NONE);
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
}
private void assertSunriseLordn(String domainName) throws Exception {
private void assertSunriseLordn() throws Exception {
assertAboutDomains()
.that(reloadResourceByForeignKey())
.hasSmdId(SMD_ID)
.and()
.hasLaunchNotice(null);
if (DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc())
.equals(MigrationState.NORDN_SQL)) {
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.SUNRISE);
} else {
String expectedPayload =
String.format(
"%s,%s,%s,1,%s",
reloadResourceByForeignKey().getRepoId(),
domainName,
SMD_ID,
SMD_VALID_TIME.plusMillis(17));
assertTasksEnqueued(QUEUE_SUNRISE, new TaskMatcher().payload(expectedPayload));
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.NONE);
}
.hasLaunchNotice(null)
.and()
.hasLordnPhase(LordnPhase.SUNRISE);
}
private void assertClaimsLordn() throws Exception {
@@ -440,20 +409,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"370d0b7c9223372036854775807",
"tmch",
DateTime.parse("2010-08-16T09:00:00.0Z"),
DateTime.parse("2009-08-16T09:00:00.0Z")));
if (DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc())
.equals(MigrationState.NORDN_SQL)) {
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.CLAIMS);
} else {
TaskMatcher task =
new TaskMatcher()
.payload(
reloadResourceByForeignKey().getRepoId()
+ ",example-one.tld,370d0b7c9223372036854775807,1,"
+ "2009-08-16T09:00:00.017Z,2009-08-16T09:00:00.000Z");
assertTasksEnqueued(QUEUE_CLAIMS, task);
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.NONE);
}
DateTime.parse("2009-08-16T09:00:00.0Z")))
.and()
.hasLordnPhase(LordnPhase.CLAIMS);
}
private void doSuccessfulTest(
@@ -944,7 +902,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts("net");
runFlowAssertResponse(loadFile("domain_create_response_idn_minna.xml"));
assertSuccessfulCreate("xn--q9jyb4c", ImmutableSet.of());
assertDnsTasksEnqueued("xn--abc-873b2e7eb1k8a4lpjvv.xn--q9jyb4c");
dnsUtilsHelper.assertDomainDnsRequests("xn--abc-873b2e7eb1k8a4lpjvv.xn--q9jyb4c");
}
@Test
@@ -953,7 +911,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts();
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
assertNoDnsTasksEnqueued();
dnsUtilsHelper.assertNoMoreDnsRequests();
}
@Test
@@ -966,7 +924,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAboutDomains()
.that(reloadResourceByForeignKey())
.hasRegistrationExpirationTime(clock.nowUtc().plusYears(1));
assertDnsTasksEnqueued("example.tld");
dnsUtilsHelper.assertDomainDnsRequests("example.tld");
}
@Test
@@ -977,27 +935,19 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_claimsNotice(boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
@Test
void testSuccess_claimsNotice() throws Exception {
clock.setTo(DateTime.parse("2009-08-16T09:00:00.0Z"));
setEppInput("domain_create_claim_notice.xml");
persistContactsAndHosts();
runFlowAssertResponse(loadFile("domain_create_response_claims.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of());
assertDnsTasksEnqueued("example-one.tld");
dnsUtilsHelper.assertDomainDnsRequests("example-one.tld");
assertClaimsLordn();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_claimsNoticeInQuietPeriod(boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
@Test
void testSuccess_claimsNoticeInQuietPeriod() throws Exception {
allocationToken =
persistResource(
new AllocationToken.Builder()
@@ -1021,7 +971,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts();
runFlowAssertResponse(loadFile("domain_create_response_claims.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of(RESERVED), allocationToken);
assertDnsTasksEnqueued("example-one.tld");
dnsUtilsHelper.assertDomainDnsRequests("example-one.tld");
assertClaimsLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@@ -1034,7 +984,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
assertSuccessfulCreate("tld", ImmutableSet.of());
assertDnsTasksEnqueued("example.tld");
dnsUtilsHelper.assertDomainDnsRequests("example.tld");
}
@Test
@@ -1043,7 +993,6 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts();
EppException thrown = assertThrows(MissingClaimsNoticeException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
}
@Test
@@ -1419,12 +1368,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_anchorTenant_withClaims(boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
@Test
void testSuccess_anchorTenant_withClaims() throws Exception {
persistResource(
new AllocationToken.Builder()
.setDomainName("example-one.tld")
@@ -1442,7 +1387,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts();
runFlowAssertResponse(loadFile("domain_create_response_claims.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertDnsTasksEnqueued("example-one.tld");
dnsUtilsHelper.assertDomainDnsRequests("example-one.tld");
assertClaimsLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@@ -1492,12 +1437,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT));
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_anchorTenantInSunrise_withSignedMark(boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
@Test
void testSuccess_anchorTenantInSunrise_withSignedMark() throws Exception {
allocationToken =
persistResource(
new AllocationToken.Builder()
@@ -1526,8 +1467,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"EXPIRATION_TIME",
SMD_VALID_TIME.plusYears(2).toString())));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT, SUNRISE), allocationToken);
assertDnsTasksEnqueued("test-validate.tld");
assertSunriseLordn("test-validate.tld");
dnsUtilsHelper.assertDomainDnsRequests("test-validate.tld");
assertSunriseLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@@ -1636,7 +1577,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class)));
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5)));
}
@@ -1686,7 +1627,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.put("EXDATE", "2004-04-03T22:00:00.0Z")
.build()));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class)));
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getCost()).isEqualTo(expectedPrice);
}
@@ -1719,7 +1660,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "104.00")));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class)));
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("rich.example");
// 1yr @ $100 + 2yrs @ $100 * (1 - 0.98) = $104
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 104.00));
@@ -1752,7 +1693,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "204.44")));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class)));
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("rich.example");
// 2yrs @ $100 + 1yr @ $100 * (1 - 0.95555) = $204.44
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 204.44));
@@ -1921,7 +1862,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class)));
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5)));
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo("abc123");
@@ -2071,6 +2012,41 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5)));
}
@Test
void testSuccess_skipsOverExpiredDefaultToken() throws Exception {
persistContactsAndHosts();
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
AllocationToken defaultToken2 =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setDiscountFraction(0.5)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
.put(clock.nowUtc().minusDays(2), TokenStatus.VALID)
.put(clock.nowUtc().minusDays(1), TokenStatus.ENDED)
.build())
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(defaultToken1.createVKey(), defaultToken2.createVKey()))
.build());
doSuccessfulTest();
}
BillingEvent.OneTime runTest_defaultToken(String token) throws Exception {
setEppInput("domain_create.xml", ImmutableMap.of("DOMAIN", "example.tld"));
runFlowAssertResponse(
@@ -2082,7 +2058,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.put("EXDATE", "2001-04-03T22:00:00.0Z")
.build()));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class)));
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo(token);
return billingEvent;
@@ -2097,13 +2073,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertSuccessfulCreate("tld", ImmutableSet.of(RESERVED));
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_reservedNameCollisionDomain_inSunrise_setsServerHoldAndPollMessage(
boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
@Test
void testSuccess_reservedNameCollisionDomain_inSunrise_setsServerHoldAndPollMessage()
throws Exception {
persistResource(
Registry.get("tld")
.asBuilder()
@@ -2125,10 +2097,10 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"EXPIRATION_TIME",
SMD_VALID_TIME.plusYears(2).toString())));
assertSunriseLordn("test-and-validate.tld");
assertSunriseLordn();
// Check for SERVER_HOLD status, no DNS tasks enqueued, and collision poll message.
assertNoDnsTasksEnqueued();
dnsUtilsHelper.assertNoMoreDnsRequests();
Domain domain = reloadResourceByForeignKey();
assertThat(domain.getStatusValues()).contains(SERVER_HOLD);
assertPollMessagesWithCollisionOneTime(domain);
@@ -2145,7 +2117,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "badcrash.tld")));
// Check for SERVER_HOLD status, no DNS tasks enqueued, and collision poll message.
assertNoDnsTasksEnqueued();
dnsUtilsHelper.assertNoMoreDnsRequests();
Domain domain = reloadResourceByForeignKey();
assertThat(domain.getStatusValues()).contains(SERVER_HOLD);
assertPollMessagesWithCollisionOneTime(domain);
@@ -2646,13 +2618,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"tld", "domain_create_response.xml", SUPERUSER, ImmutableMap.of("DOMAIN", "example.tld"));
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark(boolean usePullQueue)
throws Exception {
if (!usePullQueue) {
useNordnSql();
}
@Test
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark() throws Exception {
createTld("tld", START_DATE_SUNRISE);
clock.setTo(SMD_VALID_TIME);
setEppInput(
@@ -2670,17 +2637,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"EXPIRATION_TIME",
SMD_VALID_TIME.plusYears(2).toString())));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE));
assertSunriseLordn("test-validate.tld");
assertSunriseLordn();
}
/** Test that missing type= argument on launch create works in start-date sunrise. */
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark_noType(boolean usePullQueue)
throws Exception {
if (!usePullQueue) {
useNordnSql();
}
@Test
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark_noType() throws Exception {
createTld("tld", START_DATE_SUNRISE);
clock.setTo(SMD_VALID_TIME);
setEppInput(
@@ -2698,7 +2660,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"EXPIRATION_TIME",
SMD_VALID_TIME.plusYears(2).toString())));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE));
assertSunriseLordn("test-validate.tld");
assertSunriseLordn();
}
@Test
@@ -3733,97 +3695,4 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.isEqualTo(
"The package token abc123 cannot be used to register names for longer than 1 year.");
}
private static void useNordnSql() {
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.build()));
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.build()));
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.build()));
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
.build()));
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
.build()));
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
.put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
.build()));
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
.put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
.put(START_OF_TIME.plusMillis(7), MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
.build()));
tm().transact(
() ->
DatabaseMigrationStateSchedule.set(
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
.put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
.put(START_OF_TIME.plusMillis(7), MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
.put(START_OF_TIME.plusMillis(8), MigrationState.NORDN_SQL)
.build()));
}
}
@@ -52,7 +52,6 @@ import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DomainSubject.assertAboutDomains;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries;
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
@@ -101,20 +100,16 @@ import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.TaskQueueExtension;
import java.util.Map;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link DomainDeleteFlow}. */
class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain> {
@RegisterExtension final TaskQueueExtension taskQueue = new TaskQueueExtension();
private Domain domain;
private DomainHistory earlierHistoryEntry;
@@ -362,7 +357,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
// The add grace period is for a billable action, so it should trigger a cancellation.
assertAutorenewClosedAndCancellationCreatedFor(
graceBillingEvent, getOnlyHistoryEntryOfType(domain, DOMAIN_DELETE, DomainHistory.class));
assertDnsTasksEnqueued("example.tld");
dnsUtilsHelper.assertDomainDnsRequests("example.tld");
// There should be no poll messages. The previous autorenew poll message should now be deleted.
assertThat(getPollMessages("TheRegistrar")).isEmpty();
}
@@ -752,7 +747,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
.build());
DateTime eventTime = clock.nowUtc();
runFlowAssertResponse(loadFile("generic_success_response.xml"));
assertDnsTasksEnqueued("example.tld");
dnsUtilsHelper.assertDomainDnsRequests("example.tld");
assertAutorenewClosedAndCancellationCreatedFor(
graceBillingEvent,
getOnlyHistoryEntryOfType(domain, DOMAIN_DELETE, DomainHistory.class),
@@ -770,7 +765,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
.build());
clock.advanceOneMilli();
runFlowAssertResponse(loadFile("domain_delete_response_pending.xml"));
assertDnsTasksEnqueued("example.tld");
dnsUtilsHelper.assertDomainDnsRequests("example.tld");
assertOnlyBillingEventIsClosedAutorenew("TheRegistrar");
}
@@ -1241,4 +1236,16 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
EppException thrown = assertThrows(UnimplementedExtensionException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_freeCreation_deletionDuringGracePeriod() throws Exception {
// Deletion during the add grace period should still work even if the credit is 0
setUpSuccessfulTest();
BillingEvent.OneTime graceBillingEvent =
persistResource(createBillingEvent(Reason.CREATE, Money.of(USD, 0)));
setUpGracePeriods(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, domain.getRepoId(), graceBillingEvent));
clock.advanceOneMilli();
runFlowAssertResponse(loadFile("domain_delete_response_fee_free_grace.xml"));
}
}

Some files were not shown because too many files have changed in this diff Show More