Compare commits

...
Author SHA1 Message Date
gbrodmanandGitHub 9a2fb6f8b4 Add SQL column for AllocationToken registration behavior (#1697)
First part of b/237683906

We'll add the Java behavior in a later PR; it'll be an enum with the
values DEFAULT, BYPASS_TLD_STATE, and ANCHOR_TENANT
2022-07-01 18:26:18 -04:00
gbrodmanandGitHub abc240fc2d Revert "Remove DatabaseMigrationStateSchedule (#1689)" (#1698)
This reverts commit 18d51738ce.
2022-07-01 17:25:13 -04:00
Michael MullerandGitHub a424030a65 Deal with some getOfyKey() references (#1696)
Now that ofy keys aren't necessarily being restored, it seemed prudent to
audit existing uses to verify that we aren't relying on any keys that may now
be null.

This fixes one case that appeared to be potentially problematic (in
ResourceFlowUtils), removes a few methods that call getOfyKey() but are no
longer used, and adds comments to one use of the key that appears to be safe
after visual inspection.
2022-07-01 16:59:03 -04:00
gbrodmanandGitHub 72abc824d5 Delete DatastoreTM and most other references to Datastore (#1681)
This includes:
- deletion of helper DB methods in tests
- deletion of various old Datastore-only classes and removal of any
  endpoints
- removal of the dual-database test concept
- removal of 'ofy' from the AppEngineExtension
2022-07-01 13:33:38 -04:00
Lai JiangandGitHub b2ec088749 Remove Ofy support from Registry (#1688)
Also made some code quality changes based on IntelliJ suggestions on
modified files.
2022-07-01 09:04:46 -04:00
Ben McIlwainandGitHub 6d40fe41e6 Use DomainPricingLogic in ExpandRecurringBillingEventsAction (#1687)
* Inject DomainPricingLogic to action file

* Remove attempt to inject

* Merge conflict

* Fix non static to static issue

* Merge branch 'master' into at_internal/Expand
2022-06-29 17:39:15 -04:00
Weimin YuandGitHub 7dafbf6ae1 Remove retries for DatabaseSnapshot tests (#1690)
* Remove retries for DatabaseSnapshot tests
2022-06-29 14:45:41 -04:00
Michael MullerandGitHub 9b8f10c595 Remove ofy scaffolding from DomainContent and core classes (#1660)
This removes the code that converts between ofy fields and SQL fields in DomainContent and a number of related core classes (basically anything that also needed modification to support the removal from DomainContent).
2022-06-29 14:39:25 -04:00
Lai JiangandGitHub 18d51738ce Remove DatabaseMigrationStateSchedule (#1689) 2022-06-29 12:14:45 -04:00
Lai JiangandGitHub 76d63b24a8 Remove Ofy support from Cursor (#1672)
Cursor was originally envisioned to support arbitary ImmutableObject
scopes. However, in practice only the Registry scope is used. The SQL
representation of Cursor assumes that and the schema uses a composite ID
with a string column for the primary key of the scope object. Without a
schema migration to persist the VKey of the scope, we cannot support any
ImmutableObject other than those with a primitive string primary key.

Given the complexity involved and the limited use case, the scope is now
explictly limited to Registry only.

Also removed mapreduces that depends on Ofy keys of Cursors, and made
some code quality improvement based on IntelliJ suggestions on modified
files.

<!-- 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/1672)
<!-- Reviewable:end -->
2022-06-28 14:59:21 -04:00
Michael MullerandGitHub eb1b283ba3 In shell mode, only do database setup once (#1686)
We were initializing ofy and JPA every time the command was run, causing shell
commands to break after 64 transactions.
2022-06-28 09:27:39 -04:00
Lai JiangandGitHub 63e4f4f10a Remove Ofy from RegistrarContact (#1680)
Also renamed the class to RegistrarPoc and deleted some unused methods.
2022-06-27 20:17:28 -04:00
sarahcaseybotandGitHub 2c3279ba95 Use VKeys instead of Ofy keys in mutating command (#1682)
* Use VKeys instead of Ofy keys in mutating command

* Add createVKey to ImmutableObject

* Use SQL only VKeys
2022-06-27 17:49:24 -04:00
Ben McIlwainandGitHub 89925f9ff2 Fix license-checking on GWT 2.10.0, which is Apache 2.0 (#1685) 2022-06-27 12:24:32 -04:00
Lai JiangandGitHub 585765b83a Remove the beam parameter in RDE staging action (#1684)
The parameter was used to force a RDE beam run, which is no longer
necessary, now that the mapreduce pipeline is deleted.
2022-06-27 10:45:52 -04:00
Ben McIlwainandGitHub cddcfc49ed Make domain:renew commands use the renewal price behavior (#1683)
* Make domain:renew commands use the renewal price behavior

This is based on PR #1592 by @rachelguan.
2022-06-24 17:36:28 -04:00
sarahcaseybotandGitHub fb7558121b Add flyway files to remove billing_identifier from Registrar (#1652) 2022-06-24 12:16:33 -04:00
Lai JiangandGitHub 1719d066cf Disable all tests that uses Ofy (#1679)
These tests use Ofy exclusively and should not run anymore, as any class
they test also use Ofy and should be deleted.

More importantly, running tests in Ofy mode makes it hard to remove Ofy
from entities, especially Registrar and RegistrarContact, as some of
them are created as canned data when tests are initiated, and the
creation would fail if they are not registered as Ofy entities.

It is therefore a prerequisite to disable these tests before we can
remove Ofy from those entities. We could have deleted them, but I think
that should be done when the corresponding classes tested by them are
deleted.

<!-- 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/1679)
<!-- Reviewable:end -->
2022-06-23 14:19:20 -04:00
gbrodmanandGitHub fa1b34b020 Remove CommitLog and MapReduce-related code (#1670) 2022-06-23 12:54:47 -04:00
Lai JiangandGitHub 4298084406 Pass the withoutCannedData boolean to JpaTransactionManagerExtension builder (#1678)
This is an oversight from #1673

<!-- 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/1678)
<!-- Reviewable:end -->
2022-06-23 10:37:14 -04:00
Weimin YuandGitHub 9b5f1756f1 Remove obsolete gradle tasks (#1677)
* Remove obsolete gradle tasks
2022-06-22 22:45:34 -04:00
Lai JiangandGitHub 62236f7581 Only use GPG2 in tests (#1676)
GPG1 is deprecated and stuck in v1.4 from 2018. GPG2 is recommended. We
only use the GPG binary in tests and when the host system has both
versions it causes problems because we hardcode the GPG import command
in GpgSystemCommandExension to use the binary named "gpg", which could
be linked to either GPG1 or GPG2, causing the other test to fail when
the version of GPG that runs in tests is incompatible with the version of GPG
that imports the keys.

With this PR we only support GPG2 from now on.
2022-06-22 11:03:41 -04:00
Lai JiangandGitHub e1ea176daa Add some helper methods to JPA test extension (#1673)
Added methods that exist in AppEngineExtension that preload some canned
data. This data is loaded by default and a lot of tests rely on them. As
we migrate away from App Engine, it is helpful to have them in the JPA
test extension which will replace the app engine extension that is
used to set up the database in dual database tests.

<!-- 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/1673)
<!-- Reviewable:end -->
2022-06-21 16:53:56 -04:00
gbrodmanandGitHub bb9b669014 Convert GenerateZoneFilesAction to SQL (#1668)
I'm not 100% sure that this is strictly necessary, but for now we can
replicate the ability to generate zonefiles for any point in time in the
recent past.
2022-06-21 15:51:52 -04:00
gbrodmanandGitHub dddeb48356 Ignore buggy gpg2 tests (#1675)
See https://b.corp.google.com/issues/236723363 for more info. We're not
sure why these are failing in Kokoro.
2022-06-21 14:58:24 -04:00
Lai JiangandGitHub c878679770 Migrate ReadDnsQueueAction to use CloudTasksUtils (#1669)
* Migrate ReadDnsQueueAction to use CloudTasksUtils

Also marked TaskQueueUtils as deprecated and fixed a few linter errors.

Note that DNS pull queue still requires the use of the GAE Task Queue API.

* Fix a test failure

* Remove TaskQueueUtils from VKeyTest

* Remove the @error exception that was inadvertently pulled in
2022-06-15 13:48:28 -04:00
682 changed files with 25563 additions and 48815 deletions
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -11,13 +11,13 @@ com.google.api.grpc:proto-google-iam-v1:1.3.4
com.google.api:api-common:2.2.0
com.google.api:gax-httpjson:0.103.1
com.google.api:gax:2.18.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.auth:google-auth-library-credentials:1.7.0
com.google.auth:google-auth-library-oauth2-http:1.7.0
com.google.auto.value:auto-value-annotations:1.9
com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
@@ -267,6 +267,12 @@
"moduleLicense": "Public Domain",
"moduleName": "org.tukaani:xz"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "2.10.0",
"moduleName": "com.google.gwt:gwt-user"
},
{
// "Apache License, Version 2.0". The plugin is able to parse up to
// 2.11.3 correctly but then something changed with 2.12.* and it no
-21
View File
@@ -196,7 +196,6 @@ dependencies {
compile deps['com.google.apis:google-api-services-storage']
testCompile deps['com.google.appengine:appengine-api-stubs']
compile deps['com.google.appengine.tools:appengine-gcs-client']
compile deps['com.google.appengine.tools:appengine-mapreduce']
compile deps['com.google.appengine.tools:appengine-pipeline']
compile deps['com.google.appengine:appengine-remote-api']
compile deps['com.google.auth:google-auth-library-credentials']
@@ -703,24 +702,9 @@ createToolTask(
'google.registry.tools.DevTool',
sourceSets.nonprod)
createToolTask(
'initSqlPipeline', 'google.registry.beam.initsql.InitSqlPipeline')
createToolTask(
'validateDatabasePipeline', 'google.registry.beam.comparedb.ValidateDatabasePipeline')
createToolTask(
'jpaDemoPipeline', 'google.registry.beam.common.JpaDemoPipeline')
// Caller must provide projectId, GCP region, runner, and the kinds to delete
// (comma-separated kind names or '*' for all). E.g.:
// nom_build :core:bulkDeleteDatastore --args="--project=domain-registry-crash \
// --region=us-central1 --runner=DataflowRunner --kindsToDelete=*"
createToolTask(
'bulkDeleteDatastore',
'google.registry.beam.datastore.BulkDeleteDatastorePipeline')
project.tasks.create('generateSqlSchema', JavaExec) {
classpath = sourceSets.nonprod.runtimeClasspath
main = 'google.registry.tools.DevTool'
@@ -765,11 +749,6 @@ createUberJar(
// User should install gcloud and login to GCP before invoking this tasks.
if (environment == 'alpha') {
def pipelines = [
bulkDeleteDatastore:
[
mainClass: 'google.registry.beam.datastore.BulkDeleteDatastorePipeline',
metaData : 'google/registry/beam/bulk_delete_datastore_pipeline_metadata.json'
],
spec11 :
[
mainClass: 'google.registry.beam.spec11.Spec11Pipeline',
+19 -25
View File
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -103,7 +101,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -115,7 +113,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -123,7 +121,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -133,28 +131,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -211,7 +208,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -263,7 +259,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -280,13 +275,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:slf4j-api:1.7.36
org.springframework:spring-core:5.3.18
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -27,11 +26,11 @@ com.github.jnr:jnr-ffi:2.2.11
com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -84,10 +83,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -102,7 +100,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -114,7 +112,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -122,7 +120,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -132,28 +130,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -206,7 +203,6 @@ io.opencensus:opencensus-exporter-stats-stackdriver:0.31.0
io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -256,7 +252,6 @@ org.checkerframework:checker-qual:3.22.0
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -272,13 +267,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:slf4j-api:1.7.36
org.springframework:spring-core:5.3.18
+20 -26
View File
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -103,8 +101,8 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:postgres-socket-factory:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud.sql:postgres-socket-factory:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -116,7 +114,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -124,7 +122,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -134,28 +132,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -219,7 +216,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -273,7 +269,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -290,13 +285,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:jcl-over-slf4j:1.7.30
org.slf4j:jul-to-slf4j:1.7.30
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -103,8 +101,8 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:postgres-socket-factory:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud.sql:postgres-socket-factory:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -116,7 +114,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -124,7 +122,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -134,28 +132,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -219,7 +216,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -272,7 +268,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -288,13 +283,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:jcl-over-slf4j:1.7.30
org.slf4j:jul-to-slf4j:1.7.30
+1 -1
View File
@@ -4,7 +4,7 @@
com.sun.activation:jakarta.activation:1.2.2
com.sun.activation:javax.activation:1.2.0
com.sun.xml.bind:jaxb-impl:2.3.3
com.sun.xml.bind:jaxb-osgi:4.0.0
com.sun.xml.bind:jaxb-osgi:4.0.0-M4
com.sun.xml.bind:jaxb-xjc:2.3.3
jakarta.activation:jakarta.activation-api:2.1.0
jakarta.xml.bind:jakarta.xml.bind-api:4.0.0
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -103,7 +101,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -115,7 +113,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -123,7 +121,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -133,28 +131,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -211,7 +208,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -263,7 +259,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -280,13 +275,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:slf4j-api:1.7.36
org.springframework:spring-core:5.3.18
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -27,11 +26,11 @@ com.github.jnr:jnr-ffi:2.2.11
com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -84,10 +83,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -102,7 +100,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -114,7 +112,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -122,7 +120,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -132,28 +130,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -206,7 +203,6 @@ io.opencensus:opencensus-exporter-stats-stackdriver:0.31.0
io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -257,7 +253,6 @@ org.checkerframework:checker-qual:3.22.0
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -274,13 +269,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:slf4j-api:1.7.36
org.springframework:spring-core:5.3.18
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -103,7 +101,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -115,7 +113,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -123,7 +121,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -133,28 +131,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -218,7 +215,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -272,7 +268,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -289,13 +284,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:jcl-over-slf4j:1.7.30
org.slf4j:jul-to-slf4j:1.7.30
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -103,7 +101,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -115,7 +113,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -123,7 +121,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -133,28 +131,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -218,7 +215,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -272,7 +268,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -289,13 +284,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:jcl-over-slf4j:1.7.30
org.slf4j:jul-to-slf4j:1.7.30
+19 -25
View File
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -103,7 +101,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -115,7 +113,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -123,7 +121,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -133,28 +131,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -218,7 +215,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -272,7 +268,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -289,13 +284,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:jcl-over-slf4j:1.7.30
org.slf4j:jul-to-slf4j:1.7.30
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-remote-api:2.0.5
@@ -103,8 +101,8 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:postgres-socket-factory:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud.sql:postgres-socket-factory:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -116,7 +114,7 @@ com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -124,7 +122,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -134,28 +132,27 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -219,7 +216,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -272,7 +268,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -288,13 +283,12 @@ org.joda:joda-money:1.0.1
org.json:json:20200518
org.jsoup:jsoup:1.15.1
org.jvnet.staxex:stax-ex:1.8
org.objenesis:objenesis:1.2
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.slf4j:jcl-over-slf4j:1.7.30
org.slf4j:jul-to-slf4j:1.7.30
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-api-stubs:2.0.5
@@ -104,7 +102,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -112,12 +110,12 @@ com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-firestore:3.0.14
com.google.cloud:google-cloud-monitoring:1.82.0
com.google.cloud:google-cloud-nio:0.124.4
com.google.cloud:google-cloud-nio:0.124.2
com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -125,7 +123,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -136,12 +134,12 @@ com.google.guava:guava-testlib:31.1-jre
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
@@ -149,18 +147,17 @@ com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:contrib:1.0.7
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.google.truth.extensions:truth-java8-extension:1.1.3
com.google.truth:truth:1.1.3
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -220,7 +217,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -273,7 +269,7 @@ org.apache.mina:mina-core:2.1.6
org.apache.sshd:sshd-core:2.0.0
org.apache.sshd:sshd-scp:2.0.0
org.apache.sshd:sshd-sftp:2.0.0
org.apache.tomcat:tomcat-annotations-api:10.1.0-M16
org.apache.tomcat:tomcat-annotations-api:10.1.0-M15
org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
@@ -283,7 +279,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -324,7 +319,7 @@ org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.seleniumhq.selenium:selenium-api:3.141.59
org.seleniumhq.selenium:selenium-chrome-driver:3.141.59
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -27,11 +26,11 @@ com.github.jnr:jnr-ffi:2.2.11
com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -84,10 +83,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-api-stubs:2.0.5
@@ -103,7 +101,7 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -111,12 +109,12 @@ com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-firestore:3.0.14
com.google.cloud:google-cloud-monitoring:1.82.0
com.google.cloud:google-cloud-nio:0.124.4
com.google.cloud:google-cloud-nio:0.124.2
com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -124,7 +122,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -135,12 +133,12 @@ com.google.guava:guava-testlib:31.1-jre
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
@@ -148,18 +146,17 @@ com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:contrib:1.0.7
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.google.truth.extensions:truth-java8-extension:1.1.3
com.google.truth:truth:1.1.3
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -215,7 +212,6 @@ io.opencensus:opencensus-exporter-stats-stackdriver:0.31.0
io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -268,7 +264,7 @@ org.apache.mina:mina-core:2.1.6
org.apache.sshd:sshd-core:2.0.0
org.apache.sshd:sshd-scp:2.0.0
org.apache.sshd:sshd-sftp:2.0.0
org.apache.tomcat:tomcat-annotations-api:10.1.0-M16
org.apache.tomcat:tomcat-annotations-api:10.1.0-M15
org.apiguardian:apiguardian-api:1.1.2
org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
@@ -278,7 +274,6 @@ org.checkerframework:checker-qual:3.22.0
org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -311,14 +306,13 @@ org.mockito:mockito-core:4.6.1
org.mockito:mockito-junit-jupiter:4.6.1
org.mortbay.jetty:jetty-util:6.1.26
org.mortbay.jetty:jetty:6.1.26
org.objenesis:objenesis:1.2
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:9.3
org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.seleniumhq.selenium:selenium-api:3.141.59
org.seleniumhq.selenium:selenium-chrome-driver:3.141.59
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-api-stubs:2.0.5
@@ -104,8 +102,8 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:postgres-socket-factory:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud.sql:postgres-socket-factory:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -113,12 +111,12 @@ com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-firestore:3.0.14
com.google.cloud:google-cloud-monitoring:1.82.0
com.google.cloud:google-cloud-nio:0.124.4
com.google.cloud:google-cloud-nio:0.124.2
com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -126,7 +124,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -137,12 +135,12 @@ com.google.guava:guava-testlib:31.1-jre
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
@@ -150,18 +148,17 @@ com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:contrib:1.0.7
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.google.truth.extensions:truth-java8-extension:1.1.3
com.google.truth:truth:1.1.3
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -229,7 +226,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -283,7 +279,7 @@ org.apache.mina:mina-core:2.1.6
org.apache.sshd:sshd-core:2.0.0
org.apache.sshd:sshd-scp:2.0.0
org.apache.sshd:sshd-sftp:2.0.0
org.apache.tomcat:tomcat-annotations-api:10.1.0-M16
org.apache.tomcat:tomcat-annotations-api:10.1.0-M15
org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
@@ -293,7 +289,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -334,7 +329,7 @@ org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.seleniumhq.selenium:selenium-api:3.141.59
org.seleniumhq.selenium:selenium-chrome-driver:3.141.59
@@ -4,7 +4,6 @@
antlr:antlr:2.7.7
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
cglib:cglib-nodep:2.2
com.101tec:zkclient:0.10
com.beust:jcommander:1.60
com.fasterxml.jackson.core:jackson-annotations:2.13.3
@@ -28,11 +27,11 @@ com.github.jnr:jnr-posix:3.1.15
com.github.jnr:jnr-unixsocket:0.38.17
com.github.jnr:jnr-x86asm:1.0.2
com.google.android:annotations:4.1.1.4
com.google.api-client:google-api-client-appengine:1.35.1
com.google.api-client:google-api-client-appengine:1.35.0
com.google.api-client:google-api-client-jackson2:1.32.2
com.google.api-client:google-api-client-java6:1.35.1
com.google.api-client:google-api-client-servlet:1.35.1
com.google.api-client:google-api-client:1.35.1
com.google.api-client:google-api-client-java6:1.35.0
com.google.api-client:google-api-client-servlet:1.35.0
com.google.api-client:google-api-client:1.35.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.10.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.134.0
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.134.0
@@ -85,10 +84,9 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20210326-1.32.1
com.google.apis:google-api-services-monitoring:v3-rev20220525-1.32.1
com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1
com.google.apis:google-api-services-sheets:v4-rev20220411-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220513-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220604-1.32.1
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220323-1.32.1
com.google.apis:google-api-services-storage:v1-rev20220509-1.32.1
com.google.appengine.tools:appengine-gcs-client:0.8.1
com.google.appengine.tools:appengine-mapreduce:0.9
com.google.appengine.tools:appengine-pipeline:0.2.13
com.google.appengine:appengine-api-1.0-sdk:2.0.5
com.google.appengine:appengine-api-stubs:2.0.5
@@ -104,8 +102,8 @@ com.google.cloud.bigdataoss:util:2.2.6
com.google.cloud.bigtable:bigtable-client-core:1.26.3
com.google.cloud.bigtable:bigtable-metrics-api:1.26.3
com.google.cloud.datastore:datastore-v1-proto-client:2.1.3
com.google.cloud.sql:jdbc-socket-factory-core:1.6.1
com.google.cloud.sql:postgres-socket-factory:1.6.1
com.google.cloud.sql:jdbc-socket-factory-core:1.6.0
com.google.cloud.sql:postgres-socket-factory:1.6.0
com.google.cloud:google-cloud-bigquerystorage:2.10.0
com.google.cloud:google-cloud-bigtable:2.5.3
com.google.cloud:google-cloud-core-grpc:2.4.0
@@ -113,12 +111,12 @@ com.google.cloud:google-cloud-core-http:2.7.1
com.google.cloud:google-cloud-core:2.7.1
com.google.cloud:google-cloud-firestore:3.0.14
com.google.cloud:google-cloud-monitoring:1.82.0
com.google.cloud:google-cloud-nio:0.124.4
com.google.cloud:google-cloud-nio:0.124.2
com.google.cloud:google-cloud-pubsub:1.116.0
com.google.cloud:google-cloud-pubsublite:1.5.0
com.google.cloud:google-cloud-secretmanager:2.2.0
com.google.cloud:google-cloud-spanner:6.20.0
com.google.cloud:google-cloud-storage:2.8.0
com.google.cloud:google-cloud-storage:2.7.2
com.google.cloud:google-cloud-tasks:2.2.0
com.google.cloud:grpc-gcp:1.1.0
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.0.14
@@ -126,7 +124,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.9.0
com.google.common.html.types:types:1.0.6
com.google.dagger:dagger:2.42
com.google.errorprone:error_prone_annotations:2.14.0
com.google.errorprone:error_prone_annotations:2.13.1
com.google.escapevelocity:escapevelocity:0.9.1
com.google.flatbuffers:flatbuffers-java:1.12.0
com.google.flogger:flogger-system-backend:0.7.4
@@ -137,12 +135,12 @@ com.google.guava:guava-testlib:31.1-jre
com.google.guava:guava:31.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.9.0
com.google.http-client:google-http-client-apache-v2:1.42.0
com.google.http-client:google-http-client-appengine:1.42.0
com.google.http-client:google-http-client-gson:1.42.0
com.google.http-client:google-http-client-apache-v2:1.41.8
com.google.http-client:google-http-client-appengine:1.41.8
com.google.http-client:google-http-client-gson:1.41.8
com.google.http-client:google-http-client-jackson2:1.41.8
com.google.http-client:google-http-client-protobuf:1.40.1
com.google.http-client:google-http-client:1.42.0
com.google.http-client:google-http-client:1.41.8
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
@@ -150,18 +148,17 @@ com.google.jsinterop:jsinterop-annotations:2.0.0
com.google.monitoring-client:contrib:1.0.7
com.google.monitoring-client:metrics:1.0.7
com.google.monitoring-client:stackdriver:1.0.7
com.google.oauth-client:google-oauth-client-appengine:1.34.1
com.google.oauth-client:google-oauth-client-java6:1.34.1
com.google.oauth-client:google-oauth-client-jetty:1.34.1
com.google.oauth-client:google-oauth-client-servlet:1.34.1
com.google.oauth-client:google-oauth-client:1.34.1
com.google.oauth-client:google-oauth-client-appengine:1.34.0
com.google.oauth-client:google-oauth-client-java6:1.34.0
com.google.oauth-client:google-oauth-client-jetty:1.34.0
com.google.oauth-client:google-oauth-client-servlet:1.34.0
com.google.oauth-client:google-oauth-client:1.34.0
com.google.protobuf:protobuf-java-util:3.20.1
com.google.protobuf:protobuf-java:3.20.1
com.google.re2j:re2j:1.6
com.google.template:soy:2021-02-01
com.google.truth.extensions:truth-java8-extension:1.1.3
com.google.truth:truth:1.1.3
com.googlecode.charts4j:charts4j:1.3
com.googlecode.json-simple:json-simple:1.1.1
com.ibm.icu:icu4j:71.1
com.jcraft:jsch:0.1.55
@@ -229,7 +226,6 @@ io.opencensus:opencensus-impl-core:0.31.0
io.opencensus:opencensus-impl:0.31.0
io.opencensus:opencensus-proto:0.2.0
io.perfmark:perfmark-api:0.25.0
it.unimi.dsi:fastutil:6.5.16
javax.activation:activation:1.1
javax.activation:javax.activation-api:1.2.0
javax.annotation:javax.annotation-api:1.3.2
@@ -283,7 +279,7 @@ org.apache.mina:mina-core:2.1.6
org.apache.sshd:sshd-core:2.0.0
org.apache.sshd:sshd-scp:2.0.0
org.apache.sshd:sshd-sftp:2.0.0
org.apache.tomcat:tomcat-annotations-api:10.1.0-M16
org.apache.tomcat:tomcat-annotations-api:10.1.0-M15
org.bouncycastle:bcpg-jdk15on:1.67
org.bouncycastle:bcpkix-jdk15on:1.67
org.bouncycastle:bcprov-jdk15on:1.67
@@ -293,7 +289,6 @@ org.codehaus.jackson:jackson-core-asl:1.9.13
org.codehaus.jackson:jackson-mapper-asl:1.9.13
org.codehaus.mojo:animal-sniffer-annotations:1.21
org.conscrypt:conscrypt-openjdk-uber:2.5.1
org.easymock:easymock:3.0
org.flywaydb:flyway-core:8.5.12
org.glassfish.jaxb:jaxb-runtime:2.3.1
org.glassfish.jaxb:txw2:2.3.1
@@ -334,7 +329,7 @@ org.ow2.asm:asm-commons:9.2
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.ow2.asm:asm:9.3
org.postgresql:postgresql:42.4.0
org.postgresql:postgresql:42.3.6
org.rnorth.duct-tape:duct-tape:1.0.8
org.seleniumhq.selenium:selenium-api:3.141.59
org.seleniumhq.selenium:selenium-chrome-driver:3.141.59
@@ -1,111 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.backup;
import static com.google.appengine.api.ThreadManager.currentRequestThreadFactory;
import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
import static google.registry.backup.ExportCommitLogDiffAction.LOWER_CHECKPOINT_TIME_PARAM;
import static google.registry.backup.ExportCommitLogDiffAction.UPPER_CHECKPOINT_TIME_PARAM;
import static google.registry.backup.RestoreCommitLogsAction.BUCKET_OVERRIDE_PARAM;
import static google.registry.backup.RestoreCommitLogsAction.FROM_TIME_PARAM;
import static google.registry.backup.RestoreCommitLogsAction.TO_TIME_PARAM;
import static google.registry.request.RequestParameters.extractOptionalParameter;
import static google.registry.request.RequestParameters.extractRequiredDatetimeParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter;
import static java.util.concurrent.Executors.newFixedThreadPool;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.ListeningExecutorService;
import dagger.Module;
import dagger.Provides;
import google.registry.cron.CommitLogFanoutAction;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import java.lang.annotation.Documented;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Qualifier;
import javax.servlet.http.HttpServletRequest;
import org.joda.time.DateTime;
/**
* Dagger module for backup package.
*
* @see "google.registry.module.backend.BackendComponent"
*/
@Module
public final class BackupModule {
/** Dagger qualifier for backups. */
@Qualifier
@Documented
public @interface Backups {}
/** Number of threads in the threaded executor. */
private static final int NUM_THREADS = 10;
@Provides
@Parameter("bucket")
static int provideBucket(HttpServletRequest req) {
String param = extractRequiredParameter(req, CommitLogFanoutAction.BUCKET_PARAM);
Integer bucket = Ints.tryParse(param);
if (bucket == null) {
throw new BadRequestException("Bad bucket id");
}
return bucket;
}
@Provides
@Parameter(LOWER_CHECKPOINT_TIME_PARAM)
static DateTime provideLowerCheckpointKey(HttpServletRequest req) {
return extractRequiredDatetimeParameter(req, LOWER_CHECKPOINT_TIME_PARAM);
}
@Provides
@Parameter(UPPER_CHECKPOINT_TIME_PARAM)
static DateTime provideUpperCheckpointKey(HttpServletRequest req) {
return extractRequiredDatetimeParameter(req, UPPER_CHECKPOINT_TIME_PARAM);
}
@Provides
@Parameter(BUCKET_OVERRIDE_PARAM)
static Optional<String> provideBucketOverride(HttpServletRequest req) {
return extractOptionalParameter(req, BUCKET_OVERRIDE_PARAM);
}
@Provides
@Parameter(FROM_TIME_PARAM)
static DateTime provideFromTime(HttpServletRequest req) {
return extractRequiredDatetimeParameter(req, FROM_TIME_PARAM);
}
@Provides
@Parameter(TO_TIME_PARAM)
static DateTime provideToTime(HttpServletRequest req) {
return extractRequiredDatetimeParameter(req, TO_TIME_PARAM);
}
@Provides
@Backups
static ListeningExecutorService provideListeningExecutorService() {
return listeningDecorator(newFixedThreadPool(NUM_THREADS, currentRequestThreadFactory()));
}
@Provides
static ScheduledExecutorService provideScheduledExecutorService() {
return Executors.newSingleThreadScheduledExecutor();
}
}
@@ -1,83 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.backup;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
/** Utilities for working with backups. */
@DeleteAfterMigration
public class BackupUtils {
/** Keys for user metadata fields on commit log files in GCS. */
public static final class GcsMetadataKeys {
private GcsMetadataKeys() {}
public static final String NUM_TRANSACTIONS = "num_transactions";
public static final String LOWER_BOUND_CHECKPOINT = "lower_bound_checkpoint";
public static final String UPPER_BOUND_CHECKPOINT = "upper_bound_checkpoint";
}
/**
* Converts the given {@link ImmutableObject} to a raw Datastore entity and write it to an {@link
* OutputStream} in delimited protocol buffer format.
*/
static void serializeEntity(ImmutableObject entity, OutputStream stream) throws IOException {
EntityTranslator.convertToPb(auditedOfy().saveIgnoringReadOnlyWithoutBackup().toEntity(entity))
.writeDelimitedTo(stream);
}
/**
* Return an iterator of {@link ImmutableObject} instances deserialized from the given stream.
*
* <p>This parses out delimited protocol buffers for raw Datastore entities and then Ofy-loads
* those as {@link ImmutableObject}.
*
* <p>The iterator reads from the stream on demand, and as such will fail if the stream is closed.
*/
public static Iterator<ImmutableObject> createDeserializingIterator(
final InputStream input, boolean withAppIdOverride) {
return new AbstractIterator<ImmutableObject>() {
@Override
protected ImmutableObject computeNext() {
EntityProto proto = new EntityProto();
if (proto.parseDelimitedFrom(input)) { // False means end of stream; other errors throw.
if (withAppIdOverride) {
proto = EntityImports.fixEntity(proto);
}
return auditedOfy().load().fromEntity(EntityTranslator.createFromPb(proto));
}
return endOfData();
}
};
}
public static ImmutableList<ImmutableObject> deserializeEntities(byte[] bytes) {
return ImmutableList.copyOf(
createDeserializingIterator(new ByteArrayInputStream(bytes), false));
}
}
@@ -1,127 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.backup;
import static google.registry.backup.ExportCommitLogDiffAction.LOWER_CHECKPOINT_TIME_PARAM;
import static google.registry.backup.ExportCommitLogDiffAction.UPPER_CHECKPOINT_TIME_PARAM;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.ofy.CommitLogCheckpoint;
import google.registry.model.ofy.CommitLogCheckpointRoot;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.auth.Auth;
import google.registry.util.CloudTasksUtils;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* Action that saves commit log checkpoints to Datastore and kicks off a diff export task.
*
* <p>We separate computing and saving the checkpoint from exporting it because the export to GCS is
* retryable but should not require the computation of a new checkpoint. Saving the checkpoint and
* enqueuing the export task are done transactionally, so any checkpoint that is saved will be
* exported to GCS very soon.
*
* <p>This action's supported method is GET rather than POST because it gets invoked via cron.
*/
@Action(
service = Action.Service.BACKEND,
path = "/_dr/cron/commitLogCheckpoint",
method = Action.Method.GET,
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
@DeleteAfterMigration
public final class CommitLogCheckpointAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String QUEUE_NAME = "export-commits";
/**
* The amount of time enqueueing should be delayed.
*
* <p>The {@link ExportCommitLogDiffAction} is enqueued in {@link CommitLogCheckpointAction},
* which is inside a Datastore transaction that persists the checkpoint to be exported. After the
* switch to CloudTasks API, the task may be invoked before the Datastore transaction commits.
* When this happens, the checkpoint is not found which leads to {@link
* com.google.common.base.VerifyException}.
*
* <p>In order to invoke the task after the transaction commits, a reasonable delay should be
* added to each task. The latency of the request is mostly in the range of 4-6 seconds; Choosing
* a value 30% greater than the upper bound should solve the issue invoking a task before the
* transaction commits.
*/
static final Duration ENQUEUE_DELAY_SECONDS = Duration.standardSeconds(8);
@Inject CommitLogCheckpointStrategy strategy;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject CommitLogCheckpointAction() {}
@Override
public void run() {
createCheckPointAndStartAsyncExport();
}
/**
* Creates a {@link CommitLogCheckpoint} and initiates an asynchronous export task.
*
* @return the {@code CommitLogCheckpoint} to be exported
*/
public Optional<CommitLogCheckpoint> createCheckPointAndStartAsyncExport() {
final CommitLogCheckpoint checkpoint = strategy.computeCheckpoint();
logger.atInfo().log(
"Generated candidate checkpoint for time: %s", checkpoint.getCheckpointTime());
boolean isCheckPointPersisted =
ofyTm()
.transact(
() -> {
DateTime lastWrittenTime =
CommitLogCheckpointRoot.loadRoot().getLastWrittenTime();
if (isBeforeOrAt(checkpoint.getCheckpointTime(), lastWrittenTime)) {
logger.atInfo().log(
"Newer checkpoint already written at time: %s", lastWrittenTime);
return false;
}
auditedOfy()
.saveIgnoringReadOnlyWithoutBackup()
.entities(
checkpoint,
CommitLogCheckpointRoot.create(checkpoint.getCheckpointTime()));
// Enqueue a diff task between previous and current checkpoints.
cloudTasksUtils.enqueue(
QUEUE_NAME,
cloudTasksUtils.createPostTaskWithDelay(
ExportCommitLogDiffAction.PATH,
Service.BACKEND.toString(),
ImmutableMultimap.of(
LOWER_CHECKPOINT_TIME_PARAM,
lastWrittenTime.toString(),
UPPER_CHECKPOINT_TIME_PARAM,
checkpoint.getCheckpointTime().toString()),
ENQUEUE_DELAY_SECONDS));
return true;
});
return isCheckPointPersisted ? Optional.of(checkpoint) : Optional.empty();
}
}
@@ -1,164 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.backup;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Maps.transformValues;
import static google.registry.model.ofy.CommitLogBucket.getBucketKey;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.earliestOf;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.googlecode.objectify.Key;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.ofy.CommitLogBucket;
import google.registry.model.ofy.CommitLogCheckpoint;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.Ofy;
import google.registry.util.Clock;
import java.util.List;
import java.util.Map.Entry;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* Implementation of the procedure for determining point-in-time consistent commit log checkpoint.
*
* <p>This algorithm examines the recently written commit log data and uses a dual-read approach to
* determine a point-in-time consistent set of checkpoint times for the commit log buckets. By
* "consistent" we mean, generally speaking, that if the Datastore were restored by replaying all
* the commit logs up to the checkpoint times of the buckets, the result would be transactionally
* correct; there must be no "holes" where restored state depends on non-restored state.
*
* <p>The consistency guarantee really has two parts, only one of which is provided by this
* algorithm. The procedure below guarantees only that if the resulting checkpoint includes any
* given commit log, it will also include all the commit logs that were both 1) actually written
* before that commit log "in real life", and 2) have an earlier timestamp than that commit log.
* (These criteria do not necessarily imply each other, due to the lack of a global shared clock.)
* The rest of the guarantee comes from our Ofy customizations, which ensure that any transaction
* that depends on state from a previous transaction does indeed have a later timestamp.
*
* <h2>Procedure description</h2>
*
* <pre>{@code
* ComputeCheckpoint() -> returns a set consisting of a timestamp c(b_i) for every bucket b_i
*
* 1) read off the latest commit timestamp t(b_i) for every bucket b_i
* 2) iterate over the buckets b_i a second time, and
* a) do a consistent query for the next commit timestamp t'(b_i) where t'(b_i) > t(b_i)
* b) if present, add this timestamp t'(b_i) to a set S
* 3) compute a threshold time T* representing a time before all commits in S, as follows:
* a) if S is empty, let T* = +∞ (or the "end of time")
* b) else, let T* = T - Δ, for T = min(S) and some small Δ > 0
* 4) return the set given by: min(t(b_i), T*) for all b_i
* }</pre>
*
* <h2>Correctness proof of algorithm</h2>
*
* <p>{@literal As described above, the algorithm is correct as long as it can ensure the following:
* given a commit log X written at time t(X) to bucket b_x, and another commit log Y that was
* written "in real life" before X and for which t(Y) < t(X), then if X is included in the
* checkpoint, so is Y; that is, t(X) <= c(b_x) implies t(Y) <= c(b_y). }
*
* <p>{@literal To prove this, first note that we always have c(b_i) <= t(b_i) for every b_i, i.e.
* every commit log included in the checkpoint must have been seen in the first pass. Hence if X was
* included, then X must have been written by the time we started the second pass. But since Y was
* written "in real life" prior to X, we must have seen Y by the second pass too. }
*
* <p>{@literal Now assume towards a contradiction that X is indeed included but Y is not, i.e. that
* we have t(X) <= c(b_x) but t(Y) > c(b_y). If Y was seen in the first pass, i.e. t(Y) <= t(b_y),
* then by our assumption c(b_y) < t(Y) <= t(b_y), and therefore c(b_y) != t(b_y). By the definition
* of c(b_y) it must then equal T*, so we have T* < t(Y). However, this is a contradiction since
* t(Y) < t(X) and t(X) <= c(b_x) <= T*. If instead Y was seen in the second pass but not the first,
* t'(b_y) exists and we must have t'(b_y) <= t(Y), but then since T* < T <= t'(b_y) by definition,
* we again reach the contradiction T* < t(Y). }
*/
@DeleteAfterMigration
class CommitLogCheckpointStrategy {
@Inject Ofy ofy;
@Inject Clock clock;
@Inject CommitLogCheckpointStrategy() {}
/** Compute and return a new CommitLogCheckpoint for the current point in time. */
public CommitLogCheckpoint computeCheckpoint() {
DateTime checkpointTime = clock.nowUtc();
ImmutableMap<Integer, DateTime> firstPassTimes = readBucketTimestamps();
DateTime threshold = readNewCommitLogsAndFindThreshold(firstPassTimes);
return CommitLogCheckpoint.create(
checkpointTime,
computeBucketCheckpointTimes(firstPassTimes, threshold));
}
/**
* Returns a map from all bucket IDs to their current last written time values, fetched without
* a transaction so with no guarantee of consistency across buckets.
*/
@VisibleForTesting
ImmutableMap<Integer, DateTime> readBucketTimestamps() {
// Use a fresh session cache so that we get the latest data from Datastore.
return ofy.doWithFreshSessionCache(
() ->
CommitLogBucket.loadAllBuckets()
.stream()
.collect(
ImmutableMap.toImmutableMap(
CommitLogBucket::getBucketNum, CommitLogBucket::getLastWrittenTime)));
}
/**
* Returns a threshold value defined as the latest timestamp that is before all new commit logs,
* where "new" means having a commit time after the per-bucket timestamp in the given map.
* When no such commit logs exist, the threshold value is set to END_OF_TIME.
*/
@VisibleForTesting
DateTime readNewCommitLogsAndFindThreshold(ImmutableMap<Integer, DateTime> bucketTimes) {
DateTime timeBeforeAllNewCommits = END_OF_TIME;
for (Entry<Integer, DateTime> entry : bucketTimes.entrySet()) {
Key<CommitLogBucket> bucketKey = getBucketKey(entry.getKey());
DateTime bucketTime = entry.getValue();
// Add 1 to handle START_OF_TIME since 0 isn't a valid id - filter then uses >= instead of >.
Key<CommitLogManifest> keyForFilter =
Key.create(CommitLogManifest.create(bucketKey, bucketTime.plusMillis(1), null));
List<Key<CommitLogManifest>> manifestKeys =
ofy.load()
.type(CommitLogManifest.class)
.ancestor(bucketKey)
.filterKey(">=", keyForFilter)
.limit(1)
.keys()
.list();
if (!manifestKeys.isEmpty()) {
timeBeforeAllNewCommits = earliestOf(
timeBeforeAllNewCommits,
CommitLogManifest.extractCommitTime(getOnlyElement(manifestKeys)).minusMillis(1));
}
}
return timeBeforeAllNewCommits;
}
/**
* Returns the bucket checkpoint times produced by clamping the given set of bucket timestamps to
* at most the given threshold value.
*/
@VisibleForTesting
ImmutableMap<Integer, DateTime> computeBucketCheckpointTimes(
ImmutableMap<Integer, DateTime> firstPassTimes,
final DateTime threshold) {
return ImmutableMap.copyOf(
transformValues(firstPassTimes, firstPassTime -> earliestOf(firstPassTime, threshold)));
}
}
@@ -1,346 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.backup;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.appengine.tools.mapreduce.Reducer;
import com.google.appengine.tools.mapreduce.ReducerInput;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.mapreduce.inputs.CommitLogManifestInput;
import google.registry.mapreduce.inputs.EppResourceInputs;
import google.registry.model.EppResource;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import google.registry.model.translators.CommitLogRevisionsTranslatorFactory;
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 javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* Task that garbage collects old {@link CommitLogManifest} entities.
*
* <p>Once commit logs have been written to GCS, we don't really need them in Datastore anymore,
* except to reconstruct point-in-time snapshots of the database. To make that possible, {@link
* EppResource}s have a {@link EppResource#getRevisions} method that returns the commit logs for
* older points in time. But that functionality is not useful after a certain amount of time, e.g.
* thirty days, so unneeded revisions are deleted (see {@link CommitLogRevisionsTranslatorFactory}).
* This leaves commit logs in the system that are unneeded (have no revisions pointing to them). So
* this task runs periodically to delete the "orphan" commit logs.
*
* <p>This action runs a mapreduce that goes over all existing {@link EppResource} and all {@link
* CommitLogManifest} older than commitLogDatastreRetention, and erases the commit logs aren't in an
* EppResource.
*/
@Action(
service = Action.Service.BACKEND,
path = "/_dr/task/deleteOldCommitLogs",
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
// No longer needed in SQL. Subject to future removal.
@Deprecated
@DeleteAfterMigration
public final class DeleteOldCommitLogsAction implements Runnable {
private static final int NUM_MAP_SHARDS = 20;
private static final int NUM_REDUCE_SHARDS = 10;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject Clock clock;
@Inject
@Config("commitLogDatastoreRetention")
Duration maxAge;
@Inject
@Parameter(PARAM_DRY_RUN)
boolean isDryRun;
@Inject
DeleteOldCommitLogsAction() {}
@Override
public void run() {
DateTime deletionThreshold = clock.nowUtc().minus(maxAge);
logger.atInfo().log(
"Processing asynchronous deletion of unreferenced CommitLogManifests older than %s.",
deletionThreshold);
mrRunner
.setJobName("Delete old commit logs")
.setModuleName("backend")
.setDefaultMapShards(NUM_MAP_SHARDS)
.setDefaultReduceShards(NUM_REDUCE_SHARDS)
.runMapreduce(
new DeleteOldCommitLogsMapper(deletionThreshold),
new DeleteOldCommitLogsReducer(deletionThreshold, isDryRun),
ImmutableList.of(
new CommitLogManifestInput(deletionThreshold),
EppResourceInputs.createKeyInput(EppResource.class)))
.sendLinkToMapreduceConsole(response);
}
/**
* A mapper that iterates over all {@link EppResource} and {CommitLogManifest} entities.
*
* <p>It emits the target key and {@code false} for all revisions of each EppResources (meaning
* "don't delete this"), and {@code true} for all CommitLogRevisions (meaning "delete this").
*
* <p>The reducer will then delete all CommitLogRevisions that only have {@code true}.
*/
private static class DeleteOldCommitLogsMapper
extends Mapper<Key<?>, Key<CommitLogManifest>, Boolean> {
private static final long serialVersionUID = 8008689353479902948L;
private static final String KIND_MANIFEST = Key.getKind(CommitLogManifest.class);
private final DateTime threshold;
DeleteOldCommitLogsMapper(DateTime threshold) {
this.threshold = threshold;
}
@Override
public void map(final Key<?> key) {
// key is either a Key<CommitLogManifest> or a Key<? extends EppResource>.
//
// If it's a CommitLogManifest we just emit it as is (no need to load it).
if (key.getKind().equals(KIND_MANIFEST)) {
getContext().incrementCounter("old commit log manifests found");
// safe because we checked getKind
@SuppressWarnings("unchecked")
Key<CommitLogManifest> manifestKey = (Key<CommitLogManifest>) key;
emit(manifestKey, true);
return;
}
// If it isn't a Key<CommitLogManifest> then it should be an EppResource, which we need to
// load to emit the revisions.
//
Object object = auditedOfy().load().key(key).now();
checkNotNull(object, "Received a key to a missing object. key: %s", key);
checkState(
object instanceof EppResource,
"Received a key to an object that isn't EppResource nor CommitLogManifest."
+ " Key: %s object type: %s",
key,
object.getClass().getName());
getContext().incrementCounter("EPP resources found");
EppResource eppResource = (EppResource) object;
if (eppResource.getCreationTime().isAfter(threshold)) {
getContext().incrementCounter("EPP resources newer than threshold");
}
for (Key<CommitLogManifest> manifestKey : eppResource.getRevisions().values()) {
emit(manifestKey, false);
}
getContext()
.incrementCounter("EPP resource revisions found", eppResource.getRevisions().size());
checkAndLogRevisionCoverageError(eppResource);
}
/**
* Check if given eppResource has the required revisions.
*
* <p>Revisions are used to recreate the state of the resource at a given day in the past
* "commitLogDatastoreRenention". To do that, we need at least one revision that's older than
* this duration (is dated before "threshold"), or at least one revision within a day of the
* resource's creation if it was created after the threshold.
*
* <p>Here we check that the given eppResource has the revisions it needs.
*
* <p>It's just a sanity check - since we're relying on the revisions to be correct for the
* deletion to work. We want to alert any problems we find in the revisions.
*
* <p>This really checks {@link CommitLogRevisionsTranslatorFactory#transformBeforeSave}.
* There's nothing we can do at this point to prevent the damage - we only report on it.
*/
private void checkAndLogRevisionCoverageError(EppResource eppResource) {
// First - check if there even are revisions
if (eppResource.getRevisions().isEmpty()) {
getContext().incrementCounter("EPP resources missing all revisions (SEE LOGS)");
logger.atSevere().log("EPP resource missing all revisions: %s", Key.create(eppResource));
return;
}
// Next, check if there's a revision that's older than "CommitLogDatastoreRetention". There
// should have been at least one at the time this resource was saved.
//
// Alternatively, if the resource is newer than the threshold - there should be at least one
// revision within a day of the creation time.
DateTime oldestRevisionDate = eppResource.getRevisions().firstKey();
if (oldestRevisionDate.isBefore(threshold)
|| oldestRevisionDate.isBefore(eppResource.getCreationTime().plusDays(1))) {
// We're OK!
return;
}
// The oldest revision date is newer than the threshold! This shouldn't happen.
getContext().incrementCounter("EPP resources missing pre-threshold revision (SEE LOGS)");
logger.atSevere().log(
"EPP resource missing old enough revision: "
+ "%s (created on %s) has %d revisions between %s and %s, while threshold is %s.",
Key.create(eppResource),
eppResource.getCreationTime(),
eppResource.getRevisions().size(),
eppResource.getRevisions().firstKey(),
eppResource.getRevisions().lastKey(),
threshold);
// We want to see how bad it is though: if the difference is less than a day then this might
// still be OK (we only need logs for the end of the day). But if it's more than a day, then
// we are 100% sure we can't recreate all the history we need from the revisions.
Duration interval = new Duration(threshold, oldestRevisionDate);
if (interval.isLongerThan(Duration.standardDays(1))) {
getContext()
.incrementCounter("EPP resources missing pre-(threshold+1d) revision (SEE LOGS)");
}
}
}
/**
* Reducer that deletes unreferenced {@link CommitLogManifest} + child {@link CommitLogMutation}.
*
* <p>It receives the manifestKey to possibly delete, and a list of boolean 'verdicts' from
* various sources (the "old manifests" source and the "still referenced" source) on whether it's
* OK to delete this manifestKey. If even one source returns "false" (meaning "it's not OK to
* delete this manifest") then it won't be deleted.
*/
static class DeleteOldCommitLogsReducer extends Reducer<Key<CommitLogManifest>, Boolean, Void> {
private static final long serialVersionUID = -4918760187627937268L;
private final DateTime deletionThreshold;
private final boolean isDryRun;
@AutoValue
abstract static class DeletionResult {
enum Status {
ALREADY_DELETED,
AFTER_THRESHOLD,
SUCCESS
}
public abstract Status status();
public abstract int numDeleted();
static DeletionResult create(Status status, int numDeleted) {
return new AutoValue_DeleteOldCommitLogsAction_DeleteOldCommitLogsReducer_DeletionResult(
status, numDeleted);
}
}
DeleteOldCommitLogsReducer(DateTime deletionThreshold, boolean isDryRun) {
this.deletionThreshold = deletionThreshold;
this.isDryRun = isDryRun;
}
@Override
public void reduce(
final Key<CommitLogManifest> manifestKey, ReducerInput<Boolean> canDeleteVerdicts) {
ImmutableMultiset<Boolean> canDeleteMultiset = ImmutableMultiset.copyOf(canDeleteVerdicts);
if (canDeleteMultiset.count(TRUE) > 1) {
getContext().incrementCounter("commit log manifests incorrectly mapped multiple times");
}
if (canDeleteMultiset.count(FALSE) > 1) {
getContext().incrementCounter("commit log manifests referenced multiple times");
}
if (canDeleteMultiset.contains(FALSE)) {
getContext()
.incrementCounter(
canDeleteMultiset.contains(TRUE)
? "old commit log manifests still referenced"
: "new (or nonexistent) commit log manifests referenced");
getContext()
.incrementCounter("EPP resource revisions handled", canDeleteMultiset.count(FALSE));
return;
}
DeletionResult deletionResult =
ofyTm()
.transactNew(
() -> {
CommitLogManifest manifest = auditedOfy().load().key(manifestKey).now();
// It is possible that the same manifestKey was run twice, if a shard had to be
// restarted or some weird failure. If this happens, we want to exit
// immediately. Note that this can never happen in dryRun.
if (manifest == null) {
return DeletionResult.create(DeletionResult.Status.ALREADY_DELETED, 0);
}
// Doing a sanity check on the date. This is the only place we use the
// CommitLogManifest, so maybe removing this test will improve performance.
// However, unless it's proven that the performance boost is significant (and
// we've tested this enough to be sure it never happens)- the safety of "let's
// not delete stuff we need from prod" is more important.
if (manifest.getCommitTime().isAfter(deletionThreshold)) {
return DeletionResult.create(DeletionResult.Status.AFTER_THRESHOLD, 0);
}
Iterable<Key<CommitLogMutation>> commitLogMutationKeys =
auditedOfy()
.load()
.type(CommitLogMutation.class)
.ancestor(manifestKey)
.keys()
.iterable();
ImmutableList<Key<?>> keysToDelete =
ImmutableList.<Key<?>>builder()
.addAll(commitLogMutationKeys)
.add(manifestKey)
.build();
// Normally in a dry run we would log the entities that would be deleted, but
// those can number in the millions so we skip the logging.
if (!isDryRun) {
auditedOfy().deleteWithoutBackup().keys(keysToDelete);
}
return DeletionResult.create(
DeletionResult.Status.SUCCESS, keysToDelete.size());
});
switch (deletionResult.status()) {
case SUCCESS:
getContext().incrementCounter("old commit log manifests deleted");
getContext().incrementCounter("total entities deleted", deletionResult.numDeleted());
break;
case ALREADY_DELETED:
getContext().incrementCounter("attempts to delete an already deleted manifest");
break;
case AFTER_THRESHOLD:
logger.atSevere().log(
"Won't delete CommitLogManifest %s that is too recent.", manifestKey);
getContext().incrementCounter("manifests incorrectly assigned for deletion (SEE LOGS)");
break;
}
}
}
}
@@ -1,114 +0,0 @@
// 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.backup;
import com.google.apphosting.api.ApiProxy;
import com.google.storage.onestore.v3.OnestoreEntity;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import com.google.storage.onestore.v3.OnestoreEntity.Path;
import com.google.storage.onestore.v3.OnestoreEntity.Property.Meaning;
import com.google.storage.onestore.v3.OnestoreEntity.PropertyValue.ReferenceValue;
import google.registry.model.annotations.DeleteAfterMigration;
import java.util.Objects;
/** Utilities for handling imported Datastore entities. */
@DeleteAfterMigration
public class EntityImports {
/**
* Transitively sets the {@code appId} of all keys in a foreign entity to that of the current
* system.
*/
public static EntityProto fixEntity(EntityProto entityProto) {
String currentAappId = ApiProxy.getCurrentEnvironment().getAppId();
if (Objects.equals(currentAappId, entityProto.getKey().getApp())) {
return entityProto;
}
return fixEntity(entityProto, currentAappId);
}
private static EntityProto fixEntity(EntityProto entityProto, String appId) {
if (entityProto.hasKey()) {
fixKey(entityProto, appId);
}
for (OnestoreEntity.Property property : entityProto.mutablePropertys()) {
fixProperty(property, appId);
}
for (OnestoreEntity.Property property : entityProto.mutableRawPropertys()) {
fixProperty(property, appId);
}
// CommitLogMutation embeds an entity as bytes, which needs additional fixes.
if (isCommitLogMutation(entityProto)) {
fixMutationEntityProtoBytes(entityProto, appId);
}
return entityProto;
}
private static boolean isCommitLogMutation(EntityProto entityProto) {
if (!entityProto.hasKey()) {
return false;
}
Path path = entityProto.getKey().getPath();
if (path.elementSize() == 0) {
return false;
}
return Objects.equals(path.getElement(path.elementSize() - 1).getType(), "CommitLogMutation");
}
private static void fixMutationEntityProtoBytes(EntityProto entityProto, String appId) {
for (OnestoreEntity.Property property : entityProto.mutableRawPropertys()) {
if (Objects.equals(property.getName(), "entityProtoBytes")) {
OnestoreEntity.PropertyValue value = property.getValue();
EntityProto fixedProto =
fixEntity(bytesToEntityProto(value.getStringValueAsBytes()), appId);
value.setStringValueAsBytes(fixedProto.toByteArray());
return;
}
}
}
private static void fixKey(EntityProto entityProto, String appId) {
entityProto.getMutableKey().setApp(appId);
}
private static void fixKey(ReferenceValue referenceValue, String appId) {
referenceValue.setApp(appId);
}
private static void fixProperty(OnestoreEntity.Property property, String appId) {
OnestoreEntity.PropertyValue value = property.getMutableValue();
if (value.hasReferenceValue()) {
fixKey(value.getMutableReferenceValue(), appId);
return;
}
if (property.getMeaningEnum().equals(Meaning.ENTITY_PROTO)) {
EntityProto embeddedProto = bytesToEntityProto(value.getStringValueAsBytes());
fixEntity(embeddedProto, appId);
value.setStringValueAsBytes(embeddedProto.toByteArray());
}
}
private static EntityProto bytesToEntityProto(byte[] bytes) {
EntityProto entityProto = new EntityProto();
boolean isParsed = entityProto.parseFrom(bytes);
if (!isParsed) {
throw new IllegalStateException("Failed to parse raw bytes as EntityProto.");
}
return entityProto;
}
}
@@ -1,223 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.backup;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verifyNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Lists.partition;
import static google.registry.backup.BackupUtils.GcsMetadataKeys.LOWER_BOUND_CHECKPOINT;
import static google.registry.backup.BackupUtils.GcsMetadataKeys.NUM_TRANSACTIONS;
import static google.registry.backup.BackupUtils.GcsMetadataKeys.UPPER_BOUND_CHECKPOINT;
import static google.registry.backup.BackupUtils.serializeEntity;
import static google.registry.model.ofy.CommitLogBucket.getBucketKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
import static java.util.Comparator.comparingLong;
import com.google.cloud.storage.BlobId;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.ofy.CommitLogBucket;
import google.registry.model.ofy.CommitLogCheckpoint;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.joda.time.DateTime;
/** Action that exports the diff between two commit log checkpoints to GCS. */
@Action(
service = Action.Service.BACKEND,
path = ExportCommitLogDiffAction.PATH,
method = Action.Method.POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
@DeleteAfterMigration
public final class ExportCommitLogDiffAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final String PATH = "/_dr/task/exportCommitLogDiff";
static final String UPPER_CHECKPOINT_TIME_PARAM = "upperCheckpointTime";
static final String LOWER_CHECKPOINT_TIME_PARAM = "lowerCheckpointTime";
public static final String DIFF_FILE_PREFIX = "commit_diff_until_";
@Inject GcsUtils gcsUtils;
@Inject @Config("commitLogGcsBucket") String gcsBucket;
@Inject @Config("commitLogDiffExportBatchSize") int batchSize;
@Inject @Parameter(LOWER_CHECKPOINT_TIME_PARAM) DateTime lowerCheckpointTime;
@Inject @Parameter(UPPER_CHECKPOINT_TIME_PARAM) DateTime upperCheckpointTime;
@Inject ExportCommitLogDiffAction() {}
@Override
public void run() {
logger.atInfo().log(
"Exporting commit log diffs between %s and %s.", lowerCheckpointTime, upperCheckpointTime);
checkArgument(isAtOrAfter(lowerCheckpointTime, START_OF_TIME));
checkArgument(lowerCheckpointTime.isBefore(upperCheckpointTime));
// Load the boundary checkpoints - lower is exclusive and may not exist (on the first export,
// when lowerCheckpointTime is START_OF_TIME), whereas the upper is inclusive and must exist.
CommitLogCheckpoint lowerCheckpoint =
lowerCheckpointTime.isAfter(START_OF_TIME)
? verifyNotNull(
auditedOfy().load().key(CommitLogCheckpoint.createKey(lowerCheckpointTime)).now())
: null;
CommitLogCheckpoint upperCheckpoint =
verifyNotNull(
auditedOfy().load().key(CommitLogCheckpoint.createKey(upperCheckpointTime)).now());
// Load the keys of all the manifests to include in this diff.
List<Key<CommitLogManifest>> sortedKeys = loadAllDiffKeys(lowerCheckpoint, upperCheckpoint);
logger.atInfo().log("Found %d manifests to export.", sortedKeys.size());
// Open an output channel to GCS, wrapped in a stream for convenience.
try (OutputStream gcsStream =
gcsUtils.openOutputStream(
BlobId.of(gcsBucket, DIFF_FILE_PREFIX + upperCheckpointTime),
ImmutableMap.of(
LOWER_BOUND_CHECKPOINT, lowerCheckpointTime.toString(),
UPPER_BOUND_CHECKPOINT, upperCheckpointTime.toString(),
NUM_TRANSACTIONS, Integer.toString(sortedKeys.size())))) {
// Export the upper checkpoint itself.
serializeEntity(upperCheckpoint, gcsStream);
// If there are no manifests to export, stop early, now that we've written out the file with
// the checkpoint itself (which is needed for restores, even if it's empty).
if (sortedKeys.isEmpty()) {
return;
}
// Export to GCS in chunks, one per fixed batch of commit logs. While processing one batch,
// asynchronously load the entities for the next one.
List<List<Key<CommitLogManifest>>> keyChunks = partition(sortedKeys, batchSize);
// Objectify's map return type is asynchronous. Calling .values() will block until it loads.
Map<?, CommitLogManifest> nextChunkToExport = auditedOfy().load().keys(keyChunks.get(0));
for (int i = 0; i < keyChunks.size(); i++) {
// Force the async load to finish.
Collection<CommitLogManifest> chunkValues = nextChunkToExport.values();
logger.atInfo().log("Loaded %d manifests.", chunkValues.size());
// Since there is no hard bound on how much data this might be, take care not to let the
// Objectify session cache fill up and potentially run out of memory. This is the only safe
// point to do this since at this point there is no async load in progress.
auditedOfy().clearSessionCache();
// Kick off the next async load, which can happen in parallel to the current GCS export.
if (i + 1 < keyChunks.size()) {
nextChunkToExport = auditedOfy().load().keys(keyChunks.get(i + 1));
}
exportChunk(gcsStream, chunkValues);
logger.atInfo().log("Exported %d manifests.", chunkValues.size());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
logger.atInfo().log("Exported %d total manifests.", sortedKeys.size());
}
/**
* Loads all the diff keys, sorted in a transaction-consistent chronological order.
*
* @param lowerCheckpoint exclusive lower bound on keys in this diff, or null if no lower bound
* @param upperCheckpoint inclusive upper bound on keys in this diff
*/
private ImmutableList<Key<CommitLogManifest>> loadAllDiffKeys(
@Nullable final CommitLogCheckpoint lowerCheckpoint,
final CommitLogCheckpoint upperCheckpoint) {
// Fetch the keys (no data) between these checkpoints, and sort by timestamp. This ordering is
// transaction-consistent by virtue of our checkpoint strategy and our customized Ofy; see
// CommitLogCheckpointStrategy for the proof. We break ties by sorting on bucket ID to ensure
// a deterministic order.
return upperCheckpoint
.getBucketTimestamps()
.keySet()
.stream()
.flatMap(
bucketNum ->
Streams.stream(loadDiffKeysFromBucket(lowerCheckpoint, upperCheckpoint, bucketNum)))
.sorted(
comparingLong(Key<CommitLogManifest>::getId)
.thenComparingLong(a -> a.getParent().getId()))
.collect(toImmutableList());
}
/**
* Loads the diff keys for one bucket.
*
* @param lowerCheckpoint exclusive lower bound on keys in this diff, or null if no lower bound
* @param upperCheckpoint inclusive upper bound on keys in this diff
* @param bucketNum the bucket to load diff keys from
*/
private Iterable<Key<CommitLogManifest>> loadDiffKeysFromBucket(
@Nullable CommitLogCheckpoint lowerCheckpoint,
CommitLogCheckpoint upperCheckpoint,
int bucketNum) {
// If no lower checkpoint exists, or if it exists but had no timestamp for this bucket number
// (because the bucket count was increased between these checkpoints), then use START_OF_TIME
// as the effective exclusive lower bound.
DateTime lowerCheckpointBucketTime =
firstNonNull(
(lowerCheckpoint == null) ? null : lowerCheckpoint.getBucketTimestamps().get(bucketNum),
START_OF_TIME);
// Since START_OF_TIME=0 is not a valid id in a key, add 1 to both bounds. Then instead of
// loading lowerBound < x <= upperBound, we can load lowerBound <= x < upperBound.
DateTime lowerBound = lowerCheckpointBucketTime.plusMillis(1);
DateTime upperBound = upperCheckpoint.getBucketTimestamps().get(bucketNum).plusMillis(1);
// If the lower and upper bounds are equal, there can't be any results, so skip the query.
if (lowerBound.equals(upperBound)) {
return ImmutableSet.of();
}
Key<CommitLogBucket> bucketKey = getBucketKey(bucketNum);
return auditedOfy()
.load()
.type(CommitLogManifest.class)
.ancestor(bucketKey)
.filterKey(">=", CommitLogManifest.createKey(bucketKey, lowerBound))
.filterKey("<", CommitLogManifest.createKey(bucketKey, upperBound))
.keys();
}
/** Writes a chunks-worth of manifests and associated mutations to GCS. */
private void exportChunk(OutputStream gcsStream, Collection<CommitLogManifest> chunk)
throws IOException {
// Kickoff async loads for all the manifests in the chunk.
ImmutableList.Builder<Iterable<? extends ImmutableObject>> entities =
new ImmutableList.Builder<>();
for (CommitLogManifest manifest : chunk) {
entities.add(ImmutableList.of(manifest));
entities.add(auditedOfy().load().type(CommitLogMutation.class).ancestor(manifest));
}
for (ImmutableObject entity : concat(entities.build())) {
serializeEntity(entity, gcsStream);
}
}
}
@@ -1,246 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.backup;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.backup.BackupUtils.GcsMetadataKeys.LOWER_BOUND_CHECKPOINT;
import static google.registry.backup.ExportCommitLogDiffAction.DIFF_FILE_PREFIX;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static google.registry.util.DateTimeUtils.latestOf;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.UncheckedExecutionException;
import google.registry.backup.BackupModule.Backups;
import google.registry.gcs.GcsUtils;
import google.registry.model.annotations.DeleteAfterMigration;
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import org.joda.time.DateTime;
/** Utility class to list commit logs diff files stored on GCS. */
@DeleteAfterMigration
class GcsDiffFileLister {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** Timeout for retrieving per-file information from GCS. */
private static final Duration FILE_INFO_TIMEOUT_DURATION = Duration.ofMinutes(1);
@Inject GcsUtils gcsUtils;
@Inject @Backups Provider<ListeningExecutorService> executorProvider;
@Inject ScheduledExecutorService scheduledExecutorService;
@Inject
GcsDiffFileLister() {}
/**
* Traverses the sequence of diff files backwards from checkpointTime and inserts the file
* metadata into "sequence". Returns true if a complete sequence was discovered, false if one or
* more files are missing.
*
* @throws UncheckedExecutionException wrapping a {@link java.util.concurrent.TimeoutException} if
* the GCS call fails to finish within one minute, or wrapping any other exception if
* something else goes wrong.
*/
private boolean constructDiffSequence(
String gcsBucket,
Map<DateTime, ListenableFuture<BlobInfo>> upperBoundTimesToBlobInfo,
DateTime fromTime,
DateTime lastTime,
TreeMap<DateTime, BlobInfo> sequence) {
DateTime checkpointTime = lastTime;
while (isBeforeOrAt(fromTime, checkpointTime)) {
BlobInfo blobInfo;
if (upperBoundTimesToBlobInfo.containsKey(checkpointTime)) {
blobInfo =
Futures.getUnchecked(
Futures.withTimeout(
upperBoundTimesToBlobInfo.get(checkpointTime),
FILE_INFO_TIMEOUT_DURATION,
scheduledExecutorService));
} else {
String filename = DIFF_FILE_PREFIX + checkpointTime;
logger.atInfo().log("Patching GCS list; discovered file: %s", filename);
blobInfo = getBlobInfo(gcsBucket, filename);
// If we hit a gap, quit.
if (blobInfo == null) {
logger.atWarning().log(
"Gap discovered in sequence terminating at %s, missing file: %s",
sequence.lastKey(), filename);
logger.atInfo().log("Found sequence from %s to %s.", checkpointTime, lastTime);
return false;
}
}
sequence.put(checkpointTime, blobInfo);
checkpointTime = getLowerBoundTime(blobInfo);
}
logger.atInfo().log("Found sequence from %s to %s.", checkpointTime, lastTime);
return true;
}
ImmutableList<BlobInfo> listDiffFiles(
String gcsBucket, DateTime fromTime, @Nullable DateTime toTime) {
logger.atInfo().log("Requested restore from time: %s", fromTime);
if (toTime != null) {
logger.atInfo().log(" Until time: %s", toTime);
}
// List all of the diff files on GCS and build a map from each file's upper checkpoint time
// (extracted from the filename) to its asynchronously-loaded metadata, keeping only files with
// an upper checkpoint time > fromTime.
TreeMap<DateTime, ListenableFuture<BlobInfo>> upperBoundTimesToBlobInfo = new TreeMap<>();
String commitLogDiffPrefix = getCommitLogDiffPrefix(fromTime, toTime);
ImmutableList<String> filenames;
try {
filenames =
gcsUtils.listFolderObjects(gcsBucket, commitLogDiffPrefix).stream()
.map(s -> commitLogDiffPrefix + s)
.collect(toImmutableList());
} catch (IOException e) {
throw new RuntimeException(e);
}
DateTime lastUpperBoundTime = START_OF_TIME;
TreeMap<DateTime, BlobInfo> sequence = new TreeMap<>();
ListeningExecutorService executor = executorProvider.get();
try {
for (String filename : filenames) {
String strippedFilename = filename.replaceFirst(DIFF_FILE_PREFIX, "");
DateTime upperBoundTime = DateTime.parse(strippedFilename);
if (isInRange(upperBoundTime, fromTime, toTime)) {
upperBoundTimesToBlobInfo.put(
upperBoundTime, executor.submit(() -> getBlobInfo(gcsBucket, filename)));
lastUpperBoundTime = latestOf(upperBoundTime, lastUpperBoundTime);
}
}
if (upperBoundTimesToBlobInfo.isEmpty()) {
logger.atInfo().log("No files found.");
return ImmutableList.of();
}
// Reconstruct the sequence of files by traversing backwards from "lastUpperBoundTime" (i.e.
// the last file that we found) and finding its previous file until we either run out of files
// or get to one that precedes "fromTime".
//
// GCS file listing is eventually consistent, so it's possible that we are missing a file. The
// metadata of a file is sufficient to identify the preceding file, so if we start from the
// last file and work backwards we can verify that we have no holes in our chain (although we
// may be missing files at the end).
logger.atInfo().log("Restoring until: %s", lastUpperBoundTime);
boolean inconsistentFileSet =
!constructDiffSequence(
gcsBucket, upperBoundTimesToBlobInfo, fromTime, lastUpperBoundTime, sequence);
// Verify that all of the elements in the original set are represented in the sequence. If we
// find anything that's not represented, construct a sequence for it.
boolean checkForMoreExtraDiffs = true; // Always loop at least once.
while (checkForMoreExtraDiffs) {
checkForMoreExtraDiffs = false;
for (DateTime key : upperBoundTimesToBlobInfo.descendingKeySet()) {
if (!isInRange(key, fromTime, toTime)) {
break;
}
if (!sequence.containsKey(key)) {
// Recalculate the sequence for purely informational purposes.
logger.atWarning().log(
"Fork found in commit log history. The following sequence "
+ "is disconnected from the sequence of the final commit:");
constructDiffSequence(gcsBucket, upperBoundTimesToBlobInfo, fromTime, key, sequence);
checkForMoreExtraDiffs = true;
inconsistentFileSet = true;
break;
}
}
}
checkState(
!inconsistentFileSet,
"Unable to compute commit diff history, there are either gaps or forks in the history "
+ "file set. Check log for details.");
} finally {
executor.shutdown();
}
logger.atInfo().log(
"Actual restore from time: %s", getLowerBoundTime(sequence.firstEntry().getValue()));
logger.atInfo().log("Found %d files to restore.", sequence.size());
return ImmutableList.copyOf(sequence.values());
}
/**
* Returns true if 'time' is in range of 'start' and 'end'.
*
* <p>If 'end' is null, returns true if 'time' is after 'start'.
*/
private boolean isInRange(DateTime time, DateTime start, @Nullable DateTime end) {
return isBeforeOrAt(start, time) && (end == null || isBeforeOrAt(time, end));
}
private DateTime getLowerBoundTime(BlobInfo blobInfo) {
return DateTime.parse(blobInfo.getMetadata().get(LOWER_BOUND_CHECKPOINT));
}
private BlobInfo getBlobInfo(String gcsBucket, String filename) {
return gcsUtils.getBlobInfo(BlobId.of(gcsBucket, filename));
}
/**
* Returns a prefix guaranteed to cover all commit log diff files in the given range.
*
* <p>The listObjects call can be fairly slow if we search over many thousands or tens of
* thousands of files, so we restrict the search space. The commit logs have a file format of
* "commit_diff_until_2021-05-11T06:48:00.070Z" so we can often filter down as far as the hour.
*
* <p>Here, we get the longest prefix possible based on which fields (year, month, day, hour) the
* times in question have in common.
*/
@VisibleForTesting
static String getCommitLogDiffPrefix(DateTime from, @Nullable DateTime to) {
StringBuilder result = new StringBuilder(DIFF_FILE_PREFIX);
if (to == null || from.getYear() != to.getYear()) {
return result.toString();
}
result.append(from.getYear()).append('-');
if (from.getMonthOfYear() != to.getMonthOfYear()) {
return result.toString();
}
result.append(String.format("%02d-", from.getMonthOfYear()));
if (from.getDayOfMonth() != to.getDayOfMonth()) {
return result.toString();
}
result.append(String.format("%02dT", from.getDayOfMonth()));
if (from.getHourOfDay() != to.getHourOfDay()) {
return result.toString();
}
result.append(String.format("%02d:", from.getHourOfDay()));
return result.toString();
}
}
@@ -1,208 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.backup;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterators.peekingIterator;
import static google.registry.backup.BackupUtils.createDeserializingIterator;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.cloud.storage.BlobInfo;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.PeekingIterator;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.util.ResultNow;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.gcs.GcsUtils;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.ofy.CommitLogBucket;
import google.registry.model.ofy.CommitLogCheckpoint;
import google.registry.model.ofy.CommitLogCheckpointRoot;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.Retrier;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.inject.Inject;
import org.joda.time.DateTime;
/** Restore Registry 2 commit logs from GCS to Datastore. */
@Action(
service = Action.Service.TOOLS,
path = RestoreCommitLogsAction.PATH,
method = Action.Method.POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
@DeleteAfterMigration
public class RestoreCommitLogsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final String PATH = "/_dr/task/restoreCommitLogs";
static final String DRY_RUN_PARAM = "dryRun";
static final String FROM_TIME_PARAM = "fromTime";
static final String TO_TIME_PARAM = "toTime";
static final String BUCKET_OVERRIDE_PARAM = "gcsBucket";
private static final ImmutableSet<RegistryEnvironment> FORBIDDEN_ENVIRONMENTS =
ImmutableSet.of(RegistryEnvironment.PRODUCTION, RegistryEnvironment.SANDBOX);
@Inject GcsUtils gcsUtils;
@Inject @Parameter(DRY_RUN_PARAM) boolean dryRun;
@Inject @Parameter(FROM_TIME_PARAM) DateTime fromTime;
@Inject @Parameter(TO_TIME_PARAM) DateTime toTime;
@Inject
@Parameter(BUCKET_OVERRIDE_PARAM)
Optional<String> gcsBucketOverride;
@Inject DatastoreService datastoreService;
@Inject GcsDiffFileLister diffLister;
@Inject
@Config("commitLogGcsBucket")
String defaultGcsBucket;
@Inject Retrier retrier;
@Inject RestoreCommitLogsAction() {}
@Override
public void run() {
checkArgument(
!FORBIDDEN_ENVIRONMENTS.contains(RegistryEnvironment.get()),
"DO NOT RUN IN PRODUCTION OR SANDBOX.");
if (dryRun) {
logger.atInfo().log("Running in dry-run mode.");
}
String gcsBucket = gcsBucketOverride.orElse(defaultGcsBucket);
logger.atInfo().log("Restoring from %s.", gcsBucket);
List<BlobInfo> diffFiles = diffLister.listDiffFiles(gcsBucket, fromTime, toTime);
if (diffFiles.isEmpty()) {
logger.atInfo().log("Nothing to restore.");
return;
}
Map<Integer, DateTime> bucketTimestamps = new HashMap<>();
CommitLogCheckpoint lastCheckpoint = null;
for (BlobInfo metadata : diffFiles) {
logger.atInfo().log("Restoring: %s", metadata.getName());
try (InputStream input = gcsUtils.openInputStream(metadata.getBlobId())) {
PeekingIterator<ImmutableObject> commitLogs =
peekingIterator(createDeserializingIterator(input, true));
lastCheckpoint = (CommitLogCheckpoint) commitLogs.next();
saveOfy(ImmutableList.of(lastCheckpoint)); // Save the checkpoint itself.
while (commitLogs.hasNext()) {
CommitLogManifest manifest = restoreOneTransaction(commitLogs);
bucketTimestamps.put(manifest.getBucketId(), manifest.getCommitTime());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// Restore the CommitLogCheckpointRoot and CommitLogBuckets.
saveOfy(
Streams.concat(
bucketTimestamps
.entrySet()
.stream()
.map(
entry ->
new CommitLogBucket.Builder()
.setBucketNum(entry.getKey())
.setLastWrittenTime(entry.getValue())
.build()),
Stream.of(CommitLogCheckpointRoot.create(lastCheckpoint.getCheckpointTime())))
.collect(toImmutableList()));
logger.atInfo().log("Restore complete.");
}
/**
* Restore the contents of one transaction to Datastore.
*
* <p>The objects to delete are listed in the {@link CommitLogManifest}, which will be the first
* object in the iterable. The objects to save follow, each as a {@link CommitLogMutation}. We
* restore by deleting the deletes and recreating the saves from their proto form. We also save
* the commit logs themselves back to Datastore, so that the commit log system itself is
* transparently restored alongside the data.
*
* @return the manifest, for use in restoring the {@link CommitLogBucket}.
*/
private CommitLogManifest restoreOneTransaction(PeekingIterator<ImmutableObject> commitLogs) {
final CommitLogManifest manifest = (CommitLogManifest) commitLogs.next();
Result<?> deleteResult = deleteAsync(manifest.getDeletions());
List<Entity> entitiesToSave = Lists.newArrayList(auditedOfy().save().toEntity(manifest));
while (commitLogs.hasNext() && commitLogs.peek() instanceof CommitLogMutation) {
CommitLogMutation mutation = (CommitLogMutation) commitLogs.next();
entitiesToSave.add(auditedOfy().save().toEntity(mutation));
entitiesToSave.add(EntityTranslator.createFromPbBytes(mutation.getEntityProtoBytes()));
}
saveRaw(entitiesToSave);
try {
deleteResult.now();
} catch (Exception e) {
retrier.callWithRetry(
() -> deleteAsync(manifest.getDeletions()).now(), RuntimeException.class);
}
return manifest;
}
private void saveRaw(List<Entity> entitiesToSave) {
if (dryRun) {
logger.atInfo().log("Would have saved entities: %s", entitiesToSave);
return;
}
retrier.callWithRetry(() -> datastoreService.put(entitiesToSave), RuntimeException.class);
}
private void saveOfy(Iterable<? extends ImmutableObject> objectsToSave) {
if (dryRun) {
logger.atInfo().log("Would have saved entities: %s", objectsToSave);
return;
}
retrier.callWithRetry(
() -> auditedOfy().saveWithoutBackup().entities(objectsToSave).now(),
RuntimeException.class);
}
private Result<?> deleteAsync(Set<Key<?>> keysToDelete) {
if (dryRun) {
logger.atInfo().log("Would have deleted entities: %s", keysToDelete);
}
return dryRun || keysToDelete.isEmpty()
? new ResultNow<Void>(null)
: auditedOfy().deleteWithoutBackup().keys(keysToDelete);
}
}
@@ -1,200 +0,0 @@
// Copyright 2020 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.backup;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.appengine.api.datastore.Key;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* A Datastore {@link Entity Entity's} serialized state with timestamp. The intended use case is a
* multi-stage pipeline where an Entity's Java form is not needed in most stages.
*
* <p>For a new or updated Entity, its serialized bytes are stored along with its Datastore {@link
* Key}. For a deleted entity, only its Datastore {@link Key} is stored, and the {@link
* #entityProtoBytes} field is left unset.
*
* <p>Storing raw bytes is motivated by two factors. First, since I/O is frequent and the Java
* objects are rarely needed in our target use case, storing raw bytes is the most efficient
* approach. More importantly, due to our data model and our customization of {@link
* google.registry.model.ofy.ObjectifyService ObjectifyService}, it is challenging to implement a
* serializer for Objectify entities that preserves the value of all properties. Without such
* serializers, Objectify entities cannot be used in a pipeline.
*
* <p>Objectify entities do not implement {@link Serializable}, serialization of such objects is as
* follows:
*
* <ul>
* <li>Convert an Objectify entity to a Datastore {@link Entity}: {@code
* auditedOfy().save().toEntity(..)}
* <li>Entity is serializable, but the more efficient approach is to convert an Entity to a
* ProtocolBuffer ({@link com.google.storage.onestore.v3.OnestoreEntity.EntityProto}) and then
* to raw bytes.
* </ul>
*
* <p>When the first conversion above is applied to an Objectify entity, a property value in the
* output may differ from the input in two situations:
*
* <ul>
* <li>If a property is of an assign-on-persist data type, e.g., {@link
* google.registry.model.UpdateAutoTimestamp}.
* <li>If it is related to CommitLog management, e.g., {@link google.registry.model.EppResource
* EppResource.revisions}.
* </ul>
*
* <p>Working around the side effects caused by our customization is difficult. Any solution would
* likely rely on Objectify's stack of context. However, many Objectify invocations in our code base
* are hardcoded to call the customized version of ObjectifyService, rendering Objectify's stack
* useless.
*
* <p>For now, this inability to use Objectify entities in pipelines is mostly a testing problem: we
* can not perform {@link org.apache.beam.sdk.testing.PAssert BEAM pipeline assertions} on Objectify
* entities. {@code InitSqlTestUtils.assertContainsExactlyElementsIn} is an example of a workaround.
*
* <p>Note that {@link Optional java.util.Optional} is not serializable, therefore cannot be used as
* property type in this class.
*/
@AutoValue
@DeleteAfterMigration
public abstract class VersionedEntity implements Serializable {
private static final long serialVersionUID = 1L;
public abstract long commitTimeMills();
/** The {@link Key} of the {@link Entity}. */
public abstract Key key();
/** Serialized form of the {@link Entity}. This property is {@code null} for a deleted Entity. */
@Nullable
abstract ImmutableBytes entityProtoBytes();
@Memoized
public Optional<Entity> getEntity() {
return Optional.ofNullable(entityProtoBytes())
.map(ImmutableBytes::getBytes)
.map(EntityTranslator::createFromPbBytes);
}
public boolean isDelete() {
return entityProtoBytes() == null;
}
/**
* Converts deleted entity keys in {@code manifest} into a {@link Stream} of {@link
* VersionedEntity VersionedEntities}. See {@link CommitLogImports#loadEntities} for more
* information.
*/
static Stream<VersionedEntity> fromManifest(CommitLogManifest manifest) {
long commitTimeMillis = manifest.getCommitTime().getMillis();
return manifest.getDeletions().stream()
.map(com.googlecode.objectify.Key::getRaw)
.map(key -> newBuilder().commitTimeMills(commitTimeMillis).key(key).build());
}
/* Converts a {@link CommitLogMutation} to a {@link VersionedEntity}. */
static VersionedEntity fromMutation(CommitLogMutation mutation) {
return from(
com.googlecode.objectify.Key.create(mutation).getParent().getId(),
mutation.getEntityProtoBytes());
}
public static VersionedEntity from(long commitTimeMillis, byte[] entityProtoBytes) {
return newBuilder()
.entityProtoBytes(entityProtoBytes)
.key(EntityTranslator.createFromPbBytes(entityProtoBytes).getKey())
.commitTimeMills(commitTimeMillis)
.build();
}
private static Builder newBuilder() {
return new AutoValue_VersionedEntity.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder commitTimeMills(long commitTimeMillis);
abstract Builder entityProtoBytes(ImmutableBytes bytes);
public abstract Builder key(Key key);
public abstract VersionedEntity build();
Builder entityProtoBytes(byte[] bytes) {
return entityProtoBytes(new ImmutableBytes(bytes));
}
}
/**
* Wraps a byte array and prevents it from being modified by its original owner.
*
* <p>While this class seems an overkill, it exists for two reasons:
*
* <ul>
* <li>It is easier to override the {@link #equals} method here (for value-equivalence check)
* than to override the AutoValue-generated {@code equals} method.
* <li>To appease the style checker, which forbids arrays as AutoValue property.
* </ul>
*/
static final class ImmutableBytes implements Serializable {
private static final long serialVersionUID = 1L;
private final byte[] bytes;
ImmutableBytes(byte[] bytes) {
this.bytes = Arrays.copyOf(bytes, bytes.length);
}
/**
* Returns the saved byte array. Invocation is restricted to trusted callers, who must not
* modify the array.
*/
byte[] getBytes() {
return bytes;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ImmutableBytes)) {
return false;
}
ImmutableBytes that = (ImmutableBytes) o;
// Do not use Objects.equals, which checks reference identity instead of data in array.
return Arrays.equals(bytes, that.bytes);
}
@Override
public int hashCode() {
// Do not use Objects.hashCode, which hashes the reference, not the data in array.
return Arrays.hashCode(bytes);
}
}
}
@@ -1,16 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.backup;
@@ -71,7 +71,7 @@ public final class AsyncTaskEnqueuer {
public AsyncTaskEnqueuer(
@Named(QUEUE_ASYNC_DELETE) Queue asyncDeletePullQueue,
@Named(QUEUE_ASYNC_HOST_RENAME) Queue asyncDnsRefreshPullQueue,
@Config("asyncDeleteFlowMapreduceDelay") Duration asyncDeleteDelay,
@Config("asyncDeleteDelay") Duration asyncDeleteDelay,
CloudTasksUtils cloudTasksUtils,
Retrier retrier) {
this.asyncDeletePullQueue = asyncDeletePullQueue;
@@ -21,6 +21,7 @@ import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_HOST_RENAME;
import static google.registry.request.RequestParameters.extractBooleanParameter;
import static google.registry.request.RequestParameters.extractIntParameter;
import static google.registry.request.RequestParameters.extractLongParameter;
import static google.registry.request.RequestParameters.extractOptionalBooleanParameter;
@@ -45,6 +46,9 @@ import org.joda.time.DateTime;
@Module
public class BatchModule {
public static final String PARAM_DRY_RUN = "dryRun";
public static final String PARAM_FAST = "fast";
@Provides
@Parameter("jobName")
static Optional<String> provideJobName(HttpServletRequest req) {
@@ -113,9 +117,15 @@ public class BatchModule {
}
@Provides
@Parameter(ResaveAllEppResourcesPipelineAction.PARAM_FAST)
static Optional<Boolean> provideIsFast(HttpServletRequest req) {
return extractOptionalBooleanParameter(req, ResaveAllEppResourcesPipelineAction.PARAM_FAST);
@Parameter(PARAM_FAST)
static boolean provideIsFast(HttpServletRequest req) {
return extractBooleanParameter(req, PARAM_FAST);
}
@Provides
@Parameter(PARAM_DRY_RUN)
static boolean provideIsDryRun(HttpServletRequest req) {
return extractBooleanParameter(req, PARAM_DRY_RUN);
}
@Provides
@@ -18,7 +18,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static google.registry.flows.FlowUtils.marshalWithLenientRetry;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -129,15 +128,13 @@ public class DeleteExpiredDomainsAction implements Runnable {
logger.atInfo().log(
"Deleting non-renewing domains with autorenew end times up through %s.", runTime);
// Note: in Datastore, this query is (and must be) non-transactional, and thus, is only
// eventually consistent.
ImmutableList<DomainBase> domainsToDelete =
transactIfJpaTm(
() ->
tm().createQueryComposer(DomainBase.class)
.where("autorenewEndTime", Comparator.LTE, runTime)
.where("deletionTime", Comparator.EQ, END_OF_TIME)
.list());
tm().transact(
() ->
tm().createQueryComposer(DomainBase.class)
.where("autorenewEndTime", Comparator.LTE, runTime)
.where("deletionTime", Comparator.EQ, END_OF_TIME)
.list());
if (domainsToDelete.isEmpty()) {
logger.atInfo().log("Found 0 domains to delete.");
response.setPayload("Found 0 domains to delete.");
@@ -16,39 +16,30 @@ package google.registry.batch;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.batch.BatchModule.PARAM_DRY_RUN;
import static google.registry.config.RegistryEnvironment.PRODUCTION;
import static google.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN;
import static google.registry.mapreduce.inputs.EppResourceInputs.createEntityInput;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryEnvironment;
import google.registry.flows.poll.PollFlowUtils;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.model.EppResource;
import google.registry.model.EppResourceUtils;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntryDao;
import google.registry.persistence.VKey;
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.List;
import javax.inject.Inject;
/**
@@ -57,8 +48,8 @@ import javax.inject.Inject;
*
* <p>This only deletes contacts and hosts, NOT domains. To delete domains, use {@link
* DeleteProberDataAction} and pass it the TLD(s) that the load test domains were created on. Note
* that DeleteProberDataAction is safe enough to run in production whereas this mapreduce is not,
* but this one does not need to be runnable in production because load testing isn't run against
* that DeleteProberDataAction is safe enough to run in production whereas this action is not, but
* this one does not need to be runnable in production because load testing isn't run against
* production.
*/
@Action(
@@ -79,48 +70,31 @@ public class DeleteLoadTestDataAction implements Runnable {
private static final ImmutableSet<String> LOAD_TEST_REGISTRARS = ImmutableSet.of("proxy");
private final boolean isDryRun;
private final MapreduceRunner mrRunner;
private final Response response;
private final Clock clock;
@Inject
DeleteLoadTestDataAction(
@Parameter(PARAM_DRY_RUN) boolean isDryRun,
MapreduceRunner mrRunner,
Response response,
Clock clock) {
this.isDryRun = isDryRun;
this.mrRunner = mrRunner;
this.response = response;
this.clock = clock;
}
@Override
public void run() {
// This mapreduce doesn't guarantee that foreign key relations are preserved, so isn't safe to
// This action doesn't guarantee that foreign key relations are preserved, so isn't safe to
// run on production. On other environments, data is fully wiped out occasionally anyway, so
// having some broken data that isn't referred to isn't the end of the world.
checkState(
!RegistryEnvironment.get().equals(PRODUCTION),
"This mapreduce is not safe to run on PRODUCTION.");
"This action is not safe to run on PRODUCTION.");
if (tm().isOfy()) {
mrRunner
.setJobName("Delete load test data")
.setModuleName("backend")
.runMapOnly(
new DeleteLoadTestDataMapper(isDryRun),
ImmutableList.of(
createEntityInput(ContactResource.class), createEntityInput(HostResource.class)))
.sendLinkToMapreduceConsole(response);
} else {
tm().transact(
() -> {
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
tm().loadAllOfStream(ContactResource.class).forEach(this::deleteContact);
tm().loadAllOfStream(HostResource.class).forEach(this::deleteHost);
});
}
tm().transact(
() -> {
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
tm().loadAllOfStream(ContactResource.class).forEach(this::deleteContact);
tm().loadAllOfStream(HostResource.class).forEach(this::deleteHost);
});
}
private void deletePollMessages(String registrarId) {
@@ -184,54 +158,4 @@ public class DeleteLoadTestDataAction implements Runnable {
tm().delete(eppResource);
}
}
/** Provides the map method that runs for each existing contact and host entity. */
public static class DeleteLoadTestDataMapper extends Mapper<EppResource, Void, Void> {
private static final long serialVersionUID = -3817710674062432694L;
private final boolean isDryRun;
public DeleteLoadTestDataMapper(boolean isDryRun) {
this.isDryRun = isDryRun;
}
@Override
public final void map(EppResource resource) {
if (LOAD_TEST_REGISTRARS.contains(resource.getPersistedCurrentSponsorRegistrarId())) {
deleteResource(resource);
getContext()
.incrementCounter(
String.format("deleted %s entities", resource.getClass().getSimpleName()));
} else {
getContext().incrementCounter("skipped, not load test data");
}
}
private void deleteResource(EppResource resource) {
final Key<EppResourceIndex> eppIndex =
Key.create(EppResourceIndex.create(Key.create(resource)));
final Key<? extends ForeignKeyIndex<?>> fki = ForeignKeyIndex.createKey(resource);
int numEntitiesDeleted =
tm().transact(
() -> {
// This ancestor query selects all descendant entities.
List<Key<Object>> resourceAndDependentKeys =
auditedOfy().load().ancestor(resource).keys().list();
ImmutableSet<Key<?>> allKeys =
new ImmutableSet.Builder<Key<?>>()
.add(fki)
.add(eppIndex)
.addAll(resourceAndDependentKeys)
.build();
if (isDryRun) {
logger.atInfo().log("Would hard-delete the following entities: %s", allKeys);
} else {
auditedOfy().deleteWithoutBackup().keys(allKeys);
}
return allKeys.size();
});
getContext().incrementCounter("total entities deleted", numEntitiesDeleted);
}
}
}
@@ -17,45 +17,32 @@ package google.registry.batch;
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.batch.BatchModule.PARAM_DRY_RUN;
import static google.registry.config.RegistryEnvironment.PRODUCTION;
import static google.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN;
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_DELETE;
import static google.registry.model.tld.Registries.getTldsOfType;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.RequestParameters.PARAM_TLDS;
import static org.joda.time.DateTimeZone.UTC;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.dns.DnsQueue;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.mapreduce.inputs.EppResourceInputs;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.EppResourceUtils;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldType;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
import org.hibernate.CacheMode;
@@ -94,8 +81,6 @@ public class DeleteProberDataAction implements Runnable {
*/
private static final Duration SOFT_DELETE_DELAY = Duration.standardHours(1);
private static final DnsQueue dnsQueue = DnsQueue.create();
// Domains to delete must:
// 1. Be in one of the prober TLDs
// 2. Not be a nic domain
@@ -115,6 +100,8 @@ public class DeleteProberDataAction implements Runnable {
/** Number of domains to retrieve and delete per SQL transaction. */
private static final int BATCH_SIZE = 1000;
@Inject DnsQueue dnsQueue;
@Inject @Parameter(PARAM_DRY_RUN) boolean isDryRun;
/** List of TLDs to work on. If empty - will work on all TLDs that end with .test. */
@Inject @Parameter(PARAM_TLDS) ImmutableSet<String> tlds;
@@ -123,8 +110,6 @@ public class DeleteProberDataAction implements Runnable {
@Config("registryAdminClientId")
String registryAdminRegistrarId;
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject DeleteProberDataAction() {}
@Override
@@ -145,21 +130,7 @@ public class DeleteProberDataAction implements Runnable {
"If tlds are given, they must all exist and be TEST tlds. Given: %s, not found: %s",
tlds,
Sets.difference(tlds, deletableTlds));
ImmutableSet<String> proberRoidSuffixes =
deletableTlds.stream()
.map(tld -> Registry.get(tld).getRoidSuffix())
.collect(toImmutableSet());
if (tm().isOfy()) {
mrRunner
.setJobName("Delete prober data")
.setModuleName("backend")
.runMapOnly(
new DeleteProberDataMapper(proberRoidSuffixes, isDryRun, registryAdminRegistrarId),
ImmutableList.of(EppResourceInputs.createKeyInput(DomainBase.class)))
.sendLinkToMapreduceConsole(response);
} else {
runSqlJob(deletableTlds);
}
runSqlJob(deletableTlds);
}
private void runSqlJob(ImmutableSet<String> deletableTlds) {
@@ -231,7 +202,7 @@ public class DeleteProberDataAction implements Runnable {
"Would soft-delete the active domain: %s (%s).",
domain.getDomainName(), domain.getRepoId());
} else {
softDeleteDomain(domain, registryAdminRegistrarId, dnsQueue);
softDeleteDomain(domain);
}
softDeletedDomains.incrementAndGet();
} else {
@@ -280,8 +251,7 @@ public class DeleteProberDataAction implements Runnable {
}
// Take a DNS queue + admin registrar id as input so that it can be called from the mapper as well
private static void softDeleteDomain(
DomainBase domain, String registryAdminRegistrarId, DnsQueue localDnsQueue) {
private void softDeleteDomain(DomainBase domain) {
DomainBase deletedDomain =
domain.asBuilder().setDeletionTime(tm().getTransactionTime()).setStatusValues(null).build();
DomainHistory historyEntry =
@@ -299,119 +269,6 @@ public class DeleteProberDataAction implements Runnable {
tm().putAllWithoutBackup(ImmutableList.of(deletedDomain, historyEntry));
// updating foreign keys is a no-op in SQL
updateForeignKeyIndexDeletionTime(deletedDomain);
localDnsQueue.addDomainRefreshTask(deletedDomain.getDomainName());
}
/** Provides the map method that runs for each existing DomainBase entity. */
public static class DeleteProberDataMapper extends Mapper<Key<DomainBase>, Void, Void> {
private static final DnsQueue dnsQueue = DnsQueue.create();
private static final long serialVersionUID = -7724537393697576369L;
private final ImmutableSet<String> proberRoidSuffixes;
private final Boolean isDryRun;
private final String registryAdminRegistrarId;
public DeleteProberDataMapper(
ImmutableSet<String> proberRoidSuffixes,
Boolean isDryRun,
String registryAdminRegistrarId) {
this.proberRoidSuffixes = proberRoidSuffixes;
this.isDryRun = isDryRun;
this.registryAdminRegistrarId = registryAdminRegistrarId;
}
@Override
public final void map(Key<DomainBase> key) {
try {
String roidSuffix = Iterables.getLast(Splitter.on('-').split(key.getName()));
if (proberRoidSuffixes.contains(roidSuffix)) {
deleteDomain(key);
} else {
getContext().incrementCounter("skipped, non-prober data");
}
} catch (Throwable t) {
logger.atSevere().withCause(t).log("Error while deleting prober data for key %s.", key);
getContext().incrementCounter(String.format("error, kind %s", key.getKind()));
}
}
private void deleteDomain(final Key<DomainBase> domainKey) {
final DomainBase domain = auditedOfy().load().key(domainKey).now();
DateTime now = DateTime.now(UTC);
if (domain == null) {
// Depending on how stale Datastore indexes are, we can get keys to resources that are
// already deleted (e.g. by a recent previous invocation of this mapreduce). So ignore them.
getContext().incrementCounter("already deleted");
return;
}
String domainName = domain.getDomainName();
if (domainName.equals("nic." + domain.getTld())) {
getContext().incrementCounter("skipped, NIC domain");
return;
}
if (now.isBefore(domain.getCreationTime().plus(DOMAIN_USED_DURATION))) {
getContext().incrementCounter("skipped, domain too new");
return;
}
if (!domain.getSubordinateHosts().isEmpty()) {
logger.atWarning().log(
"Cannot delete domain %s (%s) because it has subordinate hosts.",
domainName, domainKey);
getContext().incrementCounter("skipped, had subordinate host(s)");
return;
}
// If the domain is still active, that means that the prober encountered a failure and did not
// successfully soft-delete the domain (thus leaving its DNS entry published). We soft-delete
// it now so that the DNS entry can be handled. The domain will then be hard-deleted the next
// time the mapreduce is run.
if (EppResourceUtils.isActive(domain, now)) {
if (isDryRun) {
logger.atInfo().log(
"Would soft-delete the active domain: %s (%s).", domainName, domainKey);
} else {
tm().transact(() -> softDeleteDomain(domain, registryAdminRegistrarId, dnsQueue));
}
getContext().incrementCounter("domains soft-deleted");
return;
}
// If the domain isn't active, we want to make sure it hasn't been active for "a while" before
// deleting it. This prevents accidental double-map with the same key from immediately
// deleting active domains
if (now.isBefore(domain.getDeletionTime().plus(SOFT_DELETE_DELAY))) {
getContext().incrementCounter("skipped, domain too recently soft deleted");
return;
}
final Key<EppResourceIndex> eppIndex = Key.create(EppResourceIndex.create(domainKey));
final Key<? extends ForeignKeyIndex<?>> fki = ForeignKeyIndex.createKey(domain);
int entitiesDeleted =
tm().transact(
() -> {
// This ancestor query selects all descendant HistoryEntries, BillingEvents,
// PollMessages, and TLD-specific entities, as well as the domain itself.
List<Key<Object>> domainAndDependentKeys =
auditedOfy().load().ancestor(domainKey).keys().list();
ImmutableSet<Key<?>> allKeys =
new ImmutableSet.Builder<Key<?>>()
.add(fki)
.add(eppIndex)
.addAll(domainAndDependentKeys)
.build();
if (isDryRun) {
logger.atInfo().log("Would hard-delete the following entities: %s", allKeys);
} else {
auditedOfy().deleteWithoutBackup().keys(allKeys);
}
return allKeys.size();
});
getContext().incrementCounter("domains hard-deleted");
getContext().incrementCounter("total entities hard-deleted", entitiesDeleted);
}
dnsQueue.addDomainRefreshTask(deletedDomain.getDomainName());
}
}
@@ -18,35 +18,25 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newHashSet;
import static google.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN;
import static google.registry.mapreduce.inputs.EppResourceInputs.createChildEntityInput;
import static google.registry.batch.BatchModule.PARAM_DRY_RUN;
import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING;
import static google.registry.model.domain.Period.Unit.YEARS;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW;
import static google.registry.persistence.transaction.QueryComposer.Comparator.EQ;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.util.CollectionUtils.union;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DateTimeUtils.earliestOf;
import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.appengine.tools.mapreduce.Reducer;
import com.google.appengine.tools.mapreduce.ReducerInput;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.domain.DomainPricingLogic;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.mapreduce.inputs.NullInput;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
@@ -70,13 +60,12 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.joda.money.Money;
import org.joda.time.DateTime;
/**
* A mapreduce that expands {@link Recurring} billing events into synthetic {@link OneTime} events.
* An action that expands {@link Recurring} billing events into synthetic {@link OneTime} events.
*
* <p>The cursor used throughout this mapreduce (overridden if necessary using the parameter {@code
* <p>The cursor used throughout this action (overridden if necessary using the parameter {@code
* cursorTime}) represents the inclusive lower bound on the range of billing times that will be
* expanded as a result of the job (the exclusive upper bound being the execution time of the job).
*/
@@ -87,11 +76,9 @@ import org.joda.time.DateTime;
public class ExpandRecurringBillingEventsAction implements Runnable {
public static final String PARAM_CURSOR_TIME = "cursorTime";
private static final String ERROR_COUNTER = "errors";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject Clock clock;
@Inject MapreduceRunner mrRunner;
@Inject
@Config("jdbcBatchSize")
@@ -108,33 +95,18 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
public void run() {
DateTime executeTime = clock.nowUtc();
DateTime persistedCursorTime =
transactIfJpaTm(
() ->
tm().loadByKeyIfPresent(Cursor.createGlobalVKey(RECURRING_BILLING))
.orElse(Cursor.createGlobal(RECURRING_BILLING, START_OF_TIME))
.getCursorTime());
tm().transact(
() ->
tm().loadByKeyIfPresent(Cursor.createGlobalVKey(RECURRING_BILLING))
.orElse(Cursor.createGlobal(RECURRING_BILLING, START_OF_TIME))
.getCursorTime());
DateTime cursorTime = cursorTimeParam.orElse(persistedCursorTime);
checkArgument(
cursorTime.isBefore(executeTime), "Cursor time must be earlier than execution time.");
logger.atInfo().log(
"Running Recurring billing event expansion for billing time range [%s, %s).",
cursorTime, executeTime);
if (tm().isOfy()) {
mrRunner
.setJobName("Expand Recurring billing events into synthetic OneTime events.")
.setModuleName("backend")
.runMapreduce(
new ExpandRecurringBillingEventsMapper(isDryRun, cursorTime, clock.nowUtc()),
new ExpandRecurringBillingEventsReducer(isDryRun, persistedCursorTime),
// Add an extra shard that maps over a null recurring event (see the mapper for why).
ImmutableList.of(
new NullInput<>(),
createChildEntityInput(
ImmutableSet.of(DomainBase.class), ImmutableSet.of(Recurring.class))))
.sendLinkToMapreduceConsole(response);
} else {
expandSqlBillingEventsInBatches(executeTime, cursorTime, persistedCursorTime);
}
expandSqlBillingEventsInBatches(executeTime, cursorTime, persistedCursorTime);
}
private void expandSqlBillingEventsInBatches(
@@ -183,7 +155,8 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
continue;
}
int billingEventsSaved =
expandBillingEvent(recurring, executeTime, cursorTime, isDryRun);
expandBillingEvent(
recurring, executeTime, cursorTime, isDryRun, domainPricingLogic);
batchBillingEventsSaved += billingEventsSaved;
if (billingEventsSaved > 0) {
expandedDomains.add(recurring.getTargetId());
@@ -257,118 +230,17 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
}
}
/** Mapper to expand {@link Recurring} billing events into synthetic {@link OneTime} events. */
public static class ExpandRecurringBillingEventsMapper
extends Mapper<Recurring, DateTime, DateTime> {
private static final long serialVersionUID = 8376442755556228455L;
private final boolean isDryRun;
private final DateTime cursorTime;
private final DateTime executeTime;
public ExpandRecurringBillingEventsMapper(
boolean isDryRun, DateTime cursorTime, DateTime executeTime) {
this.isDryRun = isDryRun;
this.cursorTime = cursorTime;
this.executeTime = executeTime;
}
@Override
public final void map(final Recurring recurring) {
// This single emit forces the reducer to run at the end of the map job, so that a mapper
// that runs without error will advance the cursor at the end of processing (unless this was
// a dry run, in which case the cursor should not be advanced).
if (recurring == null) {
emit(cursorTime, executeTime);
return;
}
getContext().incrementCounter("Recurring billing events encountered");
// Ignore any recurring billing events that have yet to apply.
if (recurring.getEventTime().isAfter(executeTime)
// This second case occurs when a domain is transferred or deleted before first renewal.
|| recurring.getRecurrenceEndTime().isBefore(recurring.getEventTime())) {
getContext().incrementCounter("Recurring billing events ignored");
return;
}
int numBillingEventsSaved = 0;
try {
numBillingEventsSaved =
tm().transactNew(
() -> expandBillingEvent(recurring, executeTime, cursorTime, isDryRun));
} catch (Throwable t) {
getContext().incrementCounter("error: " + t.getClass().getSimpleName());
getContext().incrementCounter(ERROR_COUNTER);
throw new RuntimeException(
String.format(
"Error while expanding Recurring billing events for %d", recurring.getId()),
t);
}
if (!isDryRun) {
getContext().incrementCounter("Saved OneTime billing events", numBillingEventsSaved);
} else {
getContext()
.incrementCounter("Generated OneTime billing events (dry run)", numBillingEventsSaved);
}
}
}
/**
* "Reducer" to advance the cursor after all map jobs have been completed. The NullInput into the
* mapper will cause the mapper to emit one timestamp pair (current cursor and execution time),
* and the cursor will be advanced (and the timestamps logged) at the end of a successful
* mapreduce.
*/
public static class ExpandRecurringBillingEventsReducer
extends Reducer<DateTime, DateTime, Void> {
private final boolean isDryRun;
private final DateTime expectedPersistedCursorTime;
public ExpandRecurringBillingEventsReducer(
boolean isDryRun, DateTime expectedPersistedCursorTime) {
this.isDryRun = isDryRun;
this.expectedPersistedCursorTime = expectedPersistedCursorTime;
}
@Override
public void reduce(final DateTime cursorTime, final ReducerInput<DateTime> executionTimeInput) {
if (getContext().getCounter(ERROR_COUNTER).getValue() > 0) {
logger.atSevere().log(
"One or more errors logged during recurring event expansion. Cursor will"
+ " not be advanced.");
return;
}
final DateTime executionTime = executionTimeInput.next();
logger.atInfo().log(
"Recurring event expansion %s complete for billing event range [%s, %s).",
isDryRun ? "(dry run) " : "", cursorTime, executionTime);
tm().transact(
() -> {
Cursor cursor =
auditedOfy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now();
DateTime currentCursorTime =
(cursor == null ? START_OF_TIME : cursor.getCursorTime());
if (!currentCursorTime.equals(expectedPersistedCursorTime)) {
logger.atSevere().log(
"Current cursor position %s does not match expected cursor position %s.",
currentCursorTime, expectedPersistedCursorTime);
return;
}
if (!isDryRun) {
tm().put(Cursor.createGlobal(RECURRING_BILLING, executionTime));
}
});
}
}
private static int expandBillingEvent(
Recurring recurring, DateTime executeTime, DateTime cursorTime, boolean isDryRun) {
Recurring recurring,
DateTime executeTime,
DateTime cursorTime,
boolean isDryRun,
DomainPricingLogic domainPricingLogic) {
ImmutableSet.Builder<OneTime> syntheticOneTimesBuilder = new ImmutableSet.Builder<>();
final Registry tld = Registry.get(getTldFromDomainName(recurring.getTargetId()));
// Determine the complete set of times at which this recurring event should
// occur (up to and including the runtime of the mapreduce).
// occur (up to and including the runtime of the action).
Iterable<DateTime> eventTimes =
recurring
.getRecurrenceTimeOfYear()
@@ -385,14 +257,10 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
VKey.create(
DomainBase.class, recurring.getDomainRepoId(), recurring.getParentKey().getParent());
Iterable<OneTime> oneTimesForDomain;
if (tm().isOfy()) {
oneTimesForDomain = auditedOfy().load().type(OneTime.class).ancestor(domainKey.getOfyKey());
} else {
oneTimesForDomain =
tm().createQueryComposer(OneTime.class)
.where("domainRepoId", EQ, recurring.getDomainRepoId())
.list();
}
oneTimesForDomain =
tm().createQueryComposer(OneTime.class)
.where("domainRepoId", EQ, recurring.getDomainRepoId())
.list();
// Determine the billing times that already have OneTime events persisted.
ImmutableSet<DateTime> existingBillingTimes =
@@ -431,13 +299,16 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
historyEntriesBuilder.add(historyEntry);
DateTime eventTime = billingTime.minus(tld.getAutoRenewGracePeriodLength());
// Determine the cost for a one-year renewal.
Money renewCost = getDomainRenewCost(recurring.getTargetId(), eventTime, 1);
syntheticOneTimesBuilder.add(
new OneTime.Builder()
.setBillingTime(billingTime)
.setRegistrarId(recurring.getRegistrarId())
.setCost(renewCost)
// Determine the cost for a one-year renewal.
.setCost(
domainPricingLogic
.getRenewPrice(tld, recurring.getTargetId(), eventTime, 1, recurring)
.getRenewCost())
.setEventTime(eventTime)
.setFlags(union(recurring.getFlags(), Flag.SYNTHETIC))
.setParent(historyEntry)
@@ -463,7 +334,7 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
/**
* Filters a set of {@link DateTime}s down to event times that are in scope for a particular
* mapreduce run, given the cursor time and the mapreduce execution time.
* action run, given the cursor time and the action execution time.
*/
protected static ImmutableSet<DateTime> getBillingTimesInScope(
Iterable<DateTime> eventTimes,
@@ -31,7 +31,7 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.domain.RegistryLock;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.tld.RegistryLockDao;
import google.registry.persistence.VKey;
import google.registry.request.Action;
@@ -120,10 +120,7 @@ public class RelockDomainAction implements Runnable {
* for more details on retry behavior. */
response.setStatus(SC_NO_CONTENT);
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
// nb: DomainLockUtils relies on the JPA transaction being the outermost transaction
// if we have Datastore as the primary DB (if SQL is the primary DB, it's irrelevant)
jpaTm().transact(() -> tm().transact(this::relockDomain));
tm().transact(this::relockDomain);
}
private void relockDomain() {
@@ -296,8 +293,8 @@ public class RelockDomainAction implements Runnable {
ImmutableSet<String> registryLockEmailAddresses =
registrar.getContacts().stream()
.filter(RegistrarContact::isRegistryLockAllowed)
.map(RegistrarContact::getRegistryLockEmailAddress)
.filter(RegistrarPoc::isRegistryLockAllowed)
.map(RegistrarPoc::getRegistryLockEmailAddress)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toImmutableSet());
@@ -1,128 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.batch;
import static google.registry.mapreduce.MapreduceRunner.PARAM_FAST;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.Key;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.mapreduce.inputs.EppResourceInputs;
import google.registry.model.EppResource;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import javax.inject.Inject;
/**
* A mapreduce that re-saves all EppResources, projecting them forward to the current time.
*
* <p>This is useful for completing data migrations on EppResource fields that are accomplished
* with @OnSave or @OnLoad annotations, and also guarantees that all EppResources will get fresh
* commit logs (for backup purposes). Additionally, pending actions such as transfers or grace
* periods that are past their effective time will be resolved.
*
* <p>Because there are no auth settings in the {@link Action} annotation, this command can only be
* run internally, or by pretending to be internal by setting the X-AppEngine-QueueName header,
* which only admin users can do.
*
* <p>If the <code>?fast=true</code> querystring parameter is passed, then entities that are not
* changed by {@link EppResource#cloneProjectedAtTime} will not be re-saved. This helps prevent
* mutation load on the DB and has the beneficial side effect of writing out smaller commit logs.
* Note that this does NOT pick up mutations caused by migrations using the {@link
* com.googlecode.objectify.annotation.OnLoad} annotation, so if you are running a one-off schema
* migration, do not use fast mode. Fast mode defaults to false for this reason, but is used by the
* monthly invocation of the mapreduce.
*/
@Action(
service = Action.Service.BACKEND,
path = "/_dr/task/resaveAllEppResources",
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
// No longer needed in SQL. Subject to future removal.
@Deprecated
@DeleteAfterMigration
public class ResaveAllEppResourcesAction implements Runnable {
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject
@Parameter(PARAM_FAST)
boolean isFast;
@Inject
ResaveAllEppResourcesAction() {}
/**
* The number of shards to run the map-only mapreduce on.
*
* <p>This is less than the default of 100 because we only run this action monthly and can afford
* it being slower, but we don't want to write out lots of large commit logs in a short period of
* time because they make the Cloud SQL migration tougher.
*/
private static final int NUM_SHARDS = 10;
@Override
public void run() {
mrRunner
.setJobName("Re-save all EPP resources")
.setModuleName("backend")
.setDefaultMapShards(NUM_SHARDS)
.runMapOnly(
new ResaveAllEppResourcesActionMapper(isFast),
ImmutableList.of(EppResourceInputs.createKeyInput(EppResource.class)))
.sendLinkToMapreduceConsole(response);
}
/** Mapper to re-save all EPP resources. */
public static class ResaveAllEppResourcesActionMapper
extends Mapper<Key<EppResource>, Void, Void> {
private static final long serialVersionUID = -7721628665138087001L;
private final boolean isFast;
ResaveAllEppResourcesActionMapper(boolean isFast) {
this.isFast = isFast;
}
@Override
public final void map(final Key<EppResource> resourceKey) {
boolean resaved =
tm().transact(
() -> {
EppResource originalResource = auditedOfy().load().key(resourceKey).now();
EppResource projectedResource =
originalResource.cloneProjectedAtTime(tm().getTransactionTime());
if (isFast && originalResource.equals(projectedResource)) {
return false;
} else {
auditedOfy().save().entity(projectedResource).now();
return true;
}
});
getContext()
.incrementCounter(
String.format(
"%s entities %s",
resourceKey.getKind(), resaved ? "re-saved" : "with no changes skipped"));
}
}
}
@@ -14,6 +14,7 @@
package google.registry.batch;
import static google.registry.batch.BatchModule.PARAM_FAST;
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;
@@ -32,7 +33,6 @@ 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;
/**
@@ -63,8 +63,6 @@ public class ResaveAllEppResourcesPipelineAction implements Runnable {
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;
@@ -78,14 +76,14 @@ public class ResaveAllEppResourcesPipelineAction implements Runnable {
@Config("projectId") String projectId,
@Config("defaultJobRegion") String jobRegion,
@Config("beamStagingBucketUrl") String stagingBucketUrl,
@Parameter(PARAM_FAST) Optional<Boolean> fast,
@Parameter(PARAM_FAST) boolean fast,
Clock clock,
Response response,
Dataflow dataflow) {
this.projectId = projectId;
this.jobRegion = jobRegion;
this.stagingBucketUrl = stagingBucketUrl;
this.fast = fast.orElse(false);
this.fast = fast;
this.clock = clock;
this.response = response;
this.dataflow = dataflow;
@@ -32,8 +32,8 @@ import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarContact.Type;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPoc.Type;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
@@ -281,9 +281,9 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
*/
@VisibleForTesting
ImmutableSet<InternetAddress> getEmailAddresses(Registrar registrar, Type contactType) {
ImmutableSortedSet<RegistrarContact> contacts = registrar.getContactsOfType(contactType);
ImmutableSortedSet<RegistrarPoc> contacts = registrar.getContactsOfType(contactType);
ImmutableSet.Builder<InternetAddress> recipientEmails = new ImmutableSet.Builder<>();
for (RegistrarContact contact : contacts) {
for (RegistrarPoc contact : contacts) {
try {
recipientEmails.add(new InternetAddress(contact.getEmailAddress()));
} catch (AddressException e) {
@@ -1,122 +0,0 @@
// 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.batch;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static google.registry.beam.BeamUtils.createJobName;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
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.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import javax.inject.Inject;
/**
* Wipes out all Cloud Datastore data in a Nomulus GCP environment.
*
* <p>This class is created for the QA environment, where migration testing with production data
* will happen. A regularly scheduled wipeout is a prerequisite to using production data there.
*/
@Action(
service = Action.Service.BACKEND,
path = "/_dr/task/wipeOutDatastore",
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
@DeleteAfterMigration
public class WipeoutDatastoreAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String PIPELINE_NAME = "bulk_delete_datastore_pipeline";
private static final ImmutableSet<RegistryEnvironment> FORBIDDEN_ENVIRONMENTS =
ImmutableSet.of(RegistryEnvironment.PRODUCTION, RegistryEnvironment.SANDBOX);
private final String projectId;
private final String jobRegion;
private final Response response;
private final Dataflow dataflow;
private final String stagingBucketUrl;
private final Clock clock;
@Inject
WipeoutDatastoreAction(
@Config("projectId") String projectId,
@Config("defaultJobRegion") String jobRegion,
@Config("beamStagingBucketUrl") String stagingBucketUrl,
Clock clock,
Response response,
Dataflow dataflow) {
this.projectId = projectId;
this.jobRegion = jobRegion;
this.stagingBucketUrl = stagingBucketUrl;
this.clock = clock;
this.response = response;
this.dataflow = dataflow;
}
@Override
public void run() {
response.setContentType(PLAIN_TEXT_UTF_8);
if (FORBIDDEN_ENVIRONMENTS.contains(RegistryEnvironment.get())) {
response.setStatus(SC_FORBIDDEN);
response.setPayload("Wipeout is not allowed in " + RegistryEnvironment.get());
return;
}
try {
LaunchFlexTemplateParameter parameters =
new LaunchFlexTemplateParameter()
.setJobName(createJobName("bulk-delete-datastore-", clock))
.setContainerSpecGcsPath(
String.format("%s/%s_metadata.json", stagingBucketUrl, PIPELINE_NAME))
.setParameters(
ImmutableMap.of(
"kindsToDelete",
"*",
"registryEnvironment",
RegistryEnvironment.get().name()));
LaunchFlexTemplateResponse launchResponse =
dataflow
.projects()
.locations()
.flexTemplates()
.launch(
projectId,
jobRegion,
new LaunchFlexTemplateRequest().setLaunchParameter(parameters))
.execute();
response.setStatus(SC_OK);
response.setPayload("Launched " + launchResponse.getJob().getName());
} catch (Exception e) {
String msg = String.format("Failed to launch %s.", PIPELINE_NAME);
logger.atSevere().withCause(e).log(msg);
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload(msg);
}
}
}
@@ -1,336 +0,0 @@
// Copyright 2020 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.datastore;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.beam.sdk.values.TypeDescriptors.kvs;
import static org.apache.beam.sdk.values.TypeDescriptors.strings;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.flogger.FluentLogger;
import com.google.datastore.v1.Entity;
import google.registry.config.RegistryEnvironment;
import google.registry.model.annotations.DeleteAfterMigration;
import java.util.Iterator;
import java.util.Map;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.extensions.gcp.options.GcpOptions;
import org.apache.beam.sdk.io.gcp.datastore.DatastoreIO;
import org.apache.beam.sdk.options.Default;
import org.apache.beam.sdk.options.Description;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.options.Validation;
import org.apache.beam.sdk.transforms.Create;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.GroupByKey;
import org.apache.beam.sdk.transforms.MapElements;
import org.apache.beam.sdk.transforms.PTransform;
import org.apache.beam.sdk.transforms.ParDo;
import org.apache.beam.sdk.transforms.Reshuffle;
import org.apache.beam.sdk.transforms.View;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.PBegin;
import org.apache.beam.sdk.values.PCollection;
import org.apache.beam.sdk.values.PCollectionTuple;
import org.apache.beam.sdk.values.PCollectionView;
import org.apache.beam.sdk.values.TupleTag;
import org.apache.beam.sdk.values.TupleTagList;
/**
* A BEAM pipeline that deletes Datastore entities in bulk.
*
* <p>This pipeline provides an alternative to the <a
* href="https://cloud.google.com/datastore/docs/bulk-delete">GCP builtin template</a> that performs
* the same task. It solves the following performance and usability problems in the builtin
* template:
*
* <ul>
* <li>When deleting all data (by using the {@code select __key__} or {@code select *} queries),
* the builtin template cannot parallelize the query, therefore has to query with a single
* worker.
* <li>When deleting all data, the builtin template also attempts to delete Datastore internal
* tables which would cause permission-denied errors, which in turn MAY cause the pipeline to
* abort before all data has been deleted.
* <li>With the builtin template, it is possible to delete multiple entity types in one pipeline
* ONLY if the user can come up with a single literal query that covers all of them. This is
* not the case with most Nomulus entity types.
* </ul>
*
* <p>A user of this pipeline must specify the types of entities to delete using the {@code
* --kindsToDelete} command line argument. To delete specific entity types, give a comma-separated
* string of their kind names; to delete all data, give {@code "*"}.
*
* <p>When deleting all data, it is recommended for the user to specify the number of user entity
* types in the Datastore using the {@code --numOfKindsHint} argument. If the default value for this
* parameter is too low, performance will suffer.
*/
@DeleteAfterMigration
public class BulkDeleteDatastorePipeline {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
// This tool is not for use in our critical projects.
private static final ImmutableSet<String> FORBIDDEN_PROJECTS =
ImmutableSet.of("domain-registry", "domain-registry-sandbox");
private final BulkDeletePipelineOptions options;
BulkDeleteDatastorePipeline(BulkDeletePipelineOptions options) {
this.options = options;
}
public void run() {
Pipeline pipeline = Pipeline.create(options);
setupPipeline(pipeline);
pipeline.run();
}
@SuppressWarnings("deprecation") // org.apache.beam.sdk.transforms.Reshuffle
private void setupPipeline(Pipeline pipeline) {
checkState(
!FORBIDDEN_PROJECTS.contains(options.getProject()),
"Bulk delete is forbidden in %s",
options.getProject());
// Pre-allocated tags to label entities by kind. In the case of delete-all, we must use a guess.
TupleTagList deletionTags;
PCollection<String> kindsToDelete;
if (options.getKindsToDelete().equals("*")) {
deletionTags = getDeletionTags(options.getNumOfKindsHint());
kindsToDelete =
pipeline.apply("DiscoverEntityKinds", discoverEntityKinds(options.getProject()));
} else {
ImmutableList<String> kindsToDeleteParam = parseKindsToDelete(options);
checkState(
!kindsToDeleteParam.contains("*"),
"The --kindsToDelete argument should not contain both '*' and other kinds.");
deletionTags = getDeletionTags(kindsToDeleteParam.size());
kindsToDelete = pipeline.apply("UseProvidedKinds", Create.of(kindsToDeleteParam));
}
// Map each kind to a tag. The "SplitByKind" stage below will group entities by kind using
// this mapping. In practice, this has been effective at avoiding entity group contentions.
PCollectionView<Map<String, TupleTag<Entity>>> kindToTagMapping =
mapKindsToDeletionTags(kindsToDelete, deletionTags).apply("GetKindsToTagMap", View.asMap());
PCollectionTuple entities =
kindsToDelete
.apply("GenerateQueries", ParDo.of(new GenerateQueries()))
.apply("ReadEntities", DatastoreV1.read().withProjectId(options.getProject()))
.apply(
"SplitByKind",
ParDo.of(new SplitEntities(kindToTagMapping))
.withSideInputs(kindToTagMapping)
.withOutputTags(getOneDeletionTag("placeholder"), deletionTags));
for (TupleTag<?> tag : deletionTags.getAll()) {
entities
.get((TupleTag<Entity>) tag)
// Reshuffle calls GroupByKey which is one way to trigger load rebalance in the pipeline.
// Using the deprecated "Reshuffle" for convenience given the short life of this tool.
.apply("RebalanceLoad", Reshuffle.viaRandomKey())
.apply(
"DeleteEntities_" + tag.getId(),
DatastoreIO.v1().deleteEntity().withProjectId(options.getProject()));
}
}
private static String toKeyOnlyQueryForKind(String kind) {
return "select __key__ from `" + kind + "`";
}
/**
* Returns a {@link TupleTag} that retains the generic type parameter and may be used in a
* multi-output {@link ParDo} (e.g. {@link SplitEntities}).
*
* <p>This method is NOT needed in tests when creating tags for assertions. Simply create them
* with {@code new TupleTag<Entity>(String)}.
*/
@VisibleForTesting
static TupleTag<Entity> getOneDeletionTag(String id) {
// The trailing {} is needed to retain generic param type.
return new TupleTag<Entity>(id) {};
}
@VisibleForTesting
static ImmutableList<String> parseKindsToDelete(BulkDeletePipelineOptions options) {
return ImmutableList.copyOf(
Splitter.on(",").omitEmptyStrings().trimResults().split(options.getKindsToDelete().trim()));
}
/**
* Returns a list of {@code n} {@link TupleTag TupleTags} numbered from {@code 0} to {@code n-1}.
*/
@VisibleForTesting
static TupleTagList getDeletionTags(int n) {
ImmutableList.Builder<TupleTag<?>> builder = new ImmutableList.Builder<>();
for (int i = 0; i < n; i++) {
builder.add(getOneDeletionTag(String.valueOf(i)));
}
return TupleTagList.of(builder.build());
}
/** Returns a {@link PTransform} that finds all entity kinds in Datastore. */
@VisibleForTesting
static PTransform<PBegin, PCollection<String>> discoverEntityKinds(String project) {
return new PTransform<PBegin, PCollection<String>>() {
@Override
public PCollection<String> expand(PBegin input) {
// Use the __kind__ table to discover entity kinds. Data in the more informational
// __Stat_Kind__ table may be up to 48-hour stale.
return input
.apply(
"LoadEntityMetaData",
DatastoreIO.v1()
.read()
.withProjectId(project)
.withLiteralGqlQuery("select * from __kind__"))
.apply(
"GetKindNames",
ParDo.of(
new DoFn<Entity, String>() {
@ProcessElement
public void processElement(
@Element Entity entity, OutputReceiver<String> out) {
String kind = entity.getKey().getPath(0).getName();
if (kind.startsWith("_")) {
return;
}
out.output(kind);
}
}));
}
};
}
@VisibleForTesting
static PCollection<KV<String, TupleTag<Entity>>> mapKindsToDeletionTags(
PCollection<String> kinds, TupleTagList tags) {
// The first two stages send all strings in the 'kinds' PCollection to one worker which
// performs the mapping in the last stage.
return kinds
.apply(
"AssignSingletonKeyToKinds",
MapElements.into(kvs(strings(), strings())).via(kind -> KV.of("", kind)))
.apply("GatherKindsIntoCollection", GroupByKey.create())
.apply("MapKindsToTag", ParDo.of(new MapKindsToTags(tags)));
}
/** Transforms each {@code kind} string into a Datastore query for that kind. */
@VisibleForTesting
static class GenerateQueries extends DoFn<String, String> {
@ProcessElement
public void processElement(@Element String kind, OutputReceiver<String> out) {
out.output(toKeyOnlyQueryForKind(kind));
}
}
private static class MapKindsToTags
extends DoFn<KV<String, Iterable<String>>, KV<String, TupleTag<Entity>>> {
private final TupleTagList tupleTags;
MapKindsToTags(TupleTagList tupleTags) {
this.tupleTags = tupleTags;
}
@ProcessElement
public void processElement(
@Element KV<String, Iterable<String>> kv,
OutputReceiver<KV<String, TupleTag<Entity>>> out) {
// Sort kinds so that mapping is deterministic.
ImmutableSortedSet<String> sortedKinds = ImmutableSortedSet.copyOf(kv.getValue());
Iterator<String> kinds = sortedKinds.iterator();
Iterator<TupleTag<?>> tags = tupleTags.getAll().iterator();
while (kinds.hasNext() && tags.hasNext()) {
out.output(KV.of(kinds.next(), (TupleTag<Entity>) tags.next()));
}
if (kinds.hasNext()) {
logger.atWarning().log(
"There are more kinds to delete (%s) than our estimate (%s). "
+ "Performance may suffer.",
sortedKinds.size(), tupleTags.size());
}
// Round robin assignment so that mapping is deterministic
while (kinds.hasNext()) {
tags = tupleTags.getAll().iterator();
while (kinds.hasNext() && tags.hasNext()) {
out.output(KV.of(kinds.next(), (TupleTag<Entity>) tags.next()));
}
}
}
}
/**
* {@link DoFn} that splits one {@link PCollection} of mixed kinds into multiple single-kind
* {@code PCollections}.
*/
@VisibleForTesting
static class SplitEntities extends DoFn<Entity, Entity> {
private final PCollectionView<Map<String, TupleTag<Entity>>> kindToTagMapping;
SplitEntities(PCollectionView<Map<String, TupleTag<Entity>>> kindToTagMapping) {
super();
this.kindToTagMapping = kindToTagMapping;
}
@ProcessElement
public void processElement(ProcessContext context) {
Entity entity = context.element();
com.google.datastore.v1.Key entityKey = entity.getKey();
String kind = entityKey.getPath(entityKey.getPathCount() - 1).getKind();
TupleTag<Entity> tag = context.sideInput(kindToTagMapping).get(kind);
context.output(tag, entity);
}
}
public static void main(String[] args) {
BulkDeletePipelineOptions options =
PipelineOptionsFactory.fromArgs(args).withValidation().as(BulkDeletePipelineOptions.class);
BulkDeleteDatastorePipeline pipeline = new BulkDeleteDatastorePipeline(options);
pipeline.run();
System.exit(0);
}
public interface BulkDeletePipelineOptions extends GcpOptions {
@Description("The Registry environment.")
RegistryEnvironment getRegistryEnvironment();
void setRegistryEnvironment(RegistryEnvironment environment);
@Description(
"The Datastore KINDs to be deleted. The format may be:\n"
+ "\t- The list of kinds to be deleted as a comma-separated string, or\n"
+ "\t- '*', which causes all kinds to be deleted.")
@Validation.Required
String getKindsToDelete();
void setKindsToDelete(String kinds);
@Description(
"An estimate of the number of KINDs to be deleted. "
+ "This is recommended if --kindsToDelete is '*' and the default value is too low.")
@Default.Integer(30)
int getNumOfKindsHint();
void setNumOfKindsHint(int numOfKindsHint);
}
}
@@ -1,768 +0,0 @@
// Copyright 2020 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.
// This class is adapted from the Apache BEAM SDK. The original license may
// be found at <a href="https://github.com/apache/beam/blob/master/LICENSE">
// this link</a>.
package google.registry.beam.datastore;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verify;
import static com.google.datastore.v1.PropertyFilter.Operator.EQUAL;
import static com.google.datastore.v1.PropertyOrder.Direction.DESCENDING;
import static com.google.datastore.v1.QueryResultBatch.MoreResultsType.NOT_FINISHED;
import static com.google.datastore.v1.client.DatastoreHelper.makeAndFilter;
import static com.google.datastore.v1.client.DatastoreHelper.makeFilter;
import static com.google.datastore.v1.client.DatastoreHelper.makeOrder;
import static com.google.datastore.v1.client.DatastoreHelper.makeValue;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.auth.Credentials;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auto.value.AutoValue;
import com.google.cloud.hadoop.util.ChainingHttpRequestInitializer;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.datastore.v1.Entity;
import com.google.datastore.v1.EntityResult;
import com.google.datastore.v1.GqlQuery;
import com.google.datastore.v1.PartitionId;
import com.google.datastore.v1.Query;
import com.google.datastore.v1.QueryResultBatch;
import com.google.datastore.v1.RunQueryRequest;
import com.google.datastore.v1.RunQueryResponse;
import com.google.datastore.v1.client.Datastore;
import com.google.datastore.v1.client.DatastoreException;
import com.google.datastore.v1.client.DatastoreFactory;
import com.google.datastore.v1.client.DatastoreHelper;
import com.google.datastore.v1.client.DatastoreOptions;
import com.google.datastore.v1.client.QuerySplitter;
import com.google.protobuf.Int32Value;
import com.google.rpc.Code;
import google.registry.model.annotations.DeleteAfterMigration;
import java.io.Serializable;
import java.util.List;
import java.util.NoSuchElementException;
import javax.annotation.Nullable;
import org.apache.beam.sdk.extensions.gcp.options.GcpOptions;
import org.apache.beam.sdk.extensions.gcp.util.RetryHttpRequestInitializer;
import org.apache.beam.sdk.metrics.Counter;
import org.apache.beam.sdk.metrics.Metrics;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.PTransform;
import org.apache.beam.sdk.transforms.ParDo;
import org.apache.beam.sdk.transforms.Reshuffle;
import org.apache.beam.sdk.transforms.display.DisplayData;
import org.apache.beam.sdk.transforms.display.HasDisplayData;
import org.apache.beam.sdk.util.BackOff;
import org.apache.beam.sdk.util.BackOffUtils;
import org.apache.beam.sdk.util.FluentBackoff;
import org.apache.beam.sdk.util.Sleeper;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.PCollection;
import org.joda.time.Duration;
/**
* Contains an adaptation of {@link org.apache.beam.sdk.io.gcp.datastore.DatastoreV1.Read}. See
* {@link MultiRead} for details.
*/
@DeleteAfterMigration
public class DatastoreV1 {
// A package-private constructor to prevent direct instantiation from outside of this package
DatastoreV1() {}
/**
* Non-retryable errors. See https://cloud.google.com/datastore/docs/concepts/errors#Error_Codes .
*/
private static final ImmutableSet<Code> NON_RETRYABLE_ERRORS =
ImmutableSet.of(
Code.FAILED_PRECONDITION,
Code.INVALID_ARGUMENT,
Code.PERMISSION_DENIED,
Code.UNAUTHENTICATED);
/**
* Returns an empty {@link MultiRead} builder. Configure the source {@code projectId}, {@code
* query}, and optionally {@code namespace} and {@code numQuerySplits} using {@link
* MultiRead#withProjectId}, {@link MultiRead#withNamespace}, {@link
* MultiRead#withNumQuerySplits}.
*/
public static MultiRead read() {
return new AutoValue_DatastoreV1_MultiRead.Builder().setNumQuerySplits(0).build();
}
/**
* A {@link PTransform} that executes every Cloud SQL queries in a {@link PCollection } and reads
* their result rows as {@code Entity} objects.
*
* <p>This class is adapted from {@link org.apache.beam.sdk.io.gcp.datastore.DatastoreV1.Read}. It
* uses literal GQL queries in the input {@link PCollection} instead of a constant query provided
* to the builder. Only the {@link #expand} method is modified from the original. Everything else
* including comments have been copied verbatim.
*/
@AutoValue
public abstract static class MultiRead
extends PTransform<PCollection<String>, PCollection<Entity>> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** An upper bound on the number of splits for a query. */
public static final int NUM_QUERY_SPLITS_MAX = 50000;
/** A lower bound on the number of splits for a query. */
static final int NUM_QUERY_SPLITS_MIN = 12;
/** Default bundle size of 64MB. */
static final long DEFAULT_BUNDLE_SIZE_BYTES = 64L * 1024L * 1024L;
/**
* Maximum number of results to request per query.
*
* <p>Must be set, or it may result in an I/O error when querying Cloud Datastore.
*/
static final int QUERY_BATCH_LIMIT = 500;
public abstract @Nullable String getProjectId();
public abstract @Nullable String getNamespace();
public abstract int getNumQuerySplits();
public abstract @Nullable String getLocalhost();
@Override
public abstract String toString();
abstract Builder toBuilder();
@AutoValue.Builder
abstract static class Builder {
abstract Builder setProjectId(String projectId);
abstract Builder setNamespace(String namespace);
abstract Builder setNumQuerySplits(int numQuerySplits);
abstract Builder setLocalhost(String localhost);
abstract MultiRead build();
}
/**
* Computes the number of splits to be performed on the given query by querying the estimated
* size from Cloud Datastore.
*/
static int getEstimatedNumSplits(Datastore datastore, Query query, @Nullable String namespace) {
int numSplits;
try {
long estimatedSizeBytes = getEstimatedSizeBytes(datastore, query, namespace);
logger.atInfo().log("Estimated size for the query is %d bytes.", estimatedSizeBytes);
numSplits =
(int)
Math.min(
NUM_QUERY_SPLITS_MAX,
Math.round(((double) estimatedSizeBytes) / DEFAULT_BUNDLE_SIZE_BYTES));
} catch (Exception e) {
logger.atWarning().withCause(e).log(
"Failed the fetch estimatedSizeBytes for query: %s", query);
// Fallback in case estimated size is unavailable.
numSplits = NUM_QUERY_SPLITS_MIN;
}
return Math.max(numSplits, NUM_QUERY_SPLITS_MIN);
}
/**
* Cloud Datastore system tables with statistics are periodically updated. This method fetches
* the latest timestamp (in microseconds) of statistics update using the {@code __Stat_Total__}
* table.
*/
private static long queryLatestStatisticsTimestamp(
Datastore datastore, @Nullable String namespace) throws DatastoreException {
Query.Builder query = Query.newBuilder();
// Note: namespace either being null or empty represents the default namespace, in which
// case we treat it as not provided by the user.
if (Strings.isNullOrEmpty(namespace)) {
query.addKindBuilder().setName("__Stat_Total__");
} else {
query.addKindBuilder().setName("__Stat_Ns_Total__");
}
query.addOrder(makeOrder("timestamp", DESCENDING));
query.setLimit(Int32Value.newBuilder().setValue(1));
RunQueryRequest request = makeRequest(query.build(), namespace);
RunQueryResponse response = datastore.runQuery(request);
QueryResultBatch batch = response.getBatch();
if (batch.getEntityResultsCount() == 0) {
throw new NoSuchElementException("Datastore total statistics unavailable");
}
Entity entity = batch.getEntityResults(0).getEntity();
return entity.getProperties().get("timestamp").getTimestampValue().getSeconds() * 1000000;
}
/** Retrieve latest table statistics for a given kind, namespace, and datastore. */
private static Entity getLatestTableStats(
String ourKind, @Nullable String namespace, Datastore datastore) throws DatastoreException {
long latestTimestamp = queryLatestStatisticsTimestamp(datastore, namespace);
logger.atInfo().log("Latest stats timestamp for kind %s is %s.", ourKind, latestTimestamp);
Query.Builder queryBuilder = Query.newBuilder();
if (Strings.isNullOrEmpty(namespace)) {
queryBuilder.addKindBuilder().setName("__Stat_Kind__");
} else {
queryBuilder.addKindBuilder().setName("__Stat_Ns_Kind__");
}
queryBuilder.setFilter(
makeAndFilter(
makeFilter("kind_name", EQUAL, makeValue(ourKind).build()).build(),
makeFilter("timestamp", EQUAL, makeValue(latestTimestamp).build()).build()));
RunQueryRequest request = makeRequest(queryBuilder.build(), namespace);
long now = System.currentTimeMillis();
RunQueryResponse response = datastore.runQuery(request);
logger.atFine().log(
"Query for per-kind statistics took %d ms.", System.currentTimeMillis() - now);
QueryResultBatch batch = response.getBatch();
if (batch.getEntityResultsCount() == 0) {
throw new NoSuchElementException(
"Datastore statistics for kind " + ourKind + " unavailable");
}
return batch.getEntityResults(0).getEntity();
}
/**
* Get the estimated size of the data returned by the given query.
*
* <p>Cloud Datastore provides no way to get a good estimate of how large the result of a query
* entity kind being queried, using the __Stat_Kind__ system table, assuming exactly 1 kind is
* specified in the query.
*
* <p>See https://cloud.google.com/datastore/docs/concepts/stats.
*/
static long getEstimatedSizeBytes(Datastore datastore, Query query, @Nullable String namespace)
throws DatastoreException {
String ourKind = query.getKind(0).getName();
Entity entity = getLatestTableStats(ourKind, namespace, datastore);
return entity.getProperties().get("entity_bytes").getIntegerValue();
}
private static PartitionId.Builder forNamespace(@Nullable String namespace) {
PartitionId.Builder partitionBuilder = PartitionId.newBuilder();
// Namespace either being null or empty represents the default namespace.
// Datastore Client libraries expect users to not set the namespace proto field in
// either of these cases.
if (!Strings.isNullOrEmpty(namespace)) {
partitionBuilder.setNamespaceId(namespace);
}
return partitionBuilder;
}
/** Builds a {@link RunQueryRequest} from the {@code query} and {@code namespace}. */
static RunQueryRequest makeRequest(Query query, @Nullable String namespace) {
return RunQueryRequest.newBuilder()
.setQuery(query)
.setPartitionId(forNamespace(namespace))
.build();
}
/** Builds a {@link RunQueryRequest} from the {@code GqlQuery} and {@code namespace}. */
private static RunQueryRequest makeRequest(GqlQuery gqlQuery, @Nullable String namespace) {
return RunQueryRequest.newBuilder()
.setGqlQuery(gqlQuery)
.setPartitionId(forNamespace(namespace))
.build();
}
/**
* A helper function to get the split queries, taking into account the optional {@code
* namespace}.
*/
private static List<Query> splitQuery(
Query query,
@Nullable String namespace,
Datastore datastore,
QuerySplitter querySplitter,
int numSplits)
throws DatastoreException {
// If namespace is set, include it in the split request so splits are calculated accordingly.
return querySplitter.getSplits(query, forNamespace(namespace).build(), numSplits, datastore);
}
/**
* Translates a Cloud Datastore gql query string to {@link Query}.
*
* <p>Currently, the only way to translate a gql query string to a Query is to run the query
* against Cloud Datastore and extract the {@code Query} from the response. To prevent reading
* any data, we set the {@code LIMIT} to 0 but if the gql query already has a limit set, we
* catch the exception with {@code INVALID_ARGUMENT} error code and retry the translation
* without the zero limit.
*
* <p>Note: This may result in reading actual data from Cloud Datastore but the service has a
* cap on the number of entities returned for a single rpc request, so this should not be a
* problem in practice.
*/
private static Query translateGqlQueryWithLimitCheck(
String gql, Datastore datastore, String namespace) throws DatastoreException {
String gqlQueryWithZeroLimit = gql + " LIMIT 0";
try {
Query translatedQuery = translateGqlQuery(gqlQueryWithZeroLimit, datastore, namespace);
// Clear the limit that we set.
return translatedQuery.toBuilder().clearLimit().build();
} catch (DatastoreException e) {
// Note: There is no specific error code or message to detect if the query already has a
// limit, so we just check for INVALID_ARGUMENT and assume that that the query might have
// a limit already set.
if (e.getCode() == Code.INVALID_ARGUMENT) {
logger.atWarning().log(
"Failed to translate Gql query '%s': %s", gqlQueryWithZeroLimit, e.getMessage());
logger.atWarning().log(
"User query might have a limit already set, so trying without zero limit.");
// Retry without the zero limit.
return translateGqlQuery(gql, datastore, namespace);
} else {
throw e;
}
}
}
/** Translates a gql query string to {@link Query}. */
private static Query translateGqlQuery(String gql, Datastore datastore, String namespace)
throws DatastoreException {
logger.atInfo().log("Translating gql %s", gql);
GqlQuery gqlQuery = GqlQuery.newBuilder().setQueryString(gql).setAllowLiterals(true).build();
RunQueryRequest req = makeRequest(gqlQuery, namespace);
return datastore.runQuery(req).getQuery();
}
/**
* Returns a new {@link MultiRead} that reads from the Cloud Datastore for the specified
* project.
*/
public MultiRead withProjectId(String projectId) {
checkArgument(projectId != null, "projectId can not be null");
return toBuilder().setProjectId(projectId).build();
}
/** Returns a new {@link MultiRead} that reads from the given namespace. */
public MultiRead withNamespace(String namespace) {
return toBuilder().setNamespace(namespace).build();
}
/**
* Returns a new {@link MultiRead} that reads by splitting the given {@code query} into {@code
* numQuerySplits}.
*
* <p>The semantics for the query splitting is defined below:
*
* <ul>
* <li>Any value less than or equal to 0 will be ignored, and the number of splits will be
* chosen dynamically at runtime based on the query data size.
* <li>Any value greater than {@link MultiRead#NUM_QUERY_SPLITS_MAX} will be capped at {@code
* NUM_QUERY_SPLITS_MAX}.
* <li>If the {@code query} has a user limit set, then {@code numQuerySplits} will be ignored
* and no split will be performed.
* <li>Under certain cases Cloud Datastore is unable to split query to the requested number of
* splits. In such cases we just use whatever the Cloud Datastore returns.
* </ul>
*/
public MultiRead withNumQuerySplits(int numQuerySplits) {
return toBuilder()
.setNumQuerySplits(Math.min(Math.max(numQuerySplits, 0), NUM_QUERY_SPLITS_MAX))
.build();
}
/**
* Returns a new {@link MultiRead} that reads from a Datastore Emulator running at the given
* localhost address.
*/
public MultiRead withLocalhost(String localhost) {
return toBuilder().setLocalhost(localhost).build();
}
/** Returns Number of entities available for reading. */
public long getNumEntities(
PipelineOptions options, String ourKind, @Nullable String namespace) {
try {
V1Options v1Options = V1Options.from(getProjectId(), getNamespace(), getLocalhost());
V1DatastoreFactory datastoreFactory = new V1DatastoreFactory();
Datastore datastore =
datastoreFactory.getDatastore(
options, v1Options.getProjectId(), v1Options.getLocalhost());
Entity entity = getLatestTableStats(ourKind, namespace, datastore);
return entity.getProperties().get("count").getIntegerValue();
} catch (Exception e) {
return -1;
}
}
@Override
public PCollection<Entity> expand(PCollection<String> gqlQueries) {
checkArgument(getProjectId() != null, "projectId cannot be null");
V1Options v1Options = V1Options.from(getProjectId(), getNamespace(), getLocalhost());
/*
* This composite transform involves the following steps:
* 1. Apply a {@link ParDo} that translates each query in {@code gqlQueries} into a {@code
* query}.
*
* 2. A {@link ParDo} splits the resulting query into {@code numQuerySplits} and
* assign each split query a unique {@code Integer} as the key. The resulting output is
* of the type {@code PCollection<KV<Integer, Query>>}.
*
* If the value of {@code numQuerySplits} is less than or equal to 0, then the number of
* splits will be computed dynamically based on the size of the data for the {@code query}.
*
* 3. The resulting {@code PCollection} is sharded using a {@link GroupByKey} operation. The
* queries are extracted from they {@code KV<Integer, Iterable<Query>>} and flattened to
* output a {@code PCollection<Query>}.
*
* 4. In the third step, a {@code ParDo} reads entities for each query and outputs
* a {@code PCollection<Entity>}.
*/
PCollection<Query> inputQuery =
gqlQueries.apply(ParDo.of(new GqlQueryTranslateFn(v1Options)));
return inputQuery
.apply("Split", ParDo.of(new SplitQueryFn(v1Options, getNumQuerySplits())))
.apply("Reshuffle", Reshuffle.viaRandomKey())
.apply("Read", ParDo.of(new ReadFn(v1Options)));
}
@Override
public void populateDisplayData(DisplayData.Builder builder) {
super.populateDisplayData(builder);
builder
.addIfNotNull(DisplayData.item("projectId", getProjectId()).withLabel("ProjectId"))
.addIfNotNull(DisplayData.item("namespace", getNamespace()).withLabel("Namespace"));
}
private static class V1Options implements HasDisplayData, Serializable {
private final String project;
private final @Nullable String namespace;
private final @Nullable String localhost;
private V1Options(String project, @Nullable String namespace, @Nullable String localhost) {
this.project = project;
this.namespace = namespace;
this.localhost = localhost;
}
public static V1Options from(
String projectId, @Nullable String namespace, @Nullable String localhost) {
return new V1Options(projectId, namespace, localhost);
}
public String getProjectId() {
return project;
}
public @Nullable String getNamespace() {
return namespace;
}
public @Nullable String getLocalhost() {
return localhost;
}
@Override
public void populateDisplayData(DisplayData.Builder builder) {
builder
.addIfNotNull(DisplayData.item("projectId", getProjectId()).withLabel("ProjectId"))
.addIfNotNull(DisplayData.item("namespace", getNamespace()).withLabel("Namespace"));
}
}
/** A DoFn that translates a Cloud Datastore gql query string to {@code Query}. */
static class GqlQueryTranslateFn extends DoFn<String, Query> {
private final V1Options v1Options;
private transient Datastore datastore;
private final V1DatastoreFactory datastoreFactory;
GqlQueryTranslateFn(V1Options options) {
this(options, new V1DatastoreFactory());
}
GqlQueryTranslateFn(V1Options options, V1DatastoreFactory datastoreFactory) {
this.v1Options = options;
this.datastoreFactory = datastoreFactory;
}
@StartBundle
public void startBundle(StartBundleContext c) {
datastore =
datastoreFactory.getDatastore(
c.getPipelineOptions(), v1Options.getProjectId(), v1Options.getLocalhost());
}
@ProcessElement
public void processElement(ProcessContext c) throws Exception {
String gqlQuery = c.element();
logger.atInfo().log("User query: '%s'.", gqlQuery);
Query query =
translateGqlQueryWithLimitCheck(gqlQuery, datastore, v1Options.getNamespace());
logger.atInfo().log("User gql query translated to Query(%s).", query);
c.output(query);
}
}
/**
* A {@link DoFn} that splits a given query into multiple sub-queries, assigns them unique keys
* and outputs them as {@link KV}.
*/
private static class SplitQueryFn extends DoFn<Query, Query> {
private final V1Options options;
// number of splits to make for a given query
private final int numSplits;
private final V1DatastoreFactory datastoreFactory;
// Datastore client
private transient Datastore datastore;
// Query splitter
private transient QuerySplitter querySplitter;
public SplitQueryFn(V1Options options, int numSplits) {
this(options, numSplits, new V1DatastoreFactory());
}
private SplitQueryFn(V1Options options, int numSplits, V1DatastoreFactory datastoreFactory) {
this.options = options;
this.numSplits = numSplits;
this.datastoreFactory = datastoreFactory;
}
@StartBundle
public void startBundle(StartBundleContext c) {
datastore =
datastoreFactory.getDatastore(
c.getPipelineOptions(), options.getProjectId(), options.getLocalhost());
querySplitter = datastoreFactory.getQuerySplitter();
}
@ProcessElement
public void processElement(ProcessContext c) {
Query query = c.element();
// If query has a user set limit, then do not split.
if (query.hasLimit()) {
c.output(query);
return;
}
int estimatedNumSplits;
// Compute the estimated numSplits if numSplits is not specified by the user.
if (numSplits <= 0) {
estimatedNumSplits = getEstimatedNumSplits(datastore, query, options.getNamespace());
} else {
estimatedNumSplits = numSplits;
}
logger.atInfo().log("Splitting the query into %d splits.", estimatedNumSplits);
List<Query> querySplits;
try {
querySplits =
splitQuery(
query, options.getNamespace(), datastore, querySplitter, estimatedNumSplits);
} catch (Exception e) {
logger.atWarning().log("Unable to parallelize the given query: %s", query, e);
querySplits = ImmutableList.of(query);
}
// assign unique keys to query splits.
for (Query subquery : querySplits) {
c.output(subquery);
}
}
@Override
public void populateDisplayData(DisplayData.Builder builder) {
super.populateDisplayData(builder);
builder.include("options", options);
if (numSplits > 0) {
builder.add(
DisplayData.item("numQuerySplits", numSplits)
.withLabel("Requested number of Query splits"));
}
}
}
/** A {@link DoFn} that reads entities from Cloud Datastore for each query. */
private static class ReadFn extends DoFn<Query, Entity> {
private final V1Options options;
private final V1DatastoreFactory datastoreFactory;
// Datastore client
private transient Datastore datastore;
private final Counter rpcErrors = Metrics.counter(ReadFn.class, "datastoreRpcErrors");
private final Counter rpcSuccesses = Metrics.counter(ReadFn.class, "datastoreRpcSuccesses");
private static final int MAX_RETRIES = 5;
private static final FluentBackoff RUNQUERY_BACKOFF =
FluentBackoff.DEFAULT
.withMaxRetries(MAX_RETRIES)
.withInitialBackoff(Duration.standardSeconds(5));
public ReadFn(V1Options options) {
this(options, new V1DatastoreFactory());
}
private ReadFn(V1Options options, V1DatastoreFactory datastoreFactory) {
this.options = options;
this.datastoreFactory = datastoreFactory;
}
@StartBundle
public void startBundle(StartBundleContext c) {
datastore =
datastoreFactory.getDatastore(
c.getPipelineOptions(), options.getProjectId(), options.getLocalhost());
}
private RunQueryResponse runQueryWithRetries(RunQueryRequest request) throws Exception {
Sleeper sleeper = Sleeper.DEFAULT;
BackOff backoff = RUNQUERY_BACKOFF.backoff();
while (true) {
try {
RunQueryResponse response = datastore.runQuery(request);
rpcSuccesses.inc();
return response;
} catch (DatastoreException exception) {
rpcErrors.inc();
if (NON_RETRYABLE_ERRORS.contains(exception.getCode())) {
throw exception;
}
if (!BackOffUtils.next(sleeper, backoff)) {
logger.atSevere().log("Aborting after %d retries.", MAX_RETRIES);
throw exception;
}
}
}
}
/** Read and output entities for the given query. */
@ProcessElement
public void processElement(ProcessContext context) throws Exception {
Query query = context.element();
String namespace = options.getNamespace();
int userLimit = query.hasLimit() ? query.getLimit().getValue() : Integer.MAX_VALUE;
boolean moreResults = true;
QueryResultBatch currentBatch = null;
while (moreResults) {
Query.Builder queryBuilder = query.toBuilder();
queryBuilder.setLimit(
Int32Value.newBuilder().setValue(Math.min(userLimit, QUERY_BATCH_LIMIT)));
if (currentBatch != null && !currentBatch.getEndCursor().isEmpty()) {
queryBuilder.setStartCursor(currentBatch.getEndCursor());
}
RunQueryRequest request = makeRequest(queryBuilder.build(), namespace);
RunQueryResponse response = runQueryWithRetries(request);
currentBatch = response.getBatch();
// MORE_RESULTS_AFTER_LIMIT is not implemented yet:
// https://groups.google.com/forum/#!topic/gcd-discuss/iNs6M1jA2Vw, so
// use result count to determine if more results might exist.
int numFetch = currentBatch.getEntityResultsCount();
if (query.hasLimit()) {
verify(
userLimit >= numFetch,
"Expected userLimit %s >= numFetch %s, because query limit %s must be <= userLimit",
userLimit,
numFetch,
query.getLimit());
userLimit -= numFetch;
}
// output all the entities from the current batch.
for (EntityResult entityResult : currentBatch.getEntityResultsList()) {
context.output(entityResult.getEntity());
}
// Check if we have more entities to be read.
moreResults =
// User-limit does not exist (so userLimit == MAX_VALUE) and/or has not been satisfied
(userLimit > 0)
// All indications from the API are that there are/may be more results.
&& ((numFetch == QUERY_BATCH_LIMIT)
|| (currentBatch.getMoreResults() == NOT_FINISHED));
}
}
@Override
public void populateDisplayData(DisplayData.Builder builder) {
super.populateDisplayData(builder);
builder.include("options", options);
}
}
}
/**
* A wrapper factory class for Cloud Datastore singleton classes {@link DatastoreFactory} and
* {@link QuerySplitter}
*
* <p>{@link DatastoreFactory} and {@link QuerySplitter} are not java serializable, hence wrapping
* them under this class, which implements {@link Serializable}.
*/
private static class V1DatastoreFactory implements Serializable {
/** Builds a Cloud Datastore client for the given pipeline options and project. */
public Datastore getDatastore(PipelineOptions pipelineOptions, String projectId) {
return getDatastore(pipelineOptions, projectId, null);
}
/**
* Builds a Cloud Datastore client for the given pipeline options, project and an optional
* locahost.
*/
public Datastore getDatastore(
PipelineOptions pipelineOptions, String projectId, @Nullable String localhost) {
Credentials credential = pipelineOptions.as(GcpOptions.class).getGcpCredential();
HttpRequestInitializer initializer;
if (credential != null) {
initializer =
new ChainingHttpRequestInitializer(
new HttpCredentialsAdapter(credential), new RetryHttpRequestInitializer());
} else {
initializer = new RetryHttpRequestInitializer();
}
DatastoreOptions.Builder builder =
new DatastoreOptions.Builder().projectId(projectId).initializer(initializer);
if (localhost != null) {
builder.localHost(localhost);
} else {
builder.host("batch-datastore.googleapis.com");
}
return DatastoreFactory.get().create(builder.build());
}
/** Builds a Cloud Datastore {@link QuerySplitter}. */
public QuerySplitter getQuerySplitter() {
return DatastoreHelper.getQuerySplitter();
}
}
}
@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static google.registry.model.common.Cursor.getCursorTimeOrStartOfTime;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.rde.RdeModule.BRDA_QUEUE;
import static google.registry.rde.RdeModule.RDE_UPLOAD_QUEUE;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -249,9 +248,6 @@ public class RdeIO {
// Now that we're done, output roll the cursor forward.
if (key.manual()) {
logger.atInfo().log("Manual operation; not advancing cursor or enqueuing upload task.");
// Temporary measure to run RDE in beam in parallel with the daily MapReduce based RDE runs.
} else if (tm().isOfy()) {
logger.atInfo().log("Ofy is primary TM; not advancing cursor or enqueuing upload task.");
} else {
outputReceiver.output(KV.of(key, revision));
}
@@ -273,16 +269,15 @@ public class RdeIO {
@ProcessElement
public void processElement(
@Element KV<PendingDeposit, Integer> input, PipelineOptions options) {
tm().transact(
() -> {
PendingDeposit key = input.getKey();
Registry registry = Registry.get(key.tld());
Optional<Cursor> cursor =
transactIfJpaTm(
() ->
tm().loadByKeyIfPresent(
Cursor.createVKey(key.cursor(), registry.getTldStr())));
tm().transact(
() ->
tm().loadByKeyIfPresent(
Cursor.createScopedVKey(key.cursor(), registry)));
DateTime position = getCursorTimeOrStartOfTime(cursor);
checkState(key.interval() != null, "Interval must be present");
DateTime newPosition = key.watermark().plus(key.interval());
@@ -295,7 +290,7 @@ public class RdeIO {
"Partial ordering of RDE deposits broken: %s %s",
position,
key);
tm().put(Cursor.create(key.cursor(), newPosition, registry));
tm().put(Cursor.createScoped(key.cursor(), newPosition, registry));
logger.atInfo().log(
"Rolled forward %s on %s cursor to %s.", key.cursor(), key.tld(), newPosition);
RdeRevision.saveRevision(key.tld(), key.watermark(), key.mode(), input.getValue());
@@ -27,14 +27,12 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import dagger.Module;
import dagger.Provides;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.util.TaskQueueUtils;
import google.registry.util.YamlUtils;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -43,7 +41,9 @@ import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Named;
@@ -225,7 +225,7 @@ public final class RegistryConfig {
/**
* Returns the Google Cloud Storage bucket for storing zone files.
*
* @see google.registry.backup.ExportCommitLogDiffAction
* @see google.registry.tools.server.GenerateZoneFilesAction
*/
@Provides
@Config("zoneFilesBucket")
@@ -233,22 +233,11 @@ public final class RegistryConfig {
return projectId + "-zonefiles";
}
/**
* Returns the Google Cloud Storage bucket for storing commit logs.
*
* @see google.registry.backup.ExportCommitLogDiffAction
*/
/** @see RegistryConfig#getDatabaseRetention() */
@Provides
@Config("commitLogGcsBucket")
public static String provideCommitLogGcsBucket(@Config("projectId") String projectId) {
return projectId + "-commits";
}
/** @see RegistryConfig#getCommitLogDatastoreRetention() */
@Provides
@Config("commitLogDatastoreRetention")
public static Duration provideCommitLogDatastoreRetention() {
return RegistryConfig.getCommitLogDatastoreRetention();
@Config("databaseRetention")
public static Duration provideDatabaseRetention() {
return RegistryConfig.getDatabaseRetention();
}
/**
@@ -262,18 +251,6 @@ public final class RegistryConfig {
return projectId + "-domain-lists";
}
/**
* Batch size for the number of transactions' worth of commit log data to process at once when
* exporting a commit log diff.
*
* @see google.registry.backup.ExportCommitLogDiffAction
*/
@Provides
@Config("commitLogDiffExportBatchSize")
public static int provideCommitLogDiffExportBatchSize() {
return 100;
}
/**
* Returns the Google Cloud Storage bucket for staging BRDA escrow deposits.
*
@@ -765,17 +742,6 @@ public final class RegistryConfig {
return config.rde.reportUrlPrefix;
}
/**
* Maximum amount of time generating an escrow deposit for a TLD could take, before killing.
*
* @see google.registry.rde.RdeStagingReducer
*/
@Provides
@Config("rdeStagingLockTimeout")
public static Duration provideRdeStagingLockTimeout() {
return Duration.standardHours(2);
}
/**
* Maximum amount of time it should ever take to upload an escrow deposit, before killing.
*
@@ -792,7 +758,7 @@ public final class RegistryConfig {
*
* <p>This value was communicated to us by the escrow provider.
*
* @see google.registry.rde.RdeStagingReducer
* @see google.registry.rde.RdeUploadAction
*/
@Provides
@Config("rdeUploadSftpCooldown")
@@ -952,7 +918,7 @@ public final class RegistryConfig {
* <p>Note that this uses {@code @Named} instead of {@code @Config} so that it can be used from
* the low-level util package, which cannot have a dependency on the config package.
*
* @see TaskQueueUtils
* @see google.registry.util.CloudTasksUtils
*/
@Provides
@Named("transientFailureRetries")
@@ -1088,8 +1054,8 @@ public final class RegistryConfig {
* @see google.registry.batch.AsyncTaskEnqueuer
*/
@Provides
@Config("asyncDeleteFlowMapreduceDelay")
public static Duration provideAsyncDeleteFlowMapreduceDelay(RegistryConfigSettings config) {
@Config("asyncDeleteDelay")
public static Duration provideAsyncDeleteDelay(RegistryConfigSettings config) {
return Duration.standardSeconds(config.misc.asyncDeleteDelaySeconds);
}
@@ -1301,7 +1267,7 @@ public final class RegistryConfig {
e.getKey().equals("START_OF_TIME")
? START_OF_TIME
: DateTime.parse(e.getKey()),
e -> e.getValue()));
Entry::getValue));
}
@Provides
@@ -1365,41 +1331,11 @@ public final class RegistryConfig {
}
/**
* Returns the Google Cloud Storage bucket for storing Datastore backups.
* Returns the length of time before commit logs should be deleted from the database.
*
* @see google.registry.export.BackupDatastoreAction
* @see google.registry.tools.server.GenerateZoneFilesAction
*/
public static String getDatastoreBackupsBucket() {
return "gs://" + getProjectId() + "-datastore-backups";
}
/**
* Number of sharded commit log buckets.
*
* <p>This number is crucial for determining how much transactional throughput the system can
* allow, because it determines how many entity groups are available for writing commit logs.
* Since entity groups have a one transaction per second SLA (which is actually like ten in
* practice), a registry that wants to be able to handle one hundred transactions per second
* should have one hundred buckets.
*
* <p><b>Warning:</b> This can be raised but never lowered.
*
* @see google.registry.model.ofy.CommitLogBucket
*/
public static int getCommitLogBucketCount() {
return CONFIG_SETTINGS.get().datastore.commitLogBucketsNum;
}
/**
* Returns the length of time before commit logs should be deleted from Datastore.
*
* <p>The only reason you'll want to retain this commit logs in Datastore is for performing
* point-in-time restoration queries for subsystems like RDE.
*
* @see google.registry.backup.DeleteOldCommitLogsAction
* @see google.registry.model.translators.CommitLogRevisionsTranslatorFactory
*/
public static Duration getCommitLogDatastoreRetention() {
public static Duration getDatabaseRetention() {
return Duration.standardDays(30);
}
@@ -107,7 +107,6 @@ public class RegistryConfigSettings {
/** Configuration for Cloud Datastore. */
public static class Datastore {
public int commitLogBucketsNum;
public int eppResourceIndexBucketsNum;
public int baseOfyRetryMillis;
}
@@ -183,10 +183,6 @@ registryPolicy:
requireSslCertificates: true
datastore:
# Number of commit log buckets in Datastore. Lowering this after initial
# install risks losing up to a days' worth of differential backups.
commitLogBucketsNum: 397
# Number of EPP resource index buckets in Datastore. Dont change after
# initial install.
eppResourceIndexBucketsNum: 997
@@ -11,7 +11,6 @@ registryPolicy:
Line 2 is this 1.
datastore:
commitLogBucketsNum: 3
eppResourceIndexBucketsNum: 3
baseOfyRetryMillis: 0
@@ -1,56 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.cron;
import com.google.common.collect.ImmutableMultimap;
import google.registry.model.ofy.CommitLogBucket;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.CloudTasksUtils;
import java.util.Optional;
import javax.inject.Inject;
/** Action for fanning out cron tasks for each commit log bucket. */
@Action(
service = Action.Service.BACKEND,
path = "/_dr/cron/commitLogFanout",
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
public final class CommitLogFanoutAction implements Runnable {
public static final String BUCKET_PARAM = "bucket";
@Inject CloudTasksUtils cloudTasksUtils;
@Inject @Parameter("endpoint") String endpoint;
@Inject @Parameter("queue") String queue;
@Inject @Parameter("jitterSeconds") Optional<Integer> jitterSeconds;
@Inject CommitLogFanoutAction() {}
@Override
public void run() {
for (int bucketId : CommitLogBucket.getBucketIds()) {
cloudTasksUtils.enqueue(
queue,
cloudTasksUtils.createPostTaskWithJitter(
endpoint,
Service.BACKEND.toString(),
ImmutableMultimap.of(BUCKET_PARAM, Integer.toString(bucketId)),
jitterSeconds));
}
}
}
@@ -41,7 +41,6 @@ import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.tld.Registries;
import google.registry.util.Clock;
import google.registry.util.NonFinalForTesting;
import google.registry.util.SystemClock;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
@@ -83,17 +82,6 @@ public class DnsQueue {
this.clock = clock;
}
/**
* Constructs a new instance.
*
* <p><b>Note:</b> Prefer <code>@Inject</code>ing DnsQueue instances instead. You should only use
* this helper method in situations for which injection does not work, e.g. inside mapper or
* reducer classes in mapreduces that need to be Serializable.
*/
public static DnsQueue create() {
return new DnsQueue(getQueue(DNS_PULL_QUEUE_NAME), new SystemClock());
}
@VisibleForTesting
public static DnsQueue createForTesting(Clock clock) {
return new DnsQueue(getQueue(DNS_PULL_QUEUE_NAME), clock);
@@ -30,14 +30,13 @@ import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_CREATED;
import static google.registry.request.RequestParameters.PARAM_TLD;
import static google.registry.util.DomainNameUtils.getSecondLevelDomain;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.TaskHandle;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.auto.value.AutoValue;
import com.google.cloud.tasks.v2.Task;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
@@ -50,20 +49,19 @@ import google.registry.dns.DnsConstants.TargetType;
import google.registry.model.tld.Registries;
import google.registry.model.tld.Registry;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.TaskQueueUtils;
import google.registry.util.CloudTasksUtils;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Named;
import org.joda.time.DateTime;
import org.joda.time.Duration;
@@ -84,7 +82,6 @@ import org.joda.time.Duration;
public final class ReadDnsQueueAction implements Runnable {
private static final String PARAM_JITTER_SECONDS = "jitterSeconds";
private static final Random random = new Random();
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
@@ -101,15 +98,31 @@ public final class ReadDnsQueueAction implements Runnable {
*/
private static final Duration LEASE_PADDING = Duration.standardMinutes(1);
@Inject @Config("dnsTldUpdateBatchSize") int tldUpdateBatchSize;
@Inject @Config("readDnsQueueActionRuntime") Duration requestedMaximumDuration;
@Inject @Named(DNS_PUBLISH_PUSH_QUEUE_NAME) Queue dnsPublishPushQueue;
@Inject @Parameter(PARAM_JITTER_SECONDS) Optional<Integer> jitterSeconds;
@Inject Clock clock;
@Inject DnsQueue dnsQueue;
@Inject HashFunction hashFunction;
@Inject TaskQueueUtils taskQueueUtils;
@Inject ReadDnsQueueAction() {}
private final int tldUpdateBatchSize;
private final Duration requestedMaximumDuration;
private final Optional<Integer> jitterSeconds;
private final Clock clock;
private final DnsQueue dnsQueue;
private final HashFunction hashFunction;
private final CloudTasksUtils cloudTasksUtils;
@Inject
ReadDnsQueueAction(
@Config("dnsTldUpdateBatchSize") int tldUpdateBatchSize,
@Config("readDnsQueueActionRuntime") Duration requestedMaximumDuration,
@Parameter(PARAM_JITTER_SECONDS) Optional<Integer> jitterSeconds,
Clock clock,
DnsQueue dnsQueue,
HashFunction hashFunction,
CloudTasksUtils cloudTasksUtils) {
this.tldUpdateBatchSize = tldUpdateBatchSize;
this.requestedMaximumDuration = requestedMaximumDuration;
this.jitterSeconds = jitterSeconds;
this.clock = clock;
this.dnsQueue = dnsQueue;
this.hashFunction = hashFunction;
this.cloudTasksUtils = cloudTasksUtils;
}
/** Container for items we pull out of the DNS pull queue and process for fanout. */
@AutoValue
@@ -322,17 +335,13 @@ public final class ReadDnsQueueAction implements Runnable {
if (numPublishLocks <= 1) {
enqueueUpdates(tld, 1, 1, tldRefreshItemsEntry.getValue());
} else {
tldRefreshItemsEntry
.getValue()
.stream()
tldRefreshItemsEntry.getValue().stream()
.collect(
toImmutableSetMultimap(
refreshItem -> getLockIndex(tld, numPublishLocks, refreshItem),
refreshItem -> refreshItem))
.asMap()
.entrySet()
.forEach(
entry -> enqueueUpdates(tld, entry.getKey(), numPublishLocks, entry.getValue()));
.forEach((key, value) -> enqueueUpdates(tld, key, numPublishLocks, value));
}
}
}
@@ -340,10 +349,10 @@ public final class ReadDnsQueueAction implements Runnable {
/**
* Returns the lock index for a given refreshItem.
*
* <p>We hash the second level domain domain for all records, to group in-balliwick hosts (the
* only ones we refresh DNS for) with their superordinate domains. We use consistent hashing to
* determine the lock index because it gives us [0,N) bucketing properties out of the box, then
* add 1 to make indexes within [1,N].
* <p>We hash the second level domain for all records, to group in-bailiwick hosts (the only ones
* we refresh DNS for) with their superordinate domains. We use consistent hashing to determine
* the lock index because it gives us [0,N) bucketing properties out of the box, then add 1 to
* make indexes within [1,N].
*/
private int getLockIndex(String tld, int numPublishLocks, RefreshItem refreshItem) {
String domain = getSecondLevelDomain(refreshItem.name(), tld);
@@ -360,33 +369,32 @@ public final class ReadDnsQueueAction implements Runnable {
DateTime earliestCreateTime =
chunk.stream().map(RefreshItem::creationTime).min(Comparator.naturalOrder()).get();
for (String dnsWriter : Registry.get(tld).getDnsWriters()) {
taskQueueUtils.enqueue(
dnsPublishPushQueue,
TaskOptions.Builder.withUrl(PublishDnsUpdatesAction.PATH)
.countdownMillis(
jitterSeconds
.map(seconds -> random.nextInt((int) SECONDS.toMillis(seconds)))
.orElse(0))
.param(PARAM_TLD, tld)
.param(PARAM_DNS_WRITER, dnsWriter)
.param(PARAM_LOCK_INDEX, Integer.toString(lockIndex))
.param(PARAM_NUM_PUBLISH_LOCKS, Integer.toString(numPublishLocks))
.param(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.param(PARAM_REFRESH_REQUEST_CREATED, earliestCreateTime.toString())
.param(
PARAM_DOMAINS,
chunk
.stream()
.filter(item -> item.type() == TargetType.DOMAIN)
.map(RefreshItem::name)
.collect(Collectors.joining(",")))
.param(
PARAM_HOSTS,
chunk
.stream()
.filter(item -> item.type() == TargetType.HOST)
.map(RefreshItem::name)
.collect(Collectors.joining(","))));
Task task =
cloudTasksUtils.createPostTaskWithJitter(
PublishDnsUpdatesAction.PATH,
Service.BACKEND.toString(),
ImmutableMultimap.<String, String>builder()
.put(PARAM_TLD, tld)
.put(PARAM_DNS_WRITER, dnsWriter)
.put(PARAM_LOCK_INDEX, Integer.toString(lockIndex))
.put(PARAM_NUM_PUBLISH_LOCKS, Integer.toString(numPublishLocks))
.put(PARAM_PUBLISH_TASK_ENQUEUED, clock.nowUtc().toString())
.put(PARAM_REFRESH_REQUEST_CREATED, earliestCreateTime.toString())
.put(
PARAM_DOMAINS,
chunk.stream()
.filter(item -> item.type() == TargetType.DOMAIN)
.map(RefreshItem::name)
.collect(Collectors.joining(",")))
.put(
PARAM_HOSTS,
chunk.stream()
.filter(item -> item.type() == TargetType.HOST)
.map(RefreshItem::name)
.collect(Collectors.joining(",")))
.build(),
jitterSeconds);
cloudTasksUtils.enqueue(DNS_PUBLISH_PUSH_QUEUE_NAME, task);
}
}
}
@@ -83,7 +83,6 @@
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
It is needed for "deleteOldCommitLogs" to work correctly.
</description>
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
@@ -92,9 +91,9 @@
<cron>
<url><![CDATA[/_dr/task/expandRecurringBillingEvents]]></url>
<description>
This job runs a mapreduce that creates synthetic OneTime billing events from Recurring billing
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the mapreduce.
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<schedule>every day 03:00</schedule>
<target>backend</target>
@@ -169,84 +169,12 @@
<url-pattern>/_dr/dnsRefresh</url-pattern>
</servlet-mapping>
<!-- Exports a Datastore backup snapshot to GCS. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/backupDatastore</url-pattern>
</servlet-mapping>
<!-- Checks the completion of a Datastore backup snapshot. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/checkDatastoreBackup</url-pattern>
</servlet-mapping>
<!-- Loads a Datastore backup snapshot into BigQuery. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/uploadDatastoreBackup</url-pattern>
</servlet-mapping>
<!-- Updates a view to point at a certain snapshot in BigQuery. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/updateSnapshotView</url-pattern>
</servlet-mapping>
<!-- Polls state of jobs in Bigquery -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/pollBigqueryJob</url-pattern>
</servlet-mapping>
<!-- Fans out a cron task over an adjustable range of TLDs. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/cron/fanout</url-pattern>
</servlet-mapping>
<!-- Backups. -->
<!-- Fans out a cron task over all commit log buckets. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/cron/commitLogFanout</url-pattern>
</servlet-mapping>
<!-- Deletes old commit logs from Datastore. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/deleteOldCommitLogs</url-pattern>
</servlet-mapping>
<!-- Checkpoints commit logs. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/cron/commitLogCheckpoint</url-pattern>
</servlet-mapping>
<!-- Exports commit log diffs. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/exportCommitLogDiff</url-pattern>
</servlet-mapping>
<!-- Deletes commit logs. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/killCommitLogs</url-pattern>
</servlet-mapping>
<!-- MapReduce servlet. -->
<servlet>
<servlet-name>mapreduce</servlet-name>
<servlet-class>com.google.appengine.tools.mapreduce.MapReduceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>mapreduce</servlet-name>
<url-pattern>/_dr/mapreduce/*</url-pattern>
</servlet-mapping>
<!-- Pipeline GUI servlets. -->
<servlet>
<servlet-name>pipeline</servlet-name>
@@ -286,24 +214,18 @@
<url-pattern>/_dr/task/exportDomainLists</url-pattern>
</servlet-mapping>
<!-- Mapreduce to delete all prober data. -->
<!-- Action to delete all prober data. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/deleteProberData</url-pattern>
</servlet-mapping>
<!-- Mapreduce to delete load test data. -->
<!-- Action to delete load test data. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/deleteLoadTestData</url-pattern>
</servlet-mapping>
<!-- Mapreduce to re-save all EppResources. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<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>
@@ -334,7 +256,7 @@
<url-pattern>/_dr/task/refreshDnsOnHostRename</url-pattern>
</servlet-mapping>
<!-- Mapreduce to expand recurring billing events into OneTimes. -->
<!-- Action to expand recurring billing events into OneTimes. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/expandRecurringBillingEvents</url-pattern>
@@ -352,30 +274,6 @@
<url-pattern>/_dr/task/sendExpiringCertificateNotificationEmail</url-pattern>
</servlet-mapping>
<!-- Mapreduce to import contacts from escrow file -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/importRdeContacts</url-pattern>
</servlet-mapping>
<!-- Mapreduce to import hosts from escrow file -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/importRdeHosts</url-pattern>
</servlet-mapping>
<!-- Mapreduce to import domains from escrow file -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/importRdeDomains</url-pattern>
</servlet-mapping>
<!-- Mapreduce to link hosts from escrow file to superordinate domains -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/linkRdeHosts</url-pattern>
</servlet-mapping>
<!-- Action to automatically re-lock a domain after unlocking it -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
@@ -395,12 +293,6 @@ have been in the database for a certain period of time. -->
<url-pattern>/_dr/task/wipeOutCloudSql</url-pattern>
</servlet-mapping>
<!-- Action to wipeout Cloud Datastore data -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/wipeOutDatastore</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>
@@ -66,40 +66,6 @@
<url-pattern>/_dr/epptool</url-pattern>
</servlet-mapping>
<!-- Mapreduce to re-save all HistoryEntries. -->
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/task/resaveAllHistoryEntries</url-pattern>
</servlet-mapping>
<!-- Mapreduce to delete EppResources, children, and indices. -->
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/task/killAllEppResources</url-pattern>
</servlet-mapping>
<!-- Mapreduce to delete all commit logs. -->
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/task/killAllCommitLogs</url-pattern>
</servlet-mapping>
<!-- Restores commit logs. -->
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/task/restoreCommitLogs</url-pattern>
</servlet-mapping>
<!-- This path serves up the App Engine results page for mapreduce runs. -->
<servlet>
<servlet-name>mapreduce</servlet-name>
<servlet-class>com.google.appengine.tools.mapreduce.MapReduceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>mapreduce</servlet-name>
<url-pattern>/_dr/mapreduce/*</url-pattern>
</servlet-mapping>
<!-- Pipeline GUI servlets. -->
<servlet>
<servlet-name>pipeline</servlet-name>
@@ -106,7 +106,6 @@
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
It is needed for "deleteOldCommitLogs" to work correctly.
</description>
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
@@ -133,9 +132,9 @@
<cron>
<url><![CDATA[/_dr/task/expandRecurringBillingEvents]]></url>
<description>
This job runs a mapreduce that creates synthetic OneTime billing events from Recurring billing
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the mapreduce.
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<schedule>every day 03:00</schedule>
<target>backend</target>
@@ -35,7 +35,6 @@
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
It is needed for "deleteOldCommitLogs" to work correctly.
</description>
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
@@ -90,7 +90,6 @@
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
It is needed for "deleteOldCommitLogs" to work correctly.
</description>
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
@@ -108,9 +107,9 @@
<cron>
<url><![CDATA[/_dr/task/expandRecurringBillingEvents]]></url>
<description>
This job runs a mapreduce that creates synthetic OneTime billing events from Recurring billing
This job runs an action that creates synthetic OneTime billing events from Recurring billing
events. Events are created for all instances of Recurring billing events that should exist
between the RECURRING_BILLING cursor's time and the execution time of the mapreduce.
between the RECURRING_BILLING cursor's time and the execution time of the action.
</description>
<schedule>every day 03:00</schedule>
<target>backend</target>
@@ -1,64 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.export;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static google.registry.util.TypeUtils.hasAnnotation;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.googlecode.objectify.Key;
import google.registry.model.EntityClasses;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.NotBackedUp;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.annotations.VirtualEntity;
/** Constants related to export code. */
@DeleteAfterMigration
public final class AnnotatedEntities {
/** Returns the names of kinds to include in Datastore backups. */
public static ImmutableSet<String> getBackupKinds() {
// Back up all entity classes that aren't annotated with @VirtualEntity (never even persisted
// to Datastore, so they can't be backed up) or @NotBackedUp (intentionally omitted).
return EntityClasses.ALL_CLASSES
.stream()
.filter(hasAnnotation(VirtualEntity.class).negate())
.filter(hasAnnotation(NotBackedUp.class).negate())
.map(Key::getKind)
.collect(toImmutableSortedSet(Ordering.natural()));
}
/** Returns the names of kinds to import into reporting tools (e.g. BigQuery). */
public static ImmutableSet<String> getReportingKinds() {
return EntityClasses.ALL_CLASSES
.stream()
.filter(hasAnnotation(ReportedOn.class))
.filter(hasAnnotation(VirtualEntity.class).negate())
.map(Key::getKind)
.collect(toImmutableSortedSet(Ordering.natural()));
}
/** Returns the names of kinds that are in the cross-TLD entity group. */
public static ImmutableSet<String> getCrossTldKinds() {
return EntityClasses.ALL_CLASSES.stream()
.filter(hasAnnotation(InCrossTld.class))
.filter(hasAnnotation(VirtualEntity.class).negate())
.map(Key::getKind)
.collect(toImmutableSortedSet(Ordering.natural()));
}
}
@@ -1,105 +0,0 @@
// Copyright 2018 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.export;
import static google.registry.request.Action.Method.POST;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig;
import google.registry.export.datastore.DatastoreAdmin;
import google.registry.export.datastore.Operation;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import javax.inject.Inject;
/**
* Action to trigger a Datastore backup job that writes a snapshot to Google Cloud Storage.
*
* <p>This is the first step of a four step workflow for exporting snapshots, with each step calling
* the next upon successful completion:
*
* <ol>
* <li>The snapshot is exported to Google Cloud Storage (this action).
* <li>The {@link CheckBackupAction} polls until the export is completed.
* <li>The {@link UploadDatastoreBackupAction} uploads the data from GCS to BigQuery.
* <li>The {@link UpdateSnapshotViewAction} updates the view in latest_datastore_export.
* </ol>
*/
@Action(
service = Action.Service.BACKEND,
path = BackupDatastoreAction.PATH,
method = POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
@DeleteAfterMigration
public class BackupDatastoreAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** Queue to use for enqueuing the task that will actually launch the backup. */
static final String QUEUE = "export-snapshot"; // See queue.xml.
static final String PATH = "/_dr/task/backupDatastore"; // See web.xml.
@Inject DatastoreAdmin datastoreAdmin;
@Inject Response response;
@Inject Clock clock;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject
BackupDatastoreAction() {}
@Override
public void run() {
try {
Operation backup =
datastoreAdmin
.export(
RegistryConfig.getDatastoreBackupsBucket(), AnnotatedEntities.getBackupKinds())
.execute();
String backupName = backup.getName();
// Enqueue a poll task to monitor the backup for completion and load reporting-related kinds
// into bigquery.
cloudTasksUtils.enqueue(
CheckBackupAction.QUEUE,
cloudTasksUtils.createPostTaskWithDelay(
CheckBackupAction.PATH,
Service.BACKEND.toString(),
ImmutableMultimap.of(
CheckBackupAction.CHECK_BACKUP_NAME_PARAM,
backupName,
CheckBackupAction.CHECK_BACKUP_KINDS_TO_LOAD_PARAM,
Joiner.on(',').join(AnnotatedEntities.getReportingKinds())),
CheckBackupAction.POLL_COUNTDOWN));
String message =
String.format(
"Datastore backup started with name: %s\nSaving to %s",
backupName, backup.getExportFolderUrl());
logger.atInfo().log(message);
response.setPayload(message);
} catch (Throwable e) {
throw new InternalServerErrorException("Exception occurred while backing up Datastore", e);
}
}
}
@@ -1,129 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.export;
import static google.registry.bigquery.BigqueryUtils.toJobReferenceString;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.model.Job;
import com.google.api.services.bigquery.model.JobReference;
import com.google.cloud.tasks.v2.Task;
import com.google.common.flogger.FluentLogger;
import com.google.protobuf.ByteString;
import dagger.Lazy;
import google.registry.request.Action;
import google.registry.request.Header;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotModifiedException;
import google.registry.request.Payload;
import google.registry.request.auth.Auth;
import google.registry.util.CloudTasksUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import javax.inject.Inject;
import org.joda.time.Duration;
/**
* An action which polls the state of a bigquery job. If it is completed then it will log its
* completion state; otherwise it will return a failure code so that the task will be retried.
*
* <p>Note that this is AUTH_INTERNAL_ONLY: we don't allow "admin" for this to mitigate a
* vulnerability, see b/177308043.
*/
@Action(
service = Action.Service.BACKEND,
path = BigqueryPollJobAction.PATH,
method = {Action.Method.GET, Action.Method.POST},
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_ONLY)
public class BigqueryPollJobAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final String QUEUE = "export-bigquery-poll"; // See queue.xml
static final String PATH = "/_dr/task/pollBigqueryJob"; // See web.xml
static final String CHAINED_TASK_QUEUE_HEADER = "X-DomainRegistry-ChainedTaskQueue";
static final String PROJECT_ID_HEADER = "X-DomainRegistry-ProjectId";
static final String JOB_ID_HEADER = "X-DomainRegistry-JobId";
static final Duration POLL_COUNTDOWN = Duration.standardSeconds(20);
@Inject Bigquery bigquery;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject @Header(CHAINED_TASK_QUEUE_HEADER) Lazy<String> chainedQueueName;
@Inject @Header(PROJECT_ID_HEADER) String projectId;
@Inject @Header(JOB_ID_HEADER) String jobId;
@Inject @Payload ByteString payload;
@Inject BigqueryPollJobAction() {}
@Override
public void run() {
boolean jobOutcome =
checkJobOutcome(); // Throws a NotModifiedException if the job hasn't completed.
// If the job failed, do not enqueue the next step.
if (!jobOutcome || payload == null || payload.size() == 0) {
return;
}
// If there is a payload, it's a chained task, so enqueue it.
Task task;
try {
task =
(Task)
new ObjectInputStream(new ByteArrayInputStream(payload.toByteArray())).readObject();
} catch (ClassNotFoundException | IOException e) {
throw new BadRequestException("Cannot deserialize task from payload", e);
}
Task enqueuedTask = cloudTasksUtils.enqueue(chainedQueueName.get(), task);
logger.atInfo().log(
"Added chained task %s for %s to queue %s: %s",
enqueuedTask.getName(),
enqueuedTask.getAppEngineHttpRequest().getRelativeUri(),
chainedQueueName.get(),
enqueuedTask);
}
/**
* Returns true if the provided job succeeded, false if it failed, and throws an exception if it
* is still pending.
*/
private boolean checkJobOutcome() {
Job job = null;
String jobRefString =
toJobReferenceString(new JobReference().setProjectId(projectId).setJobId(jobId));
try {
job = bigquery.jobs().get(projectId, jobId).execute();
} catch (IOException e) {
// We will throw a new exception because done==false, but first log this exception.
logger.atWarning().withCause(e).log("Error checking outcome of BigQuery job %s.", jobId);
}
// If job is not yet done, then throw an exception so that we'll return a failing HTTP status
// code and the task will be retried.
if (job == null || !job.getStatus().getState().equals("DONE")) {
throw new NotModifiedException(jobRefString);
}
// Check if the job ended with an error.
if (job.getStatus().getErrorResult() != null) {
logger.atSevere().log("Bigquery job failed - %s - %s.", jobRefString, job);
return false;
}
logger.atInfo().log("Bigquery job succeeded - %s.", jobRefString);
return true;
}
}
@@ -1,195 +0,0 @@
// Copyright 2018 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.export;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.intersection;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import google.registry.export.datastore.DatastoreAdmin;
import google.registry.export.datastore.Operation;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.HttpException;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.request.HttpException.NoContentException;
import google.registry.request.HttpException.NotModifiedException;
import google.registry.request.Parameter;
import google.registry.request.RequestMethod;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import java.io.IOException;
import java.util.Set;
import javax.inject.Inject;
import org.joda.time.Duration;
import org.joda.time.PeriodType;
import org.joda.time.format.PeriodFormat;
/**
* Action that checks the status of a snapshot, and if complete, trigger loading it into BigQuery.
*/
@Action(
service = Action.Service.BACKEND,
path = CheckBackupAction.PATH,
method = {POST, GET},
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
@DeleteAfterMigration
public class CheckBackupAction implements Runnable {
/** Parameter names for passing parameters into this action. */
static final String CHECK_BACKUP_NAME_PARAM = "name";
static final String CHECK_BACKUP_KINDS_TO_LOAD_PARAM = "kindsToLoad";
/** Action-specific details needed for enqueuing tasks against itself. */
static final String QUEUE = "export-snapshot-poll"; // See queue.xml.
static final String PATH = "/_dr/task/checkDatastoreBackup"; // See web.xml.
static final Duration POLL_COUNTDOWN = Duration.standardMinutes(2);
/** The maximum amount of time we allow a backup to run before abandoning it. */
static final Duration MAXIMUM_BACKUP_RUNNING_TIME = Duration.standardHours(20);
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject DatastoreAdmin datastoreAdmin;
@Inject Clock clock;
@Inject Response response;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject @RequestMethod Action.Method requestMethod;
@Inject
@Parameter(CHECK_BACKUP_NAME_PARAM)
String backupName;
@Inject
@Parameter(CHECK_BACKUP_KINDS_TO_LOAD_PARAM)
String kindsToLoadParam;
@Inject
CheckBackupAction() {}
@Override
public void run() {
try {
if (requestMethod == POST) {
checkAndLoadBackupIfComplete();
} else {
// This is a GET request.
// TODO(weiminyu): consider moving this functionality to Registry tool.
response.setPayload(getExportStatus().toPrettyString());
}
} catch (HttpException e) {
// Rethrow and let caller propagate status code and error message to the response.
// See google.registry.request.RequestHandler#handleRequest.
throw e;
} catch (Throwable e) {
throw new InternalServerErrorException(
"Exception occurred while checking datastore exports.", e);
}
}
private Operation getExportStatus() throws IOException {
try {
return datastoreAdmin.get(backupName).execute();
} catch (GoogleJsonResponseException e) {
if (e.getStatusCode() == SC_NOT_FOUND) {
String message = String.format("Bad backup name %s: %s", backupName, e.getMessage());
// TODO(b/19081569): Ideally this would return a 2XX error so the task would not be
// retried but we might abandon backups that start late and haven't yet written to
// Datastore. We could fix that by replacing this with a two-phase polling strategy.
throw new BadRequestException(message, e);
}
throw e;
}
}
private void checkAndLoadBackupIfComplete() throws IOException {
Set<String> kindsToLoad = ImmutableSet.copyOf(Splitter.on(',').split(kindsToLoadParam));
Operation backup = getExportStatus();
checkArgument(backup.isExport(), "Expecting an export operation: [%s].", backupName);
if (backup.isProcessing()
&& backup.getRunningTime(clock).isShorterThan(MAXIMUM_BACKUP_RUNNING_TIME)) {
// Backup might still be running, so send a 304 to have the task retry.
throw new NotModifiedException(
String.format(
"Datastore backup %s still in progress: %s", backupName, backup.getProgress()));
}
if (!backup.isSuccessful()) {
// Declare the backup a lost cause, and send 204 No Content so the task will
// not be retried.
String message =
String.format(
"Datastore backup %s abandoned - not complete after %s. Progress: %s",
backupName,
PeriodFormat.getDefault()
.print(
backup
.getRunningTime(clock)
.toPeriod()
.normalizedStandard(PeriodType.dayTime().withMillisRemoved())),
backup.getProgress());
throw new NoContentException(message);
}
String backupId = backup.getExportId();
// Log a warning if kindsToLoad is not a subset of the exported kinds.
if (!backup.getKinds().containsAll(kindsToLoad)) {
logger.atWarning().log(
"Kinds to load included non-exported kinds: %s",
Sets.difference(kindsToLoad, backup.getKinds()));
}
// Load kinds from the backup, limited to those also in kindsToLoad (if it's present).
ImmutableSet<String> exportedKindsToLoad =
ImmutableSet.copyOf(intersection(backup.getKinds(), kindsToLoad));
String message = String.format("Datastore backup %s complete - ", backupName);
if (exportedKindsToLoad.isEmpty()) {
message += "no kinds to load into BigQuery.";
} else {
/** Enqueue a task for starting a backup load. */
cloudTasksUtils.enqueue(
UploadDatastoreBackupAction.QUEUE,
cloudTasksUtils.createPostTask(
UploadDatastoreBackupAction.PATH,
Service.BACKEND.toString(),
ImmutableMultimap.of(
UploadDatastoreBackupAction.UPLOAD_BACKUP_ID_PARAM,
backupId,
UploadDatastoreBackupAction.UPLOAD_BACKUP_FOLDER_PARAM,
backup.getExportFolderUrl(),
UploadDatastoreBackupAction.UPLOAD_BACKUP_KINDS_PARAM,
Joiner.on(',').join(exportedKindsToLoad))));
message += "BigQuery load task enqueued.";
}
logger.atInfo().log(message);
response.setPayload(message);
}
}
@@ -15,49 +15,33 @@
package google.registry.export;
import static com.google.common.base.Verify.verifyNotNull;
import static google.registry.mapreduce.inputs.EppResourceInputs.createEntityInput;
import static google.registry.model.EppResourceUtils.isActive;
import static google.registry.model.tld.Registries.getTldsOfType;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.appengine.tools.mapreduce.Reducer;
import com.google.appengine.tools.mapreduce.ReducerInput;
import com.google.cloud.storage.BlobId;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.model.domain.DomainBase;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldType;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.storage.drive.DriveConnection;
import google.registry.util.Clock;
import google.registry.util.NonFinalForTesting;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.List;
import java.util.function.Supplier;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* A mapreduce that exports the list of active domains on all real TLDs to Google Drive and GCS.
* An action that exports the list of active domains on all real TLDs to Google Drive and GCS.
*
* <p>Each TLD's active domain names are exported as a newline-delimited flat text file with the
* name TLD.txt into the domain-lists bucket. Note that this overwrites the files in place.
@@ -70,11 +54,8 @@ import org.joda.time.DateTime;
public class ExportDomainListsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final int MAX_NUM_REDUCE_SHARDS = 100;
public static final String REGISTERED_DOMAINS_FILENAME = "registered_domains.txt";
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject Clock clock;
@Inject DriveConnection driveConnection;
@Inject GcsUtils gcsUtils;
@@ -86,56 +67,44 @@ public class ExportDomainListsAction implements Runnable {
public void run() {
ImmutableSet<String> realTlds = getTldsOfType(TldType.REAL);
logger.atInfo().log("Exporting domain lists for TLDs %s.", realTlds);
if (tm().isOfy()) {
mrRunner
.setJobName("Export domain lists")
.setModuleName("backend")
.setDefaultReduceShards(Math.min(realTlds.size(), MAX_NUM_REDUCE_SHARDS))
.runMapreduce(
new ExportDomainListsMapper(clock.nowUtc(), realTlds),
new ExportDomainListsReducer(gcsBucket, gcsUtils),
ImmutableList.of(createEntityInput(DomainBase.class)))
.sendLinkToMapreduceConsole(response);
} else {
realTlds.forEach(
tld -> {
List<String> domains =
tm().transact(
() ->
// Note that if we had "creationTime <= :now" in the condition (not
// necessary as there is no pending creation, the order of deletionTime
// and creationTime in the query would have been significant and it
// should come after deletionTime. When Hibernate substitutes "now" it
// will first validate that the **first** field that is to be compared
// with it (deletionTime) is assignable from the substituted Java object
// (click.nowUtc()). Since creationTime is a CreateAutoTimestamp, if it
// comes first, we will need to substitute "now" with
// CreateAutoTimestamp.create(clock.nowUtc()). This might look a bit
// strange as the Java object type is clearly incompatible between the
// two fields deletionTime (DateTime) and creationTime, yet they are
// compared with the same "now". It is actually OK because in the end
// Hibernate converts everything to SQL types (and Java field names to
// SQL column names) to run the query. Both CreateAutoTimestamp and
// DateTime are persisted as timestamp_z in SQL. It is only the
// validation that compares the Java types, and only with the first
// field that compares with the substituted value.
jpaTm()
.query(
"SELECT fullyQualifiedDomainName FROM Domain "
+ "WHERE tld = :tld "
+ "AND deletionTime > :now "
+ "ORDER by fullyQualifiedDomainName ASC",
String.class)
.setParameter("tld", tld)
.setParameter("now", clock.nowUtc())
.getResultList());
String domainsList = Joiner.on("\n").join(domains);
logger.atInfo().log(
"Exporting %d domains for TLD %s to GCS and Drive.", domains.size(), tld);
exportToGcs(tld, domainsList, gcsBucket, gcsUtils);
exportToDrive(tld, domainsList, driveConnection);
});
}
realTlds.forEach(
tld -> {
List<String> domains =
tm().transact(
() ->
// Note that if we had "creationTime <= :now" in the condition (not
// necessary as there is no pending creation, the order of deletionTime
// and creationTime in the query would have been significant and it
// should come after deletionTime. When Hibernate substitutes "now" it
// will first validate that the **first** field that is to be compared
// with it (deletionTime) is assignable from the substituted Java object
// (click.nowUtc()). Since creationTime is a CreateAutoTimestamp, if it
// comes first, we will need to substitute "now" with
// CreateAutoTimestamp.create(clock.nowUtc()). This might look a bit
// strange as the Java object type is clearly incompatible between the
// two fields deletionTime (DateTime) and creationTime, yet they are
// compared with the same "now". It is actually OK because in the end
// Hibernate converts everything to SQL types (and Java field names to
// SQL column names) to run the query. Both CreateAutoTimestamp and
// DateTime are persisted as timestamp_z in SQL. It is only the
// validation that compares the Java types, and only with the first
// field that compares with the substituted value.
jpaTm()
.query(
"SELECT fullyQualifiedDomainName FROM Domain "
+ "WHERE tld = :tld "
+ "AND deletionTime > :now "
+ "ORDER by fullyQualifiedDomainName ASC",
String.class)
.setParameter("tld", tld)
.setParameter("now", clock.nowUtc())
.getResultList());
String domainsList = Joiner.on("\n").join(domains);
logger.atInfo().log(
"Exporting %d domains for TLD %s to GCS and Drive.", domains.size(), tld);
exportToGcs(tld, domainsList, gcsBucket, gcsUtils);
exportToDrive(tld, domainsList, driveConnection);
});
}
protected static boolean exportToDrive(
@@ -178,80 +147,4 @@ public class ExportDomainListsAction implements Runnable {
}
return true;
}
static class ExportDomainListsMapper extends Mapper<DomainBase, String, String> {
private static final long serialVersionUID = -7312206212434039854L;
private final DateTime exportTime;
private final ImmutableSet<String> realTlds;
ExportDomainListsMapper(DateTime exportTime, ImmutableSet<String> realTlds) {
this.exportTime = exportTime;
this.realTlds = realTlds;
}
@Override
public void map(DomainBase domain) {
if (realTlds.contains(domain.getTld()) && isActive(domain, exportTime)) {
emit(domain.getTld(), domain.getDomainName());
getContext().incrementCounter(String.format("domains in tld %s", domain.getTld()));
}
}
}
static class ExportDomainListsReducer extends Reducer<String, String, Void> {
private static final long serialVersionUID = 7035260977259119087L;
/** Allows overriding the default {@link DriveConnection} in tests. */
@NonFinalForTesting
private static Supplier<DriveConnection> driveConnectionSupplier =
Suppliers.memoize(() -> DaggerDriveModule_DriveComponent.create().driveConnection());
private final String gcsBucket;
private final GcsUtils gcsUtils;
/**
* Non-serializable {@link DriveConnection} that will be created when an instance of {@link
* ExportDomainListsReducer} is deserialized in a MR pipeline worker.
*
* <p>See {@link #readObject(ObjectInputStream)}.
*/
private transient DriveConnection driveConnection;
public ExportDomainListsReducer(String gcsBucket, GcsUtils gcsUtils) {
this.gcsBucket = gcsBucket;
this.gcsUtils = gcsUtils;
}
@SuppressWarnings("unused")
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
is.defaultReadObject();
driveConnection = driveConnectionSupplier.get();
}
@Override
public void reduce(String tld, ReducerInput<String> fqdns) {
ImmutableList<String> domains = ImmutableList.sortedCopyOf(() -> fqdns);
String domainsList = Joiner.on('\n').join(domains);
logger.atInfo().log("Exporting %d domains for TLD %s to GCS and Drive.", domains.size(), tld);
if (exportToGcs(tld, domainsList, gcsBucket, gcsUtils)) {
getContext().incrementCounter("domain lists successful written out to GCS");
} else {
getContext().incrementCounter("domain lists failed to write out to GCS");
}
if (exportToDrive(tld, domainsList, driveConnection)) {
getContext().incrementCounter("domain lists successfully written out to Drive");
} else {
getContext().incrementCounter("domain lists failed to write out to Drive");
}
}
@VisibleForTesting
static void setDriveConnectionForTesting(
Supplier<DriveConnection> testDriveConnectionSupplier) {
driveConnectionSupplier = testDriveConnectionSupplier;
}
}
}
@@ -1,113 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.export;
import static google.registry.export.BigqueryPollJobAction.CHAINED_TASK_QUEUE_HEADER;
import static google.registry.export.BigqueryPollJobAction.JOB_ID_HEADER;
import static google.registry.export.BigqueryPollJobAction.PROJECT_ID_HEADER;
import static google.registry.export.CheckBackupAction.CHECK_BACKUP_KINDS_TO_LOAD_PARAM;
import static google.registry.export.CheckBackupAction.CHECK_BACKUP_NAME_PARAM;
import static google.registry.export.UpdateSnapshotViewAction.UPDATE_SNAPSHOT_DATASET_ID_PARAM;
import static google.registry.export.UpdateSnapshotViewAction.UPDATE_SNAPSHOT_KIND_PARAM;
import static google.registry.export.UpdateSnapshotViewAction.UPDATE_SNAPSHOT_TABLE_ID_PARAM;
import static google.registry.export.UpdateSnapshotViewAction.UPDATE_SNAPSHOT_VIEWNAME_PARAM;
import static google.registry.export.UploadDatastoreBackupAction.UPLOAD_BACKUP_FOLDER_PARAM;
import static google.registry.export.UploadDatastoreBackupAction.UPLOAD_BACKUP_ID_PARAM;
import static google.registry.export.UploadDatastoreBackupAction.UPLOAD_BACKUP_KINDS_PARAM;
import static google.registry.request.RequestParameters.extractRequiredHeader;
import static google.registry.request.RequestParameters.extractRequiredParameter;
import dagger.Module;
import dagger.Provides;
import google.registry.request.Header;
import google.registry.request.Parameter;
import javax.servlet.http.HttpServletRequest;
/** Dagger module for data export tasks. */
@Module
public final class ExportRequestModule {
@Provides
@Parameter(UPDATE_SNAPSHOT_DATASET_ID_PARAM)
static String provideUpdateSnapshotDatasetId(HttpServletRequest req) {
return extractRequiredParameter(req, UPDATE_SNAPSHOT_DATASET_ID_PARAM);
}
@Provides
@Parameter(UPDATE_SNAPSHOT_TABLE_ID_PARAM)
static String provideUpdateSnapshotTableId(HttpServletRequest req) {
return extractRequiredParameter(req, UPDATE_SNAPSHOT_TABLE_ID_PARAM);
}
@Provides
@Parameter(UPDATE_SNAPSHOT_KIND_PARAM)
static String provideUpdateSnapshotKind(HttpServletRequest req) {
return extractRequiredParameter(req, UPDATE_SNAPSHOT_KIND_PARAM);
}
@Provides
@Parameter(UPDATE_SNAPSHOT_VIEWNAME_PARAM)
static String provideUpdateSnapshotViewName(HttpServletRequest req) {
return extractRequiredParameter(req, UPDATE_SNAPSHOT_VIEWNAME_PARAM);
}
@Provides
@Parameter(UPLOAD_BACKUP_FOLDER_PARAM)
static String provideSnapshotUrlPrefix(HttpServletRequest req) {
return extractRequiredParameter(req, UPLOAD_BACKUP_FOLDER_PARAM);
}
@Provides
@Parameter(UPLOAD_BACKUP_ID_PARAM)
static String provideLoadSnapshotId(HttpServletRequest req) {
return extractRequiredParameter(req, UPLOAD_BACKUP_ID_PARAM);
}
@Provides
@Parameter(UPLOAD_BACKUP_KINDS_PARAM)
static String provideLoadSnapshotKinds(HttpServletRequest req) {
return extractRequiredParameter(req, UPLOAD_BACKUP_KINDS_PARAM);
}
@Provides
@Parameter(CHECK_BACKUP_NAME_PARAM)
static String provideCheckSnapshotName(HttpServletRequest req) {
return extractRequiredParameter(req, CHECK_BACKUP_NAME_PARAM);
}
@Provides
@Parameter(CHECK_BACKUP_KINDS_TO_LOAD_PARAM)
static String provideCheckSnapshotKindsToLoad(HttpServletRequest req) {
return extractRequiredParameter(req, CHECK_BACKUP_KINDS_TO_LOAD_PARAM);
}
@Provides
@Header(CHAINED_TASK_QUEUE_HEADER)
static String provideChainedTaskQueue(HttpServletRequest req) {
return extractRequiredHeader(req, CHAINED_TASK_QUEUE_HEADER);
}
@Provides
@Header(JOB_ID_HEADER)
static String provideJobId(HttpServletRequest req) {
return extractRequiredHeader(req, JOB_ID_HEADER);
}
@Provides
@Header(PROJECT_ID_HEADER)
static String provideProjectId(HttpServletRequest req) {
return extractRequiredHeader(req, PROJECT_ID_HEADER);
}
}
@@ -32,7 +32,7 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GroupsConnection;
import google.registry.groups.GroupsConnection.Role;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
@@ -47,7 +47,7 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
/**
* Action that syncs changes to {@link RegistrarContact} entities with Google Groups.
* Action that syncs changes to {@link RegistrarPoc} entities with Google Groups.
*
* <p>This uses the <a href="https://developers.google.com/admin-sdk/directory/">Directory API</a>.
*/
@@ -99,7 +99,7 @@ public final class SyncGroupMembersAction implements Runnable {
* Returns the Google Groups email address for the given registrar ID and RegistrarContact.Type.
*/
public static String getGroupEmailAddressForContactType(
String registrarId, RegistrarContact.Type type, String gSuiteDomainName) {
String registrarId, RegistrarPoc.Type type, String gSuiteDomainName) {
// Take the registrar's ID, make it lowercase, and remove all characters that aren't
// alphanumeric, hyphens, or underscores.
return String.format(
@@ -171,18 +171,17 @@ public final class SyncGroupMembersAction implements Runnable {
private void syncRegistrarContacts(Registrar registrar) {
String groupKey = "";
try {
Set<RegistrarContact> registrarContacts = registrar.getContacts();
Set<RegistrarPoc> registrarPocs = registrar.getContacts();
long totalAdded = 0;
long totalRemoved = 0;
for (final RegistrarContact.Type type : RegistrarContact.Type.values()) {
for (final RegistrarPoc.Type type : RegistrarPoc.Type.values()) {
groupKey =
getGroupEmailAddressForContactType(registrar.getRegistrarId(), type, gSuiteDomainName);
Set<String> currentMembers = groupsConnection.getMembersOfGroup(groupKey);
Set<String> desiredMembers =
registrarContacts
.stream()
registrarPocs.stream()
.filter(contact -> contact.getTypes().contains(type))
.map(RegistrarContact::getEmailAddress)
.map(RegistrarPoc::getEmailAddress)
.collect(toImmutableSet());
for (String email : Sets.difference(desiredMembers, currentMembers)) {
groupsConnection.addMemberToGroup(groupKey, email, Role.MEMBER);
@@ -1,146 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.export;
import static google.registry.request.Action.Method.POST;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.model.Table;
import com.google.api.services.bigquery.model.TableReference;
import com.google.api.services.bigquery.model.ViewDefinition;
import com.google.common.flogger.FluentLogger;
import google.registry.bigquery.CheckedBigquery;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.request.Action;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.SqlTemplate;
import java.io.IOException;
import javax.inject.Inject;
/** Update a well-known view to point at a certain Datastore snapshot table in BigQuery. */
@Action(
service = Action.Service.BACKEND,
path = UpdateSnapshotViewAction.PATH,
method = POST,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
@DeleteAfterMigration
public class UpdateSnapshotViewAction implements Runnable {
/** Headers for passing parameters into the servlet. */
static final String UPDATE_SNAPSHOT_DATASET_ID_PARAM = "dataset";
static final String UPDATE_SNAPSHOT_TABLE_ID_PARAM = "table";
static final String UPDATE_SNAPSHOT_KIND_PARAM = "kind";
static final String UPDATE_SNAPSHOT_VIEWNAME_PARAM = "viewname";
/** Servlet-specific details needed for enqueuing tasks against itself. */
// For now this queue is shared by the backup workflows started by BackupDatastoreAction.
// TODO(weiminyu): update queue name (snapshot->backup) after ExportSnapshot flow is removed.
static final String QUEUE = "export-snapshot-update-view"; // See queue.xml.
static final String PATH = "/_dr/task/updateSnapshotView"; // See web.xml.
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject
@Parameter(UPDATE_SNAPSHOT_DATASET_ID_PARAM)
String datasetId;
@Inject
@Parameter(UPDATE_SNAPSHOT_TABLE_ID_PARAM)
String tableId;
@Inject
@Parameter(UPDATE_SNAPSHOT_KIND_PARAM)
String kindName;
@Inject
@Parameter(UPDATE_SNAPSHOT_VIEWNAME_PARAM)
String viewName;
@Inject
@Config("projectId")
String projectId;
@Inject CheckedBigquery checkedBigquery;
@Inject
UpdateSnapshotViewAction() {}
@Override
public void run() {
try {
SqlTemplate sqlTemplate =
SqlTemplate.create(
"#standardSQL\nSELECT * FROM `%PROJECT%.%SOURCE_DATASET%.%SOURCE_TABLE%`");
updateSnapshotView(datasetId, tableId, kindName, viewName, sqlTemplate);
} catch (Throwable e) {
throw new InternalServerErrorException(
String.format("Could not update snapshot view %s for table %s", viewName, tableId), e);
}
}
private void updateSnapshotView(
String sourceDatasetId,
String sourceTableId,
String kindName,
String viewDataset,
SqlTemplate viewQueryTemplate)
throws IOException {
Bigquery bigquery = checkedBigquery.ensureDataSetExists(projectId, viewDataset);
updateTable(
bigquery,
new Table()
.setTableReference(
new TableReference()
.setProjectId(projectId)
.setDatasetId(viewDataset)
.setTableId(kindName))
.setView(
new ViewDefinition()
.setUseLegacySql(false)
.setQuery(
viewQueryTemplate
.put("PROJECT", projectId)
.put("SOURCE_DATASET", sourceDatasetId)
.put("SOURCE_TABLE", sourceTableId)
.build())));
logger.atInfo().log(
"Updated view [%s:%s.%s] to point at snapshot table [%s:%s.%s].",
projectId, viewDataset, kindName, projectId, sourceDatasetId, sourceTableId);
}
private static void updateTable(Bigquery bigquery, Table table) throws IOException {
TableReference ref = table.getTableReference();
try {
bigquery
.tables()
.update(ref.getProjectId(), ref.getDatasetId(), ref.getTableId(), table)
.execute();
} catch (GoogleJsonResponseException e) {
if (e.getDetails() != null && e.getDetails().getCode() == 404) {
bigquery.tables().insert(ref.getProjectId(), ref.getDatasetId(), table).execute();
} else {
logger.atWarning().withCause(e).log("UpdateSnapshotViewAction errored out.");
}
}
}
}
@@ -1,215 +0,0 @@
// Copyright 2018 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.export;
import static com.google.common.base.MoreObjects.firstNonNull;
import static google.registry.request.Action.Method.POST;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.model.Job;
import com.google.api.services.bigquery.model.JobConfiguration;
import com.google.api.services.bigquery.model.JobConfigurationLoad;
import com.google.api.services.bigquery.model.JobReference;
import com.google.api.services.bigquery.model.TableReference;
import com.google.cloud.tasks.v2.Task;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.HttpHeaders;
import com.google.common.net.MediaType;
import com.google.protobuf.ByteString;
import com.google.protobuf.util.Timestamps;
import google.registry.bigquery.BigqueryUtils.SourceFormat;
import google.registry.bigquery.BigqueryUtils.WriteDisposition;
import google.registry.bigquery.CheckedBigquery;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import javax.inject.Inject;
/** Action to load a Datastore backup from Google Cloud Storage into BigQuery. */
@Action(
service = Action.Service.BACKEND,
path = UploadDatastoreBackupAction.PATH,
method = POST,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
@DeleteAfterMigration
public class UploadDatastoreBackupAction implements Runnable {
/** Parameter names for passing parameters into the servlet. */
static final String UPLOAD_BACKUP_ID_PARAM = "id";
static final String UPLOAD_BACKUP_FOLDER_PARAM = "folder";
static final String UPLOAD_BACKUP_KINDS_PARAM = "kinds";
static final String BACKUP_DATASET = "datastore_backups";
/** Servlet-specific details needed for enqueuing tasks against itself. */
static final String QUEUE = "export-snapshot"; // See queue.xml.
static final String LATEST_BACKUP_VIEW_NAME = "latest_datastore_export";
static final String PATH = "/_dr/task/uploadDatastoreBackup"; // See web.xml.
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject CheckedBigquery checkedBigquery;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject Clock clock;
@Inject @Config("projectId") String projectId;
@Inject
@Parameter(UPLOAD_BACKUP_FOLDER_PARAM)
String backupFolderUrl;
@Inject
@Parameter(UPLOAD_BACKUP_ID_PARAM)
String backupId;
@Inject
@Parameter(UPLOAD_BACKUP_KINDS_PARAM)
String backupKinds;
@Inject
UploadDatastoreBackupAction() {}
@Override
public void run() {
try {
String message = uploadBackup(backupId, backupFolderUrl, Splitter.on(',').split(backupKinds));
logger.atInfo().log("Loaded backup successfully: %s", message);
} catch (Throwable e) {
logger.atSevere().withCause(e).log("Error loading backup.");
if (e instanceof IllegalArgumentException) {
throw new BadRequestException("Error calling load backup: " + e.getMessage(), e);
} else {
throw new InternalServerErrorException(
"Error loading backup: " + firstNonNull(e.getMessage(), e.toString()));
}
}
}
private String uploadBackup(String backupId, String backupFolderUrl, Iterable<String> kinds)
throws IOException {
Bigquery bigquery = checkedBigquery.ensureDataSetExists(projectId, BACKUP_DATASET);
String loadMessage =
String.format("Loading Datastore backup %s from %s...", backupId, backupFolderUrl);
logger.atInfo().log(loadMessage);
String sanitizedBackupId = sanitizeForBigquery(backupId);
StringBuilder builder = new StringBuilder(loadMessage + "\n");
builder.append("Load jobs:\n");
for (String kindName : kinds) {
String jobId = String.format("load-backup-%s-%s", sanitizedBackupId, kindName);
JobReference jobRef = new JobReference().setProjectId(projectId).setJobId(jobId);
String sourceUri = getBackupInfoFileForKind(backupFolderUrl, kindName);
String tableId = String.format("%s_%s", sanitizedBackupId, kindName);
// Launch the load job.
Job job = makeLoadJob(jobRef, sourceUri, tableId);
bigquery.jobs().insert(projectId, job).execute();
// Serialize the chainedTask into a byte array to put in the task payload.
ByteArrayOutputStream taskBytes = new ByteArrayOutputStream();
new ObjectOutputStream(taskBytes)
.writeObject(
cloudTasksUtils.createPostTask(
UpdateSnapshotViewAction.PATH,
Service.BACKEND.toString(),
ImmutableMultimap.of(
UpdateSnapshotViewAction.UPDATE_SNAPSHOT_DATASET_ID_PARAM,
BACKUP_DATASET,
UpdateSnapshotViewAction.UPDATE_SNAPSHOT_TABLE_ID_PARAM,
tableId,
UpdateSnapshotViewAction.UPDATE_SNAPSHOT_KIND_PARAM,
kindName,
UpdateSnapshotViewAction.UPDATE_SNAPSHOT_VIEWNAME_PARAM,
LATEST_BACKUP_VIEW_NAME)));
// Enqueues a task to poll for the success or failure of the referenced BigQuery job and to
// launch the provided task in the specified queue if the job succeeds.
cloudTasksUtils.enqueue(
BigqueryPollJobAction.QUEUE,
Task.newBuilder()
.setAppEngineHttpRequest(
cloudTasksUtils
.createPostTask(BigqueryPollJobAction.PATH, Service.BACKEND.toString(), null)
.getAppEngineHttpRequest()
.toBuilder()
.putHeaders(BigqueryPollJobAction.PROJECT_ID_HEADER, jobRef.getProjectId())
.putHeaders(BigqueryPollJobAction.JOB_ID_HEADER, jobRef.getJobId())
.putHeaders(
BigqueryPollJobAction.CHAINED_TASK_QUEUE_HEADER,
UpdateSnapshotViewAction.QUEUE)
// need to include CONTENT_TYPE in header when body is not empty
.putHeaders(HttpHeaders.CONTENT_TYPE, MediaType.FORM_DATA.toString())
.setBody(ByteString.copyFrom(taskBytes.toByteArray()))
.build())
.setScheduleTime(
Timestamps.fromMillis(
clock.nowUtc().plus(BigqueryPollJobAction.POLL_COUNTDOWN).getMillis()))
.build());
builder.append(String.format(" - %s:%s\n", projectId, jobId));
logger.atInfo().log("Submitted load job %s:%s.", projectId, jobId);
}
return builder.toString();
}
private static String sanitizeForBigquery(String backupId) {
return backupId.replaceAll("[^a-zA-Z0-9_]", "_");
}
@VisibleForTesting
static String getBackupInfoFileForKind(String backupFolderUrl, String kindName) {
return Joiner.on('/')
.join(
backupFolderUrl,
"all_namespaces",
String.format("kind_%s", kindName),
String.format("all_namespaces_kind_%s.%s", kindName, "export_metadata"));
}
private Job makeLoadJob(JobReference jobRef, String sourceUri, String tableId) {
TableReference tableReference =
new TableReference()
.setProjectId(jobRef.getProjectId())
.setDatasetId(BACKUP_DATASET)
.setTableId(tableId);
return new Job()
.setJobReference(jobRef)
.setConfiguration(new JobConfiguration()
.setLoad(new JobConfigurationLoad()
.setWriteDisposition(WriteDisposition.WRITE_EMPTY.toString())
.setSourceFormat(SourceFormat.DATASTORE_BACKUP.toString())
.setSourceUris(ImmutableList.of(sourceUri))
.setDestinationTable(tableReference)));
}
}
@@ -1,279 +0,0 @@
// Copyright 2018 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.export.datastore;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClient;
import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.util.Key;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import google.registry.model.annotations.DeleteAfterMigration;
import java.util.Collection;
import java.util.Optional;
/**
* Java client to <a href="https://cloud.google.com/datastore/docs/reference/admin/rest/">Cloud
* Datastore Admin REST API</a>.
*/
@DeleteAfterMigration
public class DatastoreAdmin extends AbstractGoogleJsonClient {
private static final String ROOT_URL = "https://datastore.googleapis.com/v1/";
private static final String SERVICE_PATH = "";
// GCP project that this instance is associated with.
private final String projectId;
protected DatastoreAdmin(Builder builder) {
super(builder);
this.projectId = checkNotNull(builder.projectId, "GCP projectId missing.");
}
/**
* Returns an {@link Export} request that starts exporting all Cloud Datastore databases owned by
* the GCP project identified by {@link #projectId}.
*
* <p>Typical usage is:
*
* <pre>
* {@code Export export = datastoreAdmin.export(parameters ...);}
* {@code Operation operation = export.execute();}
* {@code while (!operation.isSuccessful()) { ...}}
* </pre>
*
* <p>Please see the <a
* href="https://cloud.google.com/datastore/docs/reference/admin/rest/v1/projects/export">API
* specification of the export method for details</a>.
*
* <p>The following undocumented behaviors with regard to {@code outputUrlPrefix} have been
* observed:
*
* <ul>
* <li>If outputUrlPrefix refers to a GCS bucket, exported data will be nested deeper in the
* bucket with a timestamped path. This is useful when periodical backups are desired
* <li>If outputUrlPrefix is a already a nested path in a GCS bucket, exported data will be put
* under this path. This means that a nested path is not reusable, since the export process
* by default would not overwrite existing files.
* </ul>
*
* @param outputUrlPrefix the full resource URL of the external storage location
* @param kinds the datastore 'kinds' to be exported. If empty, all kinds will be exported
*/
public Export export(String outputUrlPrefix, Collection<String> kinds) {
return new Export(new ExportRequest(outputUrlPrefix, kinds));
}
/**
* Imports the entire backup specified by {@code backupUrl} back to Cloud Datastore.
*
* <p>A successful backup restores deleted entities and reverts updates to existing entities since
* the backup time. However, it does not affect newly added entities.
*/
public Import importBackup(String backupUrl) {
return new Import(new ImportRequest(backupUrl, ImmutableList.of()));
}
/**
* Imports the backup specified by {@code backupUrl} back to Cloud Datastore. Only entities whose
* types are included in {@code kinds} are imported.
*
* @see #importBackup(String)
*/
public Import importBackup(String backupUrl, Collection<String> kinds) {
return new Import(new ImportRequest(backupUrl, kinds));
}
/**
* Returns a {@link Get} request that retrieves the details of an export or import {@link
* Operation}.
*
* @param operationName name of the {@code Operation} as returned by an export or import request
*/
public Get get(String operationName) {
return new Get(operationName);
}
/**
* Returns a {@link ListOperations} request that retrieves all export or import {@link Operation
* operations} matching {@code filter}.
*
* <p>Sample usage: find all operations started after 2018-10-31 00:00:00 UTC and has stopped:
*
* <pre>
* {@code String filter = "metadata.common.startTime>\"2018-10-31T0:0:0Z\" AND done=true";}
* {@code List<Operation> operations = datastoreAdmin.list(filter);}
* </pre>
*
* <p>Please refer to {@link Operation} for how to reference operation properties.
*/
public ListOperations list(String filter) {
checkArgument(!Strings.isNullOrEmpty(filter), "Filter must not be null or empty.");
return new ListOperations(Optional.of(filter));
}
/**
* Returns a {@link ListOperations} request that retrieves all export or import {@link Operation *
* operations}.
*/
public ListOperations listAll() {
return new ListOperations(Optional.empty());
}
/** Builder for {@link DatastoreAdmin}. */
public static class Builder extends AbstractGoogleJsonClient.Builder {
private String projectId;
public Builder(
HttpTransport httpTransport,
JsonFactory jsonFactory,
HttpRequestInitializer httpRequestInitializer) {
super(httpTransport, jsonFactory, ROOT_URL, SERVICE_PATH, httpRequestInitializer, false);
}
@Override
public Builder setApplicationName(String applicationName) {
return (Builder) super.setApplicationName(applicationName);
}
/** Sets the GCP project ID of the Cloud Datastore databases being managed. */
public Builder setProjectId(String projectId) {
this.projectId = projectId;
return this;
}
@Override
public DatastoreAdmin build() {
return new DatastoreAdmin(this);
}
}
/** A request to export Cloud Datastore databases. */
public class Export extends DatastoreAdminRequest<Operation> {
Export(ExportRequest exportRequest) {
super(
DatastoreAdmin.this,
"POST",
"projects/{projectId}:export",
exportRequest,
Operation.class);
set("projectId", projectId);
}
}
/** A request to restore an backup to a Cloud Datastore database. */
public class Import extends DatastoreAdminRequest<Operation> {
Import(ImportRequest importRequest) {
super(
DatastoreAdmin.this,
"POST",
"projects/{projectId}:import",
importRequest,
Operation.class);
set("projectId", projectId);
}
}
/** A request to retrieve details of an export or import operation. */
public class Get extends DatastoreAdminRequest<Operation> {
Get(String operationName) {
super(DatastoreAdmin.this, "GET", operationName, null, Operation.class);
}
}
/** A request to retrieve all export or import operations matching a given filter. */
public class ListOperations extends DatastoreAdminRequest<Operation.OperationList> {
ListOperations(Optional<String> filter) {
super(
DatastoreAdmin.this,
"GET",
"projects/{projectId}/operations",
null,
Operation.OperationList.class);
set("projectId", projectId);
filter.ifPresent(f -> set("filter", f));
}
}
/** Base class of all DatastoreAdmin requests. */
abstract static class DatastoreAdminRequest<T> extends AbstractGoogleJsonClientRequest<T> {
/**
* @param client Google JSON client
* @param requestMethod HTTP Method
* @param uriTemplate URI template for the path relative to the base URL. If it starts with a
* "/" the base path from the base URL will be stripped out. The URI template can also be a
* full URL. URI template expansion is done using {@link
* com.google.api.client.http.UriTemplate#expand(String, String, Object, boolean)}
* @param jsonContent POJO that can be serialized into JSON content or {@code null} for none
* @param responseClass response class to parse into
*/
protected DatastoreAdminRequest(
DatastoreAdmin client,
String requestMethod,
String uriTemplate,
Object jsonContent,
Class<T> responseClass) {
super(client, requestMethod, uriTemplate, jsonContent, responseClass);
}
}
/**
* Model object that describes the JSON content in an export request.
*
* <p>Please note that some properties defined in the API are excluded, e.g., {@code databaseId}
* (not supported by Cloud Datastore) and labels (not used by Domain Registry).
*/
@SuppressWarnings("unused")
static class ExportRequest extends GenericJson {
@Key private final String outputUrlPrefix;
@Key private final EntityFilter entityFilter;
ExportRequest(String outputUrlPrefix, Collection<String> kinds) {
checkNotNull(outputUrlPrefix, "outputUrlPrefix");
checkArgument(!kinds.isEmpty(), "kinds must not be empty");
this.outputUrlPrefix = outputUrlPrefix;
this.entityFilter = new EntityFilter(kinds);
}
}
/**
* Model object that describes the JSON content in an export request.
*
* <p>Please note that some properties defined in the API are excluded, e.g., {@code databaseId}
* (not supported by Cloud Datastore) and labels (not used by Domain Registry).
*/
@SuppressWarnings("unused")
static class ImportRequest extends GenericJson {
@Key private final String inputUrl;
@Key private final EntityFilter entityFilter;
ImportRequest(String inputUrl, Collection<String> kinds) {
checkNotNull(inputUrl, "outputUrlPrefix");
this.inputUrl = inputUrl;
this.entityFilter = new EntityFilter(kinds);
}
}
}
@@ -1,43 +0,0 @@
// Copyright 2018 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.export.datastore;
import dagger.Module;
import dagger.Provides;
import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.util.GoogleCredentialsBundle;
import javax.inject.Singleton;
/** Dagger module that configures provision of {@link DatastoreAdmin}. */
@Module
@DeleteAfterMigration
public abstract class DatastoreAdminModule {
@Singleton
@Provides
static DatastoreAdmin provideDatastoreAdmin(
@CredentialModule.DefaultCredential GoogleCredentialsBundle credentialsBundle,
@RegistryConfig.Config("projectId") String projectId) {
return new DatastoreAdmin.Builder(
credentialsBundle.getHttpTransport(),
credentialsBundle.getJsonFactory(),
credentialsBundle.getHttpRequestInitializer())
.setApplicationName(projectId)
.setProjectId(projectId)
.build();
}
}
@@ -1,49 +0,0 @@
// Copyright 2018 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.export.datastore;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Key;
import com.google.common.collect.ImmutableList;
import google.registry.model.annotations.DeleteAfterMigration;
import java.util.Collection;
import java.util.List;
/**
* Model object that describes the Cloud Datastore 'kinds' to be exported or imported. The JSON form
* of this type is found in export/import requests and responses.
*
* <p>Please note that properties not used by Domain Registry are not included, e.g., {@code
* namespaceIds}.
*/
@DeleteAfterMigration
public class EntityFilter extends GenericJson {
@Key private List<String> kinds = ImmutableList.of();
/** For JSON deserialization. */
public EntityFilter() {}
EntityFilter(Collection<String> kinds) {
checkNotNull(kinds, "kinds");
this.kinds = ImmutableList.copyOf(kinds);
}
List<String> getKinds() {
return ImmutableList.copyOf(kinds);
}
}
@@ -1,250 +0,0 @@
// Copyright 2018 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.export.datastore;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Key;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.export.datastore.DatastoreAdmin.Get;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.util.Clock;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* Model object that describes the details of an export or import operation in Cloud Datastore.
*
* <p>{@link Operation} instances are parsed from the JSON payload in Datastore response messages.
*/
@DeleteAfterMigration
public class Operation extends GenericJson {
private static final String STATE_SUCCESS = "SUCCESSFUL";
private static final String STATE_PROCESSING = "PROCESSING";
@Key private String name;
@Key private Metadata metadata;
@Key private boolean done;
/** For JSON deserialization. */
public Operation() {}
/** Returns the name of this operation, which may be used in a {@link Get} request. */
public String getName() {
checkState(name != null, "Name must not be null.");
return name;
}
public boolean isExport() {
return !isNullOrEmpty(getExportFolderUrl());
}
public boolean isImport() {
return !isNullOrEmpty(getMetadata().getInputUrl());
}
public boolean isDone() {
return done;
}
private String getState() {
return getMetadata().getCommonMetadata().getState();
}
public boolean isSuccessful() {
return getState().equals(STATE_SUCCESS);
}
public boolean isProcessing() {
return getState().equals(STATE_PROCESSING);
}
/**
* Returns the elapsed time since starting if this operation is still running, or the total
* running time if this operation has completed.
*/
public Duration getRunningTime(Clock clock) {
return new Duration(
getStartTime(), getMetadata().getCommonMetadata().getEndTime().orElse(clock.nowUtc()));
}
public DateTime getStartTime() {
return getMetadata().getCommonMetadata().getStartTime();
}
public ImmutableSet<String> getKinds() {
return ImmutableSet.copyOf(getMetadata().getEntityFilter().getKinds());
}
/**
* Returns the URL to the GCS folder that holds the exported data. This folder is created by
* Datastore and is under the {@code outputUrlPrefix} set to {@linkplain
* DatastoreAdmin#export(String, java.util.Collection) the export request}.
*
* @throws IllegalStateException if this is not an export operation
*/
public String getExportFolderUrl() {
return getMetadata().getOutputUrlPrefix();
}
/**
* Returns the last segment of the {@linkplain #getExportFolderUrl() export folder URL} which can
* be used as unique identifier of this export operation. This is a better ID than the {@linkplain
* #getName() operation name}, which is opaque.
*
* @throws IllegalStateException if this is not an export operation
*/
public String getExportId() {
String exportFolderUrl = getExportFolderUrl();
return exportFolderUrl.substring(exportFolderUrl.lastIndexOf('/') + 1);
}
public String getProgress() {
StringBuilder result = new StringBuilder();
Progress progress = getMetadata().getProgressBytes();
if (progress != null) {
result.append(
String.format(" [%s/%s bytes]", progress.workCompleted, progress.workEstimated));
}
progress = getMetadata().getProgressEntities();
if (progress != null) {
result.append(
String.format(" [%s/%s entities]", progress.workCompleted, progress.workEstimated));
}
if (result.length() == 0) {
return "Progress: N/A";
}
return "Progress:" + result;
}
private Metadata getMetadata() {
checkState(metadata != null, "Response metadata missing.");
return metadata;
}
/** Models the common metadata properties of all operations. */
public static class CommonMetadata extends GenericJson {
@Key private String startTime;
@Key @Nullable private String endTime;
@Key private String operationType;
@Key private String state;
public CommonMetadata() {}
String getOperationType() {
checkState(!isNullOrEmpty(operationType), "operationType may not be null or empty");
return operationType;
}
String getState() {
checkState(!isNullOrEmpty(state), "state may not be null or empty");
return state;
}
DateTime getStartTime() {
checkState(startTime != null, "StartTime missing.");
return DateTime.parse(startTime);
}
Optional<DateTime> getEndTime() {
return Optional.ofNullable(endTime).map(DateTime::parse);
}
}
/** Models the metadata of a Cloud Datatore export or import operation. */
public static class Metadata extends GenericJson {
@Key("common")
private CommonMetadata commonMetadata;
@Key private Progress progressEntities;
@Key private Progress progressBytes;
@Key private EntityFilter entityFilter;
@Key private String inputUrl;
@Key private String outputUrlPrefix;
public Metadata() {}
CommonMetadata getCommonMetadata() {
checkState(commonMetadata != null, "CommonMetadata field is null.");
return commonMetadata;
}
public Progress getProgressEntities() {
return progressEntities;
}
public Progress getProgressBytes() {
return progressBytes;
}
public EntityFilter getEntityFilter() {
return entityFilter;
}
public String getInputUrl() {
return checkUrls().inputUrl;
}
public String getOutputUrlPrefix() {
return checkUrls().outputUrlPrefix;
}
Metadata checkUrls() {
checkState(
isNullOrEmpty(inputUrl) || isNullOrEmpty(outputUrlPrefix),
"inputUrl and outputUrlPrefix must not be both present");
checkState(
!isNullOrEmpty(inputUrl) || !isNullOrEmpty(outputUrlPrefix),
"inputUrl and outputUrlPrefix must not be both missing");
return this;
}
}
/** Progress of an export or import operation. */
public static class Progress extends GenericJson {
@Key private long workCompleted;
@Key private long workEstimated;
public Progress() {}
long getWorkCompleted() {
return workCompleted;
}
public long getWorkEstimated() {
return workEstimated;
}
}
/** List of {@link Operation Operations}. */
public static class OperationList extends GenericJson {
@Key private List<Operation> operations;
/** For JSON deserialization. */
public OperationList() {}
ImmutableList<Operation> toList() {
return ImmutableList.copyOf(operations);
}
}
}
@@ -17,15 +17,14 @@ package google.registry.export.sheet;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.common.Cursor.CursorType.SYNC_REGISTRAR_SHEET;
import static google.registry.model.registrar.RegistrarContact.Type.ABUSE;
import static google.registry.model.registrar.RegistrarContact.Type.ADMIN;
import static google.registry.model.registrar.RegistrarContact.Type.BILLING;
import static google.registry.model.registrar.RegistrarContact.Type.LEGAL;
import static google.registry.model.registrar.RegistrarContact.Type.MARKETING;
import static google.registry.model.registrar.RegistrarContact.Type.TECH;
import static google.registry.model.registrar.RegistrarContact.Type.WHOIS;
import static google.registry.model.registrar.RegistrarPoc.Type.ABUSE;
import static google.registry.model.registrar.RegistrarPoc.Type.ADMIN;
import static google.registry.model.registrar.RegistrarPoc.Type.BILLING;
import static google.registry.model.registrar.RegistrarPoc.Type.LEGAL;
import static google.registry.model.registrar.RegistrarPoc.Type.MARKETING;
import static google.registry.model.registrar.RegistrarPoc.Type.TECH;
import static google.registry.model.registrar.RegistrarPoc.Type.WHOIS;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.base.Joiner;
@@ -36,7 +35,7 @@ import com.google.common.collect.Ordering;
import google.registry.model.common.Cursor;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.util.Clock;
import google.registry.util.DateTimeUtils;
import java.io.IOException;
@@ -63,8 +62,7 @@ class SyncRegistrarsSheet {
*/
boolean wereRegistrarsModified() {
Optional<Cursor> cursor =
transactIfJpaTm(
() -> tm().loadByKeyIfPresent(Cursor.createGlobalVKey(SYNC_REGISTRAR_SHEET)));
tm().transact(() -> tm().loadByKeyIfPresent(Cursor.createGlobalVKey(SYNC_REGISTRAR_SHEET)));
DateTime lastUpdateTime = !cursor.isPresent() ? START_OF_TIME : cursor.get().getCursorTime();
for (Registrar registrar : Registrar.loadAllCached()) {
if (DateTimeUtils.isAtOrAfter(registrar.getLastUpdateTime(), lastUpdateTime)) {
@@ -92,7 +90,7 @@ class SyncRegistrarsSheet {
.map(
registrar -> {
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
ImmutableSortedSet<RegistrarContact> contacts = registrar.getContacts();
ImmutableSortedSet<RegistrarPoc> contacts = registrar.getContacts();
RegistrarAddress address =
firstNonNull(
registrar.getLocalizedAddress(),
@@ -130,10 +128,10 @@ class SyncRegistrarsSheet {
builder.put("billingContacts", convertContacts(contacts, byType(BILLING)));
builder.put(
"contactsMarkedAsWhoisAdmin",
convertContacts(contacts, RegistrarContact::getVisibleInWhoisAsAdmin));
convertContacts(contacts, RegistrarPoc::getVisibleInWhoisAsAdmin));
builder.put(
"contactsMarkedAsWhoisTech",
convertContacts(contacts, RegistrarContact::getVisibleInWhoisAsTech));
convertContacts(contacts, RegistrarPoc::getVisibleInWhoisAsTech));
builder.put("emailAddress", convert(registrar.getEmailAddress()));
builder.put("address.street", convert(address.getStreet()));
builder.put("address.city", convert(address.getCity()));
@@ -158,10 +156,10 @@ class SyncRegistrarsSheet {
}
private static String convertContacts(
Iterable<RegistrarContact> contacts, Predicate<RegistrarContact> filter) {
Iterable<RegistrarPoc> contacts, Predicate<RegistrarPoc> filter) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (RegistrarContact contact : contacts) {
for (RegistrarPoc contact : contacts) {
if (!filter.test(contact)) {
continue;
}
@@ -175,7 +173,7 @@ class SyncRegistrarsSheet {
return result.toString();
}
private static Predicate<RegistrarContact> byType(final RegistrarContact.Type type) {
private static Predicate<RegistrarPoc> byType(final RegistrarPoc.Type type) {
return contact -> contact.getTypes().contains(type);
}
@@ -84,5 +84,11 @@ public class EppToolAction implements Runnable {
static String provideClientId(HttpServletRequest req) {
return extractRequiredParameter(req, "clientId");
}
@Provides
@Parameter("dryRun")
static boolean provideIsDryRun(HttpServletRequest req) {
return extractBooleanParameter(req, "dryRun");
}
}
}
@@ -53,7 +53,7 @@ public final class FlowUtils {
}
}
/** Persists the saves and deletes in an {@link EntityChanges} to Datastore. */
/** Persists the saves and deletes in an {@link EntityChanges} to the DB. */
public static void persistEntityChanges(EntityChanges entityChanges) {
tm().putAll(entityChanges.getSaves());
tm().delete(entityChanges.getDeletes());
@@ -19,7 +19,6 @@ import static google.registry.model.EppResourceUtils.isLinked;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -170,13 +169,13 @@ public final class ResourceFlowUtils {
// The roid should match one of the contacts.
Optional<VKey<ContactResource>> foundContact =
domain.getReferencedContacts().stream()
.filter(key -> key.getOfyKey().getName().equals(authRepoId))
.filter(key -> key.getSqlKey().equals(authRepoId))
.findFirst();
if (!foundContact.isPresent()) {
throw new BadAuthInfoForResourceException();
}
// Check the authInfo against the contact.
verifyAuthInfo(authInfo, transactIfJpaTm(() -> tm().loadByKey(foundContact.get())));
verifyAuthInfo(authInfo, tm().transact(() -> tm().loadByKey(foundContact.get())));
}
/** Check that the given {@link AuthInfo} is valid for the given contact. */
@@ -17,7 +17,7 @@ package google.registry.flows;
/**
* Interface for a {@link Flow} that needs to be run transactionally.
*
* <p>Any flow that mutates Datastore should implement this so that {@link FlowRunner} will know how
* to run it.
* <p>Any flow that mutates the DB should implement this so that {@link FlowRunner} will know how to
* run it.
*/
public interface TransactionalFlow extends Flow {}
@@ -26,6 +26,7 @@ import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactHistory.ContactHistoryId;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.PostalInfo;
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
@@ -84,7 +85,9 @@ public class ContactFlowUtils {
transferData.getTransferStatus().isApproved(),
transferData.getTransferRequestTrid(),
now)))
.setParentKey(contactHistoryKey)
.setContactHistoryId(
new ContactHistoryId(
contactHistoryKey.getParent().getName(), contactHistoryKey.getId()))
.build();
}
@@ -96,7 +99,9 @@ public class ContactFlowUtils {
.setEventTime(transferData.getPendingTransferExpirationTime())
.setMsg(transferData.getTransferStatus().getMessage())
.setResponseData(ImmutableList.of(createTransferResponse(targetId, transferData)))
.setParentKey(contactHistoryKey)
.setContactHistoryId(
new ContactHistoryId(
contactHistoryKey.getParent().getName(), contactHistoryKey.getId()))
.build();
}
@@ -134,6 +134,8 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
.asBuilder()
.setTransferStatus(TransferStatus.PENDING)
.setServerApproveEntities(
serverApproveGainingPollMessage.getContactRepoId(),
contactHistoryKey.getId(),
ImmutableSet.of(
serverApproveGainingPollMessage.createVKey(),
serverApproveLosingPollMessage.createVKey()))
@@ -50,8 +50,7 @@ public class DomainPricingCustomLogic extends BaseFlowCustomLogic {
/** A hook that customizes the renew price. */
@SuppressWarnings("unused")
public FeesAndCredits customizeRenewPrice(RenewPriceParameters priceParameters)
throws EppException {
public FeesAndCredits customizeRenewPrice(RenewPriceParameters priceParameters) {
return priceParameters.feesAndCredits();
}
@@ -87,6 +87,7 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.DomainCommand.Create;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
@@ -357,7 +358,8 @@ public final class DomainCreateFlow implements TransactionalFlow {
.setIdnTableName(validateDomainNameWithIdnTables(domainName))
.setRegistrationExpirationTime(registrationExpirationTime)
.setAutorenewBillingEvent(autorenewBillingEvent.createVKey())
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
.setAutorenewPollMessage(
autorenewPollMessage.createVKey(), autorenewPollMessage.getHistoryRevisionId())
.setLaunchNotice(hasClaimsNotice ? launchCreate.get().getNotice() : null)
.setSmdId(signedMarkId)
.setDsData(secDnsCreate.map(SecDnsCreateExtension::getDsData).orElse(null))
@@ -576,7 +578,8 @@ public final class DomainCreateFlow implements TransactionalFlow {
.setRegistrarId(registrarId)
.setEventTime(registrationExpirationTime)
.setMsg("Domain was auto-renewed.")
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
}
@@ -608,7 +611,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
ImmutableList.of(
DomainPendingActionNotificationResponse.create(
fullyQualifiedDomainName, true, historyEntry.getTrid(), now)))
.setParent(historyEntry)
.setHistoryEntry(historyEntry)
.build();
}
@@ -66,6 +66,7 @@ import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Credit;
@@ -355,7 +356,8 @@ public final class DomainDeleteFlow implements TransactionalFlow {
ImmutableList.of(
DomainPendingActionNotificationResponse.create(
existingDomain.getDomainName(), true, trid, deletionTime)))
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
}
@@ -367,7 +369,8 @@ public final class DomainDeleteFlow implements TransactionalFlow {
return new PollMessage.OneTime.Builder()
.setRegistrarId(existingDomain.getPersistedCurrentSponsorRegistrarId())
.setEventTime(now)
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setMsg(
String.format(
"Domain %s was deleted by registry administrator with final deletion effective: %s",
@@ -63,7 +63,6 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AuthorizationErrorException;
import google.registry.flows.EppException.CommandUseErrorException;
@@ -89,6 +88,7 @@ import google.registry.model.domain.DomainCommand.CreateOrUpdate;
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.ForeignKeyedDesignatedContact;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.BaseFee;
@@ -592,14 +592,15 @@ public class DomainFlowUtils {
// where all autorenew poll messages had already been delivered (this would cause the poll
// message to be deleted), and then subsequently the transfer was canceled, rejected, or deleted
// (which would cause the poll message to be recreated here).
Key<PollMessage.Autorenew> existingAutorenewKey = domain.getAutorenewPollMessage().getOfyKey();
PollMessage.Autorenew updatedAutorenewPollMessage =
autorenewPollMessage.isPresent()
? autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build()
: newAutorenewPollMessage(domain)
.setId(existingAutorenewKey.getId())
.setId((Long) domain.getAutorenewPollMessage().getSqlKey())
.setAutorenewEndTime(newEndTime)
.setParentKey(existingAutorenewKey.getParent())
.setDomainHistoryId(
new DomainHistoryId(
domain.getRepoId(), domain.getAutorenewPollMessageHistoryId()))
.build();
// If the resultant autorenew poll message would have no poll messages to deliver, then just
@@ -22,7 +22,6 @@ import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
import static google.registry.flows.domain.DomainFlowUtils.loadForeignKeyedDesignatedContacts;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -110,7 +109,7 @@ public final class DomainInfoFlow implements Flow {
.setRepoId(domain.getRepoId())
.setCurrentSponsorClientId(domain.getCurrentSponsorRegistrarId())
.setRegistrant(
transactIfJpaTm(() -> tm().loadByKey(domain.getRegistrant())).getContactId());
tm().transact(() -> tm().loadByKey(domain.getRegistrant())).getContactId());
// If authInfo is non-null, then the caller is authorized to see the full information since we
// will have already verified the authInfo is valid.
if (registrarId.equals(domain.getCurrentSponsorRegistrarId()) || authInfo.isPresent()) {
@@ -118,7 +117,7 @@ public final class DomainInfoFlow implements Flow {
infoBuilder
.setStatusValues(domain.getStatusValues())
.setContacts(
transactIfJpaTm(() -> loadForeignKeyedDesignatedContacts(domain.getContacts())))
tm().transact(() -> loadForeignKeyedDesignatedContacts(domain.getContacts())))
.setNameservers(hostsRequest.requestDelegated() ? domain.loadNameserverHostNames() : null)
.setSubordinateHosts(
hostsRequest.requestSubordinate() ? domain.getSubordinateHosts() : null)
@@ -51,10 +51,12 @@ import org.joda.time.DateTime;
*/
public final class DomainPricingLogic {
@Inject DomainPricingCustomLogic customLogic;
private final DomainPricingCustomLogic customLogic;
@Inject
DomainPricingLogic() {}
public DomainPricingLogic(DomainPricingCustomLogic customLogic) {
this.customLogic = customLogic;
}
/**
* Returns a new create price for the pricer.
@@ -105,13 +107,12 @@ public final class DomainPricingLogic {
}
/** Returns a new renewal cost for the pricer. */
FeesAndCredits getRenewPrice(
public FeesAndCredits getRenewPrice(
Registry registry,
String domainName,
DateTime dateTime,
int years,
@Nullable Recurring recurringBillingEvent)
throws EppException {
@Nullable Recurring recurringBillingEvent) {
checkArgument(years > 0, "Number of years must be positive");
Money renewCost;
boolean isRenewCostPremiumPrice;
@@ -54,9 +54,11 @@ import google.registry.flows.custom.EntityChanges;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand.Renew;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.DomainRenewData;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
@@ -153,9 +155,15 @@ public final class DomainRenewFlow implements TransactionalFlow {
validateRegistrationPeriod(now, newExpirationTime);
Optional<FeeRenewCommandExtension> feeRenew =
eppInput.getSingleExtension(FeeRenewCommandExtension.class);
Recurring existingRecurringBillingEvent =
tm().loadByKey(existingDomain.getAutorenewBillingEvent());
FeesAndCredits feesAndCredits =
pricingLogic.getRenewPrice(
Registry.get(existingDomain.getTld()), targetId, now, years, null);
Registry.get(existingDomain.getTld()),
targetId,
now,
years,
existingRecurringBillingEvent);
validateFeeChallenge(targetId, now, feeRenew, feesAndCredits);
flowCustomLogic.afterValidation(
AfterValidationParameters.newBuilder()
@@ -173,12 +181,16 @@ public final class DomainRenewFlow implements TransactionalFlow {
BillingEvent.Recurring newAutorenewEvent =
newAutorenewBillingEvent(existingDomain)
.setEventTime(newExpirationTime)
.setRenewalPrice(existingRecurringBillingEvent.getRenewalPrice().orElse(null))
.setRenewalPriceBehavior(existingRecurringBillingEvent.getRenewalPriceBehavior())
.setParent(domainHistoryKey)
.build();
PollMessage.Autorenew newAutorenewPollMessage =
newAutorenewPollMessage(existingDomain)
.setEventTime(newExpirationTime)
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
// End the old autorenew billing event and poll message now. This may delete the poll message.
updateAutorenewRecurrenceEndTime(existingDomain, now);
@@ -189,7 +201,9 @@ public final class DomainRenewFlow implements TransactionalFlow {
.setLastEppUpdateRegistrarId(registrarId)
.setRegistrationExpirationTime(newExpirationTime)
.setAutorenewBillingEvent(newAutorenewEvent.createVKey())
.setAutorenewPollMessage(newAutorenewPollMessage.createVKey())
.setAutorenewPollMessage(
newAutorenewPollMessage.createVKey(),
newAutorenewPollMessage.getHistoryRevisionId())
.addGracePeriod(
GracePeriod.forBillingEvent(
GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent))
@@ -53,6 +53,7 @@ import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
@@ -173,7 +174,9 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
newAutorenewPollMessage(existingDomain)
.setEventTime(newExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
DomainBase newDomain =
performRestore(
@@ -245,7 +248,8 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
.setGracePeriods(null)
.setDeletePollMessage(null)
.setAutorenewBillingEvent(autorenewEvent.createVKey())
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
.setAutorenewPollMessage(
autorenewPollMessage.createVKey(), autorenewPollMessage.getHistoryRevisionId())
// Clear the autorenew end time so if it had expired but is now explicitly being restored,
// it won't immediately be deleted again.
.setAutorenewEndTime(Optional.empty())
@@ -51,6 +51,7 @@ import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.rgp.GracePeriodStatus;
@@ -176,7 +177,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.setEventTime(newExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setMsg("Domain was auto-renewed.")
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
// Construct the post-transfer domain.
DomainBase partiallyApprovedDomain =
@@ -194,7 +197,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.build())
.setRegistrationExpirationTime(newExpirationTime)
.setAutorenewBillingEvent(autorenewEvent.createVKey())
.setAutorenewPollMessage(gainingClientAutorenewPollMessage.createVKey())
.setAutorenewPollMessage(
gainingClientAutorenewPollMessage.createVKey(),
gainingClientAutorenewPollMessage.getHistoryRevisionId())
// Remove all the old grace periods and add a new one for the transfer.
.setGracePeriods(
billingEvent
@@ -63,7 +63,7 @@ import org.joda.time.DateTime;
* transfer is automatically approved. Within that window, this flow allows the losing client to
* reject the transfer request.
*
* <p>When the transfer was requested, poll messages and billing events were saved to Datastore with
* <p>When the transfer was requested, poll messages and billing events were saved to SQL with
* timestamps such that they only would become active when the transfer period passed. In this flow,
* those speculative objects are deleted.
*
@@ -208,6 +208,8 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
// Create the transfer data that represents the pending transfer.
DomainTransferData pendingTransferData =
createPendingTransferData(
domainAtTransferTime.getRepoId(),
domainHistoryKey.getId(),
new DomainTransferData.Builder()
.setTransferRequestTrid(trid)
.setTransferRequestTime(now)
@@ -26,6 +26,7 @@ import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
import google.registry.model.domain.rgp.GracePeriodStatus;
@@ -51,6 +52,8 @@ public final class DomainTransferUtils {
/** Sets up {@link TransferData} for a domain with links to entities for server approval. */
public static DomainTransferData createPendingTransferData(
String domainRepoId,
Long historyId,
DomainTransferData.Builder transferDataBuilder,
ImmutableSet<TransferServerApproveEntity> serverApproveEntities,
Period transferPeriod) {
@@ -82,7 +85,7 @@ public final class DomainTransferUtils {
.map(PollMessage.Autorenew.class::cast)
.collect(onlyElement())
.createVKey())
.setServerApproveEntities(serverApproveEntityKeys.build())
.setServerApproveEntities(domainRepoId, historyId, serverApproveEntityKeys.build())
.setTransferPeriod(transferPeriod)
.build();
}
@@ -180,7 +183,8 @@ public final class DomainTransferUtils {
transferData.getTransferStatus().isApproved(),
transferData.getTransferRequestTrid(),
now)))
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
}
@@ -197,7 +201,8 @@ public final class DomainTransferUtils {
.setResponseData(
ImmutableList.of(
createTransferResponse(targetId, transferData, extendedRegistrationExpirationTime)))
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
}
@@ -228,7 +233,8 @@ public final class DomainTransferUtils {
.setEventTime(serverApproveNewExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setMsg("Domain was auto-renewed.")
.setParentKey(domainHistoryKey)
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.build();
}
@@ -48,7 +48,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.InternetDomainName;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
@@ -67,6 +66,7 @@ import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTl
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainCommand.Update.AddRemove;
@@ -146,8 +146,6 @@ public final class DomainUpdateFlow implements TransactionalFlow {
private static final ImmutableSet<StatusValue> UPDATE_DISALLOWED_STATUSES =
ImmutableSet.of(StatusValue.PENDING_DELETE, StatusValue.SERVER_UPDATE_PROHIBITED);
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject ResourceCommand resourceCommand;
@Inject ExtensionManager extensionManager;
@Inject EppInput eppInput;
@@ -246,6 +244,13 @@ public final class DomainUpdateFlow implements TransactionalFlow {
validateRegistrantIsntBeingRemoved(change);
Optional<SecDnsUpdateExtension> secDnsUpdate =
eppInput.getSingleExtension(SecDnsUpdateExtension.class);
// We have to verify no duplicate contacts _before_ constructing the domain because it is
// illegal to construct a domain with duplicate contacts.
Sets.SetView<DesignatedContact> newContacts =
Sets.union(Sets.difference(domain.getContacts(), remove.getContacts()), add.getContacts());
validateNoDuplicateContacts(newContacts);
DomainBase.Builder domainBuilder =
domain
.asBuilder()
@@ -266,8 +271,8 @@ public final class DomainUpdateFlow implements TransactionalFlow {
.removeStatusValues(remove.getStatusValues())
.addNameservers(add.getNameservers().stream().collect(toImmutableSet()))
.removeNameservers(remove.getNameservers().stream().collect(toImmutableSet()))
.addContacts(add.getContacts())
.removeContacts(remove.getContacts())
.addContacts(add.getContacts())
.setRegistrant(firstNonNull(change.getRegistrant(), domain.getRegistrant()))
.setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo()));
Optional<DomainUpdateSuperuserExtension> superuserExt =
@@ -289,7 +294,6 @@ public final class DomainUpdateFlow implements TransactionalFlow {
}
private void validateNewState(DomainBase newDomain) throws EppException {
validateNoDuplicateContacts(newDomain.getContacts());
validateRequiredContactsPresent(newDomain.getRegistrant(), newDomain.getContacts());
validateDsData(newDomain.getDsData());
validateNameserversCountForTld(
@@ -363,7 +367,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
return Optional.ofNullable(
new PollMessage.OneTime.Builder()
.setParent(historyEntry)
.setHistoryEntry(historyEntry)
.setEventTime(now)
.setRegistrarId(existingDomain.getCurrentSponsorRegistrarId())
.setMsg(msg)
@@ -16,7 +16,6 @@ package google.registry.flows.domain.token;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@@ -161,7 +160,7 @@ public class AllocationTokenFlowUtils {
throw new InvalidAllocationTokenException();
}
Optional<AllocationToken> maybeTokenEntity =
transactIfJpaTm(() -> tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token)));
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token)));
if (!maybeTokenEntity.isPresent()) {
throw new InvalidAllocationTokenException();
}
@@ -72,8 +72,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
StatusValue.PENDING_DELETE,
StatusValue.SERVER_DELETE_PROHIBITED);
private static final DnsQueue dnsQueue = DnsQueue.create();
@Inject DnsQueue dnsQueue;
@Inject ExtensionManager extensionManager;
@Inject @RegistrarId String registrarId;
@Inject @TargetId String targetId;
@@ -19,7 +19,6 @@ import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.model.EppResourceUtils.isLinked;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import com.google.common.collect.ImmutableSet;
import google.registry.flows.EppException;
@@ -78,8 +77,8 @@ public final class HostInfoFlow implements Flow {
// there is no superordinate domain, the host's own values for these fields will be correct.
if (host.isSubordinate()) {
DomainBase superordinateDomain =
transactIfJpaTm(
() -> tm().loadByKey(host.getSuperordinateDomain()).cloneProjectedAtTime(now));
tm().transact(
() -> tm().loadByKey(host.getSuperordinateDomain()).cloneProjectedAtTime(now));
hostInfoDataBuilder
.setCurrentSponsorClientId(superordinateDomain.getCurrentSponsorRegistrarId())
.setLastTransferTime(host.computeLastTransferTime(superordinateDomain));

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