1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

...

21 Commits

Author SHA1 Message Date
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
sarahcaseybot 83e9e7fb5c Add allowedEppActions field to AllocationToken (#1957) 2023-03-10 14:14:47 -05:00
Pavlo Tkach 438c523fcb Remove app engine deps from Lock (#1910) 2023-03-09 10:47:48 -05:00
Lai Jiang 025a2faff2 Drop the indexs and columns for dns_refresh_request_time (#1949) 2023-03-09 10:29:31 -05:00
gbrodman fd822dd333 Add create/delete/update commands for User objects (#1936)
This also includes the change of allowing the Java User object to have a
null GAIA ID (when creating user objects, we don't know what the GAIA ID
is).
2023-03-07 17:18:48 -05:00
Ben McIlwain 9b93749d43 Double the number of frontend instances from 12 to 24 (#1954)
It seems like we're hitting App Engine capacity issues resulting in actual pages
now (for whatever reason, but likely one customer), and we obviously don't want
that.
2023-03-06 16:04:28 -05:00
Pavlo Tkach 71a8579ece Move App Engine cron jobs to cloud scheduler (#1939) 2023-03-01 13:40:56 -05:00
Lai Jiang cda51f13dc Remove dnsRefreshRequestTime from EppResources (#1943)
We have decided to use a separate table (#1940) to track DNS refresh requests
due to performance reasons.

See: go/registry-pull-queue-redesign
2023-03-01 13:40:30 -05:00
Lai Jiang 1de5b5dcc1 Add a process time column to DnsRefreshRequest (#1948)
The value of the column would be set to START_OF_TIME for new entries.
Every time a row is read, the value is updated to the current time. This
allows concurrent reads to not repeatedly read the same entry that has the
earliest request time, because they would only look for rows that have a value
of process time that is before current time - some padding time.

This basically fulfills the same function that LEASE_PADDING gives us
when using a pull queue, whereas a task would be leased for a certain
time, during which time they would not be leased by anyone else.

See: https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/dns/ReadDnsQueueAction.java;l=99?q=readdnsqueue&ss=nomulus%2Fnomulus
2023-02-28 16:52:02 -05:00
sarahcaseybot 32279e42e4 Allow incorrect fee extensions on domain creates with default tokens (#1927)
* Modify fee extension to accept larger costs on creates with default tokens

* Add tests

* Add some comments to tests
2023-02-28 14:24:03 -05:00
Lai Jiang ba0f90bdaf Add support for Nordn upload without using pull queues. (#1925)
This PR adds an alternative method to upload Lordn to Nordn server without
using App Engine pull queue. A new database migration stage is added to control
whether a new task is scheduled with the old or new method. The
NordnUploadAction is configured to process both kind of tasks. Once the tasks
scheduled for the old tasks are all processed, we can start using the
new method exclusively.

See: go/registry-pull-queue-redesign
2023-02-28 12:57:27 -05:00
Lai Jiang 85308eb975 Ignore invalid old CRL when performing update. (#1946)
There is no point comparing the old CRL to the new ones when the old one
is invalid. This could happen when the CA cert rotates, after which the
old CRL stop being valid as it fails signature verification against the
new cert.

This change will allow us to keep updating the CRL after a CA rotation without
having to manually delete the old CRL from the database.

See b/270983553.

<!-- 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/1946)
<!-- Reviewable:end -->
2023-02-28 10:00:18 -05:00
Lai Jiang ed62f27a4a Update kythe vnames mapping (#1944) 2023-02-27 17:09:57 -05:00
Ben McIlwain 75851399ba Remove "letter G with stroke" from Extended Latin IDN table (#1938)
ICANN doesn't like this character because it's confusable with a normal G (the
stroke tends to get lost in the visual clutter of the descender), and .com's
Extended Latin table doesn't use it either. Best to get rid of it.
2023-02-23 16:27:15 -05:00
Lai Jiang 6d54c8d113 Add allowed license for json (#1942)
For some reason `./gradlew clean build` on master is failing for me on
multiple machines due to a new org.json:json version triggering license
violations, even though the lock files are not changing.

Note that the old versions are still present because if I remove
"The JSON license", which the old versions use, the check also fails...
2023-02-23 11:37:31 -05:00
Lai Jiang 34dfa2760e Add a table to record EPP resources needing DNS refresh (#1940) 2023-02-22 14:18:28 -05:00
Lai Jiang ff39a4a763 Change default beam job region (#1937)
For reasons that I cannot explain, the same expand recurring billing
event pipeline would fail in us-east1 but succeed in us-central1.

See:

https://pantheon.corp.google.com/dataflow/jobs/us-central1/2023-02-09_14_52_24-162498476138221714;graphView=0?project=domain-registry

https://pantheon.corp.google.com/dataflow/jobs/us-east1/2023-02-09_14_26_07-4564782062878841960;graphView=1?project=domain-registry

Also improved how the accuracy of the metrics:

It is observed that both counters are consistently higher for the same
start and end times when running in dry run mode. There is no way to
test for consistency when not running in dry run, for obviously reasons.

I can make the recurrings in scope counter consistent by not updating it
in a side-effect-causing transaction, but there is no way around the
other counter. It can only be trusted when running in dry run mode,
unfortunately.
2023-02-13 15:57:32 -05:00
112 changed files with 6890 additions and 5632 deletions
@@ -270,6 +270,10 @@
"moduleLicense": "Public Domain",
"moduleName": "org.tukaani:xz"
},
{
"moduleLicense": "Public Domain",
"moduleName": "org.json:json"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
@@ -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);
}
@@ -138,9 +138,15 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
private final boolean isDryRun;
private final boolean advanceCursor;
private final Counter recurringsInScopeCounter =
Metrics.counter("ExpandBilling", "RecurringsInScope");
private final Counter expandedOneTimeCounter =
Metrics.counter("ExpandBilling", "ExpandedOneTime");
Metrics.counter("ExpandBilling", "Recurrings in scope for expansion");
// Note that this counter is only accurate when running in dry run mode. Because SQL persistence
// is a side effect and not idempotent, a transaction to save OneTimes could be successful but the
// transform that contains it could be still be retried, rolling back the counter increment. The
// same transform, when retried, would skip the already expanded OneTime, causing this counter to
// be lower than it should be when not in dry run mode.
// See: https://beam.apache.org/documentation/programming-guide/#user-code-idempotence
private final Counter oneTimesToExpandCounter =
Metrics.counter("ExpandBilling", "OneTimes that would be expanded");
ExpandRecurringBillingEventsPipeline(
ExpandRecurringBillingEventsPipelineOptions options, Clock clock) {
@@ -195,6 +201,7 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
endTime.minusYears(1)),
true,
(BigInteger id) -> {
recurringsInScopeCounter.inc();
// Note that because all elements are mapped to the same dummy key, the next
// batching transform will effectively be serial. This however does not matter for
// our use case because the elements were obtained from a SQL read query, which
@@ -271,13 +278,9 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
} catch (IllegalArgumentException e) {
return;
}
recurringsInScopeCounter.inc();
Domain domain = tm().loadByKey(Domain.createVKey(recurring.getDomainRepoId()));
Registry tld = Registry.get(domain.getTld());
// Find the times for which the OneTime billing event are already created, making this expansion
// idempotent. There is no need to match to the domain repo ID as the cancellation matching
// billing event itself can only be for a single domain.
@@ -301,7 +304,7 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
// Create new OneTime and DomainHistory for EventTimes that needs to be expanded.
for (DateTime eventTime : eventTimesToExpand) {
recurrenceLastExpansionTime = latestOf(recurrenceLastExpansionTime, eventTime);
expandedOneTimeCounter.inc();
oneTimesToExpandCounter.inc();
DateTime billingTime = eventTime.plus(tld.getAutoRenewGracePeriodLength());
// Note that the DomainHistory is created as of transaction time, as opposed to event time.
// This might be counterintuitive because other DomainHistories are created at the time
@@ -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(
@@ -106,6 +106,12 @@ public final class RegistryConfig {
return config.gcpProject.projectId;
}
@Provides
@Config("cloudSchedulerServiceAccountEmail")
public static String provideCloudSchedulerServiceAccountEmail(RegistryConfigSettings config) {
return config.gcpProject.cloudSchedulerServiceAccountEmail;
}
@Provides
@Config("projectIdNumber")
public static long provideProjectIdNumber(RegistryConfigSettings config) {
@@ -924,7 +930,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,6 +54,7 @@ public class RegistryConfigSettings {
public String backendServiceUrl;
public String toolsServiceUrl;
public String pubapiServiceUrl;
public String cloudSchedulerServiceAccountEmail;
}
/** Configuration options for OAuth settings for authenticating users. */
@@ -22,6 +22,8 @@ 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
gSuite:
# Publicly accessible domain name of the running G Suite instance.
@@ -427,7 +429,7 @@ misc:
beam:
# The default region to run Apache Beam (Cloud Dataflow) jobs in.
defaultJobRegion: us-east1
defaultJobRegion: us-central1
# The GCE machine type to use when a job is CPU-intensive (e. g. RDE). Be sure
# to check the VM CPU quota for the job region. In a massively parallel
# pipeline this quota can be easily reached and needs to be raised, otherwise
@@ -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);
}
}
@@ -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;
@@ -339,7 +339,7 @@ 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)
@@ -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;
@@ -372,7 +372,7 @@ 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)
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<taskentries>
<task>
<url>/_dr/task/rdeStaging</url>
<name>rdeStaging</name>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<schedule>7 0 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<name>rdeUpload</name>
<description>
This job is a no-op unless RdeUploadCursor falls behind for some reason.
</description>
<schedule>0 */4 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
<name>tmchDnl</name>
<description>
This job downloads the latest DNL from MarksDB and inserts it into the database.
(See: TmchDnlAction, ClaimsList)
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
<name>tmchSmdrl</name>
<description>
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
(See: TmchSmdrlAction, SignedMarkRevocationList)
</description>
<schedule>15 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
<name>tmchCrl</name>
<description>
This job downloads the latest CRL from MarksDB and inserts it into the database.
(See: TmchCrlAction)
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<name>syncGroupMembers</name>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>0 */1 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<name>syncRegistrarsSheet</name>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>0 */1 * * *</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/expandRecurringBillingEvents?advanceCursor]]></url>
<name>expandRecurringBillingEvents</name>
<description>
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<schedule>0 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<name>deleteExpiredDomains</name>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
<name>deleteProberData</name>
<description>
This job clears out data from probers and runs once a week.
</description>
<schedule>0 14 * * 1</schedule>
</task>
<!-- TODO: Add borgmon job to check that these files are created and updated successfully. -->
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
<name>exportReservedTerms</name>
<description>
Reserved terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>30 5 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
<name>exportPremiumTerms</name>
<description>
Premium terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>0 5 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<name>readDnsQueue</name>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>*/1 * * * *</schedule>
</task>
</taskentries>
@@ -1,150 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
<!--
/cron/fanout params:
queue=<QUEUE_NAME>
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
runInEmpty // Run once, with no tld parameter
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
exclude=TLD1[,TLD2] // exclude something otherwise included
-->
<cron>
<url>/_dr/task/rdeStaging</url>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<schedule>every day 00:07</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<description>
This job is a no-op unless RdeUploadCursor falls behind for some reason.
</description>
<schedule>every 4 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
<description>
This job downloads the latest DNL from MarksDB and inserts it into the database.
(See: TmchDnlAction, ClaimsList)
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
<description>
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
(See: TmchSmdrlAction, SignedMarkRevocationList)
</description>
<schedule>every 12 hours from 00:15 to 12:15</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
<description>
This job downloads the latest CRL from MarksDB and inserts it into the database.
(See: TmchCrlAction)
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
</description>
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
<description>
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<schedule>every day 03:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>every day 03:07</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
<description>
This job clears out data from probers and runs once a week.
</description>
<schedule>every monday 14:00</schedule>
<timezone>UTC</timezone>
<target>backend</target>
</cron>
<!-- TODO: Add borgmon job to check that these files are created and updated successfully. -->
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
<description>
Reserved terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>every day 05:30</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
<description>
Premium terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>every day 05:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>every 1 minutes synchronized</schedule>
<target>backend</target>
</cron>
</cronentries>
@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<taskentries>
<!--
/cron/fanout params:
queue=<QUEUE_NAME>
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
runInEmpty // Run once, with no tld parameter
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
exclude=TLD1[,TLD2] // exclude something otherwise included
-->
<task>
<url>/_dr/task/rdeStaging</url>
<name>rdeStaging</name>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<!--
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 as quickly as
possible. The only job that'll run under normal circumstances is the one that's
close to midnight, since if the cursor is up-to-date, the task is a no-op.
We want it to be close to midnight because that reduces the chance that the
point-in-time code won't have to go to the extra trouble of fetching old
versions of objects from the database. However, we don't want it to run too
close to midnight, because there's always a chance that a change which was
timestamped before midnight hasn't fully been committed to the database. So
we add a 4+ minute grace period to ensure the transactions cool down, since
our queries are not transactional.
-->
<schedule>7 */4 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<name>rdeUpload</name>
<description>
This job is a no-op unless RdeUploadCursor falls behind for some reason.
</description>
<schedule>0 */4 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=rde-report&endpoint=/_dr/task/rdeReport&forEachRealTld]]></url>
<name>rdeReport</name>
<description>
This job is a no-op unless RdeReportCursor falls behind for some reason.
</description>
<schedule>0 */4 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
<name>tmchDnl</name>
<description>
This job downloads the latest DNL from MarksDB and inserts it into the database.
(See: TmchDnlAction, ClaimsList)
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
<name>tmchSmdrl</name>
<description>
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
(See: TmchSmdrlAction, SignedMarkRevocationList)
</description>
<schedule>15 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
<name>tmchCrl</name>
<description>
This job downloads the latest CRL from MarksDB and inserts it into the database.
(See: TmchCrlAction)
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<name>syncGroupMembers</name>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>0 */1 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<name>syncRegistrarsSheet</name>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>0 */1 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
<name>deleteProberData</name>
<description>
This job clears out data from probers and runs once a week.
</description>
<schedule>0 14 * * 1</schedule>
</task>
<!-- TODO: Add borgmon job to check that these files are created and updated successfully. -->
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
<name>exportReservedTerms</name>
<description>
Reserved terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>30 5 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
<name>exportPremiumTerms</name>
<description>
Exports premium price lists to the Google Drive folders for each TLD once per day.
</description>
<schedule>0 5 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<name>readDnsQueue</name>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<name>deleteExpiredDomains</name>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
</task>
<!--
The next two wipeout jobs are required when crash has production data.
-->
<task>
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
<name>wipeOutCloudSql</name>
<description>
This job runs an action that deletes all data in Cloud SQL.
</description>
<schedule>7 3 * * 6</schedule>
</task>
</taskentries>
@@ -1,165 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
<!--
/cron/fanout params:
queue=<QUEUE_NAME>
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
runInEmpty // Run once, with no tld parameter
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
exclude=TLD1[,TLD2] // exclude something otherwise included
-->
<cron>
<url>/_dr/task/rdeStaging</url>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<!--
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 as quickly as
possible. The only job that'll run under normal circumstances is the one that's
close to midnight, since if the cursor is up-to-date, the task is a no-op.
We want it to be close to midnight because that reduces the chance that the
point-in-time code won't have to go to the extra trouble of fetching old
versions of objects from the database. However, we don't want it to run too
close to midnight, because there's always a chance that a change which was
timestamped before midnight hasn't fully been committed to the database. So
we add a 4+ minute grace period to ensure the transactions cool down, since
our queries are not transactional.
-->
<schedule>every 4 hours from 00:07 to 20:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<description>
This job is a no-op unless RdeUploadCursor falls behind for some reason.
</description>
<schedule>every 4 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=rde-report&endpoint=/_dr/task/rdeReport&forEachRealTld]]></url>
<description>
This job is a no-op unless RdeReportCursor falls behind for some reason.
</description>
<schedule>every 4 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
<description>
This job downloads the latest DNL from MarksDB and inserts it into the database.
(See: TmchDnlAction, ClaimsList)
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
<description>
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
(See: TmchSmdrlAction, SignedMarkRevocationList)
</description>
<schedule>every 12 hours from 00:15 to 12:15</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
<description>
This job downloads the latest CRL from MarksDB and inserts it into the database.
(See: TmchCrlAction)
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
<description>
This job clears out data from probers and runs once a week.
</description>
<schedule>every monday 14:00</schedule>
<timezone>UTC</timezone>
<target>backend</target>
</cron>
<!-- TODO: Add borgmon job to check that these files are created and updated successfully. -->
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
<description>
Reserved terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>every day 05:30</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
<description>
Exports premium price lists to the Google Drive folders for each TLD once per day.
</description>
<schedule>every day 05:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>every 1 minutes synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>every day 03:07</schedule>
<target>backend</target>
</cron>
<!--
The next two wipeout jobs are required when crash has production data.
-->
<cron>
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
<description>
This job runs an action that deletes all data in Cloud SQL.
</description>
<schedule>every saturday 03:07</schedule>
<target>backend</target>
</cron>
</cronentries>
@@ -7,7 +7,7 @@
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
<instances>12</instances>
<instances>24</instances>
</manual-scaling>
<system-properties>
@@ -0,0 +1,299 @@
<?xml version="1.0" encoding="UTF-8"?>
<taskentries>
<task>
<url>/_dr/task/rdeStaging</url>
<name>rdeStaging</name>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<!--
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 as quickly as
possible. The only job that'll run under normal circumstances is the one that's
close to midnight, since if the cursor is up-to-date, the task is a no-op.
We want it to be close to midnight because that reduces the chance that the
point-in-time code won't have to go to the extra trouble of fetching old
versions of objects from the database. However, we don't want it to run too
close to midnight, because there's always a chance that a change which was
timestamped before midnight hasn't fully been committed to the database. So
we add a 4+ minute grace period to ensure the transactions cool down, since
our queries are not transactional.
-->
<schedule>7 */8 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<name>rdeUpload</name>
<description>
This job is a no-op unless RdeUploadCursor falls behind for some reason.
</description>
<schedule>0 */4 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=rde-report&endpoint=/_dr/task/rdeReport&forEachRealTld]]></url>
<name>rdeReport</name>
<description>
This job is a no-op unless RdeReportCursor falls behind for some reason.
</description>
<schedule>0 */4 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
<name>tmchDnl</name>
<description>
This job downloads the latest DNL from MarksDB and inserts it into the database.
(See: TmchDnlAction, ClaimsList)
</description>
<schedule>0 0,12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
<name>tmchSmdrl</name>
<description>
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
(See: TmchSmdrlAction, SignedMarkRevocationList)
</description>
<schedule>15 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
<name>tmchCrl</name>
<description>
This job downloads the latest CRL from MarksDB and inserts it into the database.
(See: TmchCrlAction)
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<name>syncGroupMembers</name>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>0 */1 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<name>syncRegistrarsSheet</name>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>0 */1 * * *</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/updateRegistrarRdapBaseUrls]]></url>
<name>updateRegistrarRdapBaseUrls</name>
<description>
This job reloads all registrar RDAP base URLs from ICANN.
</description>
<schedule>34 2 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
<name>exportDomainLists</name>
<description>
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
<name>expandRecurringBillingEvents</name>
<description>
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<schedule>0 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<name>deleteExpiredDomains</name>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/sendExpiringCertificateNotificationEmail]]></url>
<name>sendExpiringCertificateNotificationEmail</name>
<description>
This job runs an action that sends emails to partners if their certificates are expiring soon.
</description>
<schedule>30 4 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=sunrise]]></url>
<name>nordnUploadSunrise</name>
<description>
This job uploads LORDN Sunrise CSV files for each TLD to MarksDB. 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]]></url>
<name>nordnUploadClaims</name>
<description>
This job uploads LORDN Claims CSV files for each TLD to MarksDB. 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=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>
<description>
This job clears out data from probers and runs once a week.
</description>
<schedule>0 14 * * 1</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
<name>exportReservedTerms</name>
<description>
Reserved terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>30 5 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
<name>exportPremiumTerms</name>
<description>
Exports premium price lists to the Google Drive folders for each TLD once per day.
</description>
<schedule>0 5 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<name>readDnsQueue</name>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingStaging&runInEmpty]]></url>
<name>icannReportingStaging</name>
<description>
Create ICANN activity and transaction reports for last month, storing them in
gs://[PROJECT-ID]-reporting/icann/monthly/yyyy-MM
Upon success, enqueues the icannReportingUpload task to POST these files to ICANN.
</description>
<schedule>0 9 2 * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingUpload&runInEmpty]]></url>
<name>icannReportingUpload</name>
<description>
Checks if the monthly ICANN reports have been successfully uploaded. If they have not, attempts to upload them again.
Most of the time, this job should not do anything since the uploads are triggered when the reports are staged.
However, in the event that an upload failed for any reason (e.g. ICANN server is down, IP allow list issues),
this cron job will continue to retry uploads daily until they succeed.
</description>
<schedule>0 15 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/generateInvoices?shouldPublish=true&runInEmpty]]></url>
<name>generateInvoices</name>
<description>
Starts the beam/billing/InvoicingPipeline Dataflow template, which creates the overall invoice and
detail report CSVs for last month, storing them in gs://[PROJECT-ID]-billing/invoices/yyyy-MM.
Upon success, sends an e-mail copy of the invoice to billing personnel, and copies detail
reports to the associated registrars' drive folders.
See GenerateInvoicesAction for more details.
</description>
<!--WARNING: This must occur AFTER expandRecurringBillingEvents and AFTER exportSnapshot, as
it uses Bigquery as the source of truth for billable events. ExportSnapshot usually takes
about 2 hours to complete, so we give 11 hours to be safe. Normally, we give 24+ hours (see
icannReportingStaging), but the invoicing team prefers receiving the e-mail on the first of
each month. -->
<schedule>0 19 1 * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/generateSpec11&runInEmpty]]></url>
<name>generateSpec11</name>
<description>
Starts the beam/spec11/Spec11Pipeline Dataflow template, which creates today's Spec11
report. This report is stored in gs://[PROJECT-ID]-reporting/icann/spec11/yyyy-MM/.
This job will only send email notifications on the second of every month.
See GenerateSpec11ReportAction for more details.
</description>
<schedule>0 15 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
<name>wipeOutContactHistoryPii</name>
<description>
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>
</task>
</taskentries>
@@ -1,288 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
<!--
/cron/fanout params:
queue=<QUEUE_NAME>
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
runInEmpty // Run once, with no tld parameter
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
exclude=TLD1[,TLD2] // exclude something otherwise included
-->
<cron>
<url>/_dr/task/rdeStaging</url>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<!--
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 as quickly as
possible. The only job that'll run under normal circumstances is the one that's
close to midnight, since if the cursor is up-to-date, the task is a no-op.
We want it to be close to midnight because that reduces the chance that the
point-in-time code won't have to go to the extra trouble of fetching old
versions of objects from the database. However, we don't want it to run too
close to midnight, because there's always a chance that a change which was
timestamped before midnight hasn't fully been committed to the database. So
we add a 4+ minute grace period to ensure the transactions cool down, since
our queries are not transactional.
-->
<schedule>every 8 hours from 00:07 to 20:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<description>
This job is a no-op unless RdeUploadCursor falls behind for some reason.
</description>
<schedule>every 4 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=rde-report&endpoint=/_dr/task/rdeReport&forEachRealTld]]></url>
<description>
This job is a no-op unless RdeReportCursor falls behind for some reason.
</description>
<schedule>every 4 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
<description>
This job downloads the latest DNL from MarksDB and inserts it into the database.
(See: TmchDnlAction, ClaimsList)
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
<description>
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
(See: TmchSmdrlAction, SignedMarkRevocationList)
</description>
<schedule>every 12 hours from 00:15 to 12:15</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
<description>
This job downloads the latest CRL from MarksDB and inserts it into the database.
(See: TmchCrlAction)
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<!-- TODO(b/249863289): disable until it is safe to run this pipeline
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
</description>
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
</cron>
-->
<cron>
<url><![CDATA[/_dr/task/updateRegistrarRdapBaseUrls]]></url>
<description>
This job reloads all registrar RDAP base URLs from ICANN.
</description>
<schedule>every day 02:34</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
<description>
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
<description>
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<schedule>every day 03:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>every day 03:07</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/sendExpiringCertificateNotificationEmail]]></url>
<description>
This job runs an action that sends emails to partners if their certificates are expiring soon.
</description>
<schedule>every day 04:30</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordn-phase=sunrise]]></url>
<description>
This job uploads LORDN Sunrise CSV files for each TLD to MarksDB. 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>every 12 hours synchronized</schedule>
<timezone>UTC</timezone>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordn-phase=claims]]></url>
<description>
This job uploads LORDN Claims CSV files for each TLD to MarksDB. 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>every 12 hours synchronized</schedule>
<timezone>UTC</timezone>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
<description>
This job clears out data from probers and runs once a week.
</description>
<schedule>every monday 14:00</schedule>
<timezone>UTC</timezone>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
<description>
Reserved terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>every day 05:30</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
<description>
Exports premium price lists to the Google Drive folders for each TLD once per day.
</description>
<schedule>every day 05:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>every 1 minutes synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingStaging&runInEmpty]]></url>
<description>
Create ICANN activity and transaction reports for last month, storing them in
gs://[PROJECT-ID]-reporting/icann/monthly/yyyy-MM
Upon success, enqueues the icannReportingUpload task to POST these files to ICANN.
</description>
<schedule>2 of month 09:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingUpload&runInEmpty]]></url>
<description>
Checks if the monthly ICANN reports have been successfully uploaded. If they have not, attempts to upload them again.
Most of the time, this job should not do anything since the uploads are triggered when the reports are staged.
However, in the event that an upload failed for any reason (e.g. ICANN server is down, IP allow list issues),
this cron job will continue to retry uploads daily until they succeed.
</description>
<schedule>every day 15:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/generateInvoices?shouldPublish=true&runInEmpty]]></url>
<description>
Starts the beam/billing/InvoicingPipeline Dataflow template, which creates the overall invoice and
detail report CSVs for last month, storing them in gs://[PROJECT-ID]-billing/invoices/yyyy-MM.
Upon success, sends an e-mail copy of the invoice to billing personnel, and copies detail
reports to the associated registrars' drive folders.
See GenerateInvoicesAction for more details.
</description>
<!--WARNING: This must occur AFTER expandRecurringBillingEvents and AFTER exportSnapshot, as
it uses Bigquery as the source of truth for billable events. ExportSnapshot usually takes
about 2 hours to complete, so we give 11 hours to be safe. Normally, we give 24+ hours (see
icannReportingStaging), but the invoicing team prefers receiving the e-mail on the first of
each month. -->
<schedule>1 of month 19:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/generateSpec11&runInEmpty]]></url>
<description>
Starts the beam/spec11/Spec11Pipeline Dataflow template, which creates today's Spec11
report. This report is stored in gs://[PROJECT-ID]-reporting/icann/spec11/yyyy-MM/.
This job will only send email notifications on the second of every month.
See GenerateSpec11ReportAction for more details.
</description>
<schedule>every day 15:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
<description>
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>every monday 15:00</schedule>
<target>backend</target>
</cron>
</cronentries>
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<taskentries>
<task>
<url>/_dr/task/rdeStaging</url>
<name>rdeStaging</name>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<schedule>7 0 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<name>readDnsQueue</name>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<name>syncRegistrarsSheet</name>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>0 */1 * * *</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/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<name>syncGroupMembers</name>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>0 */1 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<name>deleteExpiredDomains</name>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
<name>wipeOutCloudSql</name>
<description>
This job runs an action that deletes all data in Cloud SQL.
</description>
<schedule>7 3 * * 6</schedule>
</task>
</taskentries>
@@ -1,70 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
<cron>
<url>/_dr/task/rdeStaging</url>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<schedule>every day 00:07</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>every 1 minutes synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
</description>
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>every day 03:07</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
<description>
This job runs an action that deletes all data in Cloud SQL.
</description>
<schedule>every saturday 03:07</schedule>
<target>backend</target>
</cron>
</cronentries>
@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<taskentries>
<task>
<url>/_dr/task/rdeStaging</url>
<name>rdeStaging</name>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<!--
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>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<name>rdeUpload</name>
<description>
This job is a no-op unless RdeUploadCursor falls behind for some reason.
</description>
<schedule>0 */4 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
<name>tmchDnl</name>
<description>
This job downloads the latest DNL from MarksDB and inserts it into the database.
(See: TmchDnlAction, ClaimsList)
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
<name>tmchSmdrl</name>
<description>
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
(See: TmchSmdrlAction, SignedMarkRevocationList)
</description>
<schedule>15 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
<name>tmchCrl</name>
<description>
This job downloads the latest CRL from MarksDB and inserts it into the database.
(See: TmchCrlAction)
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<name>syncGroupMembers</name>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>0 */1 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<name>syncRegistrarsSheet</name>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>0 */1 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
<name>exportDomainLists</name>
<description>
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
</description>
<schedule>0 */12 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
<name>expandRecurringBillingEvents</name>
<description>
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<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>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
<name>deleteProberData</name>
<description>
This job clears out data from probers and runs once a week.
</description>
<schedule>0 14 * * 1</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
<name>exportReservedTerms</name>
<description>
Reserved terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>30 5 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
<name>exportPremiumTerms</name>
<description>
Exports premium price lists to the Google Drive folders for each TLD once per day.
</description>
<schedule>0 5 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<name>readDnsQueue</name>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>*/1 * * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
<name>wipeOutContactHistoryPii</name>
<description>
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>
</task>
</taskentries>
@@ -1,177 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
<cronentries>
<!--
/cron/fanout params:
queue=<QUEUE_NAME>
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
runInEmpty // Run once, with no tld parameter
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
exclude=TLD1[,TLD2] // exclude something otherwise included
-->
<cron>
<url>/_dr/task/rdeStaging</url>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML document
and streams it to cloud storage. When this job has finished successfully, it'll
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
</description>
<!--
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>every 12 hours from 00:07 to 12:07</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<description>
This job is a no-op unless RdeUploadCursor falls behind for some reason.
</description>
<schedule>every 4 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
<description>
This job downloads the latest DNL from MarksDB and inserts it into the database.
(See: TmchDnlAction, ClaimsList)
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
<description>
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
(See: TmchSmdrlAction, SignedMarkRevocationList)
</description>
<schedule>every 12 hours from 00:15 to 12:15</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
<description>
This job downloads the latest CRL from MarksDB and inserts it into the database.
(See: TmchCrlAction)
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
<description>
Syncs RegistrarContact changes in the past hour to Google Groups.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
<description>
Synchronize Registrar entities to Google Spreadsheets.
</description>
<schedule>every 1 hours synchronized</schedule>
<target>backend</target>
</cron>
<!-- TODO(b/249863289): disable until it is safe to run this pipeline
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
</description>
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
</cron>
-->
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
<description>
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
<description>
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<schedule>every day 03:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<description>
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>every day 03:07</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
<description>
This job clears out data from probers and runs once a week.
</description>
<schedule>every monday 14:00</schedule>
<timezone>UTC</timezone>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
<description>
Reserved terms export to Google Drive job for creating once-daily exports.
</description>
<schedule>every day 05:30</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
<description>
Exports premium price lists to the Google Drive folders for each TLD once per day.
</description>
<schedule>every day 05:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<description>
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
group.
</description>
<schedule>every 1 minutes synchronized</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
<description>
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>every monday 15:00</schedule>
<target>backend</target>
</cron>
</cronentries>
@@ -86,6 +86,8 @@ 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;
@@ -123,6 +125,7 @@ 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;
@@ -277,8 +280,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
registrarId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
boolean defaultTokenUsed = false;
if (!allocationToken.isPresent() && !registry.getDefaultPromoTokens().isEmpty()) {
allocationToken = checkForDefaultToken(registry, command);
if (allocationToken.isPresent()) {
defaultTokenUsed = true;
}
}
boolean isAnchorTenant =
isAnchorTenant(
@@ -337,7 +344,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
FeesAndCredits feesAndCredits =
pricingLogic.getCreatePrice(
registry, targetId, now, years, isAnchorTenant, allocationToken);
validateFeeChallenge(feeCreate, feesAndCredits);
validateFeeChallenge(feeCreate, feesAndCredits, defaultTokenUsed);
Optional<SecDnsCreateExtension> secDnsCreate =
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
DateTime registrationExpirationTime = leapSafeAddYears(now, years);
@@ -377,7 +384,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
reservationTypes.contains(NAME_COLLISION)
? ImmutableSet.of(SERVER_HOLD)
: ImmutableSet.of();
Domain domain =
Domain.Builder domainBuilder =
new Domain.Builder()
.setCreationRegistrarId(registrarId)
.setPersistedCurrentSponsorRegistrarId(registrarId)
@@ -397,7 +404,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
.setContacts(command.getContacts())
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
.build();
.setLordnPhase(
!DatabaseMigrationStateSchedule.getValueAtTime(tm().getTransactionTime())
.equals(MigrationState.NORDN_SQL)
? LordnPhase.NONE
: hasSignedMarks
? LordnPhase.SUNRISE
: hasClaimsNotice ? LordnPhase.CLAIMS : LordnPhase.NONE);
Domain domain = domainBuilder.build();
if (allocationToken.isPresent()
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
if (years > 1) {
@@ -697,7 +711,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
if (newDomain.shouldPublishToDns()) {
dnsQueue.addDomainRefreshTask(newDomain.getDomainName());
}
if (hasClaimsNotice || hasSignedMarks) {
if (!DatabaseMigrationStateSchedule.getValueAtTime(tm().getTransactionTime())
.equals(MigrationState.NORDN_SQL)
&& (hasClaimsNotice || hasSignedMarks)) {
LordnTaskUtils.enqueueDomainTask(newDomain);
}
}
@@ -778,12 +778,13 @@ public class DomainFlowUtils {
*/
public static void validateFeeChallenge(
final Optional<? extends FeeTransformCommandExtension> feeCommand,
FeesAndCredits feesAndCredits)
FeesAndCredits feesAndCredits,
boolean defaultTokenUsed)
throws EppException {
if (feesAndCredits.hasAnyPremiumFees() && !feeCommand.isPresent()) {
throw new FeesRequiredForPremiumNameException();
}
validateFeesAckedIfPresent(feeCommand, feesAndCredits);
validateFeesAckedIfPresent(feeCommand, feesAndCredits, defaultTokenUsed);
}
/**
@@ -795,7 +796,8 @@ public class DomainFlowUtils {
*/
public static void validateFeesAckedIfPresent(
final Optional<? extends FeeTransformCommandExtension> feeCommand,
FeesAndCredits feesAndCredits)
FeesAndCredits feesAndCredits,
boolean defaultTokenUsed)
throws EppException {
// Check for the case where a fee command extension was required but not provided.
// This only happens when the total fees are non-zero and include custom fees requiring the
@@ -856,7 +858,9 @@ public class DomainFlowUtils {
// Checking if total amount is expected. Extra fees that we are not expecting may be passed in.
// Or if there is only a single fee type expected.
if (!feeTotal.equals(feesAndCredits.getTotalCost())) {
throw new FeesMismatchException(feesAndCredits.getTotalCost());
if (!defaultTokenUsed || feeTotal.isLessThan(feesAndCredits.getTotalCost())) {
throw new FeesMismatchException(feesAndCredits.getTotalCost());
}
}
}
@@ -199,7 +199,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
now,
years,
existingRecurringBillingEvent);
validateFeeChallenge(feeRenew, feesAndCredits);
validateFeeChallenge(feeRenew, feesAndCredits, false);
flowCustomLogic.afterValidation(
AfterValidationParameters.newBuilder()
.setExistingDomain(existingDomain)
@@ -226,7 +226,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
if (!existingDomain.getGracePeriodStatuses().contains(GracePeriodStatus.REDEMPTION)) {
throw new DomainNotEligibleForRestoreException();
}
validateFeeChallenge(feeUpdate, feesAndCredits);
validateFeeChallenge(feeUpdate, feesAndCredits, false);
}
private static Domain performRestore(
@@ -210,7 +210,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
}
if (feesAndCredits.isPresent()) {
validateFeeChallenge(feeTransfer, feesAndCredits.get());
validateFeeChallenge(feeTransfer, feesAndCredits.get(), false);
}
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder
@@ -231,7 +231,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
Optional<FeeUpdateCommandExtension> feeUpdate =
eppInput.getSingleExtension(FeeUpdateCommandExtension.class);
FeesAndCredits feesAndCredits = pricingLogic.getUpdatePrice(registry, targetId, now);
validateFeesAckedIfPresent(feeUpdate, feesAndCredits);
validateFeesAckedIfPresent(feeUpdate, feesAndCredits, false);
verifyNotInPendingDelete(
add.getContacts(),
command.getInnerChange().getRegistrant(),
@@ -36,6 +36,7 @@ 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.batch.CloudTasksUtils;
import google.registry.dns.DnsQueue;
import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.flows.EppException;
@@ -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;
@@ -280,7 +280,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
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,7 +68,6 @@ 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,
@@ -33,7 +33,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.annotations.Expose;
import google.registry.config.RegistryConfig;
import google.registry.dns.RefreshDnsAction;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
@@ -42,7 +41,6 @@ import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
@@ -137,23 +135,6 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
/** Status values associated with this resource. */
@Expose Set<StatusValue> statuses;
/**
* When this domain/host's DNS was requested to be refreshed, or null if its DNS is up-to-date.
*
* <p>This will almost always be null except in the couple of minutes' interval between when a
* DNS-affecting create or update operation takes place and when the {@link RefreshDnsAction}
* runs, which resets this back to null upon completion of the DNS refresh task. This is a {@link
* DateTime} rather than a simple dirty boolean so that the DNS refresh action can order by the
* DNS refresh request time and take action on the oldest ones first.
*
* <p>Note that in the {@code DomainHistory}/{@code HostHistory} table this value means something
* slightly different: It means that the given domain/host action requested a DNS update. Unlike
* on the {@code Domain}/{code Host} table, this value is not then subsequently nulled out once
* the DNS refresh is complete; rather, it remains as a permanent record of which actions were
* DNS-affecting and which were not.
*/
@Transient @Nullable protected DateTime dnsRefreshRequestTime;
public String getRepoId() {
return repoId;
}
@@ -205,19 +186,6 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
return deletionTime;
}
/**
* Returns the DNS refresh request time iff this domain/host's DNS needs refreshing, otherwise
* absent.
*/
public Optional<DateTime> getDnsRefreshRequestTime() {
return Optional.ofNullable(dnsRefreshRequestTime);
}
@SuppressWarnings("unused")
private void setInternalDnsRefreshRequestTime(DateTime time) {
dnsRefreshRequestTime = time;
}
/** Return a clone of the resource with timed status values modified using the given time. */
public abstract EppResource cloneProjectedAtTime(DateTime now);
@@ -371,11 +339,6 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
return thisCastToDerived();
}
public B setDnsRefreshRequestTime(Optional<DateTime> dnsRefreshRequestTime) {
getInstance().dnsRefreshRequestTime = dnsRefreshRequestTime.orElse(null);
return thisCastToDerived();
}
/** Build the resource, nullifying empty strings and sets and setting defaults. */
@Override
public T build() {
@@ -44,6 +44,8 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static boolean useUncachedForTest = false;
public enum PrimaryDatabase {
CLOUD_SQL,
DATASTORE
@@ -85,7 +87,10 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
SQL_ONLY(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY),
/** Toggles SQL Sequence based allocateId */
SEQUENCE_BASED_ALLOCATE_ID(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
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);
private final PrimaryDatabase primaryDatabase;
private final boolean isReadOnly;
@@ -164,7 +169,9 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
MigrationState.SQL_ONLY,
MigrationState.SQL_PRIMARY_READ_ONLY,
MigrationState.SQL_PRIMARY)
.putAll(MigrationState.SQL_ONLY, MigrationState.SEQUENCE_BASED_ALLOCATE_ID);
.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);
// 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));
@@ -181,8 +188,8 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
public TimedTransitionProperty<MigrationState> migrationTransitions =
TimedTransitionProperty.withInitialValue(MigrationState.DATASTORE_ONLY);
// Required for Objectify initialization
private DatabaseMigrationStateSchedule() {}
// Required for Hibernate initialization
protected DatabaseMigrationStateSchedule() {}
@VisibleForTesting
public DatabaseMigrationStateSchedule(
@@ -205,6 +212,11 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
CACHE.invalidateAll();
}
@VisibleForTesting
public static void useUncachedForTest() {
useUncachedForTest = true;
}
/** Loads the currently-set migration schedule from the cache, or the default if none exists. */
public static TimedTransitionProperty<MigrationState> get() {
return CACHE.get(DatabaseMigrationStateSchedule.class);
@@ -212,7 +224,9 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
/** Returns the database migration status at the given time. */
public static MigrationState getValueAtTime(DateTime dateTime) {
return get().getValueAtTime(dateTime);
return useUncachedForTest
? getUncached().getValueAtTime(dateTime)
: get().getValueAtTime(dateTime);
}
/** Loads the currently-set migration schedule from SQL, or the default if none exists. */
@@ -24,6 +24,7 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.UpdateAutoTimestampEntity;
import google.registry.persistence.VKey;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
@@ -47,7 +48,6 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
private Long id;
/** GAIA ID associated with the user in question. */
@Column(nullable = false)
private String gaiaId;
/** Email address of the user in question. */
@@ -118,6 +118,11 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
return new Builder(clone(this));
}
@Override
public VKey<User> createVKey() {
return VKey.create(User.class, getId());
}
/** Builder for constructing immutable {@link User} objects. */
public static class Builder extends Buildable.Builder<User> {
@@ -129,7 +134,6 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
@Override
public User build() {
checkArgumentNotNull(getInstance().gaiaId, "Gaia ID cannot be null");
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
return super.build();
@@ -58,7 +58,6 @@ import org.joda.time.DateTime;
@Index(columnList = "techContact"),
@Index(columnList = "tld"),
@Index(columnList = "registrantContact"),
@Index(columnList = "dnsRefreshRequestTime"),
@Index(columnList = "lordnPhase"),
@Index(columnList = "billing_recurrence_id"),
@Index(columnList = "transfer_billing_event_id"),
@@ -200,7 +199,6 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
.setSubordinateHosts(domainBase.getSubordinateHosts())
.setStatusValues(domainBase.getStatusValues())
.setTransferData(domainBase.getTransferData())
.setDnsRefreshRequestTime(domainBase.getDnsRefreshRequestTime())
.setLordnPhase(domainBase.getLordnPhase())
.setCurrentPackageToken(domainBase.getCurrentPackageToken().orElse(null));
}
@@ -274,13 +274,6 @@ public class DomainBase extends EppResource
return lordnPhase;
}
@Access(AccessType.PROPERTY)
@SuppressWarnings("unused")
@Column(name = "dnsRefreshRequestTime")
private DateTime getInternalDnsRefreshRequestTime() {
return getDnsRefreshRequestTime().orElse(null);
}
public ImmutableSet<String> getSubordinateHosts() {
return nullToEmptyImmutableCopy(subordinateHosts);
}
@@ -49,7 +49,6 @@ import javax.persistence.Table;
@Index(columnList = "creationTime"),
@Index(columnList = "deletionTime"),
@Index(columnList = "currentSponsorRegistrarId"),
@Index(columnList = "dnsRefreshRequestTime")
})
@ExternalMessagingName("host")
@WithVKey(String.class)
@@ -94,7 +93,6 @@ public class Host extends HostBase implements ForeignKeyedEppResource {
.setPersistedCurrentSponsorRegistrarId(hostBase.getPersistedCurrentSponsorRegistrarId())
.setRepoId(hostBase.getRepoId())
.setSuperordinateDomain(hostBase.getSuperordinateDomain())
.setDnsRefreshRequestTime(hostBase.getDnsRefreshRequestTime())
.setStatusValues(hostBase.getStatusValues());
}
}
@@ -32,7 +32,6 @@ import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
import org.joda.time.DateTime;
@@ -86,13 +85,6 @@ public class HostBase extends EppResource {
*/
DateTime lastSuperordinateChange;
@Access(AccessType.PROPERTY)
@SuppressWarnings("unused")
@Column(name = "dnsRefreshRequestTime")
private DateTime getInternalDnsRefreshRequestTime() {
return getDnsRefreshRequestTime().orElse(null);
}
public String getHostName() {
return hostName;
}
@@ -25,8 +25,6 @@ import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import google.registry.util.RequestStatusChecker;
import google.registry.util.RequestStatusCheckerImpl;
import java.io.Serializable;
import java.util.Optional;
import java.util.function.Supplier;
@@ -70,8 +68,7 @@ public class Lock extends ImmutableObject implements Serializable {
enum LockState {
IN_USE,
FREE,
TIMED_OUT,
OWNER_DIED
TIMED_OUT
}
@VisibleForTesting static LockMetrics lockMetrics = new LockMetrics();
@@ -79,17 +76,6 @@ public class Lock extends ImmutableObject implements Serializable {
/** The name of the locked resource. */
@Transient @Id String lockId;
/**
* Unique log ID of the request that owns this lock.
*
* <p>When that request is no longer running (is finished), the lock can be considered implicitly
* released.
*
* <p>See {@link RequestStatusCheckerImpl#getLogId} for details about how it's created in
* practice.
*/
@Column String requestLogId;
/** When the lock can be considered implicitly released. */
@Column(nullable = false)
DateTime expirationTime;
@@ -124,7 +110,6 @@ public class Lock extends ImmutableObject implements Serializable {
private static Lock create(
String resourceName,
String scope,
String requestLogId,
DateTime acquiredTime,
Duration leaseLength) {
checkArgument(!Strings.isNullOrEmpty(resourceName), "resourceName cannot be null or empty");
@@ -132,7 +117,6 @@ public class Lock extends ImmutableObject implements Serializable {
// Add the scope to the Lock's id so that it is unique for locks acquiring the same resource
// across different TLDs.
instance.lockId = makeLockId(resourceName, scope);
instance.requestLogId = requestLogId;
instance.expirationTime = acquiredTime.plus(leaseLength);
instance.acquiredTime = acquiredTime;
instance.resourceName = resourceName;
@@ -172,18 +156,13 @@ public class Lock extends ImmutableObject implements Serializable {
switch (acquireResult.lockState()) {
case IN_USE:
logger.atInfo().log(
"Existing lock by request %s is still valid now %s (until %s) lock: %s",
lock.requestLogId, now, lock.expirationTime, lock.lockId);
"Existing lock by request is still valid now %s (until %s) lock: %s",
now, lock.expirationTime, lock.lockId);
break;
case TIMED_OUT:
logger.atInfo().log(
"Existing lock by request %s is timed out now %s (was valid until %s) lock: %s",
lock.requestLogId, now, lock.expirationTime, lock.lockId);
break;
case OWNER_DIED:
logger.atInfo().log(
"Existing lock is valid now %s (until %s), but owner (%s) isn't running lock: %s",
now, lock.expirationTime, lock.requestLogId, lock.lockId);
"Existing lock by request is timed out now %s (was valid until %s) lock: %s",
now, lock.expirationTime, lock.lockId);
break;
case FREE:
// There was no existing lock
@@ -203,11 +182,7 @@ public class Lock extends ImmutableObject implements Serializable {
/** Try to acquire a lock. Returns absent if it can't be acquired. */
public static Optional<Lock> acquire(
String resourceName,
@Nullable String tld,
Duration leaseLength,
RequestStatusChecker requestStatusChecker,
boolean checkThreadRunning) {
String resourceName, @Nullable String tld, Duration leaseLength) {
String scope = tld != null ? tld : GLOBAL;
Supplier<AcquireResult> lockAcquirer =
() -> {
@@ -219,22 +194,19 @@ public class Lock extends ImmutableObject implements Serializable {
.orElse(null);
if (lock != null) {
logger.atInfo().log(
"Loaded existing lock: %s for request: %s", lock.lockId, lock.requestLogId);
"Loaded existing lock: %s for resource: %s", lock.lockId, lock.resourceName);
}
LockState lockState;
if (lock == null) {
lockState = LockState.FREE;
} else if (isAtOrAfter(now, lock.expirationTime)) {
lockState = LockState.TIMED_OUT;
} else if (checkThreadRunning && !requestStatusChecker.isRunning(lock.requestLogId)) {
lockState = LockState.OWNER_DIED;
} else {
lockState = LockState.IN_USE;
return AcquireResult.create(now, lock, null, lockState);
}
Lock newLock =
create(resourceName, scope, requestStatusChecker.getLogId(), now, leaseLength);
Lock newLock = create(resourceName, scope, now, leaseLength);
tm().put(newLock);
return AcquireResult.create(now, lock, newLock, lockState);
@@ -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,
@@ -39,8 +39,6 @@ import google.registry.request.HttpException.UnsupportedMediaTypeException;
import google.registry.request.auth.AuthResult;
import google.registry.request.lock.LockHandler;
import google.registry.request.lock.LockHandlerImpl;
import google.registry.util.RequestStatusChecker;
import google.registry.util.RequestStatusCheckerImpl;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
@@ -192,18 +190,6 @@ public final class RequestModule {
return lockHandler;
}
@Provides
static RequestStatusChecker provideRequestStatusChecker(
RequestStatusCheckerImpl requestStatusChecker) {
return requestStatusChecker;
}
@Provides
@RequestLogId
static String provideRequestLogId(RequestStatusChecker requestStatusChecker) {
return requestStatusChecker.getLogId();
}
@Provides
@JsonPayload
@SuppressWarnings("unchecked")
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import javax.inject.Qualifier;
import javax.inject.Singleton;
/**
@@ -30,15 +31,26 @@ import javax.inject.Singleton;
public class AuthModule {
private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";
private static final String SA_ISSUER_URL = "https://accounts.google.com";
/** Provides the custom authentication mechanisms (including OAuth). */
@Provides
ImmutableList<AuthenticationMechanism> provideApiAuthenticationMechanisms(
OAuthAuthenticationMechanism oauthAuthenticationMechanism,
IapHeaderAuthenticationMechanism iapHeaderAuthenticationMechanism) {
return ImmutableList.of(oauthAuthenticationMechanism, iapHeaderAuthenticationMechanism);
IapHeaderAuthenticationMechanism iapHeaderAuthenticationMechanism,
ServiceAccountAuthenticationMechanism serviceAccountAuthenticationMechanism) {
return ImmutableList.of(
oauthAuthenticationMechanism,
iapHeaderAuthenticationMechanism,
serviceAccountAuthenticationMechanism);
}
@Qualifier
@interface IAP {}
@Qualifier
@interface ServiceAccount {}
/** Provides the OAuthService instance. */
@Provides
OAuthService provideOauthService() {
@@ -46,10 +58,18 @@ public class AuthModule {
}
@Provides
@IAP
@Singleton
TokenVerifier provideTokenVerifier(
@Config("projectId") String projectId, @Config("projectIdNumber") long projectIdNumber) {
String audience = String.format("/projects/%d/apps/%s", projectIdNumber, projectId);
return TokenVerifier.newBuilder().setAudience(audience).setIssuer(IAP_ISSUER_URL).build();
}
@Provides
@ServiceAccount
@Singleton
TokenVerifier provideServiceAccountTokenVerifier(@Config("projectId") String projectId) {
return TokenVerifier.newBuilder().setAudience(projectId).setIssuer(SA_ISSUER_URL).build();
}
}
@@ -14,15 +14,11 @@
package google.registry.request.auth;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.auth.oauth2.TokenVerifier;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryEnvironment;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.request.auth.AuthModule.IAP;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
@@ -37,41 +33,22 @@ import javax.servlet.http.HttpServletRequest;
* @see <a href="https://cloud.google.com/iap/docs/signed-headers-howto">the documentation on GCP
* IAP's signed headers for more information.</a>
*/
public class IapHeaderAuthenticationMechanism implements AuthenticationMechanism {
public class IapHeaderAuthenticationMechanism extends IdTokenAuthenticationBase {
private static final String ID_TOKEN_HEADER_NAME = "X-Goog-IAP-JWT-Assertion";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
// A workaround that allows "use" of the IAP-based authenticator when running local testing, i.e.
// the RegistryTestServer
private static Optional<User> userForTesting = Optional.empty();
private final TokenVerifier tokenVerifier;
@Inject
public IapHeaderAuthenticationMechanism(TokenVerifier tokenVerifier) {
this.tokenVerifier = tokenVerifier;
public IapHeaderAuthenticationMechanism(@IAP TokenVerifier tokenVerifier) {
super(tokenVerifier);
}
@Override
public AuthResult authenticate(HttpServletRequest request) {
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
&& userForTesting.isPresent()) {
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userForTesting.get()));
}
String rawIdToken = request.getHeader(ID_TOKEN_HEADER_NAME);
if (rawIdToken == null) {
return AuthResult.NOT_AUTHENTICATED;
}
JsonWebSignature token;
try {
token = tokenVerifier.verify(rawIdToken);
} catch (TokenVerifier.VerificationException e) {
logger.atInfo().withCause(e).log("Error when verifying access token");
return AuthResult.NOT_AUTHENTICATED;
}
String emailAddress = (String) token.getPayload().get("email");
String rawTokenFromRequest(HttpServletRequest request) {
return request.getHeader(ID_TOKEN_HEADER_NAME);
}
@Override
AuthResult authResultFromEmail(String emailAddress) {
Optional<User> maybeUser = UserDao.loadUser(emailAddress);
if (!maybeUser.isPresent()) {
logger.atInfo().log("No user found for email address %s", emailAddress);
@@ -80,8 +57,4 @@ public class IapHeaderAuthenticationMechanism implements AuthenticationMechanism
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get()));
}
@VisibleForTesting
public static void setUserAuthInfoForTestServer(@Nullable User user) {
userForTesting = Optional.ofNullable(user);
}
}
@@ -0,0 +1,70 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.request.auth;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.auth.oauth2.TokenVerifier;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryEnvironment;
import google.registry.model.console.User;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
public abstract class IdTokenAuthenticationBase implements AuthenticationMechanism {
public static final FluentLogger logger = FluentLogger.forEnclosingClass();
// A workaround that allows "use" of the IAP-based authenticator when running local testing, i.e.
// the RegistryTestServer
private static Optional<User> userForTesting = Optional.empty();
private final TokenVerifier tokenVerifier;
public IdTokenAuthenticationBase(TokenVerifier tokenVerifier) {
this.tokenVerifier = tokenVerifier;
}
abstract String rawTokenFromRequest(HttpServletRequest request);
abstract AuthResult authResultFromEmail(String email);
@Override
public AuthResult authenticate(HttpServletRequest request) {
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
&& userForTesting.isPresent()) {
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userForTesting.get()));
}
String rawIdToken = rawTokenFromRequest(request);
if (rawIdToken == null) {
return AuthResult.NOT_AUTHENTICATED;
}
JsonWebSignature token;
try {
token = tokenVerifier.verify(rawIdToken);
} catch (Exception e) {
logger.atInfo().withCause(e).log("Error when verifying access token");
return AuthResult.NOT_AUTHENTICATED;
}
String emailAddress = (String) token.getPayload().get("email");
return authResultFromEmail(emailAddress);
}
@VisibleForTesting
public static void setUserAuthInfoForTestServer(@Nullable User user) {
userForTesting = Optional.ofNullable(user);
}
}
@@ -0,0 +1,62 @@
// 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.request.auth;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static google.registry.request.auth.AuthLevel.APP;
import com.google.auth.oauth2.TokenVerifier;
import google.registry.config.RegistryConfig.Config;
import google.registry.request.auth.AuthModule.ServiceAccount;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
/**
* A way to authenticate HTTP requests signed by Service Account
*
* <p>Currently used by cloud scheduler service account
*/
public class ServiceAccountAuthenticationMechanism extends IdTokenAuthenticationBase {
private final String cloudSchedulerEmailPrefix;
private static final String BEARER_PREFIX = "Bearer ";
@Inject
public ServiceAccountAuthenticationMechanism(
@ServiceAccount TokenVerifier tokenVerifier,
@Config("cloudSchedulerServiceAccountEmail") String cloudSchedulerEmailPrefix) {
super(tokenVerifier);
this.cloudSchedulerEmailPrefix = cloudSchedulerEmailPrefix;
}
@Override
String rawTokenFromRequest(HttpServletRequest request) {
String rawToken = request.getHeader(AUTHORIZATION);
if (rawToken != null && rawToken.startsWith(BEARER_PREFIX)) {
return rawToken.substring(BEARER_PREFIX.length());
}
return null;
}
@Override
AuthResult authResultFromEmail(String emailAddress) {
if (emailAddress.equals(cloudSchedulerEmailPrefix)) {
return AuthResult.create(APP);
} else {
return AuthResult.NOT_AUTHENTICATED;
}
}
}
@@ -26,7 +26,6 @@ import com.google.common.util.concurrent.UncheckedExecutionException;
import google.registry.model.server.Lock;
import google.registry.util.AppEngineTimeLimiter;
import google.registry.util.Clock;
import google.registry.util.RequestStatusChecker;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@@ -48,12 +47,10 @@ public class LockHandlerImpl implements LockHandler {
/** Fudge factor to make sure we kill threads before a lock actually expires. */
private static final Duration LOCK_TIMEOUT_FUDGE = Duration.standardSeconds(5);
private final RequestStatusChecker requestStatusChecker;
private final Clock clock;
@Inject
public LockHandlerImpl(RequestStatusChecker requestStatusChecker, Clock clock) {
this.requestStatusChecker = requestStatusChecker;
public LockHandlerImpl(Clock clock) {
this.clock = clock;
}
@@ -114,7 +111,7 @@ public class LockHandlerImpl implements LockHandler {
/** Allows injection of mock Lock in tests. */
@VisibleForTesting
Optional<Lock> acquire(String lockName, @Nullable String tld, Duration leaseLength) {
return Lock.acquire(lockName, tld, leaseLength, requestStatusChecker, true);
return Lock.acquire(lockName, tld, leaseLength);
}
private interface LockAcquirer {
@@ -49,7 +49,6 @@ 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
@@ -66,7 +66,12 @@ public final class LordnTaskUtils {
}
/** Returns the corresponding CSV LORDN line for a sunrise domain. */
public static String getCsvLineForSunriseDomain(Domain domain, DateTime transactionTime) {
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(),
@@ -77,7 +82,12 @@ public final class LordnTaskUtils {
}
/** Returns the corresponding CSV LORDN line for a claims domain. */
public static String getCsvLineForClaimsDomain(Domain domain, DateTime transactionTime) {
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(),
@@ -100,16 +110,8 @@ public final class LordnTaskUtils {
private LordnTaskUtils() {}
public enum LordnPhase {
SUNRISE(QUEUE_SUNRISE),
CLAIMS(QUEUE_CLAIMS),
NONE(null);
final String queue;
LordnPhase(String queue) {
this.queue = queue;
}
SUNRISE,
CLAIMS,
NONE
}
}
@@ -19,9 +19,13 @@ 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;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
import static google.registry.tmch.LordnTaskUtils.COLUMNS_CLAIMS;
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;
@@ -33,6 +37,7 @@ 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;
@@ -41,7 +46,9 @@ 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;
import google.registry.request.Action.Service;
import google.registry.request.Parameter;
@@ -49,8 +56,8 @@ import google.registry.request.RequestParameters;
import google.registry.request.UrlConnectionService;
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;
@@ -59,6 +66,7 @@ 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;
@@ -81,9 +89,11 @@ import org.joda.time.Duration;
public final class NordnUploadAction implements Runnable {
static final String PATH = "/_dr/task/nordnUpload";
static final String LORDN_PHASE_PARAM = "lordn-phase";
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 QUEUE_BATCH_SIZE = 1000;
private static final int BATCH_SIZE = 1000;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Duration LEASE_PERIOD = Duration.standardHours(1);
@@ -100,31 +110,102 @@ public final class NordnUploadAction implements Runnable {
@Inject LordnRequestInitializer lordnRequestInitializer;
@Inject UrlConnectionService urlConnectionService;
@Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl;
@Inject @Parameter(LORDN_PHASE_PARAM) String phase;
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
@Inject
@Config("tmchMarksdbUrl")
String tmchMarksdbUrl;
@Inject
@Parameter(LORDN_PHASE_PARAM)
String phase;
@Inject
@Parameter(RequestParameters.PARAM_TLD)
String tld;
@Inject
@Parameter(PULL_QUEUE_PARAM)
Optional<Boolean> usePullQueue;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject NordnUploadAction() {}
@Inject
NordnUploadAction() {}
/**
* These LORDN parameter names correspond to the relative paths in LORDN URLs and cannot be
* changed on our end.
*/
private static final String PARAM_LORDN_PHASE_SUNRISE = "sunrise";
private static final String PARAM_LORDN_PHASE_SUNRISE =
Ascii.toLowerCase(LordnPhase.SUNRISE.toString());
private static final String PARAM_LORDN_PHASE_CLAIMS = "claims";
private static final String PARAM_LORDN_PHASE_CLAIMS =
Ascii.toLowerCase(LordnPhase.CLAIMS.toString());
/** How long to wait before attempting to verify an upload by fetching the log. */
private static final Duration VERIFY_DELAY = Duration.standardMinutes(30);
@Override
public void run() {
try {
processLordnTasks();
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
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());
});
}
}
@@ -158,7 +239,7 @@ public final class NordnUploadAction implements Runnable {
queue.leaseTasks(
LeaseOptions.Builder.withTag(tld)
.leasePeriod(LEASE_PERIOD.getMillis(), TimeUnit.MILLISECONDS)
.countLimit(QUEUE_BATCH_SIZE)),
.countLimit(BATCH_SIZE)),
TransientFailureException.class,
DeadlineExceededException.class);
if (tasks.isEmpty()) {
@@ -171,7 +252,7 @@ public final class NordnUploadAction implements Runnable {
private void processLordnTasks() throws IOException, GeneralSecurityException {
checkArgument(
phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS),
"Invalid phase specified to Nordn servlet: %s.",
"Invalid phase specified to NordnUploadAction: %s.",
phase);
DateTime now = clock.nowUtc();
Queue queue =
@@ -184,12 +265,12 @@ public final class NordnUploadAction implements Runnable {
// 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 mulitple uploads. This is probably OK; all that we really cannot have is a missing
// 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, QUEUE_BATCH_SIZE)
Lists.partition(tasks, BATCH_SIZE)
.forEach(
batch ->
retrier.callWithRetry(
@@ -252,7 +333,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)
@@ -22,6 +22,7 @@ import static google.registry.util.ResourceUtils.readResourceUtf8;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
import google.registry.model.CacheUtils;
@@ -55,6 +56,7 @@ public final class TmchCertificateAuthority {
private static final String ROOT_CRT_PILOT_FILE = "icann-tmch-pilot.crt";
private static final String CRL_FILE = "icann-tmch.crl";
private static final String CRL_PILOT_FILE = "icann-tmch-pilot.crl";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final TmchCaMode tmchCaMode;
private final Clock clock;
@@ -142,8 +144,14 @@ public final class TmchCertificateAuthority {
* @see X509Utils#verifyCrl
*/
public void updateCrl(String asciiCrl, String url) throws GeneralSecurityException {
X509CRL crl = X509Utils.loadCrl(asciiCrl);
X509Utils.verifyCrl(getAndValidateRoot(), getCrl(), crl, clock.nowUtc().toDate());
X509CRL newCrl = X509Utils.loadCrl(asciiCrl);
X509CRL oldCrl = null;
try {
oldCrl = getCrl();
} catch (Exception e) {
logger.atWarning().withCause(e).log("Old CRL is invalid, ignored during CRL update.");
}
X509Utils.verifyCrl(getAndValidateRoot(), oldCrl, newCrl, clock.nowUtc().toDate());
TmchCrl.set(asciiCrl, url);
}
@@ -16,6 +16,7 @@ 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;
@@ -25,6 +26,7 @@ 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;
@@ -64,4 +66,10 @@ 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);
}
}
@@ -0,0 +1,85 @@
// 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.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.beust.jcommander.Parameter;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.tools.params.KeyValueMapParameter.StringToRegistrarRoleMap;
import java.util.Optional;
import javax.annotation.Nullable;
/** Shared base class for commands that create or modify a {@link User}. */
public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
@Nullable
@Parameter(names = "--email", description = "Email address of the user", required = true)
String email;
@Nullable
@Parameter(
names = "--admin",
description = "Whether or not the user in question is an admin",
arity = 1)
private Boolean isAdmin;
@Nullable
@Parameter(
names = "--global_role",
description = "Global role, e.g. SUPPORT_LEAD, to apply to the user")
private GlobalRole globalRole;
@Nullable
@Parameter(
names = "--registrar_roles",
converter = StringToRegistrarRoleMap.class,
validateWith = StringToRegistrarRoleMap.class,
description =
"Comma-delimited mapping of registrar name to role that the user has on that registrar")
private ImmutableMap<String, RegistrarRole> registrarRolesMap;
@Nullable
abstract User getExistingUser(String email);
@Override
protected final String execute() throws Exception {
checkArgumentNotNull(email, "Email must be provided");
tm().transact(this::executeInTransaction);
return String.format("Saved user with email %s", email);
}
private void executeInTransaction() {
User user = getExistingUser(email);
UserRoles.Builder userRolesBuilder =
(user == null) ? new UserRoles.Builder() : user.getUserRoles().asBuilder();
Optional.ofNullable(globalRole).ifPresent(userRolesBuilder::setGlobalRole);
Optional.ofNullable(registrarRolesMap).ifPresent(userRolesBuilder::setRegistrarRoles);
Optional.ofNullable(isAdmin).ifPresent(userRolesBuilder::setIsAdmin);
User.Builder builder =
(user == null) ? new User.Builder().setEmailAddress(email) : user.asBuilder();
builder.setUserRoles(userRolesBuilder.build());
User newUser = builder.build();
UserDao.saveUser(newUser);
}
}
@@ -0,0 +1,35 @@
// 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.tools;
import static com.google.common.base.Preconditions.checkArgument;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import javax.annotation.Nullable;
/** Command to create a new User. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class CreateUserCommand extends CreateOrUpdateUserCommand {
@Nullable
@Override
User getExistingUser(String email) {
checkArgument(
!UserDao.loadUser(email).isPresent(), "A user with email %s already exists", email);
return null;
}
}
@@ -0,0 +1,53 @@
// 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.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import java.util.Optional;
import javax.annotation.Nullable;
/** Deletes a {@link User}. */
@Parameters(separators = " =", commandDescription = "Delete a user account")
public class DeleteUserCommand extends ConfirmingCommand {
@Nullable
@Parameter(names = "--email", description = "Email address of the user", required = true)
String email;
@Override
protected String prompt() {
checkArgumentNotNull(email, "Email must be provided");
checkArgumentPresent(UserDao.loadUser(email), "Email does not correspond to a valid user");
return String.format("Delete user with email %s?", email);
}
@Override
protected String execute() throws Exception {
tm().transact(
() -> {
Optional<User> optionalUser = UserDao.loadUser(email);
checkArgumentPresent(optionalUser, "Email no longer corresponds to a valid user");
tm().delete(optionalUser.get());
});
return String.format("Deleted user with email %s", email);
}
}
@@ -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),
@@ -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()));
}
}
@@ -94,10 +94,10 @@ final class GenerateLordnCommand implements Command {
Domain domain) {
String status = " ";
if (domain.getLaunchNotice() == null && domain.getSmdId() != null) {
sunriseCsv.add(LordnTaskUtils.getCsvLineForSunriseDomain(domain, domain.getCreationTime()));
sunriseCsv.add(LordnTaskUtils.getCsvLineForSunriseDomain(domain));
status = "S";
} else if (domain.getLaunchNotice() != null || domain.getSmdId() != null) {
claimsCsv.add(LordnTaskUtils.getCsvLineForClaimsDomain(domain, domain.getCreationTime()));
claimsCsv.add(LordnTaskUtils.getCsvLineForClaimsDomain(domain));
status = "C";
}
System.out.printf("%s[%s] ", domain.getDomainName(), status);
@@ -46,6 +46,7 @@ public final class RegistryTool {
.put("create_registrar_groups", CreateRegistrarGroupsCommand.class)
.put("create_reserved_list", CreateReservedListCommand.class)
.put("create_tld", CreateTldCommand.class)
.put("create_user", CreateUserCommand.class)
.put("curl", CurlCommand.class)
.put("delete_allocation_tokens", DeleteAllocationTokensCommand.class)
.put("delete_domain", DeleteDomainCommand.class)
@@ -53,6 +54,7 @@ public final class RegistryTool {
.put("delete_premium_list", DeletePremiumListCommand.class)
.put("delete_reserved_list", DeleteReservedListCommand.class)
.put("delete_tld", DeleteTldCommand.class)
.put("delete_user", DeleteUserCommand.class)
.put("encrypt_escrow_deposit", EncryptEscrowDepositCommand.class)
.put("enqueue_poll_message", EnqueuePollMessageCommand.class)
.put("execute_epp", ExecuteEppCommand.class)
@@ -110,6 +112,7 @@ public final class RegistryTool {
.put("update_reserved_list", UpdateReservedListCommand.class)
.put("update_server_locks", UpdateServerLocksCommand.class)
.put("update_tld", UpdateTldCommand.class)
.put("update_user", UpdateUserCommand.class)
.put("upload_claims_list", UploadClaimsListCommand.class)
.put("validate_escrow_deposit", ValidateEscrowDepositCommand.class)
.put("validate_login_credentials", ValidateLoginCredentialsCommand.class)
@@ -0,0 +1,33 @@
// 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.tools;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import javax.annotation.Nullable;
/** Updates a user, assuming that the user in question already exists. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class UpdateUserCommand extends CreateOrUpdateUserCommand {
@Nullable
@Override
User getExistingUser(String email) {
return checkArgumentPresent(UserDao.loadUser(email), "User %s not found", email);
}
}
@@ -17,6 +17,7 @@ package google.registry.tools.params;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.RegistrarRole;
import java.util.Map;
import org.joda.money.CurrencyUnit;
@@ -107,4 +108,18 @@ public abstract class KeyValueMapParameter<K, V>
return value;
}
}
/** Combined converter/validator class for maps of registrar names to registrar roles. */
public static class StringToRegistrarRoleMap extends KeyValueMapParameter<String, RegistrarRole> {
@Override
protected String parseKey(String rawKey) {
return rawKey;
}
@Override
protected RegistrarRole parseValue(String rawValue) {
return RegistrarRole.valueOf(rawValue);
}
}
}
@@ -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(
@@ -23,12 +23,6 @@
"helpText": "The exclusive upper bound of the operation window, in ISO 8601 format.",
"is_optional": false
},
{
"name": "shard",
"label": "The exclusive upper bound of the operation window.",
"helpText": "The exclusive upper bound of the operation window, in ISO 8601 format.",
"is_optional": true
},
{
"name": "isDryRun",
"label": "Whether this job is a dry run.",
@@ -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);
@@ -20,6 +20,7 @@ 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;
@@ -36,7 +37,6 @@ 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;
@@ -155,6 +155,8 @@ 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;
@@ -182,6 +184,7 @@ 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;
import google.registry.tmch.TmchTestData;
@@ -192,9 +195,12 @@ 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;
@@ -224,6 +230,11 @@ 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");
@@ -390,7 +401,10 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.that(reloadResourceByForeignKey())
.hasSmdId(null)
.and()
.hasLaunchNotice(null);
.hasLaunchNotice(null)
.and()
.hasLordnPhase(LordnPhase.NONE);
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
}
@@ -400,14 +414,20 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.hasSmdId(SMD_ID)
.and()
.hasLaunchNotice(null);
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));
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);
}
}
private void assertClaimsLordn() throws Exception {
@@ -421,13 +441,19 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"tmch",
DateTime.parse("2010-08-16T09:00:00.0Z"),
DateTime.parse("2009-08-16T09:00:00.0Z")));
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);
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);
}
}
private void doSuccessfulTest(
@@ -677,7 +703,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
persistContactsAndHosts();
doSuccessfulTest(
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
"tld",
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "26.00"));
}
@Test
@@ -685,7 +713,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
persistContactsAndHosts();
doSuccessfulTest(
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
"tld",
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "26.00"));
}
@Test
@@ -693,7 +723,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
persistContactsAndHosts();
doSuccessfulTest(
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
"tld",
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "26.00"));
}
@Test
@@ -701,7 +733,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
persistContactsAndHosts();
doSuccessfulTest(
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
"tld",
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "26.00"));
}
@Test
@@ -709,7 +743,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
persistContactsAndHosts();
doSuccessfulTest(
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
"tld",
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "26.00"));
}
@Test
@@ -717,7 +753,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
persistContactsAndHosts();
doSuccessfulTest(
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
"tld",
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "26.00"));
}
@Test
@@ -939,8 +977,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_claimsNotice() throws Exception {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_claimsNotice(boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
clock.setTo(DateTime.parse("2009-08-16T09:00:00.0Z"));
setEppInput("domain_create_claim_notice.xml");
persistContactsAndHosts();
@@ -950,8 +992,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertClaimsLordn();
}
@Test
void testSuccess_claimsNoticeInQuietPeriod() throws Exception {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_claimsNoticeInQuietPeriod(boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
allocationToken =
persistResource(
new AllocationToken.Builder()
@@ -1077,6 +1123,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v06() throws Exception {
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.setCreateBillingCost(Money.of(USD, 8))
.build());
// Expects fee of $26
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
persistContactsAndHosts();
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
runFlowAssertResponse(
loadFile(
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "12.00")));
}
@Test
void testFailure_wrongFeeAmountTooLow_defaultToken_v06() throws Exception {
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.setCreateBillingCost(Money.of(USD, 100))
.build());
// Expects fee of $26
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
persistContactsAndHosts();
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_wrongFeeAmount_v11() {
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
@@ -1087,6 +1184,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v11() throws Exception {
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.setCreateBillingCost(Money.of(USD, 8))
.build());
// Expects fee of $26
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
persistContactsAndHosts();
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
runFlowAssertResponse(
loadFile(
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "12.00")));
}
@Test
void testFailure_wrongFeeAmountTooLow_defaultToken_v11() throws Exception {
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.setCreateBillingCost(Money.of(USD, 100))
.build());
// Expects fee of $26
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
persistContactsAndHosts();
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_wrongFeeAmount_v12() {
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
@@ -1097,6 +1245,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v12() throws Exception {
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.setCreateBillingCost(Money.of(USD, 8))
.build());
// Expects fee of $26
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
persistContactsAndHosts();
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
runFlowAssertResponse(
loadFile(
"domain_create_response_fee.xml",
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "12.00")));
}
@Test
void testFailure_wrongFeeAmountTooLow_defaultToken_v12() throws Exception {
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setDiscountFraction(0.5)
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.setCreateBillingCost(Money.of(USD, 100))
.build());
// Expects fee of $26
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
persistContactsAndHosts();
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_wrongCurrency_v06() {
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "EUR"));
@@ -1220,8 +1419,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@Test
void testSuccess_anchorTenant_withClaims() throws Exception {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_anchorTenant_withClaims(boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
persistResource(
new AllocationToken.Builder()
.setDomainName("example-one.tld")
@@ -1289,8 +1492,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT));
}
@Test
void testSuccess_anchorTenantInSunrise_withSignedMark() throws Exception {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_anchorTenantInSunrise_withSignedMark(boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
allocationToken =
persistResource(
new AllocationToken.Builder()
@@ -1890,9 +2097,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertSuccessfulCreate("tld", ImmutableSet.of(RESERVED));
}
@Test
void testSuccess_reservedNameCollisionDomain_inSunrise_setsServerHoldAndPollMessage()
throws Exception {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_reservedNameCollisionDomain_inSunrise_setsServerHoldAndPollMessage(
boolean usePullQueue) throws Exception {
if (!usePullQueue) {
useNordnSql();
}
persistResource(
Registry.get("tld")
.asBuilder()
@@ -2435,8 +2646,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"tld", "domain_create_response.xml", SUPERUSER, ImmutableMap.of("DOMAIN", "example.tld"));
}
@Test
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark() throws Exception {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark(boolean usePullQueue)
throws Exception {
if (!usePullQueue) {
useNordnSql();
}
createTld("tld", START_DATE_SUNRISE);
clock.setTo(SMD_VALID_TIME);
setEppInput(
@@ -2458,8 +2674,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
}
/** Test that missing type= argument on launch create works in start-date sunrise. */
@Test
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark_noType() throws Exception {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark_noType(boolean usePullQueue)
throws Exception {
if (!usePullQueue) {
useNordnSql();
}
createTld("tld", START_DATE_SUNRISE);
clock.setTo(SMD_VALID_TIME);
setEppInput(
@@ -3512,4 +3733,97 @@ 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()));
}
}
@@ -72,11 +72,7 @@ public class UserTest extends EntityTestCase {
assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setUserRoles(null)))
.hasMessageThat()
.isEqualTo("User roles cannot be null");
assertThat(assertThrows(IllegalArgumentException.class, builder::build))
.hasMessageThat()
.isEqualTo("Gaia ID cannot be null");
builder.setGaiaId("gaiaId");
assertThat(assertThrows(IllegalArgumentException.class, builder::build))
.hasMessageThat()
.isEqualTo("Email address cannot be null");
@@ -221,7 +221,6 @@ public class DomainTest {
"TheRegistrar",
oneTimeBillKey))
.setAutorenewEndTime(Optional.of(fakeClock.nowUtc().plusYears(2)))
.setDnsRefreshRequestTime(Optional.of(fakeClock.nowUtc()))
.build()));
}
@@ -41,8 +41,6 @@ import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.util.SerializeUtils;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -97,7 +95,6 @@ public class DomainHistoryTest extends EntityTestCase {
.asBuilder()
.setNameservers(host.createVKey())
.setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, new byte[] {0, 1, 2})))
.setDnsRefreshRequestTime(Optional.of(DateTime.parse("2020-03-09T16:40:00Z")))
.build();
insertInDb(domain);
return domain;
@@ -18,17 +18,14 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.server.Lock.LockState.FREE;
import static google.registry.model.server.Lock.LockState.IN_USE;
import static google.registry.model.server.Lock.LockState.OWNER_DIED;
import static google.registry.model.server.Lock.LockState.TIMED_OUT;
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 static org.mockito.Mockito.when;
import google.registry.model.EntityTestCase;
import google.registry.model.server.Lock.LockState;
import google.registry.util.RequestStatusChecker;
import java.util.Optional;
import org.joda.time.Duration;
import org.junit.jupiter.api.AfterEach;
@@ -41,7 +38,6 @@ public class LockTest extends EntityTestCase {
private static final String RESOURCE_NAME = "foo";
private static final Duration ONE_DAY = Duration.standardDays(1);
private static final Duration TWO_MILLIS = Duration.millis(2);
private static final RequestStatusChecker requestStatusChecker = mock(RequestStatusChecker.class);
private LockMetrics origLockMetrics;
@@ -52,7 +48,7 @@ public class LockTest extends EntityTestCase {
private static Optional<Lock> acquire(
String tld, Duration leaseLength, LockState expectedLockState) {
Lock.lockMetrics = mock(LockMetrics.class);
Optional<Lock> lock = Lock.acquire(RESOURCE_NAME, tld, leaseLength, requestStatusChecker, true);
Optional<Lock> lock = Lock.acquire(RESOURCE_NAME, tld, leaseLength);
verify(Lock.lockMetrics).recordAcquire(RESOURCE_NAME, tld, expectedLockState);
verifyNoMoreInteractions(Lock.lockMetrics);
Lock.lockMetrics = null;
@@ -72,8 +68,6 @@ public class LockTest extends EntityTestCase {
void beforeEach() {
origLockMetrics = Lock.lockMetrics;
Lock.lockMetrics = null;
when(requestStatusChecker.getLogId()).thenReturn("current-request-id");
when(requestStatusChecker.isRunning("current-request-id")).thenReturn(true);
}
@AfterEach
@@ -111,9 +105,6 @@ public class LockTest extends EntityTestCase {
assertThat(acquire("", ONE_DAY, FREE)).isPresent();
// We can't get it again while request is active
assertThat(acquire("", ONE_DAY, IN_USE)).isEmpty();
// But if request is finished, we can get it.
when(requestStatusChecker.isRunning("current-request-id")).thenReturn(false);
assertThat(acquire("", ONE_DAY, OWNER_DIED)).isPresent();
}
@Test
@@ -134,9 +125,7 @@ public class LockTest extends EntityTestCase {
@Test
void testFailure_emptyResourceName() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> Lock.acquire("", "", TWO_MILLIS, requestStatusChecker, true));
assertThrows(IllegalArgumentException.class, () -> Lock.acquire("", "", TWO_MILLIS));
assertThat(thrown).hasMessageThat().contains("resourceName cannot be null or empty");
}
}
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.beam.BeamActionTestBase;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
@@ -30,7 +31,6 @@ import google.registry.reporting.ReportingModule;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.FakeClock;
import google.registry.util.CloudTasksUtils;
import java.io.IOException;
import org.joda.time.Duration;
import org.joda.time.YearMonth;
@@ -31,10 +31,10 @@ import com.google.api.services.dataflow.Dataflow.Projects.Locations.Jobs.Get;
import com.google.api.services.dataflow.model.Job;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.FakeResponse;
import google.registry.util.CloudTasksUtils;
import java.io.IOException;
import org.joda.time.YearMonth;
import org.junit.jupiter.api.BeforeEach;
@@ -21,12 +21,12 @@ import static org.mockito.Mockito.when;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.beam.BeamActionTestBase;
import google.registry.reporting.ReportingModule;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.FakeClock;
import google.registry.util.CloudTasksUtils;
import java.io.IOException;
import org.joda.time.DateTime;
import org.joda.time.Duration;
@@ -0,0 +1,75 @@
// 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.request.auth;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.api.client.json.webtoken.JsonWebSignature.Header;
import com.google.auth.oauth2.TokenVerifier;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class ServiceAccountAuthenticationMechanismTest {
@Mock private TokenVerifier tokenVerifier;
@Mock private HttpServletRequest request;
private JsonWebSignature token;
private ServiceAccountAuthenticationMechanism serviceAccountAuthenticationMechanism;
@BeforeEach
void beforeEach() throws Exception {
serviceAccountAuthenticationMechanism =
new ServiceAccountAuthenticationMechanism(tokenVerifier, "sa-prefix@email.com");
when(request.getHeader(AUTHORIZATION)).thenReturn("Bearer jwtValue");
Payload payload = new Payload();
payload.setEmail("sa-prefix@email.com");
token = new JsonWebSignature(new Header(), payload, new byte[0], new byte[0]);
when(tokenVerifier.verify("jwtValue")).thenReturn(token);
}
@Test
void testSuccess_authenticates() throws Exception {
AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request);
assertThat(authResult.isAuthenticated()).isTrue();
assertThat(authResult.authLevel()).isEqualTo(AuthLevel.APP);
}
@Test
void testFails_authenticateWrongEmail() throws Exception {
token.getPayload().set("email", "not-service-account-email@email.com");
AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request);
assertThat(authResult.isAuthenticated()).isFalse();
}
@Test
void testFails_authenticateWrongHeader() throws Exception {
when(request.getHeader(AUTHORIZATION)).thenReturn("BEARER asd");
AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request);
assertThat(authResult.isAuthenticated()).isFalse();
}
}
@@ -23,7 +23,6 @@ import static org.mockito.Mockito.verify;
import google.registry.model.server.Lock;
import google.registry.testing.FakeClock;
import google.registry.testing.UserServiceExtension;
import google.registry.util.RequestStatusCheckerImpl;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeoutException;
@@ -134,7 +133,7 @@ final class LockHandlerImplTest {
}
private LockHandler createTestLockHandler(@Nullable Lock acquiredLock) {
return new LockHandlerImpl(new RequestStatusCheckerImpl(), clock) {
return new LockHandlerImpl(clock) {
private static final long serialVersionUID = 0L;
@Override
@@ -43,8 +43,8 @@ import com.google.protobuf.Timestamp;
import com.google.protobuf.util.Timestamps;
import dagger.Module;
import dagger.Provides;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.ImmutableObject;
import google.registry.util.CloudTasksUtils;
import google.registry.util.Retrier;
import java.io.Serializable;
import java.net.URI;
@@ -312,6 +312,7 @@ public final class DatabaseHelper {
return persistResource(domain.asBuilder().setDeletionTime(deletionTime).build());
}
// TODO: delete after pull queue migration.
/** Persists a domain and enqueues a LORDN task of the appropriate type for it. */
public static Domain persistDomainAndEnqueueLordn(final Domain domain) {
final Domain persistedDomain = persistResource(domain);
@@ -27,6 +27,7 @@ import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.testing.TruthChainer.And;
import google.registry.tmch.LordnTaskUtils.LordnPhase;
import java.util.Set;
import org.joda.time.DateTime;
@@ -41,7 +42,7 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
}
public And<DomainSubject> hasDomainName(String domainName) {
return hasValue(domainName, actual.getDomainName(), "has domainName");
return hasValue(domainName, actual.getDomainName(), "domainName");
}
public And<DomainSubject> hasExactlyDsData(DomainDsData... dsData) {
@@ -49,15 +50,19 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
}
public And<DomainSubject> hasExactlyDsData(Set<DomainDsData> dsData) {
return hasValue(dsData, actual.getDsData(), "has dsData");
return hasValue(dsData, actual.getDsData(), "dsData");
}
public And<DomainSubject> hasNumDsData(int num) {
return hasValue(num, actual.getDsData().size(), "has num dsData");
return hasValue(num, actual.getDsData().size(), "dsData.size()");
}
public And<DomainSubject> hasLaunchNotice(LaunchNotice launchNotice) {
return hasValue(launchNotice, actual.getLaunchNotice(), "has launchNotice");
return hasValue(launchNotice, actual.getLaunchNotice(), "launchNotice");
}
public And<DomainSubject> hasLordnPhase(LordnPhase lordnPhase) {
return hasValue(lordnPhase, actual.getLordnPhase(), "lordnPhase");
}
public And<DomainSubject> hasAuthInfoPwd(String pw) {
@@ -67,7 +72,7 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
public And<DomainSubject> hasCurrentSponsorRegistrarId(String registrarId) {
return hasValue(
registrarId, actual.getCurrentSponsorRegistrarId(), "has currentSponsorRegistrarId");
registrarId, actual.getCurrentSponsorRegistrarId(), "currentSponsorRegistrarId");
}
public And<DomainSubject> hasRegistrationExpirationTime(DateTime expiration) {
@@ -14,13 +14,17 @@
package google.registry.tmch;
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static com.google.common.net.HttpHeaders.LOCATION;
import static com.google.common.net.MediaType.FORM_DATA;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ForeignKeyUtils.load;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.newDomain;
import static google.registry.testing.DatabaseHelper.persistDomainAndEnqueueLordn;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -34,6 +38,7 @@ import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.google.appengine.api.taskqueue.LeaseOptions;
@@ -45,19 +50,21 @@ import com.google.appengine.api.taskqueue.TransientFailureException;
import com.google.apphosting.api.DeadlineExceededException;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.domain.Domain;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.tld.Registry;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.RequestParameters;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeSleeper;
import google.registry.testing.FakeUrlConnectionService;
import google.registry.testing.TaskQueueExtension;
import google.registry.util.CloudTasksUtils;
import google.registry.tmch.LordnTaskUtils.LordnPhase;
import google.registry.util.Retrier;
import google.registry.util.UrlConnectionException;
import java.io.ByteArrayInputStream;
@@ -67,25 +74,32 @@ import java.net.URL;
import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
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;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
/** Unit tests for {@link NordnUploadAction}. */
class NordnUploadActionTest {
private static final String CLAIMS_CSV =
"1,2010-05-01T10:11:12.000Z,1\n"
"1,2010-05-04T10:11:12.000Z,2\n"
+ "roid,domain-name,notice-id,registrar-id,registration-datetime,ack-datetime,"
+ "application-datetime\n"
+ "2-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-01T10:11:12.000Z,"
+ "1969-12-31T23:00:00.000Z\n";
+ "6-TLD,claims-landrush2.tld,landrush2tcn,88888,2010-05-03T10:11:12.000Z,"
+ "2010-05-03T08:11:12.000Z\n"
+ "8-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-04T10:11:12.000Z,"
+ "2010-05-04T09:11:12.000Z\n";
private static final String SUNRISE_CSV =
"1,2010-05-01T10:11:12.000Z,1\n"
"1,2010-05-04T10:11:12.000Z,2\n"
+ "roid,domain-name,SMD-id,registrar-id,registration-datetime,application-datetime\n"
+ "2-TLD,sunrise1.tld,my-smdid,99999,2010-05-01T10:11:12.000Z\n";
+ "2-TLD,sunrise2.tld,new-smdid,88888,2010-05-01T10:11:12.000Z\n"
+ "4-TLD,sunrise1.tld,my-smdid,99999,2010-05-02T10:11:12.000Z\n";
private static final String LOCATION_URL = "http://trololol";
@@ -116,8 +130,12 @@ class NordnUploadActionTest {
when(httpUrlConnection.getHeaderField(LOCATION)).thenReturn("http://trololol");
when(httpUrlConnection.getOutputStream()).thenReturn(connectionOutputStream);
persistResource(loadRegistrar("TheRegistrar").asBuilder().setIanaIdentifier(99999L).build());
persistResource(loadRegistrar("NewRegistrar").asBuilder().setIanaIdentifier(88888L).build());
createTld("tld");
persistResource(Registry.get("tld").asBuilder().setLordnUsername("lolcat").build());
persistSunriseModeDomain();
clock.advanceBy(Duration.standardDays(1));
persistClaimsModeDomain();
action.clock = clock;
action.cloudTasksUtils = cloudTasksUtils;
action.urlConnectionService = urlConnectionService;
@@ -127,6 +145,7 @@ class NordnUploadActionTest {
action.tmchMarksdbUrl = "http://127.0.0.1";
action.random = new SecureRandom();
action.retrier = new Retrier(new FakeSleeper(clock), 3);
action.usePullQueue = Optional.empty();
}
@Test
@@ -137,7 +156,7 @@ class NordnUploadActionTest {
makeTaskHandle("task1", "example", "csvLine1", "lordn-sunrise"),
makeTaskHandle("task3", "example", "ending", "lordn-sunrise"));
assertThat(NordnUploadAction.convertTasksToCsv(tasks, clock.nowUtc(), "col1,col2"))
.isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
.isEqualTo("1,2010-05-04T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
}
@Test
@@ -149,13 +168,13 @@ class NordnUploadActionTest {
makeTaskHandle("task3", "example", "ending", "lordn-sunrise"),
makeTaskHandle("task1", "example", "csvLine1", "lordn-sunrise"));
assertThat(NordnUploadAction.convertTasksToCsv(tasks, clock.nowUtc(), "col1,col2"))
.isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
.isEqualTo("1,2010-05-04T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
}
@Test
void test_convertTasksToCsv_doesntFailOnEmptyTasks() {
assertThat(NordnUploadAction.convertTasksToCsv(ImmutableList.of(), clock.nowUtc(), "col1,col2"))
.isEqualTo("1,2010-05-01T10:11:12.000Z,0\ncol1,col2\n");
.isEqualTo("1,2010-05-04T10:11:12.000Z,0\ncol1,col2\n");
}
@Test
@@ -187,122 +206,105 @@ class NordnUploadActionTest {
}
@Test
void testRun_claimsMode_appendsTldAndClaimsToRequestUrl() throws Exception {
persistClaimsModeDomain();
action.run();
assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/LORDN/tld/claims"));
}
@Test
void testRun_sunriseMode_appendsTldAndClaimsToRequestUrl() throws Exception {
persistSunriseModeDomain();
action.run();
assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/LORDN/tld/sunrise"));
}
@Test
void testRun_usesMultipartContentType() throws Exception {
persistClaimsModeDomain();
action.run();
verify(httpUrlConnection)
.setRequestProperty(eq(CONTENT_TYPE), startsWith("multipart/form-data; boundary="));
verify(httpUrlConnection).setRequestMethod("POST");
}
@Test
void testRun_hasPassword_setsAuthorizationHeader() {
persistClaimsModeDomain();
action.run();
verify(httpUrlConnection)
.setRequestProperty(
AUTHORIZATION, "Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
}
@Test
void testRun_noPassword_doesntSendAuthorizationHeader() {
void testSuccess_noPassword_doesntSendAuthorizationHeader() {
action.lordnRequestInitializer = new LordnRequestInitializer(Optional.empty());
persistClaimsModeDomain();
action.run();
verify(httpUrlConnection, times(0)).setRequestProperty(eq(AUTHORIZATION), anyString());
}
@Test
void testRun_claimsMode_payloadMatchesClaimsCsv() {
persistClaimsModeDomain();
void testSuccess_nothingScheduled() {
persistResource(
loadByKey(load(Domain.class, "claims-landrush1.tld", clock.nowUtc()))
.asBuilder()
.setLordnPhase(LordnPhase.NONE)
.build());
persistResource(
loadByKey(load(Domain.class, "claims-landrush2.tld", clock.nowUtc()))
.asBuilder()
.setLordnPhase(LordnPhase.NONE)
.build());
action.run();
assertThat(connectionOutputStream.toString(UTF_8)).contains(CLAIMS_CSV);
verifyNoInteractions(httpUrlConnection);
cloudTasksHelper.assertNoTasksEnqueued(NordnVerifyAction.QUEUE);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_claimsMode(boolean usePullQueue) throws Exception {
testRun("claims", "claims-landrush1.tld", "claims-landrush2.tld", CLAIMS_CSV, usePullQueue);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testSuccess_sunriseMode(boolean usePullQueue) throws Exception {
testRun("sunrise", "sunrise1.tld", "sunrise2.tld", SUNRISE_CSV, usePullQueue);
}
@Test
void testRun_claimsMode_verifyTaskGetsEnqueuedWithClaimsCsv() {
persistClaimsModeDomain();
action.run();
cloudTasksHelper.assertTasksEnqueued(
NordnVerifyAction.QUEUE,
new TaskMatcher()
.url(NordnVerifyAction.PATH)
.param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
.header(CONTENT_TYPE, FORM_DATA.toString()));
}
@Test
void testRun_sunriseMode_payloadMatchesSunriseCsv() {
persistSunriseModeDomain();
action.run();
assertThat(connectionOutputStream.toString(UTF_8)).contains(SUNRISE_CSV);
}
@Test
void test_noResponseContent_stillWorksNormally() throws Exception {
void testSuccess_noResponseContent_stillWorksNormally() throws Exception {
// Returning null only affects logging.
when(httpUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[] {}));
persistSunriseModeDomain();
action.run();
assertThat(connectionOutputStream.toString(UTF_8)).contains(SUNRISE_CSV);
}
@Test
void testRun_sunriseMode_verifyTaskGetsEnqueuedWithSunriseCsv() {
persistSunriseModeDomain();
action.run();
cloudTasksHelper.assertTasksEnqueued(
NordnVerifyAction.QUEUE,
new TaskMatcher()
.url(NordnVerifyAction.PATH)
.param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
.header(CONTENT_TYPE, FORM_DATA.toString()));
testRun("claims", "claims-landrush1.tld", "claims-landrush2.tld", CLAIMS_CSV, false);
}
@Test
void testFailure_nullRegistryUser() {
persistClaimsModeDomain();
persistResource(Registry.get("tld").asBuilder().setLordnUsername(null).build());
VerifyException thrown = assertThrows(VerifyException.class, action::run);
assertThat(thrown).hasMessageThat().contains("lordnUsername is not set for tld.");
}
@Test
void testFetchFailure() throws Exception {
persistClaimsModeDomain();
void testFailure_errorResponseCode() throws Exception {
when(httpUrlConnection.getResponseCode()).thenReturn(SC_INTERNAL_SERVER_ERROR);
assertThrows(UrlConnectionException.class, action::run);
}
private static void persistClaimsModeDomain() {
Domain domain = DatabaseHelper.newDomain("claims-landrush1.tld");
@Test
void testFailure_noLocationHeaderInResponse() throws Exception {
when(httpUrlConnection.getHeaderField(LOCATION)).thenReturn(null);
assertThrows(UrlConnectionException.class, action::run);
}
private void persistClaimsModeDomain() {
persistDomainAndEnqueueLordn(
domain
newDomain("claims-landrush2.tld")
.asBuilder()
.setCreationTimeForTest(clock.nowUtc())
.setCreationRegistrarId("NewRegistrar")
.setLaunchNotice(
LaunchNotice.create(
"landrush1tcn", null, null, domain.getCreationTime().minusHours(1)))
LaunchNotice.create("landrush2tcn", null, null, clock.nowUtc().minusHours(2)))
.setLordnPhase(LordnPhase.CLAIMS)
.build());
clock.advanceBy(Duration.standardDays(1));
persistDomainAndEnqueueLordn(
newDomain("claims-landrush1.tld")
.asBuilder()
.setCreationTimeForTest(clock.nowUtc())
.setLaunchNotice(
LaunchNotice.create("landrush1tcn", null, null, clock.nowUtc().minusHours(1)))
.setLordnPhase(LordnPhase.CLAIMS)
.build());
}
private void persistSunriseModeDomain() {
action.phase = "sunrise";
Domain domain = DatabaseHelper.newDomain("sunrise1.tld");
persistDomainAndEnqueueLordn(domain.asBuilder().setSmdId("my-smdid").build());
persistDomainAndEnqueueLordn(
newDomain("sunrise2.tld")
.asBuilder()
.setCreationTimeForTest(clock.nowUtc())
.setCreationRegistrarId("NewRegistrar")
.setSmdId("new-smdid")
.setLordnPhase(LordnPhase.SUNRISE)
.build());
clock.advanceBy(Duration.standardDays(1));
persistDomainAndEnqueueLordn(
newDomain("sunrise1.tld")
.asBuilder()
.setCreationTimeForTest(clock.nowUtc())
.setSmdId("my-smdid")
.setLordnPhase(LordnPhase.SUNRISE)
.build());
}
private static TaskHandle makeTaskHandle(
@@ -311,4 +313,46 @@ class NordnUploadActionTest {
TaskOptions.Builder.withPayload(payload).method(Method.PULL).tag(tag).taskName(taskName),
queue);
}
private void verifyColumnCleared(String domainName) {
VKey<Domain> domainKey = load(Domain.class, domainName, clock.nowUtc());
Domain domain = loadByKey(domainKey);
assertThat(domain.getLordnPhase()).isEqualTo(LordnPhase.NONE);
}
private void testRun(
String phase, String domain1, String domain2, String csv, boolean usePullQueue)
throws Exception {
action.usePullQueue = Optional.of(usePullQueue);
action.phase = phase;
action.run();
verify(httpUrlConnection)
.setRequestProperty(
AUTHORIZATION, "Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
verify(httpUrlConnection)
.setRequestProperty(eq(CONTENT_TYPE), startsWith("multipart/form-data; boundary="));
verify(httpUrlConnection).setRequestMethod("POST");
assertThat(httpUrlConnection.getURL())
.isEqualTo(new URL("http://127.0.0.1/LORDN/tld/" + phase));
assertThat(connectionOutputStream.toString(UTF_8)).contains(csv);
if (!usePullQueue) {
verifyColumnCleared(domain1);
verifyColumnCleared(domain2);
} else {
assertThat(
getQueue("lordn-" + phase)
.leaseTasks(
LeaseOptions.Builder.withTag("tld")
.leasePeriod(1, TimeUnit.HOURS)
.countLimit(100)))
.isEmpty();
}
cloudTasksHelper.assertTasksEnqueued(
NordnVerifyAction.QUEUE,
new TaskMatcher()
.url(NordnVerifyAction.PATH)
.param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
.param(RequestParameters.PARAM_TLD, "tld")
.header(CONTENT_TYPE, FORM_DATA.toString()));
}
}
@@ -0,0 +1,81 @@
// 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.tools;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.testing.DatabaseHelper;
import org.junit.jupiter.api.Test;
/** Tests for {@link CreateUserCommand}. */
public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
@Test
void testSuccess() throws Exception {
runCommandForced("--email", "user@example.test");
User onlyUser = Iterables.getOnlyElement(DatabaseHelper.loadAllOf(User.class));
assertThat(onlyUser.getEmailAddress()).isEqualTo("user@example.test");
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
}
@Test
void testSuccess_admin() throws Exception {
runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
}
@Test
void testSuccess_globalRole() throws Exception {
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE);
}
@Test
void testSuccess_registrarRoles() throws Exception {
runCommandForced(
"--email",
"user@example.test",
"--registrar_roles",
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEqualTo(
ImmutableMap.of(
"TheRegistrar",
RegistrarRole.ACCOUNT_MANAGER,
"NewRegistrar",
RegistrarRole.PRIMARY_CONTACT));
}
@Test
void testFailure_alreadyExists() throws Exception {
runCommandForced("--email", "user@example.test");
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--email", "user@example.test")))
.hasMessageThat()
.isEqualTo("A user with email user@example.test already exists");
}
}
@@ -0,0 +1,53 @@
// 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.tools;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static org.junit.Assert.assertThrows;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import org.junit.jupiter.api.Test;
/** Tests for {@link DeleteUserCommand}. */
public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
@Test
void testSuccess_deletesUser() throws Exception {
User user =
new User.Builder()
.setEmailAddress("email@example.test")
.setUserRoles(
new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build())
.build();
UserDao.saveUser(user);
assertThat(UserDao.loadUser("email@example.test")).isPresent();
runCommandForced("--email", "email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
}
@Test
void testFailure_nonexistent() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--email", "nonexistent@example.test")))
.hasMessageThat()
.isEqualTo("Email does not correspond to a valid user");
}
}
@@ -0,0 +1,89 @@
// 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.tools;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Tests for {@link UpdateUserCommand}. */
public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
@BeforeEach
void beforeEach() throws Exception {
UserDao.saveUser(
new User.Builder()
.setEmailAddress("user@example.test")
.setUserRoles(new UserRoles.Builder().build())
.build());
}
@Test
void testSuccess_admin() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
runCommandForced("--email", "user@example.test", "--admin", "false");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
}
@Test
void testSuccess_registrarRoles() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEmpty();
runCommandForced(
"--email",
"user@example.test",
"--registrar_roles",
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEqualTo(
ImmutableMap.of(
"TheRegistrar",
RegistrarRole.ACCOUNT_MANAGER,
"NewRegistrar",
RegistrarRole.PRIMARY_CONTACT));
runCommandForced("--email", "user@example.test", "--registrar_roles", "");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEmpty();
}
@Test
void testSuccess_globalRole() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.NONE);
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE);
}
@Test
void testFailure_doesntExist() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--email", "nonexistent@example.test")))
.hasMessageThat()
.isEqualTo("User nonexistent@example.test not found");
}
}
@@ -1,129 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.util;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.appengine.api.log.LogQuery;
import com.google.appengine.api.log.LogService;
import com.google.appengine.api.log.RequestLogs;
import com.google.apphosting.api.ApiProxy;
import com.google.common.collect.ImmutableList;
import com.google.common.testing.TestLogHandler;
import google.registry.testing.UserServiceExtension;
import java.util.logging.Level;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RequestStatusCheckerImpl}. */
final class RequestStatusCheckerImplTest {
private static final TestLogHandler logHandler = new TestLogHandler();
private static final RequestStatusChecker requestStatusChecker = new RequestStatusCheckerImpl();
/**
* Matcher for the expected LogQuery in {@link RequestStatusCheckerImpl#isRunning}.
*
* Because LogQuery doesn't have a .equals function, we have to create an actual matcher to make
* sure we have the right argument in our mocks.
*/
private static LogQuery expectedLogQuery(final String requestLogId) {
return argThat(
object -> {
assertThat(object).isInstanceOf(LogQuery.class);
assertThat(object.getRequestIds()).containsExactly(requestLogId);
assertThat(object.getIncludeAppLogs()).isFalse();
assertThat(object.getIncludeIncomplete()).isTrue();
return true;
});
}
// We do not actually need to set up user service, rather, we just need this extension to set up
// App Engine environment so the status checker can make an App Engine API call.
@RegisterExtension UserServiceExtension userService = new UserServiceExtension("");
@BeforeEach
void beforeEach() {
JdkLoggerConfig.getConfig(RequestStatusCheckerImpl.class).addHandler(logHandler);
RequestStatusCheckerImpl.logService = mock(LogService.class);
}
@AfterEach
void afterEach() {
JdkLoggerConfig.getConfig(RequestStatusCheckerImpl.class).removeHandler(logHandler);
}
// If a logId is unrecognized, it could be that the log hasn't been uploaded yet - so we assume
// it's a request that has just started running recently.
@Test
void testIsRunning_unrecognized() {
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
.thenReturn(ImmutableList.of());
assertThat(requestStatusChecker.isRunning("12345678")).isTrue();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Queried an unrecognized requestLogId");
}
@Test
void testIsRunning_notFinished() {
RequestLogs requestLogs = new RequestLogs();
requestLogs.setFinished(false);
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
.thenReturn(ImmutableList.of(requestLogs));
assertThat(requestStatusChecker.isRunning("12345678")).isTrue();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "isFinished: false");
}
@Test
void testIsRunning_finished() {
RequestLogs requestLogs = new RequestLogs();
requestLogs.setFinished(true);
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
.thenReturn(ImmutableList.of(requestLogs));
assertThat(requestStatusChecker.isRunning("12345678")).isFalse();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "isFinished: true");
}
@Test
void testGetLogId_returnsRequestLogId() {
String expectedLogId = ApiProxy.getCurrentEnvironment().getAttributes().get(
"com.google.appengine.runtime.request_log_id").toString();
assertThat(requestStatusChecker.getLogId()).isEqualTo(expectedLogId);
}
@Test
void testGetLogId_createsLog() {
requestStatusChecker.getLogId();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Current requestLogId: ");
}
}
@@ -14,7 +14,7 @@
<extension>
<fee:creData xmlns:fee="urn:ietf:params:xml:ns:fee-%FEE_VERSION%">
<fee:currency>USD</fee:currency>
<fee:fee description="create">26.00</fee:fee>
<fee:fee description="create">%FEE%</fee:fee>
</fee:creData>
</extension>
<trID>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5
View File
@@ -133,3 +133,8 @@ V132__drop_gae_user_id_from_registrar_poc.sql
V133__add_pull_queue_replace_columns.sql
V134__drop_not_null_request_id_lock_table.sql
V135__null_gaia_id_user.sql
V136__add_dns_refresh_request_table.sql
V137__add_process_time_column.sql
V138__drop_dns_refresh_request_time_column.sql
V139__add_allowed_epp_actions_column.sql
V140__rename_process_time_column_in_dns_refresh_request_table.sql
@@ -0,0 +1,25 @@
-- 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.
create table "DnsRefreshRequest"
(
id bigserial not null,
name text not null,
request_time timestamptz not null,
tld text not null,
type text not null,
primary key (id)
);
create index IDX8gtvnbk64yskcvrdp61f5ied3 on "DnsRefreshRequest" (request_time);
@@ -0,0 +1,18 @@
-- 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.
ALTER TABLE "DnsRefreshRequest"
ADD COLUMN IF NOT EXISTS process_time timestamptz NOT NULL;
CREATE INDEX IDX3i7i2ktts9d7lcjbs34h0pvwo ON "DnsRefreshRequest" (process_time);
@@ -0,0 +1,19 @@
-- 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.
ALTER TABLE "Domain" DROP COLUMN IF EXISTS dns_refresh_request_time;
ALTER TABLE "DomainHistory" DROP COLUMN IF EXISTS dns_refresh_request_time;
ALTER TABLE "Host" DROP COLUMN IF EXISTS dns_refresh_request_time;
ALTER TABLE "HostHistory" DROP COLUMN IF EXISTS dns_refresh_request_time;
@@ -0,0 +1,15 @@
-- 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.
ALTER TABLE "AllocationToken" ADD COLUMN allowed_epp_actions text[];
@@ -0,0 +1,20 @@
-- 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.
ALTER TABLE "DnsRefreshRequest" RENAME COLUMN process_time TO last_process_time;
CREATE INDEX IDXfdk2xpil2x1gh0omt84k2y3o1 ON "DnsRefreshRequest" (last_process_time);
DROP INDEX IDX3i7i2ktts9d7lcjbs34h0pvwo;
@@ -276,7 +276,6 @@
deletion_poll_message_id int8,
domain_name text,
idn_table_name text,
dns_refresh_request_time timestamptz,
last_transfer_time timestamptz,
launch_notice_accepted_time timestamptz,
launch_notice_expiration_time timestamptz,
@@ -349,7 +348,6 @@
deletion_poll_message_id int8,
domain_name text,
idn_table_name text,
dns_refresh_request_time timestamptz,
last_transfer_time timestamptz,
launch_notice_accepted_time timestamptz,
launch_notice_expiration_time timestamptz,
@@ -451,7 +449,6 @@
statuses text[],
host_name text,
inet_addresses text[],
dns_refresh_request_time timestamptz,
last_superordinate_change timestamptz,
last_transfer_time timestamptz,
superordinate_domain text,
@@ -472,7 +469,6 @@
history_xml_bytes bytea,
host_name text,
inet_addresses text[],
dns_refresh_request_time timestamptz,
last_superordinate_change timestamptz,
last_transfer_time timestamptz,
superordinate_domain text,
@@ -492,7 +488,6 @@
scope text not null,
acquired_time timestamptz not null,
expiration_time timestamptz not null,
request_log_id text,
primary key (resource_name, scope)
);
@@ -751,7 +746,7 @@
id bigserial not null,
update_timestamp timestamptz,
email_address text not null,
gaia_id text not null,
gaia_id text,
registry_lock_password_hash text,
registry_lock_password_salt text,
global_role text not null,
@@ -801,7 +796,6 @@ create index IDXc5aw4pk1vkd6ymhvkpanmoadv on "Domain" (domain_name);
create index IDXr22ciyccwi9rrqmt1ro0s59qf on "Domain" (tech_contact);
create index IDXrwl38wwkli1j7gkvtywi9jokq on "Domain" (tld);
create index IDXa7fu0bqynfb79rr80528b4jqt on "Domain" (registrant_contact);
create index IDXcws5mvmpl8o10wrhde780ors2 on "Domain" (dns_refresh_request_time);
create index IDXnjhib7v6fj7dhj5qydkefkl2u on "Domain" (lordn_phase);
create index IDXsfci08jgsymxy6ovh4k7r358c on "Domain" (billing_recurrence_id);
create index IDX3y3k7m2bkgahm9sixiohgyrga on "Domain" (transfer_billing_event_id);
@@ -825,7 +819,6 @@ create index IDXkpkh68n6dy5v51047yr6b0e9l on "Host" (host_name);
create index IDXy98mebut8ix1v07fjxxdkqcx on "Host" (creation_time);
create index IDXovmntef6l45tw2bsfl56tcugx on "Host" (deletion_time);
create index IDXl49vydnq0h5j1piefwjy4i8er on "Host" (current_sponsor_registrar_id);
create index IDX7wg0yn3wdux3xsc4pfaljqf08 on "Host" (dns_refresh_request_time);
create index IDXfg2nnjlujxo6cb9fha971bq2n on "HostHistory" (creation_time);
create index IDX1iy7njgb7wjmj9piml4l2g0qi on "HostHistory" (history_registrar_id);
create index IDXkkwbwcwvrdkkqothkiye4jiff on "HostHistory" (host_name);
@@ -53,7 +53,8 @@ CREATE TABLE public."AllocationToken" (
token_type text,
redemption_domain_history_id bigint,
renewal_price_behavior text DEFAULT 'DEFAULT'::text NOT NULL,
registration_behavior text DEFAULT 'DEFAULT'::text NOT NULL
registration_behavior text DEFAULT 'DEFAULT'::text NOT NULL,
allowed_epp_actions text[]
);
@@ -340,6 +341,39 @@ CREATE TABLE public."DelegationSignerData" (
);
--
-- Name: DnsRefreshRequest; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."DnsRefreshRequest" (
id bigint NOT NULL,
name text NOT NULL,
request_time timestamp with time zone NOT NULL,
tld text NOT NULL,
type text NOT NULL,
last_process_time timestamp with time zone NOT NULL
);
--
-- Name: DnsRefreshRequest_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."DnsRefreshRequest_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: DnsRefreshRequest_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."DnsRefreshRequest_id_seq" OWNED BY public."DnsRefreshRequest".id;
--
-- Name: Domain; Type: TABLE; Schema: public; Owner: -
--
@@ -395,7 +429,6 @@ CREATE TABLE public."Domain" (
transfer_history_entry_id bigint,
transfer_repo_id text,
transfer_poll_message_id_3 bigint,
dns_refresh_request_time timestamp with time zone,
current_package_token text,
lordn_phase text DEFAULT 'NONE'::text NOT NULL
);
@@ -485,7 +518,6 @@ CREATE TABLE public."DomainHistory" (
transfer_history_entry_id bigint,
transfer_repo_id text,
transfer_poll_message_id_3 bigint,
dns_refresh_request_time timestamp with time zone,
current_package_token text,
lordn_phase text DEFAULT 'NONE'::text NOT NULL
);
@@ -597,8 +629,7 @@ CREATE TABLE public."Host" (
superordinate_domain text,
inet_addresses text[],
update_timestamp timestamp with time zone,
transfer_poll_message_id_3 bigint,
dns_refresh_request_time timestamp with time zone
transfer_poll_message_id_3 bigint
);
@@ -631,8 +662,7 @@ CREATE TABLE public."HostHistory" (
statuses text[],
host_repo_id text NOT NULL,
update_timestamp timestamp with time zone,
transfer_poll_message_id_3 bigint,
dns_refresh_request_time timestamp with time zone
transfer_poll_message_id_3 bigint
);
@@ -1133,6 +1163,13 @@ CREATE SEQUENCE public.project_wide_unique_id_seq
ALTER TABLE ONLY public."ClaimsList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ClaimsList_revision_id_seq"'::regclass);
--
-- Name: DnsRefreshRequest id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."DnsRefreshRequest" ALTER COLUMN id SET DEFAULT nextval('public."DnsRefreshRequest_id_seq"'::regclass);
--
-- Name: DomainTransactionRecord id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -1277,6 +1314,14 @@ ALTER TABLE ONLY public."DelegationSignerData"
ADD CONSTRAINT "DelegationSignerData_pkey" PRIMARY KEY (domain_repo_id, key_tag, algorithm, digest_type, digest);
--
-- Name: DnsRefreshRequest DnsRefreshRequest_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."DnsRefreshRequest"
ADD CONSTRAINT "DnsRefreshRequest_pkey" PRIMARY KEY (id);
--
-- Name: DomainDsDataHistory DomainDsDataHistory_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -1531,13 +1576,6 @@ CREATE INDEX allocation_token_domain_name_idx ON public."AllocationToken" USING
CREATE UNIQUE INDEX database_migration_state_schedule_singleton ON public."DatabaseMigrationStateSchedule" USING btree ((true));
--
-- Name: domain_dns_refresh_request_time_idx; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX domain_dns_refresh_request_time_idx ON public."Domain" USING btree (dns_refresh_request_time);
--
-- Name: domain_history_to_ds_data_history_idx; Type: INDEX; Schema: public; Owner: -
--
@@ -1665,10 +1703,10 @@ CREATE INDEX idx73l103vc5900ig3p4odf0cngt ON public."BillingEvent" USING btree (
--
-- Name: idx7wg0yn3wdux3xsc4pfaljqf08; Type: INDEX; Schema: public; Owner: -
-- Name: idx8gtvnbk64yskcvrdp61f5ied3; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx7wg0yn3wdux3xsc4pfaljqf08 ON public."Host" USING btree (dns_refresh_request_time) WHERE (dns_refresh_request_time IS NOT NULL);
CREATE INDEX idx8gtvnbk64yskcvrdp61f5ied3 ON public."DnsRefreshRequest" USING btree (request_time);
--
@@ -1776,6 +1814,13 @@ CREATE INDEX idxe7wu46c7wpvfmfnj4565abibp ON public."PollMessage" USING btree (r
CREATE INDEX idxeokttmxtpq2hohcioe5t2242b ON public."BillingCancellation" USING btree (registrar_id);
--
-- Name: idxfdk2xpil2x1gh0omt84k2y3o1; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxfdk2xpil2x1gh0omt84k2y3o1 ON public."DnsRefreshRequest" USING btree (last_process_time);
--
-- Name: idxfg2nnjlujxo6cb9fha971bq2n; Type: INDEX; Schema: public; Owner: -
--

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