1
0
mirror of https://github.com/google/nomulus synced 2026-01-29 00:52:22 +00:00

Compare commits

...

15 Commits

Author SHA1 Message Date
gbrodman
073d0a416a Create a Dataflow pipeline to resave EPP resources (#1553)
* Create a Dataflow pipeline to resave EPP resources

This has two modes.

If `fast` is false, then we will just load all EPP resources, project them to the current time, and save them.

If `fast` is true, we will attempt to intelligently load and save only resources that we expect to have changes applied when we project them to the current time. This means resources with pending transfers that have expired, domains with expired grace periods, and non-deleted domains that have expired (we expect that they autorenewed).
2022-04-15 15:46:35 -04:00
Rachel Guan
f2ead5a0e3 Make code change for AllocationToken schema change (#1581)
* Make code change for AllocationToken schema change
2022-04-15 15:28:39 -04:00
Lai Jiang
212dbbe520 Fix a in issue with RDE (#1593)
For some inexplicable reason, the RDE beam pipeline in both sandbox and
production has been broken for the past week or so. Our investigations
revealed that during the CoGropuByKey stage, some repo ID -> revision ID
pairs were duplicated. This may be a problem with the Dataflow runtime
which somehow introduced the duplicate during reshuffling.

This PR attempts to fix the symptom only by deduping the revision IDs. We
will do some more investigation and possibly follow up with the Dataflow
team if we determine it is an upstream issue.

TESTED=deployed the pipeline and successfully run sandbox RDE with it.
2022-04-15 10:32:44 -04:00
Ben McIlwain
8594a61fd4 Begin migration from Guava Cache to Caffeine (#1590)
* Begin migration from Guava Cache to Caffeine

Caffeine is apparently strictly superior to the older Guava Cache (and is even
recommended in lieu of Guava Cache on Guava Cache's own documentation).

This adds the relevant dependencies and switch over just a single call site to
use the new Caffeine cache. It also implements a new pattern, asynchronously
refreshing the cache value starting from half of our configuration time. For
frequently accessed entities this will allow us to NEVER block on a load, as it
will be asynchronously refreshed in the background long before it ever expires
synchronously during a read operation.
2022-04-14 13:38:53 -04:00
Rachel Guan
36837eb3e6 Change to hasSize() for assertions (#1588)
* Change to hasSize() for assertions
2022-04-13 18:20:34 -04:00
Rachel Guan
3a9a8c6557 Add new columns to BillingEvent (#1573)
* Add new columns to BillingEvent.java

* Improve PR and modifyJodaMoneyType to handle null currency in override

* Add test cases for edge cases of nullSafeGet in JodaMoneyType

* Improve assertions
2022-04-11 20:09:26 -04:00
Weimin Yu
65c2570b8f Remove dos.xml from the configs (#1587)
* Remove dos.xml from the configs

We don't have dos config right now, and applying dos from "gcloud app
deploy" is deprecated and has started causing problems.

If we add dos configs, it should be using "gcloud app firewall-rules".
2022-04-11 15:22:42 -04:00
Weimin Yu
86acaa1b31 Build Java8-compatible release (#1586)
* Build Java8-compatible release

Use the new options.release Gradle property to make sure builds are
compatible with Java 8, which is the runtime on Appengine.

This new property replaces sourceCompatibility, targetCompatibility, and
bootclasspath (wasn't previously set, which is the reason why we
couldn't detect Java9 api usage when building).
2022-04-11 11:00:00 -04:00
Weimin Yu
436cc03be9 Remove Optional.isEmpty() in code (#1585)
* Remove Optional.isEmpty() in code
2022-04-08 21:30:22 +00:00
Ben McIlwain
e110ddd412 Canonicalize domain/host names in nomulus tool commands (#1583)
* Canonicalize domain/host names in nomulus tool commands

This helps prevent some common user errors.
2022-04-06 18:35:38 -04:00
Michael Muller
214b23e99c Ignore read-only when saving commit logs (#1584)
* Ignore read-only when saving commit logs

Ignore read-only when saving commit logs and commit log mutations so that we
can safely replicate in read-only mode.  This should be safe, as we only ever
to the situation of saving commit logs and mutations when something has
already actually been modified in a transaction, meaning that we should have hit
the "read only" sentinel already.

This also introduces the ability to set the Clock in the
TransactionManagerFactory so that we can test this functionality.

* Changes per review

* Fix issues affecting tests

- Restore clobbered async phase in testNoInMigrationState_doesNothing
- Restore system clock to TransactionManagerFactory to avoid affecting other
  tests.
2022-04-06 13:06:08 -04:00
Rachel Guan
743dea9ca2 Add renewal price behavior to AllocationToken (#1580) 2022-04-04 18:51:49 -04:00
sarahcaseybot
41f9f1ef7d Change use of BillingIdentifier to BillingAccountMap in invoicing pipeline (#1577)
* Change billingIdentifier to BillingAccountMap in invoicing pipeline

* Add a default for billing account map

* Throw error on missing PAK

* Add unit test
2022-04-04 16:16:43 -04:00
Michael Muller
44ede2b022 Check for error suggesting another nomulus running (#1582)
Check for a PSQLException referencing a failed connection to "google:5433",
which likely indicates that there is another nomulus tool instance running.

It's worth giving this hint because in cases like this it's not at all obvious
that the other instance of nomulus is problematic.
2022-04-04 11:14:43 -04:00
Ben McIlwain
e4312322dc Add a no-async actions DB migration phase (#1579)
* Add a no-async actions DB migration phase

This needs to be set several hours prior to entering the READONLY stage. This is
not a read-only stage; all synchronous actions under Datastore (such as domain
creates) will continue to succeed. The only thing that will fail is host
deletes, host renames, and contact deletes, as these three actions require a
mapreduce to run before they are complete, and we don't want mapreduces hanging
around and executing during what is supposed to be a short duration READONLY
period.
2022-04-01 16:55:51 -04:00
167 changed files with 4840 additions and 2941 deletions

View File

@@ -331,6 +331,13 @@ subprojects {
apply from: "${rootDir.path}/java_common.gradle"
if (project.name != 'docs') {
compileJava {
// TODO: Remove this once we migrate off AppEngine.
options.release = 8
}
}
if (project.name == 'third_party') return
project.tasks.test.dependsOn runPresubmits

View File

@@ -172,6 +172,7 @@ dependencies {
testRuntime files(sourceSets.test.resources.srcDirs)
compile deps['com.beust:jcommander']
compile deps['com.github.ben-manes.caffeine:caffeine']
compile deps['com.google.api:gax']
compile deps['com.google.api.grpc:proto-google-cloud-datastore-v1']
compile deps['com.google.api.grpc:proto-google-common-protos']
@@ -799,6 +800,11 @@ if (environment == 'alpha') {
mainClass: 'google.registry.beam.comparedb.ValidateDatabasePipeline',
metaData: 'google/registry/beam/validate_database_pipeline_metadata.json'
],
resaveAllEppResources:
[
mainClass: 'google.registry.beam.resave.ResaveAllEppResourcesPipeline',
metaData: 'google/registry/beam/resave_all_epp_resources_pipeline_metadata.json'
],
]
project.tasks.create("stageBeamPipelines") {
doLast {

View File

@@ -13,6 +13,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -118,7 +119,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -243,7 +244,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -13,6 +13,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7

View File

@@ -17,6 +17,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -123,7 +124,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -253,7 +254,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -17,6 +17,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -123,7 +124,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -252,7 +253,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -13,6 +13,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -118,7 +119,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -243,7 +244,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -13,6 +13,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -117,7 +118,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -236,7 +237,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.conscrypt:conscrypt-openjdk-uber:2.5.1

View File

@@ -17,6 +17,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -122,7 +123,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -252,7 +253,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -17,6 +17,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -122,7 +123,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -252,7 +253,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -17,6 +17,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -122,7 +123,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -252,7 +253,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -17,6 +17,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7

View File

@@ -13,6 +13,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -120,7 +121,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -264,7 +265,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -13,6 +13,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -119,7 +120,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -257,7 +258,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.conscrypt:conscrypt-openjdk-uber:2.5.1

View File

@@ -17,6 +17,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -125,7 +126,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -274,7 +275,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -17,6 +17,7 @@ com.fasterxml.jackson.core:jackson-databind:2.13.0
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0
com.fasterxml.jackson:jackson-bom:2.13.0
com.fasterxml:classmate:1.5.1
com.github.ben-manes.caffeine:caffeine:3.0.6
com.github.docker-java:docker-java-api:3.2.7
com.github.docker-java:docker-java-transport-zerodep:3.2.7
com.github.docker-java:docker-java-transport:3.2.7
@@ -125,7 +126,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.9
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.33
com.google.errorprone:error_prone_annotations:2.10.0
com.google.errorprone:error_prone_annotations:2.11.0
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -274,7 +275,7 @@ org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:3.21.0
org.checkerframework:checker-qual:3.21.3
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.20

View File

@@ -112,6 +112,12 @@ public class BatchModule {
req, ExpandRecurringBillingEventsAction.PARAM_CURSOR_TIME);
}
@Provides
@Parameter(ResaveAllEppResourcesPipelineAction.PARAM_FAST)
static Optional<Boolean> provideIsFast(HttpServletRequest req) {
return extractOptionalBooleanParameter(req, ResaveAllEppResourcesPipelineAction.PARAM_FAST);
}
@Provides
@Named(QUEUE_ASYNC_ACTIONS)
static Queue provideAsyncActionsPushQueue() {

View File

@@ -0,0 +1,129 @@
// 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.batch;
import static google.registry.beam.BeamUtils.createJobName;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.api.services.dataflow.Dataflow;
import com.google.api.services.dataflow.model.LaunchFlexTemplateParameter;
import com.google.api.services.dataflow.model.LaunchFlexTemplateRequest;
import com.google.api.services.dataflow.model.LaunchFlexTemplateResponse;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import java.util.Optional;
import javax.inject.Inject;
/**
* Starts a Dataflow pipeline that resaves all EPP resources projected to the current time.
*
* <p>This is useful for a few situations. First, as a fallback option for resource transfers that
* have expired pending transfers (this will resolve them), just in case the enqueued action fails.
* Second, it will reflect domain autorenews that have happened. Third, it will remove any expired
* grace periods.
*
* <p>There's also the general principle that it's good to have the data in the database remain as
* current as is reasonably possible.
*
* <p>If the <code>?isFast=true</code> query string parameter is passed as true, the pipeline will
* only attempt to load, project, and resave entities where we expect one of the previous situations
* has occurred. Otherwise, we will load, project, and resave all EPP resources.
*
* <p>This runs the {@link google.registry.beam.resave.ResaveAllEppResourcesPipeline}.
*/
@Action(
service = Action.Service.BACKEND,
path = ResaveAllEppResourcesPipelineAction.PATH,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
public class ResaveAllEppResourcesPipelineAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final String PATH = "/_dr/task/resaveAllEppResourcesPipeline";
static final String PIPELINE_NAME = "resave_all_epp_resources_pipeline";
public static final String PARAM_FAST = "fast";
private final String projectId;
private final String jobRegion;
private final String stagingBucketUrl;
private final boolean fast;
private final Clock clock;
private final Response response;
private final Dataflow dataflow;
@Inject
ResaveAllEppResourcesPipelineAction(
@Config("projectId") String projectId,
@Config("defaultJobRegion") String jobRegion,
@Config("beamStagingBucketUrl") String stagingBucketUrl,
@Parameter(PARAM_FAST) Optional<Boolean> fast,
Clock clock,
Response response,
Dataflow dataflow) {
this.projectId = projectId;
this.jobRegion = jobRegion;
this.stagingBucketUrl = stagingBucketUrl;
this.fast = fast.orElse(false);
this.clock = clock;
this.response = response;
this.dataflow = dataflow;
}
@Override
public void run() {
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
logger.atInfo().log("Launching ResaveAllEppResourcesPipeline");
try {
LaunchFlexTemplateParameter parameter =
new LaunchFlexTemplateParameter()
.setJobName(createJobName("resave-all-epp-resources", clock))
.setContainerSpecGcsPath(
String.format("%s/%s_metadata.json", stagingBucketUrl, PIPELINE_NAME))
.setParameters(
new ImmutableMap.Builder<String, String>()
.put(PARAM_FAST, Boolean.toString(fast))
.put("registryEnvironment", RegistryEnvironment.get().name())
.build());
LaunchFlexTemplateResponse launchResponse =
dataflow
.projects()
.locations()
.flexTemplates()
.launch(
projectId,
jobRegion,
new LaunchFlexTemplateRequest().setLaunchParameter(parameter))
.execute();
logger.atInfo().log("Got response: %s", launchResponse.getJob().toPrettyString());
String jobId = launchResponse.getJob().getId();
response.setStatus(SC_OK);
response.setPayload(String.format("Launched resaveAllEppResources pipeline: %s", jobId));
} catch (Exception e) {
logger.atSevere().withCause(e).log("Template Launch failed.");
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload(String.format("Pipeline launch failed: %s", e.getMessage()));
}
}
}

View File

@@ -158,7 +158,7 @@ public class InitSqlPipeline implements Serializable {
.addAll(toKindStrings(PHASE_TWO_ORDERED))
.build()));
// Set up the pipeline to write entity kinds from PHASE_ONE_ORDERED to SQL. Return a object
// Set up the pipeline to write entity kinds from PHASE_ONE_ORDERED to SQL. Return an object
// that signals the completion of the phase.
PCollection<Void> blocker =
scheduleOnePhaseWrites(datastoreSnapshot, PHASE_ONE_ORDERED, Optional.empty(), null);

View File

@@ -22,7 +22,7 @@ import static google.registry.beam.initsql.BackupPaths.getExportFilePatterns;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static java.util.Comparator.comparing;
import static org.apache.beam.sdk.values.TypeDescriptors.kvs;
import static org.apache.beam.sdk.values.TypeDescriptors.strings;
@@ -343,11 +343,11 @@ public final class Transforms {
// Canonicalize old domain/host names from 2016 and earlier before we were enforcing this.
entity.setIndexedProperty(
"fullyQualifiedDomainName",
canonicalizeDomainName((String) entity.getProperty("fullyQualifiedDomainName")));
canonicalizeHostname((String) entity.getProperty("fullyQualifiedDomainName")));
} else if (entity.getKind().equals("HostResource")) {
entity.setIndexedProperty(
"fullyQualifiedHostName",
canonicalizeDomainName((String) entity.getProperty("fullyQualifiedHostName")));
canonicalizeHostname((String) entity.getProperty("fullyQualifiedHostName")));
}
return entity;
}

View File

@@ -14,6 +14,7 @@
package google.registry.beam.invoicing;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.beam.BeamUtils.getQueryFromFile;
import static org.apache.beam.sdk.values.TypeDescriptors.strings;
@@ -53,6 +54,7 @@ import org.apache.beam.sdk.transforms.PTransform;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.PCollection;
import org.apache.beam.sdk.values.TypeDescriptor;
import org.joda.money.CurrencyUnit;
/**
* Definition of a Dataflow Flex pipeline template, which generates a given month's invoices.
@@ -122,12 +124,19 @@ public class InvoicingPipeline implements Serializable {
google.registry.model.billing.BillingEvent.OneTime oneTime =
(google.registry.model.billing.BillingEvent.OneTime) row[0];
Registrar registrar = (Registrar) row[1];
CurrencyUnit currency = oneTime.getCost().getCurrencyUnit();
checkState(
registrar.getBillingAccountMap().containsKey(currency),
"Registrar %s does not have a product account key for the currency unit: %s",
registrar.getRegistrarId(),
currency);
return BillingEvent.create(
oneTime.getId(),
DateTimeUtils.toZonedDateTime(oneTime.getBillingTime(), ZoneId.of("UTC")),
DateTimeUtils.toZonedDateTime(oneTime.getEventTime(), ZoneId.of("UTC")),
registrar.getRegistrarId(),
registrar.getBillingIdentifier().toString(),
registrar.getBillingAccountMap().get(currency),
registrar.getPoNumber().orElse(""),
DomainNameUtils.getTldFromDomainName(oneTime.getTargetId()),
oneTime.getReason().toString(),

View File

@@ -15,6 +15,7 @@
package google.registry.beam.rde;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.beam.rde.RdePipeline.TupleTags.DOMAIN_FRAGMENTS;
import static google.registry.beam.rde.RdePipeline.TupleTags.EXTERNAL_HOST_FRAGMENTS;
@@ -28,10 +29,12 @@ import static google.registry.model.reporting.HistoryEntryDao.RESOURCE_TYPES_TO_
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static org.apache.beam.sdk.values.TypeDescriptors.kvs;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
import dagger.BindsInstance;
import dagger.Component;
@@ -198,6 +201,8 @@ public class RdePipeline implements Serializable {
HostHistory.class,
"hostBase");
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject
RdePipeline(RdePipelineOptions options, GcsUtils gcsUtils, CloudTasksUtils cloudTasksUtils) {
this.options = options;
@@ -357,6 +362,33 @@ public class RdePipeline implements Serializable {
.setCoder(KvCoder.of(StringUtf8Coder.of(), VarLongCoder.of()));
}
private <T extends HistoryEntry> EppResource loadResourceByHistoryEntryId(
Class<T> historyEntryClazz, String repoId, Iterable<Long> revisionIds) {
ImmutableList<Long> ids = ImmutableList.copyOf(revisionIds);
// The size should always be 1 because we are only getting one repo ID -> revision ID pair per
// repo ID from the source transform (the JPA query in the method above). But for some reason
// after CoGroupByKey (joining the revision IDs and the pending deposits on repo IDs), in
// #removedUnreferencedResources, duplicate revision IDs are sometimes introduced. Here we
// attempt to deduplicate the iterable. If it contains multiple revision IDs that are NOT the
// same, we have a more serious problem as we cannot be sure which one to use. We should use the
// highest revision ID, but we don't even know where it comes from, as the query should
// definitively only give us one revision ID per repo ID. In this case we have to abort and
// require manual intervention.
if (ids.size() != 1) {
ImmutableSet<Long> dedupedIds = ImmutableSet.copyOf(ids);
checkState(
dedupedIds.size() == 1,
"Multiple unique revision IDs detected for %s repo ID %s: %s",
EPP_RESOURCE_FIELD_NAME.get(historyEntryClazz),
repoId,
ids);
logger.atSevere().log(
"Duplicate revision IDs detected for %s repo ID %s: %s",
EPP_RESOURCE_FIELD_NAME.get(historyEntryClazz), repoId, ids);
}
return loadResourceByHistoryEntryId(historyEntryClazz, repoId, ids.get(0));
}
private <T extends HistoryEntry> EppResource loadResourceByHistoryEntryId(
Class<T> historyEntryClazz, String repoId, long revisionId) {
try {
@@ -516,7 +548,7 @@ public class RdePipeline implements Serializable {
loadResourceByHistoryEntryId(
ContactHistory.class,
kv.getKey(),
kv.getValue().getOnly(REVISION_ID));
kv.getValue().getAll(REVISION_ID));
DepositFragment fragment = marshaller.marshalContact(contact);
ImmutableSet<KV<PendingDeposit, DepositFragment>> fragments =
Streams.stream(kv.getValue().getAll(PENDING_DEPOSIT))
@@ -549,8 +581,8 @@ public class RdePipeline implements Serializable {
loadResourceByHistoryEntryId(
HostHistory.class,
kv.getKey(),
kv.getValue().getOnly(REVISION_ID));
// When a host is subordinate, we need to find it's superordinate domain and
kv.getValue().getAll(REVISION_ID));
// When a host is subordinate, we need to find its superordinate domain and
// include it in the deposit as well.
if (host.isSubordinate()) {
subordinateHostCounter.inc();
@@ -627,7 +659,7 @@ public class RdePipeline implements Serializable {
loadResourceByHistoryEntryId(
DomainHistory.class,
kv.getKey(),
kv.getValue().getOnly(REVISION_ID));
kv.getValue().getAll(REVISION_ID));
ImmutableSet.Builder<KV<PendingDeposit, DepositFragment>> results =
new ImmutableSet.Builder<>();
for (KV<String, CoGbkResult> hostToPendingDeposits :
@@ -637,7 +669,7 @@ public class RdePipeline implements Serializable {
loadResourceByHistoryEntryId(
HostHistory.class,
hostToPendingDeposits.getKey(),
hostToPendingDeposits.getValue().getOnly(REVISION_ID));
hostToPendingDeposits.getValue().getAll(REVISION_ID));
DepositFragment fragment =
marshaller.marshalSubordinateHost(host, superordinateDomain);
Streams.stream(hostToPendingDeposits.getValue().getAll(PENDING_DEPOSIT))
@@ -696,6 +728,7 @@ public class RdePipeline implements Serializable {
* CoGbkResult}s are used.
*/
protected abstract static class TupleTags {
protected static final TupleTag<KV<PendingDeposit, DepositFragment>> DOMAIN_FRAGMENTS =
new TupleTag<KV<PendingDeposit, DepositFragment>>() {};
@@ -729,10 +762,12 @@ public class RdePipeline implements Serializable {
UtilsModule.class
})
interface RdePipelineComponent {
RdePipeline rdePipeline();
@Component.Builder
interface Builder {
@BindsInstance
Builder options(RdePipelineOptions options);

View File

@@ -0,0 +1,174 @@
// 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.beam.resave;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static org.apache.beam.sdk.values.TypeDescriptors.integers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.beam.common.RegistryJpaIO;
import google.registry.beam.common.RegistryJpaIO.Read;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.transaction.CriteriaQueryBuilder;
import google.registry.util.DateTimeUtils;
import java.io.Serializable;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.PipelineResult;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.GroupIntoBatches;
import org.apache.beam.sdk.transforms.ParDo;
import org.apache.beam.sdk.transforms.WithKeys;
import org.apache.beam.sdk.util.ShardedKey;
import org.apache.beam.sdk.values.KV;
import org.joda.time.DateTime;
/**
* A Dataflow Flex pipeline that resaves changed EPP resources in SQL.
*
* <p>Due to the way that Hibernate works, if an entity is unchanged by {@link
* EppResource#cloneProjectedAtTime(DateTime)} it will not actually be re-persisted to the database.
* Thus, the only actual changes occur when objects are changed by projecting them to now, such as
* when a pending transfer is resolved.
*/
public class ResaveAllEppResourcesPipeline implements Serializable {
private static final ImmutableSet<Class<? extends EppResource>> EPP_RESOURCE_CLASSES =
ImmutableSet.of(ContactResource.class, DomainBase.class, HostResource.class);
/**
* There exist three possible situations where we know we'll want to project domains to the
* current point in time:
*
* <ul>
* <li>A pending domain transfer has expired.
* <li>A domain is past its expiration time without being deleted (this means it autorenewed).
* <li>A domain has expired grace periods.
* </ul>
*
* <p>This command contains all three scenarios so that we can avoid querying the Domain table
* multiple times, and to avoid projecting and resaving the same domain multiple times.
*/
private static final String DOMAINS_TO_PROJECT_QUERY =
"FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
+ " d.transferData.pendingTransferExpirationTime < current_timestamp()) OR"
+ " (d.registrationExpirationTime < current_timestamp() AND d.deletionTime ="
+ " (:END_OF_TIME)) OR (EXISTS (SELECT 1 FROM GracePeriod gp WHERE gp.domainRepoId ="
+ " d.repoId AND gp.expirationTime < current_timestamp()))";
private final ResaveAllEppResourcesPipelineOptions options;
ResaveAllEppResourcesPipeline(ResaveAllEppResourcesPipelineOptions options) {
this.options = options;
}
PipelineResult run() {
Pipeline pipeline = Pipeline.create(options);
setupPipeline(pipeline);
return pipeline.run();
}
void setupPipeline(Pipeline pipeline) {
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_READ_COMMITTED);
if (options.getFast()) {
fastResaveContacts(pipeline);
fastResaveDomains(pipeline);
} else {
EPP_RESOURCE_CLASSES.forEach(clazz -> forceResaveAllResources(pipeline, clazz));
}
}
/** Projects to the current time and saves any contacts with expired transfers. */
private void fastResaveContacts(Pipeline pipeline) {
Read<ContactResource, ContactResource> read =
RegistryJpaIO.read(
"FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
+ " transferData.pendingTransferExpirationTime < current_timestamp()",
ContactResource.class,
c -> c);
projectAndResaveResources(pipeline, ContactResource.class, read);
}
/**
* Projects to the current time and saves any domains with expired pending actions (e.g.
* transfers, grace periods).
*
* <p>The logic of what might have changed is paraphrased from {@link
* google.registry.model.domain.DomainContent#cloneProjectedAtTime(DateTime)}.
*/
private void fastResaveDomains(Pipeline pipeline) {
Read<DomainBase, DomainBase> read =
RegistryJpaIO.read(
DOMAINS_TO_PROJECT_QUERY,
ImmutableMap.of("END_OF_TIME", DateTimeUtils.END_OF_TIME),
DomainBase.class,
d -> d);
projectAndResaveResources(pipeline, DomainBase.class, read);
}
/** Projects all resources to the current time and saves them. */
private <T extends EppResource> void forceResaveAllResources(Pipeline pipeline, Class<T> clazz) {
Read<T, T> read = RegistryJpaIO.read(() -> CriteriaQueryBuilder.create(clazz).build());
projectAndResaveResources(pipeline, clazz, read);
}
/** Projects and re-saves the result of the provided {@link Read}. */
private <T extends EppResource> void projectAndResaveResources(
Pipeline pipeline, Class<T> clazz, Read<?, T> read) {
int numShards = options.getSqlWriteShards();
int batchSize = options.getSqlWriteBatchSize();
String className = clazz.getSimpleName();
pipeline
.apply("Read " + className, read)
.apply(
"Shard data for class" + className,
WithKeys.<Integer, T>of(e -> ThreadLocalRandom.current().nextInt(numShards))
.withKeyType(integers()))
.apply(
"Group into batches for class" + className,
GroupIntoBatches.<Integer, T>ofSize(batchSize).withShardedKey())
.apply("Map " + className + " to now", ParDo.of(new BatchedProjectionFunction<>()))
.apply(
"Write transformed " + className,
RegistryJpaIO.<EppResource>write()
.withName("Write transformed " + className)
.withBatchSize(batchSize)
.withShards(numShards));
}
private static class BatchedProjectionFunction<T extends EppResource>
extends DoFn<KV<ShardedKey<Integer>, Iterable<T>>, EppResource> {
@ProcessElement
public void processElement(
@Element KV<ShardedKey<Integer>, Iterable<T>> element,
OutputReceiver<EppResource> outputReceiver) {
jpaTm()
.transact(
() ->
element
.getValue()
.forEach(
resource ->
outputReceiver.output(
resource.cloneProjectedAtTime(jpaTm().getTransactionTime()))));
}
}
}

View File

@@ -0,0 +1,26 @@
// 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.beam.resave;
import google.registry.beam.common.RegistryPipelineOptions;
import org.apache.beam.sdk.options.Description;
public interface ResaveAllEppResourcesPipelineOptions extends RegistryPipelineOptions {
@Description("True if we should attempt to run only over potentially out-of-date EPP resources")
boolean getFast();
void setFast(boolean fast);
}

View File

@@ -1438,8 +1438,8 @@ public final class RegistryConfig {
}
/** Returns the amount of time a singleton should be cached, before expiring. */
public static Duration getSingletonCacheRefreshDuration() {
return Duration.standardSeconds(CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds);
public static java.time.Duration getSingletonCacheRefreshDuration() {
return java.time.Duration.ofSeconds(CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds);
}
/**

View File

@@ -316,6 +316,12 @@
<url-pattern>/_dr/task/resaveAllEppResources</url-pattern>
</servlet-mapping>
<!-- Dataflow pipeline to re-save all EPP resources. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/resaveAllEppResourcesPipeline</url-pattern>
</servlet-mapping>
<!-- Reread all Registrar RDAP Base Urls from the ICANN endpoint. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<blacklistentries>
<!-- Example IPv4 CIDR Subnet
<blacklist>
<subnet>1.2.3.4/24</subnet>
<description>An IPv4 subnet</description>
</blacklist> -->
<!-- Example IPv6 CIDR Subnet
<blacklist>
<subnet>abcd::123:4567/48</subnet>
<description>An IPv6 subnet</description>
</blacklist> -->
</blacklistentries>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<blacklistentries>
<!-- Example IPv4 CIDR Subnet
<blacklist>
<subnet>1.2.3.4/24</subnet>
<description>An IPv4 subnet</description>
</blacklist> -->
<!-- Example IPv6 CIDR Subnet
<blacklist>
<subnet>abcd::123:4567/48</subnet>
<description>An IPv6 subnet</description>
</blacklist> -->
</blacklistentries>

View File

@@ -31,7 +31,7 @@ import static google.registry.monitoring.whitebox.CheckApiMetric.Status.UNKNOWN_
import static google.registry.monitoring.whitebox.CheckApiMetric.Tier.PREMIUM;
import static google.registry.monitoring.whitebox.CheckApiMetric.Tier.STANDARD;
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static org.json.simple.JSONValue.toJSONString;
import com.google.common.collect.ImmutableList;
@@ -105,7 +105,7 @@ public class CheckApiAction implements Runnable {
String domainString;
InternetDomainName domainName;
try {
domainString = canonicalizeDomainName(nullToEmpty(domain));
domainString = canonicalizeHostname(nullToEmpty(domain));
domainName = validateDomainName(domainString);
} catch (IllegalArgumentException | EppException e) {
metricBuilder.status(INVALID_NAME);

View File

@@ -25,6 +25,7 @@ import static google.registry.model.ResourceTransferUtils.handlePendingTransferO
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
import static google.registry.model.transfer.TransferStatus.SERVER_CANCELLED;
import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
@@ -94,6 +95,7 @@ public final class ContactDeleteFlow implements TransactionalFlow {
extensionManager.register(MetadataExtension.class);
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
assertAsyncActionsAreAllowed();
DateTime now = tm().getTransactionTime();
checkLinkedDomains(targetId, now, ContactResource.class, DomainBase::getReferencedContacts);
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);

View File

@@ -22,6 +22,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
@@ -96,6 +97,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
extensionManager.register(MetadataExtension.class);
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
assertAsyncActionsAreAllowed();
DateTime now = tm().getTransactionTime();
validateHostName(targetId);
checkLinkedDomains(targetId, now, HostResource.class, DomainBase::getNameservers);

View File

@@ -28,6 +28,7 @@ import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomain
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.reporting.HistoryEntry.Type.HOST_UPDATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
@@ -136,6 +137,9 @@ public final class HostUpdateFlow implements TransactionalFlow {
validateHostName(targetId);
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
boolean isHostRename = suppliedNewHostName != null;
if (isHostRename) {
assertAsyncActionsAreAllowed();
}
String oldHostName = targetId;
String newHostName = firstNonNull(suppliedNewHostName, oldHostName);
DomainBase oldSuperordinateDomain =

View File

@@ -17,10 +17,10 @@ package google.registry.model;
import static com.google.common.base.Suppliers.memoizeWithExpiration;
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.joda.time.Duration.ZERO;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Supplier;
import org.joda.time.Duration;
import java.time.Duration;
/** Utility methods related to caching Datastore entities. */
public class CacheUtils {
@@ -41,8 +41,36 @@ public class CacheUtils {
*/
public static <T> Supplier<T> tryMemoizeWithExpiration(
Duration expiration, Supplier<T> original) {
return expiration.isEqual(ZERO)
return expiration.isZero()
? original
: memoizeWithExpiration(original, expiration.getMillis(), MILLISECONDS);
: memoizeWithExpiration(original, expiration.toMillis(), MILLISECONDS);
}
/** Creates and returns a new {@link Caffeine} builder. */
public static Caffeine<Object, Object> newCacheBuilder() {
return Caffeine.newBuilder();
}
/**
* Creates and returns a new {@link Caffeine} builder with the specified cache expiration.
*
* <p>This also sets the refresh duration to half of the cache expiration. The resultant behavior
* is that a cache entry is eligible to be asynchronously refreshed after access once more than
* half of its cache duration has elapsed, and then it is synchronously refreshed (blocking the
* read) once its full cache duration has elapsed. So you will never get data older than the cache
* expiration, but for frequently accessed keys it will be refreshed more often than that and the
* cost of the load will never be incurred during the read.
*/
public static Caffeine<Object, Object> newCacheBuilder(Duration expireAfterWrite) {
Duration refreshAfterWrite = expireAfterWrite.dividedBy(2);
Caffeine<Object, Object> caffeine = Caffeine.newBuilder().expireAfterWrite(expireAfterWrite);
// In tests, the cache duration is usually set to 0, which means the cache load synchronously
// blocks every time it is called anyway because of the expireAfterWrite() above. Thus, setting
// the refreshAfterWrite won't do anything, plus it's not legal to call it with a zero value
// anyway (Caffeine allows expireAfterWrite to be zero but not refreshAfterWrite).
if (!refreshAfterWrite.isZero()) {
caffeine = caffeine.refreshAfterWrite(refreshAfterWrite);
}
return caffeine;
}
}

View File

@@ -126,7 +126,40 @@ public abstract class BillingEvent extends ImmutableObject
* This flag will be added to any {@link OneTime} events that are created via, e.g., an
* automated process to expand {@link Recurring} events.
*/
SYNTHETIC
SYNTHETIC;
}
/**
* Sets of renewal price behaviors that can be applied to billing recurrences.
*
* <p>When a client renews a domain, they could be charged differently, depending on factors such
* as the client type and the domain itself.
*/
public enum RenewalPriceBehavior {
/**
* This indicates the renewal price is the default price.
*
* <p>By default, if the domain is premium, then premium price will be used. Otherwise, the
* standard price of the TLD will be used.
*/
DEFAULT,
/**
* This indicates the domain will be renewed at standard price even if it's a premium domain.
*
* <p>We chose to name this "NONPREMIUM" rather than simply "STANDARD" to avoid confusion
* between "STANDARD" and "DEFAULT".
*
* <p>This price behavior is used with anchor tenants.
*/
NONPREMIUM,
/**
* This indicates that the renewalPrice in {@link BillingEvent.Recurring} will be used for
* domain renewal.
*
* <p>The renewalPrice has a non-null value iff the price behavior is set to "SPECIFIED". This
* behavior is used with internal registrations.
*/
SPECIFIED;
}
/** Entity id. */
@@ -555,6 +588,22 @@ public abstract class BillingEvent extends ImmutableObject
})
TimeOfYear recurrenceTimeOfYear;
/**
* The renewal price for domain renewal if and only if it's specified.
*
* <p>This price column remains null except when the renewal price behavior of the billing is
* SPECIFIED. This column is used for internal registrations.
*/
@Nullable
@Type(type = JodaMoneyType.TYPE_NAME)
@Columns(
columns = {@Column(name = "renewalPriceAmount"), @Column(name = "renewalPriceCurrency")})
Money renewalPrice;
@Enumerated(EnumType.STRING)
@Column(name = "renewalPriceBehavior", nullable = false)
RenewalPriceBehavior renewalPriceBehavior = RenewalPriceBehavior.DEFAULT;
public DateTime getRecurrenceEndTime() {
return recurrenceEndTime;
}
@@ -563,6 +612,14 @@ public abstract class BillingEvent extends ImmutableObject
return recurrenceTimeOfYear;
}
public RenewalPriceBehavior getRenewalPriceBehavior() {
return renewalPriceBehavior;
}
public Optional<Money> getRenewalPrice() {
return Optional.ofNullable(renewalPrice);
}
@Override
public VKey<Recurring> createVKey() {
return VKey.create(Recurring.class, getId(), Key.create(this));
@@ -591,11 +648,26 @@ public abstract class BillingEvent extends ImmutableObject
return this;
}
public Builder setRenewalPriceBehavior(RenewalPriceBehavior renewalPriceBehavior) {
getInstance().renewalPriceBehavior = renewalPriceBehavior;
return this;
}
public Builder setRenewalPrice(@Nullable Money renewalPrice) {
getInstance().renewalPrice = renewalPrice;
return this;
}
@Override
public Recurring build() {
Recurring instance = getInstance();
checkNotNull(instance.eventTime);
checkNotNull(instance.reason);
checkArgument(
(instance.renewalPriceBehavior == RenewalPriceBehavior.SPECIFIED)
^ (instance.renewalPrice == null),
"Renewal price can have a value if and only if the renewal price behavior is"
+ " SPECIFIED");
instance.recurrenceTimeOfYear = TimeOfYear.fromDateTime(instance.eventTime);
instance.recurrenceEndTime =
Optional.ofNullable(instance.recurrenceEndTime).orElse(END_OF_TIME);

View File

@@ -30,6 +30,7 @@ import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.model.replay.SqlOnlyEntity;
import java.time.Duration;
import java.util.Arrays;
import javax.persistence.Entity;
import javax.persistence.PersistenceException;
import org.joda.time.DateTime;
@@ -62,11 +63,28 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
* not the phase is read-only.
*/
public enum MigrationState {
/** Datastore is the only DB being used. */
DATASTORE_ONLY(PrimaryDatabase.DATASTORE, false, ReplayDirection.NO_REPLAY),
/** Datastore is the primary DB, with changes replicated to Cloud SQL. */
DATASTORE_PRIMARY(PrimaryDatabase.DATASTORE, false, ReplayDirection.DATASTORE_TO_SQL),
/** Datastore is the primary DB, with replication, and async actions are disallowed. */
DATASTORE_PRIMARY_NO_ASYNC(PrimaryDatabase.DATASTORE, false, ReplayDirection.DATASTORE_TO_SQL),
/** Datastore is the primary DB, with replication, and all mutating actions are disallowed. */
DATASTORE_PRIMARY_READ_ONLY(PrimaryDatabase.DATASTORE, true, ReplayDirection.DATASTORE_TO_SQL),
/**
* Cloud SQL is the primary DB, with replication back to Datastore, and all mutating actions are
* disallowed.
*/
SQL_PRIMARY_READ_ONLY(PrimaryDatabase.CLOUD_SQL, true, ReplayDirection.SQL_TO_DATASTORE),
/** Cloud SQL is the primary DB, with changes replicated to Datastore. */
SQL_PRIMARY(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.SQL_TO_DATASTORE),
/** Cloud SQL is the only DB being used. */
SQL_ONLY(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
private final PrimaryDatabase primaryDatabase;
@@ -146,11 +164,17 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
.putAll(
MigrationState.DATASTORE_PRIMARY,
MigrationState.DATASTORE_ONLY,
MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.putAll(
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
MigrationState.DATASTORE_ONLY,
MigrationState.DATASTORE_PRIMARY,
MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.putAll(
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
MigrationState.DATASTORE_ONLY,
MigrationState.DATASTORE_PRIMARY,
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
MigrationState.SQL_PRIMARY_READ_ONLY,
MigrationState.SQL_PRIMARY)
.putAll(
@@ -165,10 +189,9 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
MigrationState.SQL_ONLY,
MigrationState.SQL_PRIMARY_READ_ONLY,
MigrationState.SQL_PRIMARY);
// In addition, we can always transition from a state to itself (useful when updating the map).
for (MigrationState migrationState : MigrationState.values()) {
builder.put(migrationState, migrationState);
}
Arrays.stream(MigrationState.values()).forEach(state -> builder.put(state, state));
return builder.build();
}
@@ -246,7 +269,7 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
* A provided map of transitions may be valid by itself (i.e. it shifts states properly, doesn't
* skip states, and doesn't backtrack incorrectly) while still being invalid. In addition to the
* transitions in the map being valid, the single transition from the current map at the current
* time to the new map at the current time time must also be valid.
* time to the new map at the current time must also be valid.
*/
private static void validateTransitionAtCurrentTime(
TimedTransitionProperty<MigrationState, MigrationStateTransition> newTransitions) {

View File

@@ -175,7 +175,7 @@ public class DomainBase extends DomainContent
@Override
public void beforeSqlSaveOnReplay() {
fullyQualifiedDomainName = DomainNameUtils.canonicalizeDomainName(fullyQualifiedDomainName);
fullyQualifiedDomainName = DomainNameUtils.canonicalizeHostname(fullyQualifiedDomainName);
dsData =
dsData.stream()
.filter(datum -> datum.getDigest() != null && datum.getDigest().length > 0)

View File

@@ -34,7 +34,7 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.earliestOf;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@@ -890,7 +890,7 @@ public class DomainContent extends EppResource
public B setDomainName(String domainName) {
checkArgument(
domainName.equals(canonicalizeDomainName(domainName)),
domainName.equals(canonicalizeHostname(domainName)),
"Domain name %s not in puny-coded, lower-case form",
domainName);
getInstance().fullyQualifiedDomainName = domainName;

View File

@@ -309,7 +309,7 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
if (domainContent == null) {
domainContent = jpaTm().getEntityManager().find(DomainBase.class, getDomainRepoId());
domainContent.fullyQualifiedDomainName =
DomainNameUtils.canonicalizeDomainName(domainContent.fullyQualifiedDomainName);
DomainNameUtils.canonicalizeHostname(domainContent.fullyQualifiedDomainName);
fillAuxiliaryFieldsFromDomain(this);
}
}

View File

@@ -43,6 +43,7 @@ import google.registry.model.BackupGroupRoot;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.common.TimedTransitionProperty.TimeMapper;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
@@ -151,6 +152,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable, Datas
@Enumerated(EnumType.STRING)
TokenType tokenType;
@Enumerated(EnumType.STRING)
@Column(name = "renewalPriceBehavior", nullable = false)
RenewalPriceBehavior renewalPriceBehavior = RenewalPriceBehavior.DEFAULT;
// TODO: Remove onLoad once all allocation tokens are migrated to have a discountYears of 1.
@OnLoad
void onLoad() {
@@ -240,6 +245,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable, Datas
return tokenStatusTransitions;
}
public RenewalPriceBehavior getRenewalPriceBehavior() {
return renewalPriceBehavior;
}
public VKey<AllocationToken> createVKey() {
return VKey.create(AllocationToken.class, getToken(), Key.create(this));
}
@@ -362,5 +371,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable, Datas
"tokenStatusTransitions must start with NOT_STARTED");
return this;
}
public Builder setRenewalPriceBehavior(RenewalPriceBehavior renewalPriceBehavior) {
getInstance().renewalPriceBehavior = renewalPriceBehavior;
return this;
}
}
}

View File

@@ -19,7 +19,7 @@ import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.annotation.IgnoreSave;
@@ -195,7 +195,7 @@ public class HostBase extends EppResource {
public B setHostName(String hostName) {
checkArgument(
hostName.equals(canonicalizeDomainName(hostName)),
hostName.equals(canonicalizeHostname(hostName)),
"Host name %s not in puny-coded, lower-case form",
hostName);
getInstance().fullyQualifiedHostName = hostName;

View File

@@ -143,7 +143,7 @@ public class HostHistory extends HistoryEntry implements SqlEntity, UnsafeSerial
if (hostBase == null) {
hostBase = jpaTm().getEntityManager().find(HostResource.class, getHostRepoId());
hostBase.fullyQualifiedHostName =
DomainNameUtils.canonicalizeDomainName(hostBase.fullyQualifiedHostName);
DomainNameUtils.canonicalizeHostname(hostBase.fullyQualifiedHostName);
}
}

View File

@@ -73,7 +73,7 @@ public class HostResource extends HostBase
@Override
public void beforeSqlSaveOnReplay() {
fullyQualifiedHostName = DomainNameUtils.canonicalizeDomainName(fullyQualifiedHostName);
fullyQualifiedHostName = DomainNameUtils.canonicalizeHostname(fullyQualifiedHostName);
}
@Override

View File

@@ -69,7 +69,7 @@ public class CommitLogMutation extends ImmutableObject implements DatastoreOnlyE
* converted to a raw Datastore Entity, serialized to bytes, and stored within the mutation.
*/
public static CommitLogMutation create(Key<CommitLogManifest> parent, Object entity) {
return createFromRaw(parent, auditedOfy().save().toEntity(entity));
return createFromRaw(parent, auditedOfy().saveIgnoringReadOnlyWithBackup().toEntity(entity));
}
/**

View File

@@ -156,7 +156,7 @@ public class CommitLoggedWork<R> implements Runnable {
.map(entity -> (ImmutableObject) CommitLogMutation.create(manifestKey, entity))
.collect(toImmutableSet());
auditedOfy()
.saveWithoutBackup()
.saveIgnoringReadOnlyWithoutBackup()
.entities(
new ImmutableSet.Builder<>()
.add(manifest)

View File

@@ -264,8 +264,7 @@ public class Registry extends ImmutableObject
/** A cache that loads the {@link Registry} for a given tld. */
private static final LoadingCache<String, Optional<Registry>> CACHE =
CacheBuilder.newBuilder()
.expireAfterWrite(
java.time.Duration.ofMillis(getSingletonCacheRefreshDuration().getMillis()))
.expireAfterWrite(getSingletonCacheRefreshDuration())
.build(
new CacheLoader<String, Optional<Registry>>() {
@Override

View File

@@ -16,7 +16,7 @@ package google.registry.model.tld.label;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.emptyToNull;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.net.InternetDomainName;
@@ -77,7 +77,7 @@ public abstract class DomainLabelEntry<T extends Comparable<?>, D extends Domain
public T build() {
checkArgumentNotNull(emptyToNull(getInstance().domainLabel), "Label must be specified");
checkArgument(
getInstance().domainLabel.equals(canonicalizeDomainName(getInstance().domainLabel)),
getInstance().domainLabel.equals(canonicalizeHostname(getInstance().domainLabel)),
"Label '%s' must be in puny-coded, lower-case form",
getInstance().domainLabel);
checkArgumentNotNull(getInstance().getValue(), "Value must be specified");

View File

@@ -31,6 +31,7 @@ import google.registry.batch.ExpandRecurringBillingEventsAction;
import google.registry.batch.RefreshDnsOnHostRenameAction;
import google.registry.batch.RelockDomainAction;
import google.registry.batch.ResaveAllEppResourcesAction;
import google.registry.batch.ResaveAllEppResourcesPipelineAction;
import google.registry.batch.ResaveEntityAction;
import google.registry.batch.SendExpiringCertificateNotificationEmailAction;
import google.registry.batch.WipeOutCloudSqlAction;
@@ -196,6 +197,8 @@ interface BackendRequestComponent {
ResaveAllEppResourcesAction resaveAllEppResourcesAction();
ResaveAllEppResourcesPipelineAction resaveAllEppResourcesPipelineAction();
ResaveEntityAction resaveEntityAction();
SendExpiringCertificateNotificationEmailAction sendExpiringCertificateNotificationEmailAction();

View File

@@ -21,6 +21,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;
import javax.annotation.Nullable;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.StandardBasicTypes;
@@ -116,20 +117,28 @@ public class JodaMoneyType implements CompositeUserType {
return Objects.hashCode(x);
}
@Nullable
@Override
public Object nullSafeGet(
ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {
BigDecimal amount = StandardBasicTypes.BIG_DECIMAL.nullSafeGet(rs, names[AMOUNT_ID], session);
CurrencyUnit currencyUnit =
CurrencyUnit.of(StandardBasicTypes.STRING.nullSafeGet(rs, names[CURRENCY_ID], session));
if (amount != null && currencyUnit != null) {
return Money.of(currencyUnit, amount.stripTrailingZeros());
}
if (amount == null && currencyUnit == null) {
String currencyUnitString =
StandardBasicTypes.STRING.nullSafeGet(rs, names[CURRENCY_ID], session);
// It is allowable for a Money object to be null, but only if both the currency unit and the
// amount are null
if (amount == null && currencyUnitString == null) {
return null;
} else if (amount != null && currencyUnitString != null) {
// CurrencyUnit.of() throws an IllegalCurrencyException for unknown currency, which means the
// currency is valid if it returns a value
return Money.of(CurrencyUnit.of(currencyUnitString), amount.stripTrailingZeros());
} else {
throw new HibernateException(
String.format(
"Mismatching null state between currency '%s' and amount '%s'",
currencyUnitString, amount));
}
throw new HibernateException("Mismatching null state between currency and amount.");
}
@Override
@@ -140,7 +149,7 @@ public class JodaMoneyType implements CompositeUserType {
String currencyUnit = value == null ? null : ((Money) value).getCurrencyUnit().getCode();
if ((amount == null && currencyUnit != null) || (amount != null && currencyUnit == null)) {
throw new HibernateException("Mismatching null state between currency and amount.");
throw new HibernateException("Mismatching null state between currency and amount");
}
StandardBasicTypes.BIG_DECIMAL.nullSafeSet(st, amount, index, session);
StandardBasicTypes.STRING.nullSafeSet(st, currencyUnit, index + 1, session);

View File

@@ -15,23 +15,25 @@
package google.registry.persistence.transaction;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY_NO_ASYNC;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static org.joda.time.DateTimeZone.UTC;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import google.registry.config.RegistryEnvironment;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.PrimaryDatabase;
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.persistence.DaggerPersistenceComponent;
import google.registry.tools.RegistryToolEnvironment;
import google.registry.util.Clock;
import google.registry.util.NonFinalForTesting;
import google.registry.util.SystemClock;
import java.util.Optional;
import java.util.function.Supplier;
import org.joda.time.DateTime;
/** Factory class to create {@link TransactionManager} instance. */
// TODO: Rename this to PersistenceFactory and move to persistence package.
@@ -42,6 +44,9 @@ public final class TransactionManagerFactory {
/** Optional override to manually set the transaction manager per-test. */
private static Optional<TransactionManager> tmForTest = Optional.empty();
/** The current clock (defined as a variable so we can override it in tests) */
private static Clock clock = new SystemClock();
/** Supplier for jpaTm so that it is initialized only once, upon first usage. */
@NonFinalForTesting
private static Supplier<JpaTransactionManager> jpaTm =
@@ -103,7 +108,7 @@ public final class TransactionManagerFactory {
if (onBeam) {
return jpaTm();
}
return DatabaseMigrationStateSchedule.getValueAtTime(DateTime.now(UTC))
return DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc())
.getPrimaryDatabase()
.equals(PrimaryDatabase.DATASTORE)
? ofyTm()
@@ -193,11 +198,33 @@ public final class TransactionManagerFactory {
}
public static void assertNotReadOnlyMode() {
if (DatabaseMigrationStateSchedule.getValueAtTime(DateTime.now(UTC)).isReadOnly()) {
if (DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc()).isReadOnly()) {
throw new ReadOnlyModeException();
}
}
/**
* Asserts that async actions (contact/host deletes and host renames) are allowed.
*
* <p>These are allowed at all times except during the {@link
* DatabaseMigrationStateSchedule.MigrationState#DATASTORE_PRIMARY_NO_ASYNC} stage. Note that
* {@link ReadOnlyModeException} may well be thrown during other read-only stages inside the
* transaction manager; this method specifically checks only async actions.
*/
@DeleteAfterMigration
public static void assertAsyncActionsAreAllowed() {
if (DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc())
.equals(DATASTORE_PRIMARY_NO_ASYNC)) {
throw new ReadOnlyModeException();
}
}
/** Allows us to set the clock used by the factory in unit tests. */
@VisibleForTesting
public static void setClockForTesting(Clock clock) {
TransactionManagerFactory.clock = clock;
}
/** Registry is currently undergoing maintenance and is in read-only mode. */
public static class ReadOnlyModeException extends IllegalStateException {
public ReadOnlyModeException() {

View File

@@ -18,7 +18,7 @@ import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
import static google.registry.request.Actions.getPathForAction;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
@@ -245,7 +245,7 @@ public abstract class RdapActionBase implements Runnable {
}
String canonicalizeName(String name) {
name = canonicalizeDomainName(name);
name = canonicalizeHostname(name);
if (name.endsWith(".")) {
name = name.substring(0, name.length() - 1);
}

View File

@@ -19,7 +19,7 @@ import static com.google.appengine.api.urlfetch.HTTPMethod.PUT;
import static com.google.common.io.BaseEncoding.base64;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
@@ -123,7 +123,7 @@ public class RdeReporter {
private URL makeReportUrl(String tld, String id) {
try {
return new URL(String.format("%s/%s/%s", reportUrlPrefix, canonicalizeDomainName(tld), id));
return new URL(String.format("%s/%s/%s", reportUrlPrefix, canonicalizeHostname(tld), id));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}

View File

@@ -143,7 +143,7 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
// If a prefix is not provided, but we are in SQL mode, try to determine the prefix. This should
// only happen when the RDE upload cron job runs to catch up any un-retried (i. e. expected)
// RDE failures.
if (prefix.isEmpty() && !tm().isOfy()) {
if (!prefix.isPresent() && !tm().isOfy()) {
// The prefix is always in the format of: rde-2022-02-21t00-00-00z-2022-02-21t00-07-33z, where
// the first datetime is the watermark and the second one is the time when the RDE beam job
// launched. We search for the latest folder that starts with "rde-[watermark]".

View File

@@ -17,11 +17,11 @@ package google.registry.tmch;
import static google.registry.config.RegistryConfig.ConfigModule.TmchCaMode.PILOT;
import static google.registry.config.RegistryConfig.ConfigModule.TmchCaMode.PRODUCTION;
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
import static google.registry.model.CacheUtils.newCacheBuilder;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
@@ -76,9 +76,7 @@ public final class TmchCertificateAuthority {
* persist the correct one for this given environment.
*/
private static final LoadingCache<TmchCaMode, X509CRL> CRL_CACHE =
CacheBuilder.newBuilder()
.expireAfterWrite(
java.time.Duration.ofMillis(getSingletonCacheRefreshDuration().getMillis()))
newCacheBuilder(getSingletonCacheRefreshDuration())
.build(
new CacheLoader<TmchCaMode, X509CRL>() {
@Override

View File

@@ -14,7 +14,7 @@
package google.registry.tools;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.Parameter;
@@ -80,7 +80,7 @@ final class CanonicalizeLabelsCommand implements Command {
private String canonicalize(String rawLabel) {
try {
return canonicalizeDomainName(rawLabel.replaceAll(" ", ""));
return canonicalizeHostname(rawLabel.replaceAll(" ", ""));
} catch (Exception e) {
System.err.printf("Error canonicalizing %s: %s\n", rawLabel, e.getMessage());
return "";

View File

@@ -15,7 +15,7 @@
package google.registry.tools;
import static google.registry.util.DomainNameUtils.ACE_PREFIX;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -38,7 +38,7 @@ final class ConvertIdnCommand implements Command {
if (label.startsWith(ACE_PREFIX)) {
System.out.println(Idn.toUnicode(Ascii.toLowerCase(label)));
} else {
System.out.println(canonicalizeDomainName(label));
System.out.println(canonicalizeHostname(label));
}
}
}

View File

@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.net.InetAddresses;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.HostCreateSoyInfo;
import google.registry.util.DomainNameUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -68,7 +69,7 @@ final class CreateHostCommand extends MutatingEppToolCommand {
addSoyRecord(
clientId,
new SoyMapData(
"hostname", hostName,
"hostname", DomainNameUtils.canonicalizeHostname(hostName),
"ipv4addresses", ipv4Addresses.build(),
"ipv6addresses", ipv6Addresses.build()));
}

View File

@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Predicates.isNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.joda.time.DateTimeZone.UTC;
@@ -319,7 +319,7 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
addAllowedTlds.isEmpty(), "Can't specify both --allowedTlds and --addAllowedTlds");
ImmutableSet.Builder<String> allowedTldsBuilder = new ImmutableSet.Builder<>();
for (String allowedTld : allowedTlds) {
allowedTldsBuilder.add(canonicalizeDomainName(allowedTld));
allowedTldsBuilder.add(canonicalizeHostname(allowedTld));
}
builder.setAllowedTlds(allowedTldsBuilder.build());
}
@@ -329,7 +329,7 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
allowedTldsBuilder.addAll(oldRegistrar.getAllowedTlds());
}
for (String allowedTld : addAllowedTlds) {
allowedTldsBuilder.add(canonicalizeDomainName(allowedTld));
allowedTldsBuilder.add(canonicalizeHostname(allowedTld));
}
builder.setAllowedTlds(allowedTldsBuilder.build());
}

View File

@@ -16,7 +16,7 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.CollectionUtils.findDuplicates;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.beust.jcommander.Parameter;
import com.google.common.base.Joiner;
@@ -263,10 +263,10 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
"Can't update roid suffixes on multiple TLDs simultaneously");
for (String tld : tlds) {
checkArgument(
tld.equals(canonicalizeDomainName(tld)),
tld.equals(canonicalizeHostname(tld)),
"TLD '%s' should be given in the canonical form '%s'",
tld,
canonicalizeDomainName(tld));
canonicalizeHostname(tld));
checkArgument(
!Character.isDigit(tld.charAt(0)),
"TLDs cannot begin with a number");

View File

@@ -18,6 +18,7 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.HostDeleteSoyInfo;
import google.registry.util.DomainNameUtils;
/** A command to delete a host via EPP. */
@Parameters(separators = " =", commandDescription = "Delete host")
@@ -50,9 +51,11 @@ final class DeleteHostCommand extends MutatingEppToolCommand {
@Override
protected void initMutatingEppToolCommand() {
setSoyTemplate(HostDeleteSoyInfo.getInstance(), HostDeleteSoyInfo.DELETEHOST);
addSoyRecord(clientId, new SoyMapData(
"hostName", hostName,
"reason", reason,
"requestedByRegistrar", requestedByRegistrar));
addSoyRecord(
clientId,
new SoyMapData(
"hostName", DomainNameUtils.canonicalizeHostname(hostName),
"reason", reason,
"requestedByRegistrar", requestedByRegistrar));
}
}

View File

@@ -14,7 +14,7 @@
package google.registry.tools;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -61,6 +61,6 @@ class EncryptEscrowDepositCommand implements CommandWithRemoteApi {
@Override
public final void run() throws Exception {
encryptor.encrypt(mode, canonicalizeDomainName(tld), revision, input, outdir);
encryptor.encrypt(mode, canonicalizeHostname(tld), revision, input, outdir);
}
}

View File

@@ -30,6 +30,7 @@ import google.registry.model.domain.DomainHistory;
import google.registry.model.poll.PollMessage;
import google.registry.model.registrar.Registrar;
import google.registry.model.reporting.HistoryEntry;
import google.registry.util.DomainNameUtils;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
@@ -79,6 +80,7 @@ class EnqueuePollMessageCommand extends MutatingCommand {
@Override
protected final void init() {
domainName = DomainNameUtils.canonicalizeHostname(domainName);
checkArgument(
!sendToAll || isNullOrEmpty(clientIds), "Cannot specify both --all and --clients");
tm().transact(

View File

@@ -20,6 +20,7 @@ import static com.google.common.collect.Maps.filterValues;
import static com.google.common.io.Resources.getResource;
import static google.registry.model.tld.Registries.findTldForNameOrThrow;
import static google.registry.tools.CommandUtilities.addHeader;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static google.registry.xml.XmlTransformer.prettyPrint;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -83,8 +84,9 @@ abstract class EppToolCommand extends ConfirmingCommand
protected static Multimap<String, String> validateAndGroupDomainNamesByTld(List<String> names) {
ImmutableMultimap.Builder<String, String> builder = new ImmutableMultimap.Builder<>();
for (String name : names) {
InternetDomainName tld = findTldForNameOrThrow(InternetDomainName.from(name));
builder.put(tld.toString(), name);
String canonicalDomain = canonicalizeHostname(name);
InternetDomainName tld = findTldForNameOrThrow(InternetDomainName.from(canonicalDomain));
builder.put(tld.toString(), canonicalDomain);
}
return builder.build();
}

View File

@@ -16,7 +16,6 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Queues.newArrayDeque;
import static com.google.common.collect.Sets.difference;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
@@ -44,15 +43,18 @@ import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.persistence.VKey;
import google.registry.tools.params.TransitionListParameter.TokenStatusTransitions;
import google.registry.util.CollectionUtils;
import google.registry.util.DomainNameUtils;
import google.registry.util.NonFinalForTesting;
import google.registry.util.Retrier;
import google.registry.util.StringGenerator;
import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
@@ -164,11 +166,12 @@ class GenerateAllocationTokensCommand implements CommandWithRemoteApi {
domainNames = null;
} else {
domainNames =
newArrayDeque(
Splitter.on('\n')
.omitEmptyStrings()
.trimResults()
.split(Files.asCharSource(new File(domainNamesFile), UTF_8).read()));
Splitter.on('\n')
.omitEmptyStrings()
.trimResults()
.splitToStream(Files.asCharSource(new File(domainNamesFile), UTF_8).read())
.map(DomainNameUtils::canonicalizeHostname)
.collect(Collectors.toCollection(ArrayDeque::new));
numTokens = domainNames.size();
}

View File

@@ -19,6 +19,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.domain.DomainBase;
import google.registry.util.DomainNameUtils;
import java.util.List;
/** Command to show a domain resource. */
@@ -33,8 +34,11 @@ final class GetDomainCommand extends GetEppResourceCommand {
@Override
public void runAndPrint() {
for (String domainName : mainParameters) {
String canonicalDomain = DomainNameUtils.canonicalizeHostname(domainName);
printResource(
"Domain", domainName, loadByForeignKey(DomainBase.class, domainName, readTimestamp));
"Domain",
canonicalDomain,
loadByForeignKey(DomainBase.class, canonicalDomain, readTimestamp));
}
}
}

View File

@@ -19,6 +19,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.host.HostResource;
import google.registry.util.DomainNameUtils;
import java.util.List;
/** Command to show one or more host resources. */
@@ -32,7 +33,9 @@ final class GetHostCommand extends GetEppResourceCommand {
@Override
public void runAndPrint() {
mainParameters.forEach(
h -> printResource("Host", h, loadByForeignKey(HostResource.class, h, readTimestamp)));
mainParameters.stream()
.map(DomainNameUtils::canonicalizeHostname)
.forEach(
h -> printResource("Host", h, loadByForeignKey(HostResource.class, h, readTimestamp)));
}
}

View File

@@ -14,6 +14,8 @@
package google.registry.tools;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.beust.jcommander.Parameters;
/**
@@ -26,6 +28,6 @@ public class LockDomainCommand extends LockOrUnlockDomainCommand {
@Override
protected void createAndApplyRequest(String domain) {
domainLockUtils.administrativelyApplyLock(domain, clientId, null, true);
domainLockUtils.administrativelyApplyLock(canonicalizeHostname(domain), clientId, null, true);
}
}

View File

@@ -38,7 +38,9 @@ import java.io.ByteArrayInputStream;
import java.net.URL;
import java.security.Security;
import java.util.Map;
import java.util.Optional;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.postgresql.util.PSQLException;
/** Container class to create and run remote commands against a Datastore instance. */
@Parameters(separators = " =", commandDescription = "Command-line interface to the registry")
@@ -178,14 +180,30 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
try {
runCommand(command);
} catch (RuntimeException ex) {
if (Throwables.getRootCause(ex) instanceof LoginRequiredException) {
} catch (RuntimeException e) {
if (Throwables.getRootCause(e) instanceof LoginRequiredException) {
System.err.println("===================================================================");
System.err.println("You must login using 'nomulus login' prior to running this command.");
System.err.println("===================================================================");
System.exit(1);
} else {
throw ex;
// See if this looks like the error we get when there's another instance of nomulus tool
// running against SQL and give the user some additional guidance if so.
Optional<Throwable> psqlException =
Throwables.getCausalChain(e).stream()
.filter(x -> x instanceof PSQLException)
.findFirst();
if (psqlException.isPresent() && psqlException.get().getMessage().contains("google:5432")) {
e.printStackTrace();
System.err.println("===================================================================");
System.err.println(
"This error is likely the result of having another instance of\n"
+ "nomulus running at the same time. Check your system, shut down\n"
+ "the other instance, and try again.");
System.err.println("===================================================================");
} else {
throw e;
}
}
}
}

View File

@@ -38,6 +38,7 @@ import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.tools.soy.DomainRenewSoyInfo;
import google.registry.tools.soy.UniformRapidSuspensionSoyInfo;
import google.registry.util.DomainNameUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -122,12 +123,14 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
protected void initMutatingEppToolCommand() {
superuser = true;
DateTime now = DateTime.now(UTC);
ImmutableSet<String> newHostsSet = ImmutableSet.copyOf(newHosts);
ImmutableList<String> newCanonicalHosts =
newHosts.stream().map(DomainNameUtils::canonicalizeHostname).collect(toImmutableList());
ImmutableSet<String> newHostsSet = ImmutableSet.copyOf(newCanonicalHosts);
Optional<DomainBase> domainOpt = loadByForeignKey(DomainBase.class, domainName, now);
checkArgumentPresent(domainOpt, "Domain '%s' does not exist or is deleted", domainName);
DomainBase domain = domainOpt.get();
Set<String> missingHosts =
difference(newHostsSet, checkResourcesExist(HostResource.class, newHosts, now));
difference(newHostsSet, checkResourcesExist(HostResource.class, newCanonicalHosts, now));
checkArgument(missingHosts.isEmpty(), "Hosts do not exist: %s", missingHosts);
checkArgument(
locksToPreserve.isEmpty() || undo,

View File

@@ -14,6 +14,8 @@
package google.registry.tools;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.beust.jcommander.Parameters;
import java.util.Optional;
@@ -27,6 +29,7 @@ public class UnlockDomainCommand extends LockOrUnlockDomainCommand {
@Override
protected void createAndApplyRequest(String domain) {
domainLockUtils.administrativelyApplyUnlock(domain, clientId, true, Optional.empty());
domainLockUtils.administrativelyApplyUnlock(
canonicalizeHostname(domain), clientId, true, Optional.empty());
}
}

View File

@@ -15,6 +15,7 @@
package google.registry.tools.params;
import com.google.common.net.InternetDomainName;
import google.registry.util.DomainNameUtils;
/** InternetDomainName CLI parameter converter/validator. */
public final class InternetDomainNameParameter
@@ -26,6 +27,6 @@ public final class InternetDomainNameParameter
@Override
public InternetDomainName convert(String value) {
return InternetDomainName.from(value);
return InternetDomainName.from(DomainNameUtils.canonicalizeHostname(value));
}
}

View File

@@ -17,7 +17,7 @@ package google.registry.ui.server;
import static com.google.common.collect.Range.atLeast;
import static com.google.common.collect.Range.atMost;
import static com.google.common.collect.Range.closed;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
@@ -334,7 +334,7 @@ public final class RegistrarFormFields {
if (!InternetDomainName.isValid(input)) {
throw new FormFieldException("Not a valid hostname.");
}
return canonicalizeDomainName(input);
return canonicalizeHostname(input);
}
public static @Nullable DateTime parseDateTime(@Nullable String input) {

View File

@@ -17,7 +17,7 @@ package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.model.tld.Registries.findTldForName;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import com.google.common.base.Joiner;
@@ -122,7 +122,7 @@ class WhoisReader {
logger.atInfo().log(
"Attempting domain lookup command using domain name '%s'.", tokens.get(1));
return commandFactory.domainLookup(
InternetDomainName.from(canonicalizeDomainName(tokens.get(1))),
InternetDomainName.from(canonicalizeHostname(tokens.get(1))),
fullOutput,
whoisRedactedEmailText);
} catch (IllegalArgumentException iae) {
@@ -152,8 +152,8 @@ class WhoisReader {
try {
logger.atInfo().log(
"Attempting nameserver lookup command using %s as a hostname.", tokens.get(1));
return commandFactory.nameserverLookupByHost(InternetDomainName.from(
canonicalizeDomainName(tokens.get(1))));
return commandFactory.nameserverLookupByHost(
InternetDomainName.from(canonicalizeHostname(tokens.get(1))));
} catch (IllegalArgumentException iae) {
// Silently ignore this exception.
}
@@ -187,7 +187,7 @@ class WhoisReader {
// Try to parse it as a domain name or host name.
try {
final InternetDomainName targetName = InternetDomainName.from(canonicalizeDomainName(arg1));
final InternetDomainName targetName = InternetDomainName.from(canonicalizeHostname(arg1));
// We don't know at this point whether we have a domain name or a host name. We have to
// search through our configured TLDs to see if there's one that prefixes the name.

View File

@@ -31,7 +31,7 @@ JOIN Domain d ON b.domainRepoId = d.repoId
JOIN Tld t ON t.tldStrId = d.tld
LEFT JOIN BillingCancellation c ON b.id = c.refOneTime.billingId
LEFT JOIN BillingCancellation cr ON b.cancellationMatchingBillingEvent = cr.refRecurring.billingId
WHERE r.billingIdentifier IS NOT NULL
WHERE r.billingAccountMap IS NOT NULL
AND r.type = 'REAL'
AND t.invoicingEnabled IS TRUE
AND b.billingTime BETWEEN CAST('%FIRST_TIMESTAMP_OF_MONTH%' AS timestamp) AND CAST('%LAST_TIMESTAMP_OF_MONTH%' AS timestamp)

View File

@@ -0,0 +1,30 @@
{
"name": "Resave all EPP Resources",
"description": "An Apache Beam pipeline that resaves all (or potentially only changed) EPP resources",
"parameters": [
{
"name": "registryEnvironment",
"label": "The Registry environment.",
"helpText": "The Registry environment.",
"is_optional": false,
"regexes": [
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
]
},
{
"name": "isolationOverride",
"label": "The desired SQL transaction isolation level.",
"helpText": "The desired SQL transaction isolation level.",
"is_optional": true,
"regexes": [
"^[0-9A-Z_]+$"
]
},
{
"name": "fast",
"label": "Whether or not to attempt to only save changed resources",
"helpText": "If true, we will attempt to only save resources that possibly have expired transfers, grace periods, etc",
"is_optional": false
}
]
}

View File

@@ -92,7 +92,7 @@ public class DeleteOldCommitLogsActionTest
contact = auditedOfy().load().type(ContactResource.class).first().now();
// The following value might change if {@link CommitLogRevisionsTranslatorFactory} changes.
assertThat(contact.getRevisions().size()).isEqualTo(6);
assertThat(contact.getRevisions()).hasSize(6);
// Before deleting the unneeded manifests - we have 11 of them (one for the first
// creation, and 10 more for the mutateContacts)

View File

@@ -20,6 +20,7 @@ import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistContactWithPendingTransfer;
import static org.joda.time.DateTimeZone.UTC;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.contact.ContactResource;
import google.registry.model.transfer.TransferStatus;
import google.registry.testing.FakeResponse;
@@ -29,6 +30,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ResaveAllEppResourcesAction}. */
// No longer needed in SQL. Subject to future removal.
@Deprecated
@DeleteAfterMigration
class ResaveAllEppResourcesActionTest extends MapreduceTestCase<ResaveAllEppResourcesAction> {
@BeforeEach

View File

@@ -0,0 +1,84 @@
// 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.batch;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.batch.ResaveAllEppResourcesPipelineAction.PARAM_FAST;
import static google.registry.batch.ResaveAllEppResourcesPipelineAction.PIPELINE_NAME;
import static google.registry.beam.BeamUtils.createJobName;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.Mockito.verify;
import com.google.api.services.dataflow.model.LaunchFlexTemplateParameter;
import com.google.api.services.dataflow.model.LaunchFlexTemplateRequest;
import com.google.common.collect.ImmutableMap;
import google.registry.beam.BeamActionTestBase;
import google.registry.config.RegistryEnvironment;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.FakeClock;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ResaveAllEppResourcesPipelineAction}. */
public class ResaveAllEppResourcesPipelineActionTest extends BeamActionTestBase {
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
private final FakeClock fakeClock = new FakeClock();
private ResaveAllEppResourcesPipelineAction createAction(boolean isFast) {
return new ResaveAllEppResourcesPipelineAction(
"test-project",
"test-region",
"staging-bucket",
Optional.of(isFast),
fakeClock,
response,
dataflow);
}
@Test
void testLaunch_notFast() throws Exception {
createAction(false).run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getPayload()).isEqualTo("Launched resaveAllEppResources pipeline: jobid");
verify(templates).launch("test-project", "test-region", createLaunchTemplateRequest(false));
}
@Test
void testLaunch_fast() throws Exception {
createAction(true).run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getPayload()).isEqualTo("Launched resaveAllEppResources pipeline: jobid");
verify(templates).launch("test-project", "test-region", createLaunchTemplateRequest(true));
}
private LaunchFlexTemplateRequest createLaunchTemplateRequest(boolean isFast) {
return new LaunchFlexTemplateRequest()
.setLaunchParameter(
new LaunchFlexTemplateParameter()
.setJobName(createJobName("resave-all-epp-resources", fakeClock))
.setContainerSpecGcsPath(
String.format("%s/%s_metadata.json", "staging-bucket", PIPELINE_NAME))
.setParameters(
new ImmutableMap.Builder<String, String>()
.put(PARAM_FAST, Boolean.toString(isFast))
.put("registryEnvironment", RegistryEnvironment.get().name())
.build()));
}
}

View File

@@ -15,6 +15,7 @@
package google.registry.batch;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.testing.AppEngineExtension.makeRegistrar1;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -430,7 +431,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
}
ImmutableList<RegistrarInfo> results = action.getRegistrarsWithExpiringCertificates();
assertThat(results.size()).isEqualTo(numOfRegistrarsWithExpiringCertificates);
assertThat(results).hasSize(numOfRegistrarsWithExpiringCertificates);
}
@TestOfyAndSql

View File

@@ -24,6 +24,10 @@ import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.CAD;
import static org.joda.money.CurrencyUnit.JPY;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -55,6 +59,7 @@ import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.Optional;
import org.apache.beam.sdk.Pipeline.PipelineExecutionException;
import org.apache.beam.sdk.coders.SerializableCoder;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.testing.PAssert;
@@ -294,6 +299,37 @@ class InvoicingPipelineTest {
pipeline.run().waitUntilFinish();
}
@Test
void testFailure_readFromCloudSqlMissingPAK() throws Exception {
Registrar registrar = persistNewRegistrar("TheRegistrar");
registrar =
registrar
.asBuilder()
.setBillingAccountMap(ImmutableMap.of(USD, "789"))
.setPoNumber(Optional.of("22446688"))
.build();
persistResource(registrar);
Registry test =
newRegistry("test", "_TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
.asBuilder()
.setInvoicingEnabled(true)
.build();
persistResource(test);
DomainBase domain = persistActiveDomain("mycanadiandomain.test");
persistOneTimeBillingEvent(1, domain, registrar, Reason.RENEW, 3, Money.of(CAD, 20.5));
PCollection<BillingEvent> billingEvents = InvoicingPipeline.readFromCloudSql(options, pipeline);
billingEvents = billingEvents.apply(new ChangeDomainRepo());
PAssert.that(billingEvents).empty();
PipelineExecutionException thrown =
assertThrows(PipelineExecutionException.class, () -> pipeline.run().waitUntilFinish());
assertThat(thrown)
.hasMessageThat()
.contains(
"Registrar TheRegistrar does not have a product account key for the currency unit:"
+ " CAD");
}
@Test
void testSuccess_saveInvoiceCsv() throws Exception {
InvoicingPipeline.saveInvoiceCsv(billingEvents, options);
@@ -338,7 +374,7 @@ class InvoicingPipelineTest {
+ "LEFT JOIN BillingCancellation c ON b.id = c.refOneTime.billingId\n"
+ "LEFT JOIN BillingCancellation cr ON b.cancellationMatchingBillingEvent ="
+ " cr.refRecurring.billingId\n"
+ "WHERE r.billingIdentifier IS NOT NULL\n"
+ "WHERE r.billingAccountMap IS NOT NULL\n"
+ "AND r.type = 'REAL'\n"
+ "AND t.invoicingEnabled IS TRUE\n"
+ "AND b.billingTime BETWEEN CAST('2017-10-01' AS timestamp) AND CAST('2017-11-01'"
@@ -362,18 +398,22 @@ class InvoicingPipelineTest {
persistNewRegistrar("NewRegistrar");
persistNewRegistrar("TheRegistrar");
Registrar registrar1 = persistNewRegistrar("theRegistrar");
registrar1 = registrar1.asBuilder().setBillingIdentifier(234L).build();
registrar1 =
registrar1
.asBuilder()
.setBillingAccountMap(ImmutableMap.of(JPY, "234", USD, "234"))
.build();
persistResource(registrar1);
Registrar registrar2 = persistNewRegistrar("bestdomains");
registrar2 =
registrar2
.asBuilder()
.setBillingIdentifier(456L)
.setBillingAccountMap(ImmutableMap.of(USD, "456"))
.setPoNumber(Optional.of("116688"))
.build();
persistResource(registrar2);
Registrar registrar3 = persistNewRegistrar("anotherRegistrar");
registrar3 = registrar3.asBuilder().setBillingIdentifier(789L).build();
registrar3 = registrar3.asBuilder().setBillingAccountMap(ImmutableMap.of(USD, "789")).build();
persistResource(registrar3);
Registry test =
@@ -397,10 +437,8 @@ class InvoicingPipelineTest {
DomainBase domain6 = persistActiveDomain("locked.test");
DomainBase domain7 = persistActiveDomain("update-prohibited.test");
persistOneTimeBillingEvent(
1, domain1, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
persistOneTimeBillingEvent(
2, domain2, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
persistOneTimeBillingEvent(1, domain1, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
persistOneTimeBillingEvent(2, domain2, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
persistOneTimeBillingEvent(
3,
domain3,
@@ -410,31 +448,27 @@ class InvoicingPipelineTest {
Money.ofMajor(CurrencyUnit.JPY, 70),
DateTime.parse("2017-09-29T00:00:00.0Z"),
DateTime.parse("2017-10-02T00:00:00.0Z"));
persistOneTimeBillingEvent(
4, domain4, registrar2, Reason.RENEW, 1, Money.of(CurrencyUnit.USD, 20.5));
persistOneTimeBillingEvent(4, domain4, registrar2, Reason.RENEW, 1, Money.of(USD, 20.5));
persistOneTimeBillingEvent(
5,
domain5,
registrar3,
Reason.CREATE,
1,
Money.of(CurrencyUnit.USD, 0),
Money.of(USD, 0),
DateTime.parse("2017-10-04T00:00:00.0Z"),
DateTime.parse("2017-10-04T00:00:00.0Z"),
Flag.SUNRISE,
Flag.ANCHOR_TENANT);
persistOneTimeBillingEvent(
6, domain6, registrar1, Reason.SERVER_STATUS, 0, Money.of(CurrencyUnit.USD, 0));
persistOneTimeBillingEvent(
7, domain7, registrar1, Reason.SERVER_STATUS, 0, Money.of(CurrencyUnit.USD, 20));
persistOneTimeBillingEvent(6, domain6, registrar1, Reason.SERVER_STATUS, 0, Money.of(USD, 0));
persistOneTimeBillingEvent(7, domain7, registrar1, Reason.SERVER_STATUS, 0, Money.of(USD, 20));
// Add billing event for a non-billable registrar
Registrar registrar4 = persistNewRegistrar("noBillRegistrar");
registrar4 = registrar4.asBuilder().setBillingIdentifier(null).build();
registrar4 = registrar4.asBuilder().setBillingAccountMap(null).build();
persistResource(registrar4);
DomainBase domain8 = persistActiveDomain("non-billable.test");
persistOneTimeBillingEvent(
8, domain8, registrar4, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
persistOneTimeBillingEvent(8, domain8, registrar4, Reason.RENEW, 3, Money.of(USD, 20.5));
// Add billing event for a non-real registrar
Registrar registrar5 = persistNewRegistrar("notRealRegistrar");
@@ -442,19 +476,17 @@ class InvoicingPipelineTest {
registrar5
.asBuilder()
.setIanaIdentifier(null)
.setBillingIdentifier(456L)
.setBillingAccountMap(ImmutableMap.of(USD, "456"))
.setType(Registrar.Type.OTE)
.build();
persistResource(registrar5);
DomainBase domain9 = persistActiveDomain("not-real.test");
persistOneTimeBillingEvent(
9, domain9, registrar5, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
persistOneTimeBillingEvent(9, domain9, registrar5, Reason.RENEW, 3, Money.of(USD, 20.5));
// Add billing event for a non-invoicing TLD
createTld("nobill");
DomainBase domain10 = persistActiveDomain("test.nobill");
persistOneTimeBillingEvent(
10, domain10, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
persistOneTimeBillingEvent(10, domain10, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
// Add billing event before October 2017
DomainBase domain11 = persistActiveDomain("july.test");
@@ -471,8 +503,7 @@ class InvoicingPipelineTest {
// Add a billing event with a corresponding cancellation
DomainBase domain12 = persistActiveDomain("cancel.test");
OneTime oneTime =
persistOneTimeBillingEvent(
12, domain12, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
persistOneTimeBillingEvent(12, domain12, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
DomainHistory domainHistory = persistDomainHistory(domain12, registrar1);
Cancellation cancellation =
@@ -507,8 +538,7 @@ class InvoicingPipelineTest {
.build();
persistResource(recurring);
OneTime oneTimeRecurring =
persistOneTimeBillingEvent(
13, domain13, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
persistOneTimeBillingEvent(13, domain13, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
oneTimeRecurring =
oneTimeRecurring
.asBuilder()

View File

@@ -0,0 +1,204 @@
// 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.beam.resave;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistContactWithPendingTransfer;
import static google.registry.testing.DatabaseHelper.persistDomainWithDependentResources;
import static google.registry.testing.DatabaseHelper.persistDomainWithPendingTransfer;
import static google.registry.testing.DatabaseHelper.persistNewRegistrars;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import google.registry.beam.TestPipelineExtension;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.GracePeriod;
import google.registry.model.eppcommon.StatusValue;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.persistence.transaction.TransactionManagerFactory;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.TmOverrideExtension;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
/** Tests for {@link ResaveAllEppResourcesPipeline}. */
public class ResaveAllEppResourcesPipelineTest {
private final FakeClock fakeClock = new FakeClock(DateTime.parse("2020-03-10T00:00:00.000Z"));
@RegisterExtension
@Order(Order.DEFAULT - 1)
final transient DatastoreEntityExtension datastore =
new DatastoreEntityExtension().allThreads(true);
@RegisterExtension
final TestPipelineExtension testPipeline =
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);
@RegisterExtension
final JpaIntegrationTestExtension database =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationTestExtension();
@RegisterExtension
@Order(Order.DEFAULT + 1)
TmOverrideExtension tmOverrideExtension = TmOverrideExtension.withJpa();
private final ResaveAllEppResourcesPipelineOptions options =
PipelineOptionsFactory.create().as(ResaveAllEppResourcesPipelineOptions.class);
@BeforeEach
void beforeEach() {
options.setFast(true);
persistNewRegistrars("TheRegistrar", "NewRegistrar");
createTld("tld");
}
@Test
void testPipeline_unchangedEntity() {
ContactResource contact = persistActiveContact("test123");
DateTime creationTime = contact.getUpdateTimestamp().getTimestamp();
fakeClock.advanceOneMilli();
assertThat(loadByEntity(contact).getUpdateTimestamp().getTimestamp()).isEqualTo(creationTime);
fakeClock.advanceOneMilli();
runPipeline();
assertThat(loadByEntity(contact)).isEqualTo(contact);
}
@Test
void testPipeline_fulfilledContactTransfer() {
ContactResource contact = persistActiveContact("test123");
DateTime now = fakeClock.nowUtc();
contact = persistContactWithPendingTransfer(contact, now, now.plusDays(5), now);
fakeClock.advanceBy(Duration.standardDays(10));
assertThat(loadByEntity(contact).getStatusValues()).contains(StatusValue.PENDING_TRANSFER);
runPipeline();
assertThat(loadByEntity(contact).getStatusValues())
.doesNotContain(StatusValue.PENDING_TRANSFER);
}
@Test
void testPipeline_fulfilledDomainTransfer() {
options.setFast(true);
DateTime now = fakeClock.nowUtc();
DomainBase domain =
persistDomainWithPendingTransfer(
persistDomainWithDependentResources(
"domain",
"tld",
persistActiveContact("jd1234"),
now.minusDays(5),
now.minusDays(5),
now.plusYears(2)),
now.minusDays(4),
now.minusDays(1),
now.plusYears(2));
assertThat(domain.getStatusValues()).contains(StatusValue.PENDING_TRANSFER);
assertThat(domain.getUpdateTimestamp().getTimestamp()).isEqualTo(now);
fakeClock.advanceOneMilli();
runPipeline();
DomainBase postPipeline = loadByEntity(domain);
assertThat(postPipeline.getStatusValues()).doesNotContain(StatusValue.PENDING_TRANSFER);
assertThat(postPipeline.getUpdateTimestamp().getTimestamp()).isEqualTo(fakeClock.nowUtc());
}
@Test
void testPipeline_autorenewedDomain() {
DateTime now = fakeClock.nowUtc();
DomainBase domain =
persistDomainWithDependentResources(
"domain", "tld", persistActiveContact("jd1234"), now, now, now.plusYears(1));
assertThat(domain.getRegistrationExpirationTime()).isEqualTo(now.plusYears(1));
fakeClock.advanceBy(Duration.standardDays(500));
runPipeline();
DomainBase postPipeline = loadByEntity(domain);
assertThat(postPipeline.getRegistrationExpirationTime()).isEqualTo(now.plusYears(2));
}
@Test
void testPipeline_expiredGracePeriod() {
DateTime now = fakeClock.nowUtc();
persistDomainWithDependentResources(
"domain", "tld", persistActiveContact("jd1234"), now, now, now.plusYears(1));
assertThat(loadAllOf(GracePeriod.class)).hasSize(1);
fakeClock.advanceBy(Duration.standardDays(500));
runPipeline();
assertThat(loadAllOf(GracePeriod.class)).isEmpty();
}
@Test
void testPipeline_fastOnlySavesChanged() {
DateTime now = fakeClock.nowUtc();
ContactResource contact = persistActiveContact("jd1234");
persistDomainWithDependentResources("renewed", "tld", contact, now, now, now.plusYears(1));
persistActiveDomain("nonrenewed.tld", now, now.plusYears(20));
// Spy the transaction manager so we can be sure we're only saving the renewed domain
JpaTransactionManager spy = spy(jpaTm());
TransactionManagerFactory.setJpaTm(() -> spy);
ArgumentCaptor<DomainBase> domainPutCaptor = ArgumentCaptor.forClass(DomainBase.class);
runPipeline();
// We should only be attempting to put the one changed domain into the DB
verify(spy).put(domainPutCaptor.capture());
assertThat(domainPutCaptor.getValue().getDomainName()).isEqualTo("renewed.tld");
}
@Test
void testPipeline_notFastResavesAll() {
options.setFast(false);
DateTime now = fakeClock.nowUtc();
ContactResource contact = persistActiveContact("jd1234");
DomainBase renewed =
persistDomainWithDependentResources("renewed", "tld", contact, now, now, now.plusYears(1));
DomainBase nonRenewed =
persistDomainWithDependentResources(
"nonrenewed", "tld", contact, now, now, now.plusYears(20));
// Spy the transaction manager so we can be sure we're attempting to save everything
JpaTransactionManager spy = spy(jpaTm());
TransactionManagerFactory.setJpaTm(() -> spy);
ArgumentCaptor<EppResource> eppResourcePutCaptor = ArgumentCaptor.forClass(EppResource.class);
runPipeline();
// We should be attempting to put both domains (and the contact) in, even the unchanged ones
verify(spy, times(3)).put(eppResourcePutCaptor.capture());
assertThat(
eppResourcePutCaptor.getAllValues().stream()
.map(EppResource::getRepoId)
.collect(toImmutableSet()))
.containsExactly(contact.getRepoId(), renewed.getRepoId(), nonRenewed.getRepoId());
}
private void runPipeline() {
ResaveAllEppResourcesPipeline pipeline = new ResaveAllEppResourcesPipeline(options);
pipeline.setupPipeline(testPipeline);
testPipeline.run().waitUntilFinish();
}
}

View File

@@ -282,7 +282,7 @@ class Spec11PipelineTest {
ImmutableList.copyOf(
ResourceUtils.readResourceUtf8(this.getClass(), "test_output.txt").split("\n"));
ImmutableList<String> resultFileContents = resultFileContents();
assertThat(resultFileContents.size()).isEqualTo(expectedFileContents.size());
assertThat(resultFileContents).hasSize(expectedFileContents.size());
assertThat(resultFileContents.get(0)).isEqualTo(expectedFileContents.get(0));
assertThat(resultFileContents.subList(1, resultFileContents.size()))
.comparingElementsUsing(

View File

@@ -254,6 +254,15 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
assertIcannReportingActivityFieldLogged("srs-cont-delete");
}
@TestOfyOnly
void testModification_duringNoAsyncPhase() throws Exception {
persistActiveContact(getUniqueIdFromCommand());
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
EppException thrown = assertThrows(ReadOnlyModeEppException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@TestOfyOnly
void testModification_duringReadOnlyPhase() throws Exception {
persistActiveContact(getUniqueIdFromCommand());

View File

@@ -840,6 +840,15 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
doSuccessfulTest();
}
@TestOfyOnly
void testSuccess_inNoAsyncPhase() throws Exception {
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
persistContactsAndHosts();
runFlowAssertResponse(
loadFile("domain_create_response_noasync.xml", ImmutableMap.of("DOMAIN", "example.tld")));
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@TestOfyAndSql
void testSuccess_maxNumberOfNameservers() throws Exception {
setEppInput("domain_create_13_nameservers.xml");

View File

@@ -358,6 +358,15 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, HostResour
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@TestOfyOnly
void testModification_duringNoAsyncPhase() {
persistActiveHost("ns1.example.tld");
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
EppException thrown = assertThrows(ReadOnlyModeEppException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
DatabaseHelper.removeDatabaseMigrationSchedule();
}
private void assertOfyDeleteSuccess(String registrarId, String clientTrid, boolean isSuperuser)
throws Exception {
HostResource deletedHost = reloadResourceByForeignKey();

View File

@@ -54,13 +54,10 @@ class HostInfoFlowTest extends ResourceFlowTestCase<HostInfoFlow, HostResource>
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithDoubleReplay(clock);
HostInfoFlowTest() {
setEppInput("host_info.xml", ImmutableMap.of("HOSTNAME", "ns1.example.tld"));
}
@BeforeEach
void initHostTest() {
createTld("foobar");
setEppInput("host_info.xml", ImmutableMap.of("HOSTNAME", "ns1.example.tld"));
}
private HostResource persistHostResource() throws Exception {

View File

@@ -1355,7 +1355,43 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
}
@TestOfyOnly
void testModification_duringReadOnlyPhase() throws Exception {
void testSuccess_nonHostRename_inNoAsyncPhase_succeeds() throws Exception {
setEppInput("host_update_name_unchanged.xml");
createTld("tld");
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
DomainBase domain = persistActiveDomain("example.tld");
HostResource oldHost = persistActiveSubordinateHost(oldHostName(), domain);
clock.advanceOneMilli();
runFlowAssertResponse(loadFile("generic_success_response.xml"));
// The example xml doesn't do a host rename, so reloading the host should work.
assertAboutHosts()
.that(reloadResourceByForeignKey())
.hasLastSuperordinateChange(oldHost.getLastSuperordinateChange())
.and()
.hasSuperordinateDomain(domain.createVKey())
.and()
.hasPersistedCurrentSponsorRegistrarId("TheRegistrar")
.and()
.hasLastTransferTime(null)
.and()
.hasOnlyOneHistoryEntryWhich()
.hasType(HistoryEntry.Type.HOST_UPDATE);
assertDnsTasksEnqueued("ns1.example.tld");
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@TestOfyOnly
void testRename_duringNoAsyncPhase_fails() throws Exception {
createTld("tld");
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
EppException thrown = assertThrows(ReadOnlyModeEppException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@TestOfyOnly
void testModification_duringReadOnlyPhase_fails() throws Exception {
createTld("tld");
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
DatabaseHelper.setMigrationScheduleToDatastorePrimaryReadOnly(clock);

View File

@@ -15,6 +15,7 @@
package google.registry.model.billing;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -36,6 +37,7 @@ import com.googlecode.objectify.Key;
import google.registry.model.EntityTestCase;
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.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
@@ -49,6 +51,7 @@ import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.DateTimeUtils;
import java.math.BigDecimal;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@@ -474,4 +477,487 @@ public class BillingEventTest extends EntityTestCase {
.setParent(domainHistory)
.build();
}
@TestOfyAndSql
void testSuccess_defaultRenewalPriceBehavior_assertsIsDefault() {
assertThat(recurring.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
assertThat(recurring.getRenewalPrice()).isEmpty();
}
@TestOfyAndSql
void testSuccess_getRenewalPriceBehavior_returnsRightBehavior() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
}
@TestOfyAndSql
void testSuccess_setRenewalPriceBehaviorThenBuild_defaultToSpecified() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
persistResource(
loadedEntity
.asBuilder()
.setRenewalPrice(Money.of(USD, 100))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.build());
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.SPECIFIED);
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).hasValue(Money.of(USD, 100));
}
@TestOfyAndSql
void testSuccess_setRenewalPriceBehaviorThenBuild_defaultToNonPremium() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
persistResource(
loadedEntity.asBuilder().setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM).build());
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.NONPREMIUM);
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
}
@TestOfyAndSql
void testSuccess_setRenewalPriceBehaviorThenBuild_nonPremiumToSpecified() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
persistResource(
loadedEntity
.asBuilder()
.setRenewalPrice(Money.of(USD, 100))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.build());
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.SPECIFIED);
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).hasValue(Money.of(USD, 100));
}
@TestOfyAndSql
void testSuccess_setRenewalPriceBehaviorThenBuild_nonPremiumToDefault() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
persistResource(
loadedEntity.asBuilder().setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT).build());
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.DEFAULT);
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
}
@TestOfyAndSql
void testSuccess_setRenewalPriceBehaviorThenBuild_specifiedToDefault() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 100))
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
persistResource(
loadedEntity
.asBuilder()
.setRenewalPrice(null)
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.build());
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.DEFAULT);
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
}
@TestOfyAndSql
void testSuccess_setRenewalPriceBehaviorThenBuild_specifiedToNonPremium() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 100))
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
persistResource(
loadedEntity
.asBuilder()
.setRenewalPrice(null)
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.build());
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.NONPREMIUM);
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
}
@TestOfyAndSql
void testFailure_setRenewalPriceBehaviorThenBuild_defaultToSpecified_needRenewalPrice() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
loadedEntity
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
@TestOfyAndSql
void testFailure_setRenewalPriceBehaviorThenBuild_defaultToPremium_noNeedToAddRenewalPrice() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
loadedEntity
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.setRenewalPrice(Money.of(USD, 100))
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
@TestOfyAndSql
void testFailure_setRenewalPriceBehaviorThenBuild_nonPremiumToDefault_noNeedToAddRenewalPrice() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
loadedEntity
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.setRenewalPrice(Money.of(USD, 100))
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
@TestOfyAndSql
void testFailure_setRenewalPriceBehaviorThenBuild_nonPremiumToSpecified_needRenewalPrice() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
loadedEntity
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
@TestOfyAndSql
void testFailure_setRenewalPriceBehaviorThenBuild_specifiedToNonPremium_removeRenewalPrice() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 100))
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
loadedEntity
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
@TestOfyAndSql
void testFailure_setRenewalPriceBehaviorThenBuild_specifiedToDefault_removeRenewalPrice() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 100))
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
assertThat(loadedEntity).isEqualTo(recurringEvent);
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
loadedEntity
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
@TestOfyAndSql
void testSuccess_buildWithDefaultRenewalBehavior() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, BigDecimal.valueOf(100)))
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
}
@TestOfyAndSql
void testSuccess_buildWithNonPremiumRenewalBehavior() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
}
@TestOfyAndSql
void testSuccess_buildWithSpecifiedRenewalBehavior() {
BillingEvent.Recurring recurringEvent =
persistResource(
commonInit(
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, BigDecimal.valueOf(100)))
.setRecurrenceEndTime(END_OF_TIME)));
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
}
@TestOfyAndSql
void testFailure_buildWithSpecifiedRenewalBehavior_requiresNonNullRenewalPrice() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRecurrenceEndTime(END_OF_TIME)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
@TestOfyAndSql
void testFailure_buildWithNonPremiumRenewalBehavior_requiresNullRenewalPrice() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.setRenewalPrice(Money.of(USD, BigDecimal.valueOf(100)))
.setRecurrenceEndTime(END_OF_TIME)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
@TestOfyAndSql
void testFailure_buildWithDefaultRenewalBehavior_requiresNullRenewalPrice() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
new BillingEvent.Recurring.Builder()
.setParent(domainHistory)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.setRenewalPrice(Money.of(USD, BigDecimal.valueOf(100)))
.setRecurrenceEndTime(END_OF_TIME)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Renewal price can have a value if and only if the "
+ "renewal price behavior is SPECIFIED");
}
}

View File

@@ -17,6 +17,7 @@ package google.registry.model.common;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_ONLY;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY_NO_ASYNC;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY_READ_ONLY;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.SQL_ONLY;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.SQL_PRIMARY;
@@ -71,10 +72,12 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
runValidTransition(DATASTORE_ONLY, DATASTORE_PRIMARY);
runValidTransition(DATASTORE_PRIMARY, DATASTORE_ONLY);
runValidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_READ_ONLY);
runValidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_NO_ASYNC);
runValidTransition(DATASTORE_PRIMARY_NO_ASYNC, DATASTORE_PRIMARY_READ_ONLY);
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, DATASTORE_ONLY);
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, DATASTORE_PRIMARY);
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, DATASTORE_PRIMARY_NO_ASYNC);
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, SQL_PRIMARY_READ_ONLY);
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, SQL_PRIMARY);
@@ -94,6 +97,7 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
runInvalidTransition(DATASTORE_ONLY, SQL_PRIMARY);
runInvalidTransition(DATASTORE_ONLY, SQL_ONLY);
runInvalidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_READ_ONLY);
runInvalidTransition(DATASTORE_PRIMARY, SQL_PRIMARY_READ_ONLY);
runInvalidTransition(DATASTORE_PRIMARY, SQL_PRIMARY);
runInvalidTransition(DATASTORE_PRIMARY, SQL_ONLY);
@@ -124,7 +128,8 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
ImmutableSortedMap.<DateTime, MigrationState>naturalOrder()
.put(START_OF_TIME, DATASTORE_ONLY)
.put(startTime.plusHours(1), DATASTORE_PRIMARY)
.put(startTime.plusHours(2), DATASTORE_PRIMARY_READ_ONLY)
.put(startTime.plusHours(2), DATASTORE_PRIMARY_NO_ASYNC)
.put(startTime.plusHours(3), DATASTORE_PRIMARY_READ_ONLY)
.build();
assertThat(
assertThrows(
@@ -163,7 +168,8 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
fakeClock.setTo(START_OF_TIME.plusDays(1));
AllocationToken token =
new AllocationToken.Builder().setToken("token").setTokenType(TokenType.SINGLE_USE).build();
runValidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_READ_ONLY);
runValidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_NO_ASYNC);
runValidTransition(DATASTORE_PRIMARY_NO_ASYNC, DATASTORE_PRIMARY_READ_ONLY);
assertThrows(ReadOnlyModeException.class, () -> persistResource(token));
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, SQL_PRIMARY_READ_ONLY);
assertThrows(ReadOnlyModeException.class, () -> persistResource(token));

View File

@@ -34,6 +34,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.googlecode.objectify.Key;
import google.registry.model.EntityTestCase;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.domain.token.AllocationToken.TokenType;
@@ -160,6 +161,52 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(tokenAfterPersisting.getCreationTime()).hasValue(fakeClock.nowUtc());
}
@TestOfyAndSql
void testgetRenewalBehavior_returnsDefaultRenewBehavior() {
assertThat(
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.build())
.getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.DEFAULT);
}
@TestOfyAndSql
void testsetRenewalBehavior_assertsRenewalBehaviorIsNotDefault() {
assertThat(
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.build())
.getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.SPECIFIED);
}
@TestOfyAndSql
void testsetRenewalBehavior_assertRenewalBehaviorIsModified() {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
.build());
AllocationToken loadedToken = loadByEntity(token);
assertThat(token).isEqualTo(loadedToken);
AllocationToken modifiedToken =
persistResource(
loadedToken
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.build());
assertThat(loadByEntity(token).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.SPECIFIED);
}
@TestOfyAndSql
void testSetCreationTime_cantCallMoreThanOnce() {
AllocationToken.Builder builder =

View File

@@ -44,6 +44,7 @@ import google.registry.model.server.Lock;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
import google.registry.persistence.transaction.TransactionEntity;
import google.registry.persistence.transaction.TransactionManagerFactory;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
@@ -52,6 +53,7 @@ import google.registry.testing.InjectExtension;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestObject;
import google.registry.util.RequestStatusChecker;
import google.registry.util.SystemClock;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.logging.Level;
@@ -105,6 +107,7 @@ public class ReplicateToDatastoreActionTest {
void tearDown() {
DatabaseHelper.removeDatabaseMigrationSchedule();
fakeClock.disableAutoIncrement();
TransactionManagerFactory.setClockForTesting(new SystemClock());
}
@RetryingTest(4)
@@ -299,13 +302,13 @@ public class ReplicateToDatastoreActionTest {
// records.
action.run();
Truth8.assertThat(ofyTm().transact(() -> ofyTm().loadByKeyIfPresent(bar.key()))).isPresent();
assertThat(ofyTm().loadAllOf(ReplayGap.class).size()).isEqualTo(30);
assertThat(ofyTm().loadAllOf(ReplayGap.class)).hasSize(30);
// Verify that we can clean up this many gap records after expiration.
fakeClock.advanceBy(Duration.millis(ReplicateToDatastoreAction.MAX_GAP_RETENTION_MILLIS + 1));
resetAction();
action.run();
assertThat(ofyTm().loadAllOf(ReplayGap.class).size()).isEqualTo(0);
assertThat(ofyTm().loadAllOf(ReplayGap.class)).isEmpty();
}
@Test
@@ -366,8 +369,9 @@ public class ReplicateToDatastoreActionTest {
ImmutableSortedMap.<DateTime, MigrationState>naturalOrder()
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusHours(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusHours(2), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusHours(3), MigrationState.SQL_PRIMARY)
.put(START_OF_TIME.plusHours(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(START_OF_TIME.plusHours(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusHours(4), MigrationState.SQL_PRIMARY)
.put(now.plusHours(1), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(now.plusHours(2), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(now.plusHours(3), MigrationState.DATASTORE_PRIMARY)
@@ -391,6 +395,50 @@ public class ReplicateToDatastoreActionTest {
+ " DATASTORE_PRIMARY.");
}
@Test
void replicationWorksInReadOnly() {
// Put us in SQL primary now, readonly in an hour, then in datastore primary after 25 hours.
// And we'll need the TransactionManagerFactory to use the fake clock.
DateTime now = fakeClock.nowUtc();
TransactionManagerFactory.setClockForTesting(fakeClock);
jpaTm()
.transact(
() ->
DatabaseMigrationStateSchedule.set(
ImmutableSortedMap.<DateTime, MigrationState>naturalOrder()
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
.put(START_OF_TIME.plusHours(1), MigrationState.DATASTORE_PRIMARY)
.put(START_OF_TIME.plusHours(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
.put(START_OF_TIME.plusHours(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusHours(4), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(START_OF_TIME.plusHours(5), MigrationState.SQL_PRIMARY)
.put(now.plusHours(1), MigrationState.SQL_PRIMARY_READ_ONLY)
.put(now.plusHours(25), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.put(now.plusHours(26), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
.build()));
TestObject foo = TestObject.create("foo");
insertInDb(foo);
TestObject bar = TestObject.create("bar");
insertInDb(bar);
TestObject baz = TestObject.create("baz");
insertInDb(baz);
jpaTm().transact(() -> jpaTm().delete(baz.key()));
// get to read-only
fakeClock.advanceBy(Duration.standardDays(1));
// process the transaction in readonly.
action.run();
// Forward the next day (datastore primary). Verify that datastore has all of the changes.
fakeClock.advanceBy(Duration.standardDays(1));
assertThat(ofyTm().transact(() -> ofyTm().loadByKey(foo.key()))).isEqualTo(foo);
assertThat(ofyTm().transact(() -> ofyTm().loadByKey(bar.key()))).isEqualTo(bar);
assertThat(ofyTm().transact(() -> ofyTm().loadByKeyIfPresent(baz.key()).isPresent())).isFalse();
}
@Test
void testFailure_cannotAcquireLock() {
assumeTrue(ReplayExtension.replayTestsEnabled());

View File

@@ -47,7 +47,9 @@ public class DatabaseMigrationScheduleTransitionConverterTest {
MigrationState.DATASTORE_ONLY,
DateTime.parse("2001-01-01T00:00:00.0Z"),
MigrationState.DATASTORE_PRIMARY,
DateTime.parse("2002-01-01T00:00:00.0Z"),
DateTime.parse("2002-01-01T01:00:00.0Z"),
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
DateTime.parse("2002-01-01T02:00:00.0Z"),
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
DateTime.parse("2002-01-02T00:00:00.0Z"),
MigrationState.SQL_PRIMARY,

View File

@@ -16,6 +16,7 @@ package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
import google.registry.model.ImmutableObject;
@@ -23,6 +24,7 @@ import google.registry.model.replay.EntityTest.EntityForTesting;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -34,9 +36,11 @@ import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.PersistenceException;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.joda.money.CurrencyUnit;
import org.joda.money.IllegalCurrencyException;
import org.joda.money.Money;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -65,7 +69,7 @@ public class JodaMoneyConverterTest {
.createNativeQuery(
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
.getResultList());
assertThat(result.size()).isEqualTo(1);
assertThat(result).hasSize(1);
// The amount property, when loaded as a raw value, has the same scale as the table column,
// which is 2.
assertThat(Arrays.asList((Object[]) result.get(0)))
@@ -91,7 +95,7 @@ public class JodaMoneyConverterTest {
.createNativeQuery(
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
.getResultList());
assertThat(result.size()).isEqualTo(1);
assertThat(result).hasSize(1);
/* The amount property, when loaded as a raw value, has the same scale as the table column,
which is 2. */
assertThat(Arrays.asList((Object[]) result.get(0)))
@@ -136,7 +140,7 @@ public class JodaMoneyConverterTest {
"SELECT my_amount, my_currency, your_amount, your_currency FROM"
+ " \"ComplexTestEntity\" WHERE name = 'id'")
.getResultList());
assertThat(result.size()).isEqualTo(1);
assertThat(result).hasSize(1);
assertThat(Arrays.asList((Object[]) result.get(0)))
.containsExactly(
BigDecimal.valueOf(100).setScale(2), "USD", BigDecimal.valueOf(80).setScale(2), "GBP")
@@ -153,7 +157,7 @@ public class JodaMoneyConverterTest {
.getResultList());
ComplexTestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(ComplexTestEntity.class, "id"));
assertThat(result.size()).isEqualTo(1);
assertThat(result).hasSize(1);
assertThat(Arrays.asList((Object[]) result.get(0)))
.containsExactly(BigDecimal.valueOf(2000).setScale(2), "JPY")
@@ -164,6 +168,124 @@ public class JodaMoneyConverterTest {
assertThat(persisted.moneyMap).containsExactlyEntriesIn(moneyMap);
}
/**
* Implicit test cases for @override method @nullSafeGet when constructing {@link Money} object
* with null/invalid column(s).
*/
@Test
void testNullSafeGet_nullAmountNullCurrency_returnsNull() throws SQLException {
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createNativeQuery(
"INSERT INTO \"TestEntity\" (name, amount, currency) VALUES('id', null,"
+ " null)")
.executeUpdate());
List<?> result =
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createNativeQuery(
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
.getResultList());
assertThat(result).hasSize(1);
assertThat(Arrays.asList((Object[]) result.get(0))).containsExactly(null, null).inOrder();
assertThat(
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery("SELECT money FROM TestEntity WHERE name = 'id'")
.getResultList())
.get(0))
.isNull();
}
@Test
void testNullSafeGet_nullAMountValidCurrency_throwsHibernateException() throws SQLException {
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createNativeQuery(
"INSERT INTO \"TestEntity\" (name, amount, currency) VALUES('id', null,"
+ " 'USD')")
.executeUpdate());
List<?> result =
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createNativeQuery(
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
.getResultList());
assertThat(Arrays.asList((Object[]) result.get(0))).containsExactly(null, "USD");
// CurrencyUnit.of() throws HibernateException for invalid currency which leads to persistance
// error
PersistenceException thrown =
assertThrows(
PersistenceException.class,
() ->
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery("SELECT money FROM TestEntity WHERE name = 'id'")
.getResultList()));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"org.hibernate.HibernateException: Mismatching null state between currency 'USD' and"
+ " amount 'null'");
}
@Test
void testNullSafeGet_nullAMountInValidCurrency_throwsIllegalCurrencyException()
throws SQLException {
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createNativeQuery(
"INSERT INTO \"TestEntity\" (name, amount, currency) VALUES('id', 100,"
+ " 'INVALIDCURRENCY')")
.executeUpdate());
List<?> result =
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createNativeQuery(
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
.getResultList());
assertThat(result).hasSize(1);
assertThat(Arrays.asList((Object[]) result.get(0)))
.containsExactly(BigDecimal.valueOf(100).setScale(2), "INVALIDCURRENCY")
.inOrder();
IllegalCurrencyException thrown =
assertThrows(
IllegalCurrencyException.class,
() ->
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery("SELECT money FROM TestEntity WHERE name = 'id'")
.getResultList()));
assertThat(thrown).hasMessageThat().isEqualTo("Unknown currency 'INVALIDCURRENCY'");
}
// Override entity name to exclude outer-class name in table name. Not necessary if class is not
// inner class.
@Entity(name = "TestEntity")

View File

@@ -170,7 +170,7 @@ public class BouncyCastleTest {
try (ByteArrayInputStream input = new ByteArrayInputStream(signatureFileData)) {
PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
assertThat(sigList.size()).isEqualTo(1);
assertThat(sigList).hasSize(1);
sig = sigList.get(0);
}
@@ -212,8 +212,8 @@ public class BouncyCastleTest {
PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
PGPOnePassSignatureList onePassList = (PGPOnePassSignatureList) pgpFact.nextObject();
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
assertThat(onePassList.size()).isEqualTo(1);
assertThat(sigList.size()).isEqualTo(1);
assertThat(onePassList).hasSize(1);
assertThat(sigList).hasSize(1);
onePass = onePassList.get(0);
sig = sigList.get(0);
}
@@ -258,7 +258,7 @@ public class BouncyCastleTest {
try (ByteArrayInputStream input = new ByteArrayInputStream(encryptedData)) {
PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpFact.nextObject();
assertThat(encDataList.size()).isEqualTo(1);
assertThat(encDataList).hasSize(1);
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encDataList.get(0);
assertThat(encData.getKeyID()).isEqualTo(publicKey.getKeyID());
assertThat(encData.getKeyID()).isEqualTo(privateKey.getKeyID());
@@ -302,7 +302,7 @@ public class BouncyCastleTest {
try (ByteArrayInputStream input = new ByteArrayInputStream(encryptedData)) {
PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpFact.nextObject();
assertThat(encDataList.size()).isEqualTo(1);
assertThat(encDataList).hasSize(1);
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encDataList.get(0);
// Bob loads the private key to which the message is addressed.
PGPPrivateKey privateKey =
@@ -350,7 +350,7 @@ public class BouncyCastleTest {
try (ByteArrayInputStream input = new ByteArrayInputStream(encryptedData)) {
PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpFact.nextObject();
assertThat(encDataList.size()).isEqualTo(1);
assertThat(encDataList).hasSize(1);
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encDataList.get(0);
// Bob loads the private key to which the message is addressed.
PGPPrivateKey privateKey =

View File

@@ -53,7 +53,7 @@ class JsonResponseTest {
void testSetHeader() {
jsonResponse.setHeader("header", "value");
Map<String, Object> headerMap = fakeResponse.getHeaders();
assertThat(headerMap.size()).isEqualTo(1);
assertThat(headerMap).hasSize(1);
assertThat(headerMap.get("header")).isEqualTo("value");
}
}

View File

@@ -158,7 +158,7 @@ public class CloudTasksHelper implements Serializable {
*/
public void assertTasksEnqueued(String queueName, Collection<TaskMatcher> taskMatchers) {
List<Task> tasks = getTestTasksFor(queueName);
assertThat(tasks.size()).isEqualTo(taskMatchers.size());
assertThat(tasks).hasSize(taskMatchers.size());
for (final TaskMatcher taskMatcher : taskMatchers) {
try {
tasks.remove(tasks.stream().filter(taskMatcher).findFirst().get());

View File

@@ -1429,6 +1429,33 @@ public class DatabaseHelper {
return entity;
}
/**
* Sets a DATASTORE_PRIMARY_NO_ASYNC state on the {@link DatabaseMigrationStateSchedule}.
*
* <p>In order to allow for tests to manipulate the clock how they need, we start the transitions
* one millisecond after the clock's current time (in case the clock's current value is
* START_OF_TIME). We then advance the clock one second so that we're in the
* DATASTORE_PRIMARY_READ_ONLY phase.
*
* <p>We must use the current time, otherwise the setting of the migration state will fail due to
* an invalid transition.
*/
public static void setMigrationScheduleToDatastorePrimaryNoAsync(FakeClock fakeClock) {
DateTime now = fakeClock.nowUtc();
jpaTm()
.transact(
() ->
DatabaseMigrationStateSchedule.set(
ImmutableSortedMap.of(
START_OF_TIME,
MigrationState.DATASTORE_ONLY,
now.plusMillis(1),
MigrationState.DATASTORE_PRIMARY,
now.plusMillis(2),
MigrationState.DATASTORE_PRIMARY_NO_ASYNC)));
fakeClock.advanceBy(Duration.standardSeconds(1));
}
/**
* Sets a DATASTORE_PRIMARY_READ_ONLY state on the {@link DatabaseMigrationStateSchedule}.
*
@@ -1452,6 +1479,8 @@ public class DatabaseHelper {
now.plusMillis(1),
MigrationState.DATASTORE_PRIMARY,
now.plusMillis(2),
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
now.plusMillis(3),
MigrationState.DATASTORE_PRIMARY_READ_ONLY)));
fakeClock.advanceBy(Duration.standardSeconds(1));
}
@@ -1478,8 +1507,10 @@ public class DatabaseHelper {
now.plusMillis(1),
MigrationState.DATASTORE_PRIMARY,
now.plusMillis(2),
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
now.plusMillis(3),
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
now.plusMillis(4),
MigrationState.SQL_PRIMARY)));
fakeClock.advanceBy(Duration.standardSeconds(1));
}

View File

@@ -37,6 +37,12 @@ class CheckDomainClaimsCommandTest extends EppToolCommandTestCase<CheckDomainCla
eppVerifier.expectDryRun().verifySent("domain_check_claims.xml");
}
@Test
void testSuccess_canonicalizesInput() throws Exception {
runCommand("--client=NewRegistrar", "exaMPle.TLD");
eppVerifier.expectDryRun().verifySent("domain_check_claims.xml");
}
@Test
void testSuccess_multipleTlds() throws Exception {
runCommand("--client=NewRegistrar", "example.tld", "example.tld2");

View File

@@ -29,7 +29,7 @@ class CreateHostCommandTest extends EppToolCommandTestCase<CreateHostCommand> {
createTld("tld");
runCommandForced(
"--client=NewRegistrar",
"--host=example.tld",
"--host=example.TLD",
"--addresses=162.100.102.99,2001:0db8:85a3:0000:0000:8a2e:0370:7334,4.5.6.7");
eppVerifier.verifySent("host_create_complete.xml");
}

View File

@@ -15,6 +15,7 @@
package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.createTlds;
@@ -546,7 +547,7 @@ public final class DomainLockUtilsTest {
assertThat(loadByEntity(domain).getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES);
ImmutableList<DomainHistory> historyEntries =
getHistoryEntriesOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE, DomainHistory.class);
assertThat(historyEntries.size()).isEqualTo(2);
assertThat(historyEntries).hasSize(2);
historyEntries.forEach(
entry -> {
assertThat(entry.getRequestedByRegistrar()).isEqualTo(!isAdmin);

View File

@@ -55,7 +55,8 @@ class EnqueuePollMessageCommandTest extends CommandTestCase<EnqueuePollMessageCo
@TestOfyAndSql
void testSuccess_domainAndMessage() throws Exception {
runCommandForced("--domain=example.tld", "--message=This domain is bad");
// Test canonicalization to lowercase example.tld as well.
runCommandForced("--domain=EXAMPLE.TLD", "--message=This domain is bad");
HistoryEntry synthetic = getOnlyHistoryEntryOfType(domain, SYNTHETIC);
assertAboutHistoryEntries()

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