1
0
mirror of https://github.com/google/nomulus synced 2026-05-20 06:41:51 +00:00

Compare commits

...

21 Commits

Author SHA1 Message Date
Rachel Guan
d0af81ecdf Change from assertThat(assertThrow()) to thrown = assertThrows() then assertThat(thrown) (#1606) 2022-04-28 16:09:36 -04:00
Michael Muller
f273783894 Ignore version UIDs during txn deserialization (#1607)
* Ignore version UIDs during txn deserialization

When deserializing transactions for replay to datastore, ignore class version
UIDs that don't match those of the local classes and just use the local class
descriptors instead.  This is a simple solution for the problem of persisted
VKeys containing references to classes where the class has been updated and
the serial version UID has changed.

Also add a "replay_txns" command that replays the transactions from a given
start point so we can verify all transactions are deserializable.

TESTED:
    Ran replay_txns against all transactions on sandbox beginning with
    transaction id 1828385, which includes Recurring billing events containing
    both the old and the new serial version UIDs.
2022-04-27 15:40:27 -04:00
Ben McIlwain
0dfabe1c64 Convert more Guava caches to Caffeine (#1603)
* Convert more Guava caches to Caffeine
2022-04-26 11:26:51 -04:00
Weimin Yu
60a011c593 Remove stray file that slipped in the repo (#1604)
* Remove stray file that slipped in the repo
2022-04-25 17:08:58 -04:00
Michael Muller
7716eebfff Change check for root directory during rollback (#1602)
* Change check for root directory during rollback

`rollback_tool` tries to infer the root of the nomulus tree by checking for a
directory named "nomulus".  This is potentially problematic (and, indeed, was
for me) since there is no guarantee what that directory will be named.

There are a number of features that characterize the root directory.  Check
for the presence of the `rollback_tool` wrapper script, as this is both at
root level and tightly coupled to the python code, so hopefully we won't
move it without testing that the script still works.
2022-04-25 12:39:16 -04:00
Ben McIlwain
1e76eeed37 Validate that a registrar has billing accounts for all its allowed TLDs (#1601)
This will require edits to a substantial number of registrars on sandbox (nearly
all of them) because almost all of them have access to at least one TLD, but
almost none of them have any billing accounts set. Until this is set, any updates
to the existing registrars that aren't adding the billing accounts will cause
failures.

Unfortunately, there wasn't any less invasive foolproof way to implement this
change, and we already had one attempt to implement it on create registrar
command that wasn't working (because allowed TLDs tend not to be added on
initial registrar creation, but rather, afterwards as an update).
2022-04-22 16:33:18 -04:00
sarahcaseybot
147d133aef Don't fail invoicing on missing PAK (#1595)
* Don't fail invoicing on missing PAK

* Skip line if missing PAK

* Add log check in test
2022-04-22 13:00:50 -04:00
Ben McIlwain
c2e1f2e640 Downgrade Caffeine to 2.9.3 (#1600)
* Downgrade Caffeine to 2.9.3

Apparently Caffeine >=3.* requires Java 11, and we're still stuck on Java 8
because of App Engine Standard.  Fortunately this doesn't affect the exposed
interface we're using, so we can simply go back to the newest Caffeine version
once Registry 3.0 Phase 3 (GKE migration) is completed.
2022-04-20 14:05:37 -04:00
Lai Jiang
5d2639834a Remove the BEAM RDE pipeline side job (#1599)
Now that SQL is the default, we do not need this side job to run
alongside the main one. Its purpose was to validate the BEAM pipeline
while Datastore was primary.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/1599)
<!-- Reviewable:end -->
2022-04-20 12:12:23 -04:00
Rachel Guan
7912576e3d Add rachelguan@ to CONTRIBUTORS (#1598) 2022-04-19 19:18:44 -04:00
Rachel Guan
8424c85258 Fix build warning for unused variable (#1594) 2022-04-19 14:11:23 -04:00
Lai Jiang
e72dd73ed8 Fix build (#1597) 2022-04-19 08:27:06 -04:00
Rachel Guan
508d221b94 Replace .get()).isEqualTo() with hasValue() (#1589)
* Replace .get()).isEqualTo() with hasValue()

* Use containsExactly for list comparison

* Fix spacing
2022-04-18 18:35:46 -04:00
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
198 changed files with 2386 additions and 554 deletions

View File

@@ -36,3 +36,4 @@ Shicong Huang <shicong@google.com>
Gustav Brodman <gbrodman@google.com>
Sarah Botwinick <sarahbot@google.com>
Legina Chen <legina@google.com>
Rachel Guan <rachelguan@google.com>

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

@@ -51,6 +51,7 @@ artifacts {
dependencies {
def deps = rootProject.dependencyMap
compile deps['com.github.ben-manes.caffeine:caffeine']
compile deps['com.google.code.findbugs:jsr305']
compile deps['com.google.guava:guava']
compile deps['javax.inject:javax.inject']

View File

@@ -1,12 +1,13 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
org.checkerframework:checker-qual:3.8.0
org.checkerframework:checker-qual:3.19.0

View File

@@ -1,12 +1,13 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
org.checkerframework:checker-qual:3.8.0
org.checkerframework:checker-qual:3.19.0

View File

@@ -1,12 +1,13 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
org.checkerframework:checker-qual:3.8.0
org.checkerframework:checker-qual:3.19.0

View File

@@ -1,12 +1,13 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
org.checkerframework:checker-qual:3.8.0
org.checkerframework:checker-qual:3.19.0

View File

@@ -1,12 +1,13 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
org.checkerframework:checker-qual:3.8.0
org.checkerframework:checker-qual:3.19.0

View File

@@ -1,12 +1,13 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
org.checkerframework:checker-qual:3.8.0
org.checkerframework:checker-qual:3.19.0

View File

@@ -1,9 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.auto.value:auto-value-annotations:1.7.4
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.flogger:flogger:0.7.4
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
@@ -16,7 +17,7 @@ joda-time:joda-time:2.9.2
junit:junit:4.13.1
org.apiguardian:apiguardian-api:1.1.0
org.checkerframework:checker-compat-qual:2.5.3
org.checkerframework:checker-qual:3.9.1
org.checkerframework:checker-qual:3.19.0
org.hamcrest:hamcrest-core:1.3
org.junit.jupiter:junit-jupiter-api:5.6.2
org.junit.jupiter:junit-jupiter-engine:5.6.2

View File

@@ -1,9 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.auto.value:auto-value-annotations:1.7.4
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.flogger:flogger:0.7.4
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
@@ -16,7 +17,7 @@ joda-time:joda-time:2.9.2
junit:junit:4.13.1
org.apiguardian:apiguardian-api:1.1.0
org.checkerframework:checker-compat-qual:2.5.3
org.checkerframework:checker-qual:3.9.1
org.checkerframework:checker-qual:3.19.0
org.hamcrest:hamcrest-core:1.3
org.junit.jupiter:junit-jupiter-api:5.6.2
org.junit.jupiter:junit-jupiter-engine:5.6.2

View File

@@ -1,9 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.auto.value:auto-value-annotations:1.7.4
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.flogger:flogger-system-backend:0.7.4
com.google.flogger:flogger:0.7.4
com.google.guava:failureaccess:1.0.1
@@ -17,7 +18,7 @@ joda-time:joda-time:2.9.2
junit:junit:4.13.1
org.apiguardian:apiguardian-api:1.1.0
org.checkerframework:checker-compat-qual:2.5.3
org.checkerframework:checker-qual:3.9.1
org.checkerframework:checker-qual:3.19.0
org.hamcrest:hamcrest-core:1.3
org.junit.jupiter:junit-jupiter-api:5.6.2
org.junit.jupiter:junit-jupiter-engine:5.6.2

View File

@@ -1,9 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.auto.value:auto-value-annotations:1.7.4
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.flogger:flogger-system-backend:0.7.4
com.google.flogger:flogger:0.7.4
com.google.guava:failureaccess:1.0.1
@@ -17,7 +18,7 @@ joda-time:joda-time:2.9.2
junit:junit:4.13.1
org.apiguardian:apiguardian-api:1.1.0
org.checkerframework:checker-compat-qual:2.5.3
org.checkerframework:checker-qual:3.9.1
org.checkerframework:checker-qual:3.19.0
org.hamcrest:hamcrest-core:1.3
org.junit.jupiter:junit-jupiter-api:5.6.2
org.junit.jupiter:junit-jupiter-engine:5.6.2

View File

@@ -1,9 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.auto.value:auto-value-annotations:1.7.4
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.flogger:flogger:0.7.4
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
@@ -15,6 +16,6 @@ javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
junit:junit:4.13.1
org.checkerframework:checker-compat-qual:2.5.3
org.checkerframework:checker-qual:3.9.1
org.checkerframework:checker-qual:3.19.0
org.hamcrest:hamcrest-core:1.3
org.ow2.asm:asm:9.0

View File

@@ -1,9 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.auto.value:auto-value-annotations:1.7.4
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.flogger:flogger:0.7.4
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1.1-jre
@@ -15,6 +16,6 @@ javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
junit:junit:4.13.1
org.checkerframework:checker-compat-qual:2.5.3
org.checkerframework:checker-qual:3.9.1
org.checkerframework:checker-qual:3.19.0
org.hamcrest:hamcrest-core:1.3
org.ow2.asm:asm:9.0

View File

@@ -1,9 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.auto.value:auto-value-annotations:1.7.4
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.flogger:flogger-system-backend:0.7.4
com.google.flogger:flogger:0.7.4
com.google.guava:failureaccess:1.0.1
@@ -16,6 +17,6 @@ javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
junit:junit:4.13.1
org.checkerframework:checker-compat-qual:2.5.3
org.checkerframework:checker-qual:3.9.1
org.checkerframework:checker-qual:3.19.0
org.hamcrest:hamcrest-core:1.3
org.ow2.asm:asm:9.0

View File

@@ -1,9 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3
com.google.auto.value:auto-value-annotations:1.7.4
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.5.1
com.google.errorprone:error_prone_annotations:2.10.0
com.google.flogger:flogger-system-backend:0.7.4
com.google.flogger:flogger:0.7.4
com.google.guava:failureaccess:1.0.1
@@ -16,6 +17,6 @@ javax.inject:javax.inject:1
joda-time:joda-time:2.9.2
junit:junit:4.13.1
org.checkerframework:checker-compat-qual:2.5.3
org.checkerframework:checker-qual:3.9.1
org.checkerframework:checker-qual:3.19.0
org.hamcrest:hamcrest-core:1.3
org.ow2.asm:asm:9.0

View File

@@ -14,9 +14,9 @@
package google.registry.testing;
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.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.flogger.FluentLogger;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@@ -29,7 +29,7 @@ public final class SystemInfo {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final LoadingCache<String, Boolean> hasCommandCache =
CacheBuilder.newBuilder()
Caffeine.newBuilder()
.build(
new CacheLoader<String, Boolean>() {
@Override

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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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:2.9.3
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

@@ -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

@@ -14,11 +14,11 @@
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;
import com.google.common.flogger.FluentLogger;
import google.registry.beam.common.RegistryJpaIO;
import google.registry.beam.common.RegistryJpaIO.Read;
import google.registry.beam.invoicing.BillingEvent.InvoiceGroupingKey;
@@ -36,6 +36,7 @@ import java.time.LocalTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import org.apache.beam.sdk.Pipeline;
@@ -75,6 +76,8 @@ public class InvoicingPipeline implements Serializable {
private static final Pattern SQL_COMMENT_REGEX =
Pattern.compile("^\\s*--.*\\n", Pattern.MULTILINE);
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final InvoicingPipelineOptions options;
InvoicingPipeline(InvoicingPipelineOptions options) {
@@ -115,38 +118,44 @@ public class InvoicingPipeline implements Serializable {
InvoicingPipelineOptions options, Pipeline pipeline) {
Read<Object[], BillingEvent> read =
RegistryJpaIO.read(
makeCloudSqlQuery(options.getYearMonth()), false, InvoicingPipeline::parseRow);
makeCloudSqlQuery(options.getYearMonth()), false, row -> parseRow(row).orElse(null));
return pipeline.apply("Read BillingEvents from Cloud SQL", read);
PCollection<BillingEvent> billingEventsWithNulls =
pipeline.apply("Read BillingEvents from Cloud SQL", read);
// Remove null billing events
return billingEventsWithNulls.apply(Filter.by(Objects::nonNull));
}
private static BillingEvent parseRow(Object[] row) {
private static Optional<BillingEvent> parseRow(Object[] row) {
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);
if (!registrar.getBillingAccountMap().containsKey(currency)) {
logger.atSevere().log(
"Registrar %s does not have a product account key for the currency unit: %s",
registrar.getRegistrarId(), currency);
return Optional.empty();
}
return BillingEvent.create(
oneTime.getId(),
DateTimeUtils.toZonedDateTime(oneTime.getBillingTime(), ZoneId.of("UTC")),
DateTimeUtils.toZonedDateTime(oneTime.getEventTime(), ZoneId.of("UTC")),
registrar.getRegistrarId(),
registrar.getBillingAccountMap().get(currency),
registrar.getPoNumber().orElse(""),
DomainNameUtils.getTldFromDomainName(oneTime.getTargetId()),
oneTime.getReason().toString(),
oneTime.getTargetId(),
oneTime.getDomainRepoId(),
Optional.ofNullable(oneTime.getPeriodYears()).orElse(0),
oneTime.getCost().getCurrencyUnit().toString(),
oneTime.getCost().getAmount().doubleValue(),
String.join(
" ", oneTime.getFlags().stream().map(Flag::toString).collect(toImmutableSet())));
return Optional.of(
BillingEvent.create(
oneTime.getId(),
DateTimeUtils.toZonedDateTime(oneTime.getBillingTime(), ZoneId.of("UTC")),
DateTimeUtils.toZonedDateTime(oneTime.getEventTime(), ZoneId.of("UTC")),
registrar.getRegistrarId(),
registrar.getBillingAccountMap().get(currency),
registrar.getPoNumber().orElse(""),
DomainNameUtils.getTldFromDomainName(oneTime.getTargetId()),
oneTime.getReason().toString(),
oneTime.getTargetId(),
oneTime.getDomainRepoId(),
Optional.ofNullable(oneTime.getPeriodYears()).orElse(0),
oneTime.getCost().getCurrencyUnit().toString(),
oneTime.getCost().getAmount().doubleValue(),
String.join(
" ", oneTime.getFlags().stream().map(Flag::toString).collect(toImmutableSet()))));
}
/** Transform that converts a {@code BillingEvent} into an invoice CSV row. */

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

@@ -36,19 +36,6 @@
<target>backend</target>
</cron>
<cron>
<url>/_dr/task/rdeStaging?beam=true</url>
<description>
This job generates a full RDE escrow deposit as a single gigantic XML
document using the Beam pipeline regardless of the current TM
configuration and streams it to cloud storage. It does not trigger the
subsequent upload tasks and is meant to run parallel with the main cron
job in order to compare the results from both runs.
</description>
<schedule>every 8 hours from 00:07 to 20:00</schedule>
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
<description>

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

@@ -21,11 +21,9 @@ import static com.google.common.collect.Maps.transformValues;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
@@ -59,36 +57,33 @@ public class ModelUtils {
/** Caches all instance fields on an object, including non-public and inherited fields. */
private static final LoadingCache<Class<?>, ImmutableMap<String, Field>> ALL_FIELDS_CACHE =
CacheBuilder.newBuilder()
CacheUtils.newCacheBuilder()
.build(
new CacheLoader<Class<?>, ImmutableMap<String, Field>>() {
@Override
public ImmutableMap<String, Field> load(Class<?> clazz) {
Deque<Class<?>> hierarchy = new ArrayDeque<>();
// Walk the hierarchy up to but not including ImmutableObject (to ignore
// hashCode).
for (; clazz != ImmutableObject.class; clazz = clazz.getSuperclass()) {
// Add to the front, so that shadowed fields show up later in the list.
// This will mean that getFieldValues will show the most derived value.
hierarchy.addFirst(clazz);
}
Map<String, Field> fields = new LinkedHashMap<>();
for (Class<?> hierarchyClass : hierarchy) {
// Don't use hierarchyClass.getFields() because it only picks up public fields.
for (Field field : hierarchyClass.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
fields.put(field.getName(), field);
}
clazz -> {
Deque<Class<?>> hierarchy = new ArrayDeque<>();
// Walk the hierarchy up to but not including ImmutableObject (to ignore
// hashCode).
for (; clazz != ImmutableObject.class; clazz = clazz.getSuperclass()) {
// Add to the front, so that shadowed fields show up later in the list.
// This will mean that getFieldValues will show the most derived value.
hierarchy.addFirst(clazz);
}
Map<String, Field> fields = new LinkedHashMap<>();
for (Class<?> hierarchyClass : hierarchy) {
// Don't use hierarchyClass.getFields() because it only picks up public fields.
for (Field field : hierarchyClass.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
fields.put(field.getName(), field);
}
}
return ImmutableMap.copyOf(fields);
}
return ImmutableMap.copyOf(fields);
});
/** Lists all instance fields on an object, including non-public and inherited fields. */
public static Map<String, Field> getAllFields(Class<?> clazz) {
return ALL_FIELDS_CACHE.getUnchecked(clazz);
return ALL_FIELDS_CACHE.get(clazz);
}
/** Return a string representing the persisted schema of a type or enum. */

View File

@@ -38,6 +38,7 @@ import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldState;
import google.registry.model.tld.Registry.TldType;
import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao;
import google.registry.persistence.VKey;
@@ -111,6 +112,17 @@ public final class OteAccountBuilder {
DateTime.parse("2030-03-01T00:00:00Z"),
Money.of(CurrencyUnit.USD, 0));
/**
* The default billing account map applied to all OT&amp;E registrars.
*
* <p>This contains dummy values for USD and JPY so that OT&amp;E registrars can be granted access
* to all existing TLDs in sandbox. Note that OT&amp;E is only on sandbox and thus these dummy
* values will never be used in production (the only environment where real invoicing takes
* place).
*/
public static final ImmutableMap<CurrencyUnit, String> DEFAULT_BILLING_ACCOUNT_MAP =
ImmutableMap.of(CurrencyUnit.USD, "123", CurrencyUnit.JPY, "456");
private final ImmutableMap<String, String> registrarIdToTld;
private final Registry sunriseTld;
private final Registry gaTld;
@@ -305,6 +317,7 @@ public final class OteAccountBuilder {
.setTldStateTransitions(ImmutableSortedMap.of(START_OF_TIME, initialTldState))
.setDnsWriters(ImmutableSet.of("VoidDnsWriter"))
.setPremiumList(premiumList.get())
.setTldType(TldType.TEST)
.setRoidSuffix(
String.format(
"%S%X",
@@ -334,6 +347,7 @@ public final class OteAccountBuilder {
.setPhoneNumber("+1.2125550100")
.setIcannReferralEmail("nightmare@registrar.test")
.setState(Registrar.State.ACTIVE)
.setBillingAccountMap(DEFAULT_BILLING_ACCOUNT_MAP)
.build();
}

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

@@ -18,14 +18,13 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryEnvironment;
import google.registry.model.CacheUtils;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.model.replay.SqlOnlyEntity;
@@ -135,18 +134,8 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
TimedTransitionProperty<MigrationState, MigrationStateTransition>>
// Each instance should cache the migration schedule for five minutes before reloading
CACHE =
CacheBuilder.newBuilder()
.expireAfterWrite(Duration.ofMinutes(5))
.build(
new CacheLoader<
Class<DatabaseMigrationStateSchedule>,
TimedTransitionProperty<MigrationState, MigrationStateTransition>>() {
@Override
public TimedTransitionProperty<MigrationState, MigrationStateTransition> load(
Class<DatabaseMigrationStateSchedule> unused) {
return DatabaseMigrationStateSchedule.getUncached();
}
});
CacheUtils.newCacheBuilder(Duration.ofMinutes(5))
.build(singletonClazz -> DatabaseMigrationStateSchedule.getUncached());
// Restrictions on the state transitions, e.g. no going from DATASTORE_ONLY to SQL_ONLY
private static final ImmutableMultimap<MigrationState, MigrationState> VALID_STATE_TRANSITIONS =
@@ -235,7 +224,7 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
/** Loads the currently-set migration schedule from the cache, or the default if none exists. */
public static TimedTransitionProperty<MigrationState, MigrationStateTransition> get() {
return CACHE.getUnchecked(DatabaseMigrationStateSchedule.class);
return CACHE.get(DatabaseMigrationStateSchedule.class);
}
/** Returns the database migration status at the given time. */

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

@@ -81,6 +81,7 @@ import google.registry.model.common.EntityGroupRoot;
import google.registry.model.registrar.Registrar.BillingAccountEntry.CurrencyMapper;
import google.registry.model.replay.DatastoreAndSqlEntity;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldType;
import google.registry.persistence.VKey;
import google.registry.util.CidrAddressBlock;
import java.security.cert.CertificateParsingException;
@@ -798,13 +799,9 @@ public class Registrar extends ImmutableObject
}
public Builder setBillingAccountMap(@Nullable Map<CurrencyUnit, String> billingAccountMap) {
if (billingAccountMap == null) {
getInstance().billingAccountMap = null;
} else {
getInstance().billingAccountMap =
billingAccountMap.entrySet().stream()
.collect(toImmutableMap(Map.Entry::getKey, BillingAccountEntry::new));
}
getInstance().billingAccountMap =
nullToEmptyImmutableCopy(billingAccountMap).entrySet().stream()
.collect(toImmutableMap(Map.Entry::getKey, BillingAccountEntry::new));
return this;
}
@@ -1015,6 +1012,20 @@ public class Registrar extends ImmutableObject
String.format(
"Supplied IANA ID is not valid for %s registrar type: %s",
getInstance().type, getInstance().ianaIdentifier));
// In order to grant access to real TLDs, the registrar must have a corresponding billing
// account ID for that TLD's billing currency.
ImmutableSet<String> nonBillableTlds =
Registry.get(getInstance().getAllowedTlds()).stream()
.filter(r -> r.getTldType() == TldType.REAL)
.filter(r -> !getInstance().getBillingAccountMap().containsKey(r.getCurrency()))
.map(Registry::getTldStr)
.collect(toImmutableSet());
checkArgument(
nonBillableTlds.isEmpty(),
"Cannot set these allowed, real TLDs because their currency is missing "
+ "from the billing account map: %s",
nonBillableTlds);
return cloneEmptyToNull(super.build());
}
}

View File

@@ -16,15 +16,14 @@ package google.registry.model.server;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.primitives.Longs;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.annotation.Unindex;
import google.registry.model.CacheUtils;
import google.registry.model.annotations.NotBackedUp;
import google.registry.model.annotations.NotBackedUp.Reason;
import google.registry.model.common.CrossTldSingleton;
@@ -32,7 +31,6 @@ import google.registry.model.replay.NonReplicatedEntity;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import javax.persistence.Column;
import javax.persistence.PostLoad;
import javax.persistence.Transient;
@@ -52,14 +50,7 @@ public class ServerSecret extends CrossTldSingleton implements NonReplicatedEnti
* Supplier that can be reset for testing purposes.
*/
private static final LoadingCache<Class<ServerSecret>, ServerSecret> CACHE =
CacheBuilder.newBuilder()
.build(
new CacheLoader<Class<ServerSecret>, ServerSecret>() {
@Override
public ServerSecret load(Class<ServerSecret> unused) {
return retrieveAndSaveSecret();
}
});
CacheUtils.newCacheBuilder().build(singletonClazz -> retrieveAndSaveSecret());
private static ServerSecret retrieveAndSaveSecret() {
if (tm().isOfy()) {
@@ -84,11 +75,7 @@ public class ServerSecret extends CrossTldSingleton implements NonReplicatedEnti
/** Returns the global ServerSecret instance, creating it if one isn't already in Datastore. */
public static ServerSecret get() {
try {
return CACHE.get(ServerSecret.class);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
return CACHE.get(ServerSecret.class);
}
/** Most significant 8 bytes of the UUID value (stored separately for legacy purposes). */

View File

@@ -72,7 +72,7 @@ public final class Registries {
.stream()
.map(Key::getName)
.collect(toImmutableSet());
return Registry.getAll(tlds).stream()
return Registry.get(tlds).stream()
.map(e -> Maps.immutableEntry(e.getTldStr(), e.getTldType()))
.collect(entriesToImmutableMap());
} else {
@@ -105,7 +105,7 @@ public final class Registries {
/** Returns the Registry entities themselves of the given type loaded fresh from Datastore. */
public static ImmutableSet<Registry> getTldEntitiesOfType(TldType type) {
return Registry.getAll(filterValues(cache.get(), equalTo(type)).keySet());
return Registry.get(filterValues(cache.get(), equalTo(type)).keySet());
}
/** Pass-through check that the specified TLD exists, otherwise throw an IAE. */

View File

@@ -142,10 +142,16 @@ public class Registry extends ImmutableObject
/** The type of TLD, which determines things like backups and escrow policy. */
public enum TldType {
/** A real, official TLD. */
/**
* A real, official TLD (but not necessarily only on production).
*
* <p>Note that, to avoid unnecessary costly DB writes, {@link
* google.registry.model.reporting.DomainTransactionRecord}s are only written out for REAL TLDs
* (these transaction records are only used for ICANN reporting purposes).
*/
REAL,
/** A test TLD, for the prober. */
/** A test TLD, for the prober, OT&amp;E, and other testing purposes. */
TEST
}
@@ -232,7 +238,7 @@ public class Registry extends ImmutableObject
}
/** Returns the registry entities for the given TLD strings, throwing if any don't exist. */
static ImmutableSet<Registry> getAll(Set<String> tlds) {
public static ImmutableSet<Registry> get(Set<String> tlds) {
try {
ImmutableMap<String, Optional<Registry>> registries = CACHE.getAll(tlds);
ImmutableSet<String> missingRegistries =
@@ -264,8 +270,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
@@ -1001,7 +1006,7 @@ public class Registry extends ImmutableObject
instance.renewBillingCostTransitions.checkValidity();
instance.eapFeeSchedule.checkValidity();
// All costs must be in the expected currency.
// TODO(b/21854155): When we move PremiumList into Datastore, verify its currency too.
checkArgumentNotNull(instance.getCurrency(), "Currency must be set");
checkArgument(
instance.getStandardCreateCost().getCurrencyUnit().equals(instance.currency),
"Create cost must be in the registry's currency");

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

@@ -33,8 +33,10 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
/**
* A SQL transaction that can be serialized and stored in its own table.
@@ -105,7 +107,8 @@ public class Transaction extends ImmutableObject implements Buildable {
}
public static Transaction deserialize(byte[] serializedTransaction) throws IOException {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(serializedTransaction));
ObjectInputStream in =
new LenientObjectInputStream(new ByteArrayInputStream(serializedTransaction));
// Verify that the data is what we expect.
int version = in.readInt();
@@ -304,4 +307,35 @@ public class Transaction extends ImmutableObject implements Buildable {
}
}
}
/**
* ObjectInputStream that ignores the UIDs of serialized objects.
*
* <p>We only really need to deserialize VKeys. However, VKeys have a class object associated with
* them, and if the class is changed and we haven't defined a serialVersionUID for it, we get an
* exception during deserialization.
*
* <p>It's safe for us to ignore this condition: we only care about attaching the correct local
* class object to the VKey. So this class effectively does so by replacing the class descriptor
* if it's version UID doesn't match that of the local class.
*/
private static class LenientObjectInputStream extends ObjectInputStream {
public LenientObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass persistedDescriptor = super.readClassDescriptor();
Class localClass = Class.forName(persistedDescriptor.getName());
ObjectStreamClass localDescriptor = ObjectStreamClass.lookup(localClass);
if (localDescriptor != null) {
if (persistedDescriptor.getSerialVersionUID() != localDescriptor.getSerialVersionUID()) {
return localDescriptor;
}
}
return persistedDescriptor;
}
}
}

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

@@ -24,14 +24,11 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameter;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.tld.Registry;
import google.registry.tools.params.KeyValueMapParameter.CurrencyUnitToStringMap;
import google.registry.tools.params.OptionalLongParameter;
import google.registry.tools.params.OptionalPhoneNumberParameter;
@@ -46,7 +43,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.joda.money.CurrencyUnit;
@@ -433,22 +429,6 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
// Require a phone passcode.
checkArgument(
newRegistrar.getPhonePasscode() != null, "--passcode is required for REAL registrars.");
// Check if registrar has billing account IDs for the currency of the TLDs that it is
// allowed to register.
ImmutableSet<CurrencyUnit> tldCurrencies =
newRegistrar
.getAllowedTlds()
.stream()
.map(tld -> Registry.get(tld).getCurrency())
.collect(toImmutableSet());
Set<CurrencyUnit> currenciesWithoutBillingAccountId =
newRegistrar.getBillingAccountMap() == null
? tldCurrencies
: Sets.difference(tldCurrencies, newRegistrar.getBillingAccountMap().keySet());
checkArgument(
currenciesWithoutBillingAccountId.isEmpty(),
"Need billing account map entries for currencies: %s",
Joiner.on(' ').join(currenciesWithoutBillingAccountId));
}
stageEntityChange(oldRegistrar, newRegistrar);

View File

@@ -15,6 +15,7 @@
package google.registry.tools;
import com.google.common.collect.ImmutableMap;
import google.registry.tools.javascrap.BackfillRegistrarBillingAccountsCommand;
import google.registry.tools.javascrap.CompareEscrowDepositsCommand;
import google.registry.tools.javascrap.HardDeleteHostCommand;
@@ -30,6 +31,7 @@ public final class RegistryTool {
public static final ImmutableMap<String, Class<? extends Command>> COMMAND_MAP =
new ImmutableMap.Builder<String, Class<? extends Command>>()
.put("ack_poll_messages", AckPollMessagesCommand.class)
.put("backfill_registrar_billing_accounts", BackfillRegistrarBillingAccountsCommand.class)
.put("canonicalize_labels", CanonicalizeLabelsCommand.class)
.put("check_domain", CheckDomainCommand.class)
.put("check_domain_claims", CheckDomainClaimsCommand.class)
@@ -102,6 +104,7 @@ public final class RegistryTool {
.put("registrar_contact", RegistrarContactCommand.class)
.put("remove_registry_one_key", RemoveRegistryOneKeyCommand.class)
.put("renew_domain", RenewDomainCommand.class)
.put("replay_txns", ReplayTxnsCommand.class)
.put("resave_entities", ResaveEntitiesCommand.class)
.put("resave_environment_entities", ResaveEnvironmentEntitiesCommand.class)
.put("resave_epp_resource", ResaveEppResourceCommand.class)

View File

@@ -43,6 +43,7 @@ import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UrlFetchServiceModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.tools.AuthModule.LocalCredentialModule;
import google.registry.tools.javascrap.BackfillRegistrarBillingAccountsCommand;
import google.registry.tools.javascrap.CompareEscrowDepositsCommand;
import google.registry.tools.javascrap.HardDeleteHostCommand;
import google.registry.util.UtilsModule;
@@ -90,6 +91,8 @@ import javax.inject.Singleton;
interface RegistryToolComponent {
void inject(AckPollMessagesCommand command);
void inject(BackfillRegistrarBillingAccountsCommand command);
void inject(CheckDomainClaimsCommand command);
void inject(CheckDomainCommand command);

View File

@@ -0,0 +1,59 @@
// 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.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.persistence.transaction.Transaction;
import google.registry.persistence.transaction.TransactionEntity;
import java.util.List;
@Parameters(separators = " =", commandDescription = "Replay a range of transactions.")
public class ReplayTxnsCommand implements CommandWithRemoteApi {
private static final int BATCH_SIZE = 200;
@Parameter(
names = {"-s", "--start-txn-id"},
description = "Transaction id to start replaying at.",
required = true)
long startTxnId;
@Override
public void run() throws Exception {
List<TransactionEntity> txns;
do {
txns =
replicaJpaTm()
.transact(
() ->
replicaJpaTm()
.query(
"SELECT txn FROM TransactionEntity txn where id >= :startTxn ORDER"
+ " BY id",
TransactionEntity.class)
.setParameter("startTxn", startTxnId)
.setMaxResults(BATCH_SIZE)
.getResultList());
for (TransactionEntity txn : txns) {
System.out.println("Replaying transaction " + txn.getId());
Transaction.deserialize(txn.getContents());
startTxnId = txn.getId() + 1;
}
} while (txns.size() > 0);
}
}

View File

@@ -0,0 +1,56 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools.javascrap;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.OteAccountBuilder.DEFAULT_BILLING_ACCOUNT_MAP;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameters;
import google.registry.config.RegistryEnvironment;
import google.registry.model.registrar.Registrar;
import google.registry.tools.CommandWithRemoteApi;
/**
* Backfills the billing account maps on all Registrars that don't have any set.
*
* <p>This should not (and cannot) be used on production. Its purpose is to backfill these values on
* sandbox, where most registrars have an empty billing account map, including all that were created
* for OT&amp;E. The same default billing account map is used for all registrars, and includes
* values for USD and JPY. The actual values here don't matter as we don't do invoicing on sandbox
* anyway.
*/
@Parameters(separators = " =", commandDescription = "Backfill registrar billing account maps.")
public class BackfillRegistrarBillingAccountsCommand implements CommandWithRemoteApi {
@Override
public void run() throws Exception {
checkState(
RegistryEnvironment.get() != RegistryEnvironment.PRODUCTION,
"Do not run this on production");
System.out.println("Populating billing account maps on all registrars missing them ...");
tm().transact(
() ->
tm().loadAllOfStream(Registrar.class)
.filter(r -> r.getBillingAccountMap().isEmpty())
.forEach(
r ->
tm().update(
r.asBuilder()
.setBillingAccountMap(DEFAULT_BILLING_ACCOUNT_MAP)
.build())));
System.out.println("Done!");
}
}

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

@@ -22,17 +22,19 @@ import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.util.logging.Level.SEVERE;
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;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.testing.TestLogHandler;
import google.registry.beam.TestPipelineExtension;
import google.registry.model.billing.BillingEvent.Cancellation;
import google.registry.model.billing.BillingEvent.Flag;
@@ -59,7 +61,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 java.util.logging.Logger;
import org.apache.beam.sdk.coders.SerializableCoder;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.testing.PAssert;
@@ -252,9 +254,14 @@ class InvoicingPipelineTest {
private File billingBucketUrl;
private PCollection<BillingEvent> billingEvents;
private final TestLogHandler logHandler = new TestLogHandler();
private final Logger loggerToIntercept =
Logger.getLogger(InvoicingPipeline.class.getCanonicalName());
@BeforeEach
void beforeEach() throws Exception {
loggerToIntercept.addHandler(logHandler);
billingBucketUrl = Files.createDirectory(tmpDir.resolve(BILLING_BUCKET_URL)).toFile();
options.setBillingBucketUrl(billingBucketUrl.getAbsolutePath());
options.setYearMonth(YEAR_MONTH);
@@ -300,8 +307,9 @@ class InvoicingPipelineTest {
}
@Test
void testFailure_readFromCloudSqlMissingPAK() throws Exception {
Registrar registrar = persistNewRegistrar("TheRegistrar");
void testSuccess_readFromCloudSqlMissingPAK() throws Exception {
setupCloudSql();
Registrar registrar = persistNewRegistrar("ARegistrar");
registrar =
registrar
.asBuilder()
@@ -317,17 +325,16 @@ class InvoicingPipelineTest {
persistResource(test);
DomainBase domain = persistActiveDomain("mycanadiandomain.test");
persistOneTimeBillingEvent(1, domain, registrar, Reason.RENEW, 3, Money.of(CAD, 20.5));
persistOneTimeBillingEvent(25, 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");
PAssert.that(billingEvents).containsInAnyOrder(INPUT_EVENTS);
pipeline.run().waitUntilFinish();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
SEVERE,
"Registrar ARegistrar does not have a product account key for the currency unit: CAD");
}
@Test

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

@@ -336,7 +336,11 @@ public class SyncRegistrarsSheetTest {
@TestOfyAndSql
void testRun_missingValues_stillWorks() throws Exception {
persistNewRegistrar("SomeRegistrar", "Some Registrar", Registrar.Type.REAL, 8L);
persistResource(
persistNewRegistrar("SomeRegistrar", "Some Registrar", Registrar.Type.REAL, 8L)
.asBuilder()
.setBillingAccountMap(ImmutableMap.of())
.build());
newSyncRegistrarsSheet().run("foobar");

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

@@ -225,7 +225,9 @@ public final class OteAccountBuilderTest {
OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid");
assertThat(assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist()))
IllegalStateException thrown =
assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist());
assertThat(thrown)
.hasMessageThat()
.contains("Found existing object(s) conflicting with OT&E objects");
}
@@ -236,7 +238,9 @@ public final class OteAccountBuilderTest {
OteAccountBuilder oteSetupHelper = OteAccountBuilder.forRegistrarId("myclientid");
assertThat(assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist()))
IllegalStateException thrown =
assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist());
assertThat(thrown)
.hasMessageThat()
.contains("Found existing object(s) conflicting with OT&E objects");
}

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

@@ -131,10 +131,11 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
.put(startTime.plusHours(2), DATASTORE_PRIMARY_NO_ASYNC)
.put(startTime.plusHours(3), DATASTORE_PRIMARY_READ_ONLY)
.build();
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(nowInvalidMap))))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(nowInvalidMap)));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Cannot transition from current state-as-of-now DATASTORE_ONLY "
@@ -143,14 +144,13 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
@Test
void testFailure_notInTransaction() {
assertThat(
assertThrows(
IllegalStateException.class,
() ->
DatabaseMigrationStateSchedule.set(
DatabaseMigrationStateSchedule.DEFAULT_TRANSITION_MAP.toValueMap())))
.hasMessageThat()
.isEqualTo("Not in a transaction");
IllegalStateException thrown =
assertThrows(
IllegalStateException.class,
() ->
DatabaseMigrationStateSchedule.set(
DatabaseMigrationStateSchedule.DEFAULT_TRANSITION_MAP.toValueMap()));
assertThat(thrown).hasMessageThat().isEqualTo("Not in a transaction");
}
@Test
@@ -188,10 +188,11 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
private void runInvalidTransition(MigrationState from, MigrationState to) {
ImmutableSortedMap<DateTime, MigrationState> transitions =
createMapEndingWithTransition(from, to);
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(transitions))))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(transitions)));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
String.format("validStateTransitions map cannot transition from %s to %s.", from, to));

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,48 @@ 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);
persistResource(
loadedToken.asBuilder().setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED).build());
assertThat(loadByEntity(token).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.SPECIFIED);
}
@TestOfyAndSql
void testSetCreationTime_cantCallMoreThanOnce() {
AllocationToken.Builder builder =

View File

@@ -30,11 +30,14 @@ import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResources;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
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;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig;
@@ -42,13 +45,17 @@ import google.registry.model.EntityTestCase;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.Registrar.Type;
import google.registry.model.tld.Registries;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldType;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.CidrAddressBlock;
import google.registry.util.SerializeUtils;
import java.math.BigDecimal;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach;
/** Unit tests for {@link Registrar}. */
@@ -60,7 +67,20 @@ class RegistrarTest extends EntityTestCase {
@BeforeEach
void setUp() {
createTld("xn--q9jyb4c");
createTld("tld");
persistResource(
newRegistry("xn--q9jyb4c", "MINNA")
.asBuilder()
.setCurrency(JPY)
.setCreateBillingCost(Money.of(JPY, new BigDecimal(1300)))
.setRestoreBillingCost(Money.of(JPY, new BigDecimal(1700)))
.setServerStatusChangeBillingCost(Money.of(JPY, new BigDecimal(1900)))
.setRegistryLockOrUnlockBillingCost(Money.of(JPY, new BigDecimal(2700)))
.setRenewBillingCostTransitions(
ImmutableSortedMap.of(START_OF_TIME, Money.of(JPY, new BigDecimal(1100))))
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(JPY)))
.setPremiumList(null)
.build());
// Set up a new persisted registrar entity.
registrar =
cloneAndSetAutoTimestamps(
@@ -251,8 +271,10 @@ class RegistrarTest extends EntityTestCase {
}
@TestOfyAndSql
void testSuccess_clearingBillingAccountMap() {
registrar = registrar.asBuilder().setBillingAccountMap(null).build();
void testSuccess_clearingBillingAccountMapAndAllowedTlds() {
registrar =
registrar.asBuilder().setAllowedTlds(ImmutableSet.of()).setBillingAccountMap(null).build();
assertThat(registrar.getAllowedTlds()).isEmpty();
assertThat(registrar.getBillingAccountMap()).isEmpty();
}
@@ -677,4 +699,48 @@ class RegistrarTest extends EntityTestCase {
assertThrows(IllegalArgumentException.class, () -> Registrar.loadByRegistrarIdCached(""));
assertThat(thrown).hasMessageThat().contains("registrarId must be specified");
}
@TestOfyAndSql
void testFailure_missingCurrenciesFromBillingMap() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
registrar
.asBuilder()
.setBillingAccountMap(null)
.setAllowedTlds(ImmutableSet.of("tld", "xn--q9jyb4c"))
.build());
assertThat(thrown)
.hasMessageThat()
.contains("their currency is missing from the billing account map: [tld, xn--q9jyb4c]");
}
@TestOfyAndSql
void testFailure_missingCurrencyFromBillingMap() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
registrar
.asBuilder()
.setBillingAccountMap(ImmutableMap.of(USD, "abc123"))
.setAllowedTlds(ImmutableSet.of("tld", "xn--q9jyb4c"))
.build());
assertThat(thrown)
.hasMessageThat()
.contains("their currency is missing from the billing account map: [xn--q9jyb4c]");
}
@TestOfyAndSql
void testSuccess_nonRealTldDoesntNeedEntryInBillingMap() {
persistResource(Registry.get("xn--q9jyb4c").asBuilder().setTldType(TldType.TEST).build());
// xn--q9jyb4c bills in JPY and we don't have a JPY entry in this billing account map, but it
// should succeed without throwing an error because xn--q9jyb4c is set to be a TEST TLD.
registrar
.asBuilder()
.setBillingAccountMap(ImmutableMap.of(USD, "abc123"))
.setAllowedTlds(ImmutableSet.of("tld", "xn--q9jyb4c"))
.build();
}
}

View File

@@ -302,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

View File

@@ -16,6 +16,7 @@ package google.registry.model.tld;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.SqlHelper.getMostRecentRegistryLockByRepoId;
import static google.registry.testing.SqlHelper.getMostRecentUnlockedRegistryLockByRepoId;
@@ -67,7 +68,7 @@ public final class RegistryLockDaoTest extends EntityTestCase {
() -> {
RegistryLock fromDatabase =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get();
assertThat(fromDatabase.getLockCompletionTime().get()).isEqualTo(fakeClock.nowUtc());
assertThat(fromDatabase.getLockCompletionTime()).hasValue(fakeClock.nowUtc());
assertThat(fromDatabase.getLastUpdateTime()).isEqualTo(fakeClock.nowUtc());
});
}
@@ -87,7 +88,7 @@ public final class RegistryLockDaoTest extends EntityTestCase {
assertThat(fromDatabase.getUnlockRequestTime()).isEqualTo(Optional.of(fakeClock.nowUtc()));
assertThat(fromDatabase.getUnlockCompletionTime()).isEqualTo(Optional.of(fakeClock.nowUtc()));
assertThat(fromDatabase.isLocked()).isFalse();
assertThat(fromDatabase.getRelockDuration().get()).isEqualTo(Duration.standardHours(6));
assertThat(fromDatabase.getRelockDuration()).hasValue(Duration.standardHours(6));
}
@Test

View File

@@ -190,7 +190,7 @@ public final class RegistryTest extends EntityTestCase {
@TestOfyAndSql
void testGetAll() {
createTld("foo");
assertThat(Registry.getAll(ImmutableSet.of("foo", "tld")))
assertThat(Registry.get(ImmutableSet.of("foo", "tld")))
.containsExactlyElementsIn(
tm().transact(
() ->
@@ -261,8 +261,8 @@ public final class RegistryTest extends EntityTestCase {
Registry registry = Registry.get("tld").asBuilder().setPremiumList(pl2).build();
Optional<String> pl = registry.getPremiumListName();
assertThat(pl).hasValue("tld2");
PremiumList stored = PremiumListDao.getLatestRevision(pl.get()).get();
assertThat(stored.getName()).isEqualTo("tld2");
assertThat(PremiumListDao.getLatestRevision("tld2")).isPresent();
assertThat(PremiumListDao.getLatestRevision("tld2").get().getName()).isEqualTo("tld2");
}
@TestOfyAndSql

View File

@@ -261,7 +261,7 @@ public class PremiumListDaoTest {
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname")).isNull();
PremiumListDao.save(testList);
PremiumList pl = PremiumListDao.getLatestRevision("testname").get();
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname").get()).isEqualTo(pl);
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname")).hasValue(pl);
TransactionManagerUtil.transactIfJpaTm(
() -> PremiumListDao.save("testname", USD, ImmutableList.of("test,USD 1")));
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname")).isNull();

View File

@@ -16,6 +16,7 @@ package google.registry.model.tld.label;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistReservedList;
@@ -100,8 +101,8 @@ public class PremiumListTest {
@Test
void testParse_canIncludeOrNotIncludeCurrencyUnit() {
PremiumListDao.save("tld", USD, ImmutableList.of("rofl,USD 90", "paper, 80"));
assertThat(PremiumListDao.getPremiumPrice("tld", "rofl").get()).isEqualTo(Money.of(USD, 90));
assertThat(PremiumListDao.getPremiumPrice("tld", "paper").get()).isEqualTo(Money.of(USD, 80));
assertThat(PremiumListDao.getPremiumPrice("tld", "rofl")).hasValue(Money.of(USD, 90));
assertThat(PremiumListDao.getPremiumPrice("tld", "paper")).hasValue(Money.of(USD, 80));
}
@Test

View File

@@ -15,9 +15,9 @@
package google.registry.model.tmch;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import google.registry.model.EntityTestCase;
import java.util.Optional;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link TmchCrl}. */
@@ -29,7 +29,7 @@ public class TmchCrlTest extends EntityTestCase {
@Test
void testSuccess() {
assertThat(TmchCrl.get()).isEqualTo(Optional.empty());
assertThat(TmchCrl.get()).isEmpty();
TmchCrl.set("lolcat", "https://lol.cat");
assertThat(TmchCrl.get().get().getCrl()).isEqualTo("lolcat");
}

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

@@ -73,8 +73,10 @@ final class AbstractJsonableObjectTest {
@JsonableElement String myString = "A";
@JsonableElement("myString") String anotherString = "B";
};
assertThat(assertThrows(JsonableException.class, () -> jsonable.toJson()))
.hasMessageThat().contains("Encountered the same field name 'myString' multiple times");
JsonableException thrown = assertThrows(JsonableException.class, () -> jsonable.toJson());
assertThat(thrown)
.hasMessageThat()
.contains("Encountered the same field name 'myString' multiple times");
}
@Test
@@ -96,8 +98,8 @@ final class AbstractJsonableObjectTest {
return in;
}
};
assertThat(assertThrows(JsonableException.class, () -> jsonable.toJson()))
.hasMessageThat().contains("must have no arguments");
JsonableException thrown = assertThrows(JsonableException.class, () -> jsonable.toJson());
assertThat(thrown).hasMessageThat().contains("must have no arguments");
}
@Test
@@ -186,8 +188,10 @@ final class AbstractJsonableObjectTest {
@JsonableElement("myList[]")
Optional<Integer> myListMeaningOfLife = Optional.of(42);
};
assertThat(assertThrows(JsonableException.class, () -> jsonable.toJson()))
.hasMessageThat().contains("Encountered the same field name 'myList' multiple times");
JsonableException thrown = assertThrows(JsonableException.class, () -> jsonable.toJson());
assertThat(thrown)
.hasMessageThat()
.contains("Encountered the same field name 'myList' multiple times");
}
@RestrictJsonNames({"allowed", "allowedList[]"})
@@ -226,7 +230,8 @@ final class AbstractJsonableObjectTest {
@JsonableElement
JsonableWithNameRestrictions wrong = new JsonableWithNameRestrictions();
};
assertThat(assertThrows(JsonableException.class, () -> jsonable.toJson()))
JsonableException thrown = assertThrows(JsonableException.class, () -> jsonable.toJson());
assertThat(thrown)
.hasMessageThat()
.contains("must be named one of ['allowed', 'allowedList[]'], but is named 'wrong'");
}
@@ -237,9 +242,8 @@ final class AbstractJsonableObjectTest {
@JsonableElement
JsonableWithNoAllowedNames wrong = new JsonableWithNoAllowedNames();
};
assertThat(assertThrows(JsonableException.class, () -> jsonable.toJson()))
.hasMessageThat()
.contains("is annotated with an empty RestrictJsonNames");
JsonableException thrown = assertThrows(JsonableException.class, () -> jsonable.toJson());
assertThat(thrown).hasMessageThat().contains("is annotated with an empty RestrictJsonNames");
}
@RestrictJsonNames({})

View File

@@ -232,17 +232,15 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
@TestOfyAndSql
void testNoTlds() {
deleteTld("tld");
assertThat(assertThrows(IllegalArgumentException.class, action::run))
.hasMessageThat()
.isEqualTo("There must exist at least one REAL TLD.");
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
assertThat(thrown).hasMessageThat().isEqualTo("There must exist at least one REAL TLD.");
}
@TestOfyAndSql
void testOnlyTestTlds() {
persistResource(Registry.get("tld").asBuilder().setTldType(TldType.TEST).build());
assertThat(assertThrows(IllegalArgumentException.class, action::run))
.hasMessageThat()
.isEqualTo("There must exist at least one REAL TLD.");
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
assertThat(thrown).hasMessageThat().isEqualTo("There must exist at least one REAL TLD.");
}
@TestOfyAndSql
@@ -278,7 +276,8 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
httpTransport.addNextResponse(badLoginResponse);
httpTransport.addNextResponse(badLoginResponse);
assertThat(assertThrows(RuntimeException.class, action::run))
RuntimeException thrown = assertThrows(RuntimeException.class, action::run);
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Error contacting MosAPI server. Tried TLDs [secondtld, tld]");
}
@@ -316,9 +315,8 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
httpTransport.addNextResponse(logoutResponse);
httpTransport.addNextResponse(badLoginResponse);
assertThat(assertThrows(RuntimeException.class, action::run))
.hasCauseThat()
.isInstanceOf(JsonSyntaxException.class);
RuntimeException thrown = assertThrows(RuntimeException.class, action::run);
assertThat(thrown).hasCauseThat().isInstanceOf(JsonSyntaxException.class);
}
private static void addValidResponses(TestHttpTransport httpTransport) {

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

@@ -403,9 +403,9 @@ class AuthenticatedRegistrarAccessorTest {
AuthenticatedRegistrarAccessor registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
assertThat(assertThrows(RegistrarAccessDeniedException.class, registrarAccessor::guessClientId))
.hasMessageThat()
.isEqualTo("TestUserId isn't associated with any registrar");
RegistrarAccessDeniedException thrown =
assertThrows(RegistrarAccessDeniedException.class, registrarAccessor::guessClientId);
assertThat(thrown).hasMessageThat().isEqualTo("TestUserId isn't associated with any registrar");
}
@TestOfyAndSql

View File

@@ -66,6 +66,7 @@ import java.util.Set;
import java.util.logging.LogManager;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -289,6 +290,7 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
.build())
.setPhoneNumber("+1.3334445555")
.setPhonePasscode("12345")
.setBillingAccountMap(ImmutableMap.of(CurrencyUnit.USD, "abc123"))
.setContactsRequireSyncing(true);
}

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

@@ -762,6 +762,7 @@ public class DatabaseHelper {
.setType(type)
.setState(State.ACTIVE)
.setIanaIdentifier(ianaIdentifier)
.setBillingAccountMap(ImmutableMap.of(USD, "abc123"))
.setLocalizedAddress(
new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("123 Fake St"))

View File

@@ -61,41 +61,38 @@ abstract class CreateOrUpdateReservedListCommandTestCase<
@Test
void testFailure_fileDoesntExist() {
assertThat(
assertThrows(
ParameterException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved",
"--input=" + reservedTermsPath + "-nonexistent")))
.hasMessageThat()
.contains("-i not found");
ParameterException thrown =
assertThrows(
ParameterException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved",
"--input=" + reservedTermsPath + "-nonexistent"));
assertThat(thrown).hasMessageThat().contains("-i not found");
}
@Test
void testFailure_fileDoesntParse() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved",
"--input=" + invalidReservedTermsPath)))
.hasMessageThat()
.contains("No enum constant");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved", "--input=" + invalidReservedTermsPath));
assertThat(thrown).hasMessageThat().contains("No enum constant");
}
@Test
void testFailure_invalidLabel_includesFullDomainName() throws Exception {
Files.asCharSink(new File(invalidReservedTermsPath), UTF_8)
.write("example.tld,FULLY_BLOCKED\n\n");
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved",
"--input=" + invalidReservedTermsPath)))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved", "--input=" + invalidReservedTermsPath));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Label example.tld must not be a multi-level domain name");
}

View File

@@ -21,8 +21,11 @@ import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT3;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT3_HASH;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.JPY;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -43,8 +46,10 @@ import google.registry.testing.DualDatabaseTest;
import google.registry.testing.InjectExtension;
import google.registry.testing.TestOfyAndSql;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -604,11 +609,11 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
Optional<Registrar> registrar = Registrar.loadByRegistrarId("clientz");
assertThat(registrar).isPresent();
assertThat(registrar.get().getBillingAccountMap())
.containsExactly(CurrencyUnit.USD, "abc123", CurrencyUnit.JPY, "789xyz");
.containsExactly(CurrencyUnit.USD, "abc123", JPY, "789xyz");
}
@TestOfyAndSql
void testFailure_billingAccountMap_doesNotContainEntryForTldAllowed() {
void testFailure_billingAccountMap_doesNotContainEntryForAllowedTld() {
createTlds("foo");
IllegalArgumentException thrown =
@@ -632,12 +637,26 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
"--cc US",
"--force",
"clientz"));
assertThat(thrown).hasMessageThat().contains("USD");
assertThat(thrown)
.hasMessageThat()
.contains("their currency is missing from the billing account map: [foo]");
}
@TestOfyAndSql
void testSuccess_billingAccountMap_onlyAppliesToRealRegistrar() throws Exception {
createTlds("foo");
persistResource(
newRegistry("foo", "FOO")
.asBuilder()
.setCurrency(JPY)
.setCreateBillingCost(Money.of(JPY, new BigDecimal(1300)))
.setRestoreBillingCost(Money.of(JPY, new BigDecimal(1700)))
.setServerStatusChangeBillingCost(Money.of(JPY, new BigDecimal(1900)))
.setRegistryLockOrUnlockBillingCost(Money.of(JPY, new BigDecimal(2700)))
.setRenewBillingCostTransitions(
ImmutableSortedMap.of(START_OF_TIME, Money.of(JPY, new BigDecimal(1100))))
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(JPY)))
.setPremiumList(null)
.build());
runCommandForced(
"--name=blobio",
@@ -656,7 +675,7 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
Optional<Registrar> registrar = Registrar.loadByRegistrarId("clientz");
assertThat(registrar).isPresent();
assertThat(registrar.get().getBillingAccountMap()).containsExactly(CurrencyUnit.JPY, "789xyz");
assertThat(registrar.get().getBillingAccountMap()).containsExactly(JPY, "789xyz");
}
@TestOfyAndSql

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;
@@ -59,7 +60,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.junit.jupiter.MockitoSettings;
@@ -316,12 +316,13 @@ public final class DomainLockUtilsTest {
domainLockUtils.saveNewRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, Optional.empty());
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, Optional.empty())))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, Optional.empty()));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("A pending unlock action already exists for example.tld");
}
@@ -331,37 +332,38 @@ public final class DomainLockUtilsTest {
RegistryLock lock =
domainLockUtils.saveNewRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", null, true);
domainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true);
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, Optional.empty())))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, Optional.empty()));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Non-admin user cannot unlock admin-locked domain example.tld");
}
@TestOfyAndSql
void testFailure_createLock_unknownDomain() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryLockRequest(
"asdf.tld", "TheRegistrar", POC_ID, false)))
.hasMessageThat()
.isEqualTo("Domain doesn't exist");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryLockRequest(
"asdf.tld", "TheRegistrar", POC_ID, false));
assertThat(thrown).hasMessageThat().isEqualTo("Domain doesn't exist");
}
@TestOfyAndSql
void testFailure_createLock_alreadyPendingLock() {
domainLockUtils.saveNewRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", POC_ID, false);
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false)))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("A pending or completed lock action already exists for example.tld");
}
@@ -369,26 +371,24 @@ public final class DomainLockUtilsTest {
@TestOfyAndSql
void testFailure_createLock_alreadyLocked() {
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already locked");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false));
assertThat(thrown).hasMessageThat().isEqualTo("Domain example.tld is already locked");
}
@TestOfyAndSql
void testFailure_createUnlock_alreadyUnlocked() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, Optional.empty())))
.hasMessageThat()
.isEqualTo("Domain example.tld is already unlocked");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
domainLockUtils.saveNewRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, Optional.empty()));
assertThat(thrown).hasMessageThat().isEqualTo("Domain example.tld is already unlocked");
}
@TestOfyAndSql
@@ -397,12 +397,11 @@ public final class DomainLockUtilsTest {
domainLockUtils.saveNewRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", POC_ID, false);
domainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false);
domain = loadByEntity(domain);
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already locked");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false));
assertThat(thrown).hasMessageThat().isEqualTo("Domain example.tld is already locked");
assertNoDomainChanges();
}
@@ -411,12 +410,11 @@ public final class DomainLockUtilsTest {
RegistryLock lock =
domainLockUtils.saveNewRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", POC_ID, false);
clock.advanceBy(standardDays(1));
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true)))
.hasMessageThat()
.isEqualTo("The pending lock has expired; please try again");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true));
assertThat(thrown).hasMessageThat().isEqualTo("The pending lock has expired; please try again");
assertNoDomainChanges();
}
@@ -424,12 +422,11 @@ public final class DomainLockUtilsTest {
void testFailure_applyLock_nonAdmin_applyAdminLock() {
RegistryLock lock =
domainLockUtils.saveNewRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", null, true);
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false)))
.hasMessageThat()
.isEqualTo("Non-admin user cannot complete admin lock");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false));
assertThat(thrown).hasMessageThat().isEqualTo("Non-admin user cannot complete admin lock");
assertNoDomainChanges();
}
@@ -443,12 +440,11 @@ public final class DomainLockUtilsTest {
DOMAIN_NAME, "TheRegistrar", false, Optional.empty());
domainLockUtils.verifyAndApplyUnlock(unlock.getVerificationCode(), false);
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyUnlock(unlock.getVerificationCode(), false)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already unlocked");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyUnlock(unlock.getVerificationCode(), false));
assertThat(thrown).hasMessageThat().isEqualTo("Domain example.tld is already unlocked");
assertNoDomainChanges();
}
@@ -460,12 +456,11 @@ public final class DomainLockUtilsTest {
// reload to pick up modification times, etc
lock = getRegistryLockByVerificationCode(verificationCode).get();
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyLock(verificationCode, false)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already locked");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.verifyAndApplyLock(verificationCode, false));
assertThat(thrown).hasMessageThat().isEqualTo("Domain example.tld is already locked");
// Failure during Datastore portion shouldn't affect the SQL object
RegistryLock afterAction = getRegistryLockByVerificationCode(lock.getVerificationCode()).get();
@@ -516,10 +511,11 @@ public final class DomainLockUtilsTest {
.setRegistrarPocId("someone@example.com")
.setVerificationCode("hi")
.build());
assertThat(
Assert.assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.enqueueDomainRelock(lockWithoutDuration)))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> domainLockUtils.enqueueDomainRelock(lockWithoutDuration));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
String.format(
@@ -546,7 +542,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

@@ -133,7 +133,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
runCommand("--prefix", "ooo", "--number", "100", "--length", "16");
// The deterministic string generator makes it too much hassle to assert about each token, so
// just assert total number.
assertThat(loadAllOf(AllocationToken.class).size()).isEqualTo(100);
assertThat(loadAllOf(AllocationToken.class)).hasSize(100);
}
@TestOfyAndSql
@@ -200,7 +200,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
Collection<String> sampleTokens = command.stringGenerator.createStrings(13, 100);
runCommand("--tokens", Joiner.on(",").join(sampleTokens));
assertInStdout(Iterables.toArray(sampleTokens, String.class));
assertThat(loadAllOf(AllocationToken.class).size()).isEqualTo(100);
assertThat(loadAllOf(AllocationToken.class)).hasSize(100);
}
@TestOfyAndSql

View File

@@ -139,14 +139,15 @@ public class SetDatabaseMigrationStateCommandTest
@TestOfyAndSql
void testFailure_invalidTransition() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
String.format(
"--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY_READ_ONLY",
START_OF_TIME, START_OF_TIME.plusHours(1)))))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
String.format(
"--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY_READ_ONLY",
START_OF_TIME, START_OF_TIME.plusHours(1))));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"validStateTransitions map cannot transition from DATASTORE_ONLY "
@@ -158,18 +159,16 @@ public class SetDatabaseMigrationStateCommandTest
// The map we pass in is valid by itself, but we can't go from DATASTORE_ONLY now to
// DATASTORE_PRIMARY_READ_ONLY now
DateTime now = fakeClock.nowUtc();
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
String.format(
"--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY,"
+ "%s=DATASTORE_PRIMARY_NO_ASYNC,%s=DATASTORE_PRIMARY_READ_ONLY",
START_OF_TIME,
now.minusHours(3),
now.minusHours(2),
now.minusHours(1)))))
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
String.format(
"--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY,"
+ "%s=DATASTORE_PRIMARY_NO_ASYNC,%s=DATASTORE_PRIMARY_READ_ONLY",
START_OF_TIME, now.minusHours(3), now.minusHours(2), now.minusHours(1))));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Cannot transition from current state-as-of-now DATASTORE_ONLY "

View File

@@ -21,8 +21,10 @@ import static google.registry.testing.CertificateSamples.SAMPLE_CERT3;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT3_HASH;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.JPY;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -38,8 +40,10 @@ import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.Registrar.Type;
import google.registry.testing.AppEngineExtension;
import google.registry.util.CidrAddressBlock;
import java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -359,6 +363,8 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
@Test
void testSuccess_billingAccountMap() throws Exception {
persistResource(
loadRegistrar("NewRegistrar").asBuilder().setBillingAccountMap(ImmutableMap.of()).build());
assertThat(loadRegistrar("NewRegistrar").getBillingAccountMap()).isEmpty();
runCommand("--billing_account_map=USD=abc123,JPY=789xyz", "--force", "NewRegistrar");
assertThat(loadRegistrar("NewRegistrar").getBillingAccountMap())
@@ -366,8 +372,14 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
}
@Test
void testFailure_billingAccountMap_doesNotContainEntryForTldAllowed() {
void testFailure_billingAccountMap_doesNotContainEntryForAllowedTld() {
createTlds("foo");
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setAllowedTlds(ImmutableSet.of())
.setBillingAccountMap(ImmutableMap.of())
.build());
assertThat(loadRegistrar("NewRegistrar").getBillingAccountMap()).isEmpty();
IllegalArgumentException thrown =
assertThrows(
@@ -379,12 +391,28 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
"--force",
"--registrar_type=REAL",
"NewRegistrar"));
assertThat(thrown).hasMessageThat().contains("USD");
assertThat(thrown)
.hasMessageThat()
.contains("their currency is missing from the billing account map: [foo]");
}
@Test
void testSuccess_billingAccountMap_onlyAppliesToRealRegistrar() throws Exception {
createTlds("foo");
persistResource(
newRegistry("foo", "FOO")
.asBuilder()
.setCurrency(JPY)
.setCreateBillingCost(Money.of(JPY, new BigDecimal(1300)))
.setRestoreBillingCost(Money.of(JPY, new BigDecimal(1700)))
.setServerStatusChangeBillingCost(Money.of(JPY, new BigDecimal(1900)))
.setRegistryLockOrUnlockBillingCost(Money.of(JPY, new BigDecimal(2700)))
.setRenewBillingCostTransitions(
ImmutableSortedMap.of(START_OF_TIME, Money.of(JPY, new BigDecimal(1100))))
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(JPY)))
.setPremiumList(null)
.build());
persistResource(
loadRegistrar("NewRegistrar").asBuilder().setBillingAccountMap(ImmutableMap.of()).build());
assertThat(loadRegistrar("NewRegistrar").getBillingAccountMap()).isEmpty();
runCommand("--billing_account_map=JPY=789xyz", "--allowed_tlds=foo", "--force", "NewRegistrar");
assertThat(loadRegistrar("NewRegistrar").getBillingAccountMap())

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