mirror of
https://github.com/google/nomulus
synced 2026-06-09 16:33:02 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e53594a626 | |||
| e6577e3f23 | |||
| c9da36be9f | |||
| 2ccae00dae | |||
| 00c8b6a76d | |||
| 09dca28122 | |||
| b412bdef9f | |||
| 62e5de8a3a | |||
| fa9b784c5c | |||
| e2bd72a74e | |||
| 28d41488b1 | |||
| 1107b9f2e3 | |||
| 9624b483d4 | |||
| 365937f22d | |||
| d5db6c16bc | |||
| c1ad06afd1 | |||
| b24670f33a | |||
| 1253fa479a | |||
| 5f0dd24906 | |||
| e25885e25f | |||
| cbdf4704ba | |||
| 207c7e7ca8 | |||
| b3a0eb6bd8 | |||
| c602aa6e67 | |||
| c6008b65a0 | |||
| eded6813ab | |||
| bbe5c058fe | |||
| 4b0cf576f8 | |||
| 045de3889b | |||
| 68fc4cd022 | |||
| ebe55146c3 | |||
| 807ddf46b9 | |||
| ff8f86090d | |||
| 5822f53e14 | |||
| d04b3299aa | |||
| ceade7f954 | |||
| 1fcf63facd |
+16
-2
@@ -319,6 +319,7 @@ dependencies {
|
||||
testCompile deps['com.google.appengine:appengine-testing']
|
||||
testCompile deps['com.google.guava:guava-testlib']
|
||||
testCompile deps['com.google.monitoring-client:contrib']
|
||||
testCompile deps['com.google.protobuf:protobuf-java-util']
|
||||
testCompile deps['com.google.truth:truth']
|
||||
testCompile deps['com.google.truth.extensions:truth-java8-extension']
|
||||
testCompile deps['org.checkerframework:checker-qual']
|
||||
@@ -676,9 +677,9 @@ Optional<List<String>> getToolArgsList() {
|
||||
|
||||
// To run the nomulus tools with these command line tokens:
|
||||
// "--foo", "bar baz", "--qux=quz"
|
||||
// gradle registryTool --args="--foo 'bar baz' --qux=quz"
|
||||
// gradle core:registryTool --args="--foo 'bar baz' --qux=quz"
|
||||
// or:
|
||||
// gradle registryTool --PtoolArgs="--foo|bar baz|--qux=quz"
|
||||
// gradle core:registryTool -PtoolArgs="--foo|bar baz|--qux=quz"
|
||||
// Note that the delimiting pipe can be backslash escaped if it is part of a
|
||||
// parameter.
|
||||
ext.createToolTask = {
|
||||
@@ -708,6 +709,9 @@ createToolTask(
|
||||
createToolTask(
|
||||
'validateSqlPipeline', 'google.registry.beam.comparedb.ValidateSqlPipeline')
|
||||
|
||||
createToolTask(
|
||||
'validateDatastorePipeline', 'google.registry.beam.comparedb.ValidateDatastorePipeline')
|
||||
|
||||
|
||||
createToolTask(
|
||||
'jpaDemoPipeline', 'google.registry.beam.common.JpaDemoPipeline')
|
||||
@@ -793,6 +797,16 @@ if (environment == 'alpha') {
|
||||
mainClass: 'google.registry.beam.rde.RdePipeline',
|
||||
metaData : 'google/registry/beam/rde_pipeline_metadata.json'
|
||||
],
|
||||
validateDatastore :
|
||||
[
|
||||
mainClass: 'google.registry.beam.comparedb.ValidateDatastorePipeline',
|
||||
metaData: 'google/registry/beam/validate_datastore_pipeline_metadata.json'
|
||||
],
|
||||
validateSql :
|
||||
[
|
||||
mainClass: 'google.registry.beam.comparedb.ValidateSqlPipeline',
|
||||
metaData: 'google/registry/beam/validate_sql_pipeline_metadata.json'
|
||||
],
|
||||
]
|
||||
project.tasks.create("stageBeamPipelines") {
|
||||
doLast {
|
||||
|
||||
@@ -17,7 +17,7 @@ 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.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
@@ -67,7 +67,8 @@ public final class CommitLogCheckpointAction implements Runnable {
|
||||
final CommitLogCheckpoint checkpoint = strategy.computeCheckpoint();
|
||||
logger.atInfo().log(
|
||||
"Generated candidate checkpoint for time: %s", checkpoint.getCheckpointTime());
|
||||
tm().transact(
|
||||
ofyTm()
|
||||
.transact(
|
||||
() -> {
|
||||
DateTime lastWrittenTime = CommitLogCheckpointRoot.loadRoot().getLastWrittenTime();
|
||||
if (isBeforeOrAt(checkpoint.getCheckpointTime(), lastWrittenTime)) {
|
||||
|
||||
@@ -18,7 +18,7 @@ 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.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
@@ -288,7 +288,8 @@ public final class DeleteOldCommitLogsAction implements Runnable {
|
||||
}
|
||||
|
||||
DeletionResult deletionResult =
|
||||
tm().transactNew(
|
||||
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
|
||||
|
||||
@@ -110,7 +110,7 @@ public final class AsyncTaskEnqueuer {
|
||||
.method(Method.POST)
|
||||
.header("Host", backendHostname)
|
||||
.countdownMillis(etaDuration.getMillis())
|
||||
.param(PARAM_RESOURCE_KEY, entityKey.getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, entityKey.stringify())
|
||||
.param(PARAM_REQUESTED_TIME, now.toString());
|
||||
if (whenToResave.size() > 1) {
|
||||
task.param(PARAM_RESAVE_TIMES, Joiner.on(',').join(whenToResave.tailSet(firstResave, false)));
|
||||
@@ -131,7 +131,7 @@ public final class AsyncTaskEnqueuer {
|
||||
TaskOptions task =
|
||||
TaskOptions.Builder.withMethod(Method.PULL)
|
||||
.countdownMillis(asyncDeleteDelay.getMillis())
|
||||
.param(PARAM_RESOURCE_KEY, resourceToDelete.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, resourceToDelete.createVKey().stringify())
|
||||
.param(PARAM_REQUESTING_CLIENT_ID, requestingRegistrarId)
|
||||
.param(PARAM_SERVER_TRANSACTION_ID, trid.getServerTransactionId())
|
||||
.param(PARAM_IS_SUPERUSER, Boolean.toString(isSuperuser))
|
||||
@@ -148,7 +148,7 @@ public final class AsyncTaskEnqueuer {
|
||||
addTaskToQueueWithRetry(
|
||||
asyncDnsRefreshPullQueue,
|
||||
TaskOptions.Builder.withMethod(Method.PULL)
|
||||
.param(PARAM_HOST_KEY, hostKey.getOfyKey().getString())
|
||||
.param(PARAM_HOST_KEY, hostKey.stringify())
|
||||
.param(PARAM_REQUESTED_TIME, now.toString()));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.persistence.PersistenceModule.BeamBulkQueryJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.BeamJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.BeamReadOnlyReplicaJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
@@ -59,6 +60,13 @@ public interface RegistryPipelineComponent {
|
||||
@BeamBulkQueryJpaTm
|
||||
Lazy<JpaTransactionManager> getBulkQueryJpaTransactionManager();
|
||||
|
||||
/**
|
||||
* A {@link JpaTransactionManager} that uses the Postgres read-only replica if configured (uses
|
||||
* the standard DB otherwise).
|
||||
*/
|
||||
@BeamReadOnlyReplicaJpaTm
|
||||
Lazy<JpaTransactionManager> getReadOnlyReplicaJpaTransactionManager();
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
|
||||
|
||||
@@ -56,6 +56,10 @@ public class RegistryPipelineWorkerInitializer implements JvmInitializer {
|
||||
case BULK_QUERY:
|
||||
transactionManagerLazy = registryPipelineComponent.getBulkQueryJpaTransactionManager();
|
||||
break;
|
||||
case READ_ONLY_REPLICA:
|
||||
transactionManagerLazy =
|
||||
registryPipelineComponent.getReadOnlyReplicaJpaTransactionManager();
|
||||
break;
|
||||
case REGULAR:
|
||||
default:
|
||||
transactionManagerLazy = registryPipelineComponent.getJpaTransactionManager();
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.backup.VersionedEntity;
|
||||
import google.registry.beam.initsql.Transforms;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.common.Cursor;
|
||||
@@ -42,6 +43,7 @@ import google.registry.model.tld.Registry;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
import org.apache.beam.sdk.transforms.DoFn;
|
||||
import org.apache.beam.sdk.transforms.ParDo;
|
||||
@@ -93,7 +95,8 @@ public final class DatastoreSnapshots {
|
||||
String commitLogDir,
|
||||
DateTime commitLogFromTime,
|
||||
DateTime commitLogToTime,
|
||||
Set<Class<?>> kinds) {
|
||||
Set<Class<?>> kinds,
|
||||
Optional<DateTime> compareStartTime) {
|
||||
PCollectionTuple snapshot =
|
||||
pipeline.apply(
|
||||
"Load Datastore snapshot.",
|
||||
@@ -112,11 +115,11 @@ public final class DatastoreSnapshots {
|
||||
perTypeSnapshots =
|
||||
perTypeSnapshots.and(
|
||||
createSqlEntityTupleTag((Class<? extends SqlEntity>) kind),
|
||||
datastoreEntityToPojo(perKindSnapshot, kind.getSimpleName()));
|
||||
datastoreEntityToPojo(perKindSnapshot, kind.getSimpleName(), compareStartTime));
|
||||
continue;
|
||||
}
|
||||
Verify.verify(kind == HistoryEntry.class, "Unexpected Non-SqlEntity class: %s", kind);
|
||||
PCollectionTuple historyEntriesByType = splitHistoryEntry(perKindSnapshot);
|
||||
PCollectionTuple historyEntriesByType = splitHistoryEntry(perKindSnapshot, compareStartTime);
|
||||
for (Map.Entry<TupleTag<?>, PCollection<?>> entry :
|
||||
historyEntriesByType.getAll().entrySet()) {
|
||||
perTypeSnapshots = perTypeSnapshots.and(entry.getKey().getId(), entry.getValue());
|
||||
@@ -129,7 +132,9 @@ public final class DatastoreSnapshots {
|
||||
* Splits a {@link PCollection} of {@link HistoryEntry HistoryEntries} into three collections of
|
||||
* its child entities by type.
|
||||
*/
|
||||
static PCollectionTuple splitHistoryEntry(PCollection<VersionedEntity> historyEntries) {
|
||||
static PCollectionTuple splitHistoryEntry(
|
||||
PCollection<VersionedEntity> historyEntries, Optional<DateTime> compareStartTime) {
|
||||
DateTime nullableStartTime = compareStartTime.orElse(null);
|
||||
return historyEntries.apply(
|
||||
"Split HistoryEntry by Resource Type",
|
||||
ParDo.of(
|
||||
@@ -138,6 +143,7 @@ public final class DatastoreSnapshots {
|
||||
public void processElement(
|
||||
@Element VersionedEntity historyEntry, MultiOutputReceiver out) {
|
||||
Optional.ofNullable(Transforms.convertVersionedEntityToSqlEntity(historyEntry))
|
||||
.filter(e -> isEntityIncludedForComparison(e, nullableStartTime))
|
||||
.ifPresent(
|
||||
sqlEntity ->
|
||||
out.get(createSqlEntityTupleTag(sqlEntity.getClass()))
|
||||
@@ -155,7 +161,8 @@ public final class DatastoreSnapshots {
|
||||
* objects.
|
||||
*/
|
||||
static PCollection<SqlEntity> datastoreEntityToPojo(
|
||||
PCollection<VersionedEntity> entities, String desc) {
|
||||
PCollection<VersionedEntity> entities, String desc, Optional<DateTime> compareStartTime) {
|
||||
DateTime nullableStartTime = compareStartTime.orElse(null);
|
||||
return entities.apply(
|
||||
"Datastore Entity to Pojo " + desc,
|
||||
ParDo.of(
|
||||
@@ -164,8 +171,23 @@ public final class DatastoreSnapshots {
|
||||
public void processElement(
|
||||
@Element VersionedEntity entity, OutputReceiver<SqlEntity> out) {
|
||||
Optional.ofNullable(Transforms.convertVersionedEntityToSqlEntity(entity))
|
||||
.filter(e -> isEntityIncludedForComparison(e, nullableStartTime))
|
||||
.ifPresent(out::output);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
static boolean isEntityIncludedForComparison(
|
||||
SqlEntity entity, @Nullable DateTime compareStartTime) {
|
||||
if (compareStartTime == null) {
|
||||
return true;
|
||||
}
|
||||
if (entity instanceof HistoryEntry) {
|
||||
return compareStartTime.isBefore(((HistoryEntry) entity).getModificationTime());
|
||||
}
|
||||
if (entity instanceof EppResource) {
|
||||
return compareStartTime.isBefore(((EppResource) entity).getUpdateTimestamp().getTimestamp());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-9
@@ -53,11 +53,11 @@ public class LatestDatastoreSnapshotFinder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds information of the most recent Datastore snapshot, including the GCS folder of the
|
||||
* exported data files and the start and stop times of the export. The folder of the CommitLogs is
|
||||
* also included in the return.
|
||||
* Finds information of the most recent Datastore snapshot that ends strictly before {@code
|
||||
* exportEndTimeUpperBound}, including the GCS folder of the exported data files and the start and
|
||||
* stop times of the export. The folder of the CommitLogs is also included in the return.
|
||||
*/
|
||||
public DatastoreSnapshotInfo getSnapshotInfo() {
|
||||
public DatastoreSnapshotInfo getSnapshotInfo(Instant exportEndTimeUpperBound) {
|
||||
String bucketName = RegistryConfig.getDatastoreBackupsBucket().substring("gs://".length());
|
||||
/**
|
||||
* Find the bucket-relative path to the overall metadata file of the last Datastore export.
|
||||
@@ -65,7 +65,8 @@ public class LatestDatastoreSnapshotFinder {
|
||||
* return value is like
|
||||
* "2021-11-19T06:00:00_76493/2021-11-19T06:00:00_76493.overall_export_metadata".
|
||||
*/
|
||||
Optional<String> metaFilePathOptional = findMostRecentExportMetadataFile(bucketName, 2);
|
||||
Optional<String> metaFilePathOptional =
|
||||
findNewestExportMetadataFileBeforeTime(bucketName, exportEndTimeUpperBound, 2);
|
||||
if (!metaFilePathOptional.isPresent()) {
|
||||
throw new NoSuchElementException("No exports found over the past 2 days.");
|
||||
}
|
||||
@@ -85,8 +86,9 @@ public class LatestDatastoreSnapshotFinder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the bucket-relative path of the overall export metadata file, in the given bucket,
|
||||
* searching back up to {@code lookBackDays} days, including today.
|
||||
* Finds the latest Datastore export that ends strictly before {@code endTimeUpperBound} and
|
||||
* returns the bucket-relative path of the overall export metadata file, in the given bucket. The
|
||||
* search goes back for up to {@code lookBackDays} days in time, including today.
|
||||
*
|
||||
* <p>The overall export metadata file is the last file created during a Datastore export. All
|
||||
* data has been exported by the creation time of this file. The name of this file, like that of
|
||||
@@ -95,7 +97,8 @@ public class LatestDatastoreSnapshotFinder {
|
||||
* <p>An example return value: {@code
|
||||
* 2021-11-19T06:00:00_76493/2021-11-19T06:00:00_76493.overall_export_metadata}.
|
||||
*/
|
||||
private Optional<String> findMostRecentExportMetadataFile(String bucketName, int lookBackDays) {
|
||||
private Optional<String> findNewestExportMetadataFileBeforeTime(
|
||||
String bucketName, Instant endTimeUpperBound, int lookBackDays) {
|
||||
DateTime today = clock.nowUtc();
|
||||
for (int day = 0; day < lookBackDays; day++) {
|
||||
String dateString = today.minusDays(day).toString("yyyy-MM-dd");
|
||||
@@ -107,7 +110,11 @@ public class LatestDatastoreSnapshotFinder {
|
||||
.sorted(Comparator.<String>naturalOrder().reversed())
|
||||
.findFirst();
|
||||
if (metaFilePath.isPresent()) {
|
||||
return metaFilePath;
|
||||
BlobInfo blobInfo = gcsUtils.getBlobInfo(BlobId.of(bucketName, metaFilePath.get()));
|
||||
Instant exportEndTime = new Instant(blobInfo.getCreateTime());
|
||||
if (exportEndTime.isBefore(endTimeUpperBound)) {
|
||||
return metaFilePath;
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
||||
@@ -14,15 +14,22 @@
|
||||
|
||||
package google.registry.beam.comparedb;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.beam.comparedb.ValidateSqlUtils.createSqlEntityTupleTag;
|
||||
import static google.registry.beam.comparedb.ValidateSqlUtils.getMedianIdForHistoryTable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.beam.common.RegistryJpaIO;
|
||||
import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.bulkquery.BulkQueryEntities;
|
||||
@@ -50,8 +57,10 @@ import google.registry.model.replay.SqlEntity;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Entity;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
import org.apache.beam.sdk.transforms.DoFn;
|
||||
import org.apache.beam.sdk.transforms.Flatten;
|
||||
@@ -65,6 +74,7 @@ import org.apache.beam.sdk.values.PCollectionList;
|
||||
import org.apache.beam.sdk.values.PCollectionTuple;
|
||||
import org.apache.beam.sdk.values.TypeDescriptor;
|
||||
import org.apache.beam.sdk.values.TypeDescriptors;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Utilities for loading SQL snapshots.
|
||||
@@ -113,28 +123,48 @@ public final class SqlSnapshots {
|
||||
public static PCollectionTuple loadCloudSqlSnapshotByType(
|
||||
Pipeline pipeline,
|
||||
ImmutableSet<Class<? extends SqlEntity>> sqlEntityTypes,
|
||||
Optional<String> snapshotId) {
|
||||
Optional<String> snapshotId,
|
||||
Optional<DateTime> compareStartTime) {
|
||||
PCollectionTuple perTypeSnapshots = PCollectionTuple.empty(pipeline);
|
||||
for (Class<? extends SqlEntity> clazz : sqlEntityTypes) {
|
||||
if (clazz == DomainBase.class) {
|
||||
perTypeSnapshots =
|
||||
perTypeSnapshots.and(
|
||||
createSqlEntityTupleTag(DomainBase.class),
|
||||
loadAndAssembleDomainBase(pipeline, snapshotId));
|
||||
loadAndAssembleDomainBase(pipeline, snapshotId, compareStartTime));
|
||||
continue;
|
||||
}
|
||||
if (clazz == DomainHistory.class) {
|
||||
perTypeSnapshots =
|
||||
perTypeSnapshots.and(
|
||||
createSqlEntityTupleTag(DomainHistory.class),
|
||||
loadAndAssembleDomainHistory(pipeline, snapshotId));
|
||||
loadAndAssembleDomainHistory(pipeline, snapshotId, compareStartTime));
|
||||
continue;
|
||||
}
|
||||
if (clazz == ContactHistory.class) {
|
||||
perTypeSnapshots =
|
||||
perTypeSnapshots.and(
|
||||
createSqlEntityTupleTag(ContactHistory.class),
|
||||
loadContactHistory(pipeline, snapshotId));
|
||||
loadContactHistory(pipeline, snapshotId, compareStartTime));
|
||||
continue;
|
||||
}
|
||||
if (clazz == HostHistory.class) {
|
||||
perTypeSnapshots =
|
||||
perTypeSnapshots.and(
|
||||
createSqlEntityTupleTag(HostHistory.class),
|
||||
loadHostHistory(
|
||||
pipeline, snapshotId, compareStartTime.orElse(DateTimeUtils.START_OF_TIME)));
|
||||
continue;
|
||||
}
|
||||
if (EppResource.class.isAssignableFrom(clazz) && compareStartTime.isPresent()) {
|
||||
perTypeSnapshots =
|
||||
perTypeSnapshots.and(
|
||||
createSqlEntityTupleTag(clazz),
|
||||
pipeline.apply(
|
||||
"SQL Load " + clazz.getSimpleName(),
|
||||
buildEppResourceQueryWithTimeFilter(
|
||||
clazz, SqlEntity.class, snapshotId, compareStartTime.get())
|
||||
.withSnapshot(snapshotId.orElse(null))));
|
||||
continue;
|
||||
}
|
||||
perTypeSnapshots =
|
||||
@@ -155,20 +185,33 @@ public final class SqlSnapshots {
|
||||
* @see BulkQueryEntities
|
||||
*/
|
||||
public static PCollection<SqlEntity> loadAndAssembleDomainBase(
|
||||
Pipeline pipeline, Optional<String> snapshotId) {
|
||||
Pipeline pipeline, Optional<String> snapshotId, Optional<DateTime> compareStartTime) {
|
||||
PCollection<KV<String, Serializable>> baseObjects =
|
||||
readAllAndAssignKey(pipeline, DomainBaseLite.class, DomainBaseLite::getRepoId, snapshotId);
|
||||
readAllAndAssignKey(
|
||||
pipeline,
|
||||
DomainBaseLite.class,
|
||||
DomainBaseLite::getRepoId,
|
||||
snapshotId,
|
||||
compareStartTime);
|
||||
PCollection<KV<String, Serializable>> gracePeriods =
|
||||
readAllAndAssignKey(pipeline, GracePeriod.class, GracePeriod::getDomainRepoId, snapshotId);
|
||||
readAllAndAssignKey(
|
||||
pipeline,
|
||||
GracePeriod.class,
|
||||
GracePeriod::getDomainRepoId,
|
||||
snapshotId,
|
||||
compareStartTime);
|
||||
PCollection<KV<String, Serializable>> delegationSigners =
|
||||
readAllAndAssignKey(
|
||||
pipeline,
|
||||
DelegationSignerData.class,
|
||||
DelegationSignerData::getDomainRepoId,
|
||||
snapshotId);
|
||||
snapshotId,
|
||||
compareStartTime);
|
||||
PCollection<KV<String, Serializable>> domainHosts =
|
||||
readAllAndAssignKey(pipeline, DomainHost.class, DomainHost::getDomainRepoId, snapshotId);
|
||||
readAllAndAssignKey(
|
||||
pipeline, DomainHost.class, DomainHost::getDomainRepoId, snapshotId, compareStartTime);
|
||||
|
||||
DateTime nullableCompareStartTime = compareStartTime.orElse(null);
|
||||
return PCollectionList.of(
|
||||
ImmutableList.of(baseObjects, gracePeriods, delegationSigners, domainHosts))
|
||||
.apply("SQL Merge DomainBase parts", Flatten.pCollections())
|
||||
@@ -184,6 +227,14 @@ public final class SqlSnapshots {
|
||||
TypedClassifier partsByType = new TypedClassifier(kv.getValue());
|
||||
ImmutableSet<DomainBaseLite> baseObjects =
|
||||
partsByType.getAllOf(DomainBaseLite.class);
|
||||
if (nullableCompareStartTime != null) {
|
||||
Verify.verify(
|
||||
baseObjects.size() <= 1,
|
||||
"Found duplicate DomainBaseLite object per repoId: " + kv.getKey());
|
||||
if (baseObjects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Verify.verify(
|
||||
baseObjects.size() == 1,
|
||||
"Expecting one DomainBaseLite object per repoId: " + kv.getKey());
|
||||
@@ -205,16 +256,16 @@ public final class SqlSnapshots {
|
||||
* <p>This method uses two queries to load data in parallel. This is a performance optimization
|
||||
* specifically for the production database.
|
||||
*/
|
||||
static PCollection<SqlEntity> loadContactHistory(Pipeline pipeline, Optional<String> snapshotId) {
|
||||
long medianId =
|
||||
getMedianIdForHistoryTable("ContactHistory")
|
||||
.orElseThrow(
|
||||
() -> new IllegalStateException("Not a valid database: no ContactHistory."));
|
||||
static PCollection<SqlEntity> loadContactHistory(
|
||||
Pipeline pipeline, Optional<String> snapshotId, Optional<DateTime> compareStartTime) {
|
||||
PartitionedQuery partitionedQuery =
|
||||
buildPartitonedHistoryQuery(ContactHistory.class, compareStartTime);
|
||||
PCollection<SqlEntity> part1 =
|
||||
pipeline.apply(
|
||||
"SQL Load ContactHistory first half",
|
||||
RegistryJpaIO.read(
|
||||
String.format("select c from ContactHistory c where id <= %s", medianId),
|
||||
partitionedQuery.firstHalfQuery(),
|
||||
partitionedQuery.parameters(),
|
||||
false,
|
||||
SqlEntity.class::cast)
|
||||
.withSnapshot(snapshotId.orElse(null)));
|
||||
@@ -222,7 +273,8 @@ public final class SqlSnapshots {
|
||||
pipeline.apply(
|
||||
"SQL Load ContactHistory second half",
|
||||
RegistryJpaIO.read(
|
||||
String.format("select c from ContactHistory c where id > %s", medianId),
|
||||
partitionedQuery.secondHalfQuery(),
|
||||
partitionedQuery.parameters(),
|
||||
false,
|
||||
SqlEntity.class::cast)
|
||||
.withSnapshot(snapshotId.orElse(null)));
|
||||
@@ -231,6 +283,19 @@ public final class SqlSnapshots {
|
||||
.apply("Combine ContactHistory parts", Flatten.pCollections());
|
||||
}
|
||||
|
||||
/** Loads all {@link HostHistory} entities from the database. */
|
||||
static PCollection<SqlEntity> loadHostHistory(
|
||||
Pipeline pipeline, Optional<String> snapshotId, DateTime compareStartTime) {
|
||||
return pipeline.apply(
|
||||
"SQL Load HostHistory",
|
||||
RegistryJpaIO.read(
|
||||
"select c from HostHistory c where :compareStartTime < modificationTime",
|
||||
ImmutableMap.of("compareStartTime", compareStartTime),
|
||||
false,
|
||||
SqlEntity.class::cast)
|
||||
.withSnapshot(snapshotId.orElse(null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk-loads all parts of {@link DomainHistory} and assembles them in the pipeline.
|
||||
*
|
||||
@@ -240,16 +305,15 @@ public final class SqlSnapshots {
|
||||
* @see BulkQueryEntities
|
||||
*/
|
||||
static PCollection<SqlEntity> loadAndAssembleDomainHistory(
|
||||
Pipeline pipeline, Optional<String> snapshotId) {
|
||||
long medianId =
|
||||
getMedianIdForHistoryTable("DomainHistory")
|
||||
.orElseThrow(
|
||||
() -> new IllegalStateException("Not a valid database: no DomainHistory."));
|
||||
Pipeline pipeline, Optional<String> snapshotId, Optional<DateTime> compareStartTime) {
|
||||
PartitionedQuery partitionedQuery =
|
||||
buildPartitonedHistoryQuery(DomainHistoryLite.class, compareStartTime);
|
||||
PCollection<KV<String, Serializable>> baseObjectsPart1 =
|
||||
queryAndAssignKey(
|
||||
pipeline,
|
||||
"first half",
|
||||
String.format("select c from DomainHistory c where id <= %s", medianId),
|
||||
partitionedQuery.firstHalfQuery(),
|
||||
partitionedQuery.parameters(),
|
||||
DomainHistoryLite.class,
|
||||
compose(DomainHistoryLite::getDomainHistoryId, DomainHistoryId::toString),
|
||||
snapshotId);
|
||||
@@ -257,7 +321,8 @@ public final class SqlSnapshots {
|
||||
queryAndAssignKey(
|
||||
pipeline,
|
||||
"second half",
|
||||
String.format("select c from DomainHistory c where id > %s", medianId),
|
||||
partitionedQuery.secondHalfQuery(),
|
||||
partitionedQuery.parameters(),
|
||||
DomainHistoryLite.class,
|
||||
compose(DomainHistoryLite::getDomainHistoryId, DomainHistoryId::toString),
|
||||
snapshotId);
|
||||
@@ -266,26 +331,31 @@ public final class SqlSnapshots {
|
||||
pipeline,
|
||||
GracePeriodHistory.class,
|
||||
compose(GracePeriodHistory::getDomainHistoryId, DomainHistoryId::toString),
|
||||
snapshotId);
|
||||
snapshotId,
|
||||
compareStartTime);
|
||||
PCollection<KV<String, Serializable>> delegationSigners =
|
||||
readAllAndAssignKey(
|
||||
pipeline,
|
||||
DomainDsDataHistory.class,
|
||||
compose(DomainDsDataHistory::getDomainHistoryId, DomainHistoryId::toString),
|
||||
snapshotId);
|
||||
snapshotId,
|
||||
compareStartTime);
|
||||
PCollection<KV<String, Serializable>> domainHosts =
|
||||
readAllAndAssignKey(
|
||||
pipeline,
|
||||
DomainHistoryHost.class,
|
||||
compose(DomainHistoryHost::getDomainHistoryId, DomainHistoryId::toString),
|
||||
snapshotId);
|
||||
snapshotId,
|
||||
compareStartTime);
|
||||
PCollection<KV<String, Serializable>> transactionRecords =
|
||||
readAllAndAssignKey(
|
||||
pipeline,
|
||||
DomainTransactionRecord.class,
|
||||
compose(DomainTransactionRecord::getDomainHistoryId, DomainHistoryId::toString),
|
||||
snapshotId);
|
||||
snapshotId,
|
||||
compareStartTime);
|
||||
|
||||
DateTime nullableCompareStartTime = compareStartTime.orElse(null);
|
||||
return PCollectionList.of(
|
||||
ImmutableList.of(
|
||||
baseObjectsPart1,
|
||||
@@ -307,6 +377,15 @@ public final class SqlSnapshots {
|
||||
TypedClassifier partsByType = new TypedClassifier(kv.getValue());
|
||||
ImmutableSet<DomainHistoryLite> baseObjects =
|
||||
partsByType.getAllOf(DomainHistoryLite.class);
|
||||
if (nullableCompareStartTime != null) {
|
||||
Verify.verify(
|
||||
baseObjects.size() <= 1,
|
||||
"Found duplicate DomainHistoryLite object per domainHistoryId: "
|
||||
+ kv.getKey());
|
||||
if (baseObjects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Verify.verify(
|
||||
baseObjects.size() == 1,
|
||||
"Expecting one DomainHistoryLite object per domainHistoryId: "
|
||||
@@ -328,12 +407,19 @@ public final class SqlSnapshots {
|
||||
Pipeline pipeline,
|
||||
Class<R> type,
|
||||
SerializableFunction<R, String> keyFunction,
|
||||
Optional<String> snapshotId) {
|
||||
Optional<String> snapshotId,
|
||||
Optional<DateTime> compareStartTime) {
|
||||
Read<R, R> queryObject;
|
||||
if (compareStartTime.isPresent() && EppResource.class.isAssignableFrom(type)) {
|
||||
queryObject =
|
||||
buildEppResourceQueryWithTimeFilter(type, type, snapshotId, compareStartTime.get());
|
||||
} else {
|
||||
queryObject =
|
||||
RegistryJpaIO.read(() -> CriteriaQueryBuilder.create(type).build())
|
||||
.withSnapshot(snapshotId.orElse(null));
|
||||
}
|
||||
return pipeline
|
||||
.apply(
|
||||
"SQL Load " + type.getSimpleName(),
|
||||
RegistryJpaIO.read(() -> CriteriaQueryBuilder.create(type).build())
|
||||
.withSnapshot(snapshotId.orElse(null)))
|
||||
.apply("SQL Load " + type.getSimpleName(), queryObject)
|
||||
.apply(
|
||||
"Assign Key to " + type.getSimpleName(),
|
||||
MapElements.into(
|
||||
@@ -346,13 +432,15 @@ public final class SqlSnapshots {
|
||||
Pipeline pipeline,
|
||||
String diffrentiator,
|
||||
String jplQuery,
|
||||
ImmutableMap<String, Object> queryParameters,
|
||||
Class<R> type,
|
||||
SerializableFunction<R, String> keyFunction,
|
||||
Optional<String> snapshotId) {
|
||||
return pipeline
|
||||
.apply(
|
||||
"SQL Load " + type.getSimpleName() + " " + diffrentiator,
|
||||
RegistryJpaIO.read(jplQuery, false, type::cast).withSnapshot(snapshotId.orElse(null)))
|
||||
RegistryJpaIO.read(jplQuery, queryParameters, false, type::cast)
|
||||
.withSnapshot(snapshotId.orElse(null)))
|
||||
.apply(
|
||||
"Assign Key to " + type.getSimpleName() + " " + diffrentiator,
|
||||
MapElements.into(
|
||||
@@ -367,6 +455,71 @@ public final class SqlSnapshots {
|
||||
return r -> f2.apply(f1.apply(r));
|
||||
}
|
||||
|
||||
static <R, T> Read<R, T> buildEppResourceQueryWithTimeFilter(
|
||||
Class<R> entityType,
|
||||
Class<T> castOutputAsType,
|
||||
Optional<String> snapshotId,
|
||||
DateTime compareStartTime) {
|
||||
String tableName = getJpaEntityName(entityType);
|
||||
String jpql =
|
||||
String.format("select c from %s c where :compareStartTime < updateTimestamp", tableName);
|
||||
return RegistryJpaIO.read(
|
||||
jpql,
|
||||
ImmutableMap.of("compareStartTime", UpdateAutoTimestamp.create(compareStartTime)),
|
||||
false,
|
||||
(R x) -> castOutputAsType.cast(x))
|
||||
.withSnapshot(snapshotId.orElse(null));
|
||||
}
|
||||
|
||||
static PartitionedQuery buildPartitonedHistoryQuery(
|
||||
Class<?> entityType, Optional<DateTime> compareStartTime) {
|
||||
String tableName = getJpaEntityName(entityType);
|
||||
Verify.verify(
|
||||
!Strings.isNullOrEmpty(tableName), "Invalid entity type %s", entityType.getSimpleName());
|
||||
long medianId =
|
||||
getMedianIdForHistoryTable(tableName)
|
||||
.orElseThrow(() -> new IllegalStateException("Not a valid database: no " + tableName));
|
||||
String firstHalfQuery = String.format("select c from %s c where id <= :historyId", tableName);
|
||||
String secondHalfQuery = String.format("select c from %s c where id > :historyId", tableName);
|
||||
if (compareStartTime.isPresent()) {
|
||||
String timeFilter = " and :compareStartTime < modificationTime";
|
||||
firstHalfQuery += timeFilter;
|
||||
secondHalfQuery += timeFilter;
|
||||
return PartitionedQuery.createPartitionedQuery(
|
||||
firstHalfQuery,
|
||||
secondHalfQuery,
|
||||
ImmutableMap.of("historyId", medianId, "compareStartTime", compareStartTime.get()));
|
||||
} else {
|
||||
return PartitionedQuery.createPartitionedQuery(
|
||||
firstHalfQuery, secondHalfQuery, ImmutableMap.of("historyId", medianId));
|
||||
}
|
||||
}
|
||||
|
||||
private static String getJpaEntityName(Class entityType) {
|
||||
Entity entityAnnotation = (Entity) entityType.getAnnotation(Entity.class);
|
||||
checkState(
|
||||
entityAnnotation != null, "Unexpected non-entity type %s", entityType.getSimpleName());
|
||||
return Strings.isNullOrEmpty(entityAnnotation.name())
|
||||
? entityType.getSimpleName()
|
||||
: entityAnnotation.name();
|
||||
}
|
||||
|
||||
/** Contains two queries that partition the target table in two. */
|
||||
@AutoValue
|
||||
abstract static class PartitionedQuery {
|
||||
abstract String firstHalfQuery();
|
||||
|
||||
abstract String secondHalfQuery();
|
||||
|
||||
abstract ImmutableMap<String, Object> parameters();
|
||||
|
||||
public static PartitionedQuery createPartitionedQuery(
|
||||
String firstHalfQuery, String secondHalfQuery, ImmutableMap<String, Object> parameters) {
|
||||
return new AutoValue_SqlSnapshots_PartitionedQuery(
|
||||
firstHalfQuery, secondHalfQuery, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/** Container that receives mixed-typed data and groups them by {@link Class}. */
|
||||
static class TypedClassifier {
|
||||
private final ImmutableSetMultimap<Class<?>, Object> classifiedEntities;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.beam.comparedb;
|
||||
|
||||
import google.registry.beam.common.RegistryPipelineOptions;
|
||||
import google.registry.beam.common.RegistryPipelineWorkerInitializer;
|
||||
import google.registry.beam.comparedb.LatestDatastoreSnapshotFinder.DatastoreSnapshotInfo;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.persistence.PersistenceModule.JpaTransactionManagerType;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import java.util.Optional;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Validates the asynchronous data replication process from Cloud SQL (primary) to Datastore
|
||||
* (secondary).
|
||||
*
|
||||
* <p>This pipeline simply compares the snapshots provided by an invoker, which is responsible for
|
||||
* obtaining two consistent snapshots for the same point of time.
|
||||
*/
|
||||
// TODO(weiminyu): Implement the invoker action in a followup PR.
|
||||
@DeleteAfterMigration
|
||||
public class ValidateDatastorePipeline {
|
||||
|
||||
private final ValidateDatastorePipelineOptions options;
|
||||
private final LatestDatastoreSnapshotFinder datastoreSnapshotFinder;
|
||||
|
||||
public ValidateDatastorePipeline(
|
||||
ValidateDatastorePipelineOptions options,
|
||||
LatestDatastoreSnapshotFinder datastoreSnapshotFinder) {
|
||||
this.options = options;
|
||||
this.datastoreSnapshotFinder = datastoreSnapshotFinder;
|
||||
}
|
||||
|
||||
void run(Pipeline pipeline) {
|
||||
DateTime latestCommitLogTime = DateTime.parse(options.getLatestCommitLogTimestamp());
|
||||
DatastoreSnapshotInfo mostRecentExport =
|
||||
datastoreSnapshotFinder.getSnapshotInfo(latestCommitLogTime.toInstant());
|
||||
|
||||
ValidateSqlPipeline.setupPipeline(
|
||||
pipeline,
|
||||
Optional.ofNullable(options.getSqlSnapshotId()),
|
||||
mostRecentExport,
|
||||
latestCommitLogTime,
|
||||
Optional.ofNullable(options.getComparisonStartTimestamp()).map(DateTime::parse));
|
||||
|
||||
pipeline.run();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
ValidateDatastorePipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(args)
|
||||
.withValidation()
|
||||
.as(ValidateDatastorePipelineOptions.class);
|
||||
RegistryPipelineOptions.validateRegistryPipelineOptions(options);
|
||||
|
||||
// Defensively set important options.
|
||||
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ);
|
||||
options.setJpaTransactionManagerType(JpaTransactionManagerType.BULK_QUERY);
|
||||
|
||||
// Reuse Dataflow worker initialization code to set up JPA in the pipeline harness.
|
||||
new RegistryPipelineWorkerInitializer().beforeProcessing(options);
|
||||
|
||||
LatestDatastoreSnapshotFinder datastoreSnapshotFinder =
|
||||
DaggerLatestDatastoreSnapshotFinder_LatestDatastoreSnapshotFinderFinderComponent.create()
|
||||
.datastoreSnapshotInfoFinder();
|
||||
new ValidateDatastorePipeline(options, datastoreSnapshotFinder).run(Pipeline.create(options));
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.beam.comparedb;
|
||||
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import javax.annotation.Nullable;
|
||||
import org.apache.beam.sdk.options.Description;
|
||||
import org.apache.beam.sdk.options.Validation;
|
||||
|
||||
/** BEAM pipeline options for {@link ValidateDatastorePipelineOptions}. */
|
||||
@DeleteAfterMigration
|
||||
public interface ValidateDatastorePipelineOptions extends ValidateSqlPipelineOptions {
|
||||
|
||||
@Description(
|
||||
"The id of the SQL snapshot to be compared with Datastore. "
|
||||
+ "If null, the current state of the SQL database is used.")
|
||||
@Nullable
|
||||
String getSqlSnapshotId();
|
||||
|
||||
void setSqlSnapshotId(String snapshotId);
|
||||
|
||||
@Description("The latest CommitLogs to load, in ISO8601 format.")
|
||||
@Validation.Required
|
||||
String getLatestCommitLogTimestamp();
|
||||
|
||||
void setLatestCommitLogTimestamp(String commitLogEndTimestamp);
|
||||
}
|
||||
@@ -26,6 +26,9 @@ import google.registry.beam.common.RegistryPipelineWorkerInitializer;
|
||||
import google.registry.beam.comparedb.LatestDatastoreSnapshotFinder.DatastoreSnapshotInfo;
|
||||
import google.registry.beam.comparedb.ValidateSqlUtils.CompareSqlEntity;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.ReplayDirection;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.replay.SqlEntity;
|
||||
@@ -35,6 +38,7 @@ import google.registry.persistence.PersistenceModule.JpaTransactionManagerType;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||
import google.registry.util.RequestStatusChecker;
|
||||
import google.registry.util.SystemClock;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
@@ -76,21 +80,16 @@ public class ValidateSqlPipeline {
|
||||
java.time.Duration.ofSeconds(30);
|
||||
|
||||
private final ValidateSqlPipelineOptions options;
|
||||
private final DatastoreSnapshotInfo mostRecentExport;
|
||||
private final LatestDatastoreSnapshotFinder datastoreSnapshotFinder;
|
||||
|
||||
public ValidateSqlPipeline(
|
||||
ValidateSqlPipelineOptions options, DatastoreSnapshotInfo mostRecentExport) {
|
||||
ValidateSqlPipelineOptions options, LatestDatastoreSnapshotFinder datastoreSnapshotFinder) {
|
||||
this.options = options;
|
||||
this.mostRecentExport = mostRecentExport;
|
||||
}
|
||||
|
||||
void run() {
|
||||
run(Pipeline.create(options));
|
||||
this.datastoreSnapshotFinder = datastoreSnapshotFinder;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void run(Pipeline pipeline) {
|
||||
// TODO(weiminyu): ensure migration stage is DATASTORE_PRIMARY or DATASTORE_PRIMARY_READ_ONLY
|
||||
Optional<Lock> lock = acquireCommitLogReplayLock();
|
||||
if (lock.isPresent()) {
|
||||
logger.atInfo().log("Acquired CommitLog Replay lock.");
|
||||
@@ -101,6 +100,8 @@ public class ValidateSqlPipeline {
|
||||
try {
|
||||
DateTime latestCommitLogTime =
|
||||
TransactionManagerFactory.jpaTm().transact(() -> SqlReplayCheckpoint.get());
|
||||
DatastoreSnapshotInfo mostRecentExport =
|
||||
datastoreSnapshotFinder.getSnapshotInfo(latestCommitLogTime.toInstant());
|
||||
Preconditions.checkState(
|
||||
latestCommitLogTime.isAfter(mostRecentExport.exportInterval().getEnd()),
|
||||
"Cannot recreate Datastore snapshot since target time is in the middle of an export.");
|
||||
@@ -109,7 +110,16 @@ public class ValidateSqlPipeline {
|
||||
lock.ifPresent(Lock::releaseSql);
|
||||
lock = Optional.empty();
|
||||
|
||||
setupPipeline(pipeline, Optional.of(databaseSnapshot.getSnapshotId()), latestCommitLogTime);
|
||||
logger.atInfo().log(
|
||||
"Starting comparison with export at %s and latestCommitLogTime at %s",
|
||||
mostRecentExport.exportDir(), latestCommitLogTime);
|
||||
|
||||
setupPipeline(
|
||||
pipeline,
|
||||
Optional.of(databaseSnapshot.getSnapshotId()),
|
||||
mostRecentExport,
|
||||
latestCommitLogTime,
|
||||
Optional.ofNullable(options.getComparisonStartTimestamp()).map(DateTime::parse));
|
||||
State state = pipeline.run().waitUntilFinish();
|
||||
if (!State.DONE.equals(state)) {
|
||||
throw new IllegalStateException("Unexpected pipeline state: " + state);
|
||||
@@ -120,8 +130,12 @@ public class ValidateSqlPipeline {
|
||||
}
|
||||
}
|
||||
|
||||
void setupPipeline(
|
||||
Pipeline pipeline, Optional<String> sqlSnapshotId, DateTime latestCommitLogTime) {
|
||||
static void setupPipeline(
|
||||
Pipeline pipeline,
|
||||
Optional<String> sqlSnapshotId,
|
||||
DatastoreSnapshotInfo mostRecentExport,
|
||||
DateTime latestCommitLogTime,
|
||||
Optional<DateTime> compareStartTime) {
|
||||
pipeline
|
||||
.getCoderRegistry()
|
||||
.registerCoderForClass(SqlEntity.class, SerializableCoder.of(Serializable.class));
|
||||
@@ -135,11 +149,12 @@ public class ValidateSqlPipeline {
|
||||
// Increase by 1ms since we want to include commitLogs latestCommitLogTime but
|
||||
// this parameter is exclusive.
|
||||
latestCommitLogTime.plusMillis(1),
|
||||
DatastoreSnapshots.ALL_DATASTORE_KINDS);
|
||||
DatastoreSnapshots.ALL_DATASTORE_KINDS,
|
||||
compareStartTime);
|
||||
|
||||
PCollectionTuple cloudSqlSnapshot =
|
||||
SqlSnapshots.loadCloudSqlSnapshotByType(
|
||||
pipeline, SqlSnapshots.ALL_SQL_ENTITIES, sqlSnapshotId);
|
||||
pipeline, SqlSnapshots.ALL_SQL_ENTITIES, sqlSnapshotId, compareStartTime);
|
||||
|
||||
verify(
|
||||
datastoreSnapshot.getAll().keySet().equals(cloudSqlSnapshot.getAll().keySet()),
|
||||
@@ -212,6 +227,10 @@ public class ValidateSqlPipeline {
|
||||
return "ValidateSqlPipeline";
|
||||
}
|
||||
|
||||
LatestDatastoreSnapshotFinder datastoreSnapshotFinder =
|
||||
DaggerLatestDatastoreSnapshotFinder_LatestDatastoreSnapshotFinderFinderComponent.create()
|
||||
.datastoreSnapshotInfoFinder();
|
||||
|
||||
@Override
|
||||
public boolean isRunning(String requestLogId) {
|
||||
return true;
|
||||
@@ -230,11 +249,16 @@ public class ValidateSqlPipeline {
|
||||
// Reuse Dataflow worker initialization code to set up JPA in the pipeline harness.
|
||||
new RegistryPipelineWorkerInitializer().beforeProcessing(options);
|
||||
|
||||
DatastoreSnapshotInfo mostRecentExport =
|
||||
DaggerLatestDatastoreSnapshotFinder_LatestDatastoreSnapshotFinderFinderComponent.create()
|
||||
.datastoreSnapshotInfoFinder()
|
||||
.getSnapshotInfo();
|
||||
MigrationState state =
|
||||
DatabaseMigrationStateSchedule.getValueAtTime(new SystemClock().nowUtc());
|
||||
if (!state.getReplayDirection().equals(ReplayDirection.DATASTORE_TO_SQL)) {
|
||||
throw new IllegalStateException("This pipeline is not designed for migration phase " + state);
|
||||
}
|
||||
|
||||
new ValidateSqlPipeline(options, mostRecentExport).run(Pipeline.create(options));
|
||||
LatestDatastoreSnapshotFinder datastoreSnapshotFinder =
|
||||
DaggerLatestDatastoreSnapshotFinder_LatestDatastoreSnapshotFinderFinderComponent.create()
|
||||
.datastoreSnapshotInfoFinder();
|
||||
|
||||
new ValidateSqlPipeline(options, datastoreSnapshotFinder).run(Pipeline.create(options));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,19 @@ package google.registry.beam.comparedb;
|
||||
|
||||
import google.registry.beam.common.RegistryPipelineOptions;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import javax.annotation.Nullable;
|
||||
import org.apache.beam.sdk.options.Description;
|
||||
|
||||
/** BEAM pipeline options for {@link ValidateSqlPipeline}. */
|
||||
@DeleteAfterMigration
|
||||
public interface ValidateSqlPipelineOptions extends RegistryPipelineOptions {}
|
||||
public interface ValidateSqlPipelineOptions extends RegistryPipelineOptions {
|
||||
|
||||
@Description(
|
||||
"For history entries and EPP resources, only those modified strictly after this time are "
|
||||
+ "included in comparison. Value is in ISO8601 format. "
|
||||
+ "Other entity types are not affected.")
|
||||
@Nullable
|
||||
String getComparisonStartTimestamp();
|
||||
|
||||
void setComparisonStartTimestamp(String comparisonStartTimestamp);
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ final class ValidateSqlUtils {
|
||||
private final HashMap<String, Counter> missingCounters = new HashMap<>();
|
||||
private final HashMap<String, Counter> unequalCounters = new HashMap<>();
|
||||
private final HashMap<String, Counter> badEntityCounters = new HashMap<>();
|
||||
private final HashMap<String, Counter> duplicateEntityCounters = new HashMap<>();
|
||||
|
||||
private volatile boolean logPrinted = false;
|
||||
|
||||
@@ -120,6 +121,8 @@ final class ValidateSqlUtils {
|
||||
counterKey, Metrics.counter("CompareDB", "Missing In One DB: " + counterKey));
|
||||
unequalCounters.put(counterKey, Metrics.counter("CompareDB", "Not Equal:" + counterKey));
|
||||
badEntityCounters.put(counterKey, Metrics.counter("CompareDB", "Bad Entities:" + counterKey));
|
||||
duplicateEntityCounters.put(
|
||||
counterKey, Metrics.counter("CompareDB", "Duplicate Entities:" + counterKey));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,12 +161,18 @@ final class ValidateSqlUtils {
|
||||
ImmutableList<SqlEntity> entities = ImmutableList.copyOf(kv.getValue());
|
||||
|
||||
verify(!entities.isEmpty(), "Can't happen: no value for key %s.", kv.getKey());
|
||||
verify(entities.size() <= 2, "Unexpected duplicates for key %s", kv.getKey());
|
||||
|
||||
String counterKey = getCounterKey(entities.get(0).getClass());
|
||||
ensureCounterExists(counterKey);
|
||||
totalCounters.get(counterKey).inc();
|
||||
|
||||
if (entities.size() > 2) {
|
||||
// Duplicates may happen with Cursors if imported across projects. Its key in Datastore, the
|
||||
// id field, encodes the project name and is not fixed by the importing job.
|
||||
duplicateEntityCounters.get(counterKey).inc();
|
||||
return;
|
||||
}
|
||||
|
||||
if (entities.size() == 1) {
|
||||
if (isSpecialCaseProberEntity(entities.get(0))) {
|
||||
return;
|
||||
|
||||
@@ -248,6 +248,9 @@ 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));
|
||||
}
|
||||
@@ -294,6 +297,10 @@ public class RdeIO {
|
||||
logger.atInfo().log(
|
||||
"Rolled forward %s on %s cursor to %s.", key.cursor(), key.tld(), newPosition);
|
||||
RdeRevision.saveRevision(key.tld(), key.watermark(), key.mode(), revision);
|
||||
// Enqueueing a task is a side effect that is not undone if the transaction rolls
|
||||
// back. So this may result in multiple copies of the same task being processed.
|
||||
// This is fine because the RdeUploadAction is guarded by a lock and tracks progress
|
||||
// by cursor. The BrdaCopyAction writes a file to GCS, which is an atomic action.
|
||||
if (key.mode() == RdeMode.FULL) {
|
||||
cloudTasksUtils.enqueue(
|
||||
RDE_UPLOAD_QUEUE,
|
||||
|
||||
@@ -33,6 +33,7 @@ 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;
|
||||
@@ -392,19 +393,26 @@ public final class RegistryConfig {
|
||||
|
||||
@Provides
|
||||
@Config("cloudSqlJdbcUrl")
|
||||
public static String providesCloudSqlJdbcUrl(RegistryConfigSettings config) {
|
||||
public static String provideCloudSqlJdbcUrl(RegistryConfigSettings config) {
|
||||
return config.cloudSql.jdbcUrl;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("cloudSqlInstanceConnectionName")
|
||||
public static String providesCloudSqlInstanceConnectionName(RegistryConfigSettings config) {
|
||||
public static String provideCloudSqlInstanceConnectionName(RegistryConfigSettings config) {
|
||||
return config.cloudSql.instanceConnectionName;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("cloudSqlReplicaInstanceConnectionName")
|
||||
public static Optional<String> provideCloudSqlReplicaInstanceConnectionName(
|
||||
RegistryConfigSettings config) {
|
||||
return Optional.ofNullable(config.cloudSql.replicaInstanceConnectionName);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("cloudSqlDbInstanceName")
|
||||
public static String providesCloudSqlDbInstance(RegistryConfigSettings config) {
|
||||
public static String provideCloudSqlDbInstance(RegistryConfigSettings config) {
|
||||
// Format of instanceConnectionName: project-id:region:instance-name
|
||||
int lastColonIndex = config.cloudSql.instanceConnectionName.lastIndexOf(':');
|
||||
return config.cloudSql.instanceConnectionName.substring(lastColonIndex + 1);
|
||||
@@ -1524,6 +1532,31 @@ public final class RegistryConfig {
|
||||
return CONFIG_SETTINGS.get().hibernate.hikariIdleTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* JDBC-specific: driver default batch size is 0, which means that every INSERT statement will be
|
||||
* sent to the database individually. Batching allows us to group together multiple inserts into
|
||||
* one single INSERT statement which can dramatically increase speed in situations with many
|
||||
* inserts.
|
||||
*
|
||||
* <p>Hibernate docs, i.e.
|
||||
* https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html,
|
||||
* recommend between 10 and 50.
|
||||
*/
|
||||
public static String getHibernateJdbcBatchSize() {
|
||||
return CONFIG_SETTINGS.get().hibernate.jdbcBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JDBC fetch size.
|
||||
*
|
||||
* <p>Postgresql-specific: driver default fetch size is 0, which disables streaming result sets.
|
||||
* Here we set a small default geared toward Nomulus server transactions. Large queries can
|
||||
* override the defaults using {@link JpaTransactionManager#setQueryFetchSize}.
|
||||
*/
|
||||
public static String getHibernateJdbcFetchSize() {
|
||||
return CONFIG_SETTINGS.get().hibernate.jdbcFetchSize;
|
||||
}
|
||||
|
||||
/** Returns the roid suffix to be used for the roids of all contacts and hosts. */
|
||||
public static String getContactAndHostRoidSuffix() {
|
||||
return CONFIG_SETTINGS.get().registryPolicy.contactAndHostRoidSuffix;
|
||||
|
||||
@@ -120,6 +120,8 @@ public class RegistryConfigSettings {
|
||||
public String hikariMinimumIdle;
|
||||
public String hikariMaximumPoolSize;
|
||||
public String hikariIdleTimeout;
|
||||
public String jdbcBatchSize;
|
||||
public String jdbcFetchSize;
|
||||
}
|
||||
|
||||
/** Configuration for Cloud SQL. */
|
||||
@@ -128,6 +130,7 @@ public class RegistryConfigSettings {
|
||||
// TODO(05012021): remove username field after it is removed from all yaml files.
|
||||
public String username;
|
||||
public String instanceConnectionName;
|
||||
public String replicaInstanceConnectionName;
|
||||
}
|
||||
|
||||
/** Configuration for Apache Beam (Cloud Dataflow). */
|
||||
|
||||
@@ -221,6 +221,17 @@ hibernate:
|
||||
hikariMinimumIdle: 1
|
||||
hikariMaximumPoolSize: 10
|
||||
hikariIdleTimeout: 300000
|
||||
# The batch size is basically the number of insertions / updates in a single
|
||||
# transaction that will be batched together into one INSERT/UPDATE statement.
|
||||
# A larger batch size is useful when inserting or updating many entities in a
|
||||
# single transaction. Hibernate docs
|
||||
# (https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html)
|
||||
# recommend between 10 and 50.
|
||||
jdbcBatchSize: 50
|
||||
# The fetch size is the number of entities retrieved at a time from the
|
||||
# database cursor. Here we set a small default geared toward Nomulus server
|
||||
# transactions. Large queries can override the defaults on a per-query basis.
|
||||
jdbcFetchSize: 20
|
||||
|
||||
cloudSql:
|
||||
# jdbc url for the Cloud SQL database.
|
||||
@@ -231,6 +242,10 @@ cloudSql:
|
||||
jdbcUrl: jdbc:postgresql://localhost
|
||||
# This name is used by Cloud SQL when connecting to the database.
|
||||
instanceConnectionName: project-id:region:instance-id
|
||||
# If non-null, we will use this instance for certain read-only actions or
|
||||
# pipelines, e.g. RDE, in order to offload some work from the primary
|
||||
# instance. Expect any write actions on this instance to fail.
|
||||
replicaInstanceConnectionName: null
|
||||
|
||||
cloudDns:
|
||||
# Set both properties to null in Production.
|
||||
|
||||
@@ -36,6 +36,19 @@
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url>/_dr/task/rdeStaging?beam=true</url>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML
|
||||
document using the Beam pipeline regardless of the current TM
|
||||
configuration and streams it to cloud storage. It does not trigger the
|
||||
subsequent upload tasks and is meant to run parallel with the main cron
|
||||
job in order to compare the results from both runs.
|
||||
</description>
|
||||
<schedule>every 8 hours from 00:07 to 20:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<description>
|
||||
@@ -349,6 +362,15 @@
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/replicateToDatastore]]></url>
|
||||
<description>
|
||||
Replays recent transactions from SQL to the Datastore secondary backend.
|
||||
</description>
|
||||
<schedule>every 3 minutes</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<description>
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
@@ -143,7 +142,7 @@ public class ExportPremiumTermsAction implements Runnable {
|
||||
PremiumListDao.getLatestRevision(premiumListName).isPresent(),
|
||||
"Could not load premium list for " + tld);
|
||||
SortedSet<String> premiumTerms =
|
||||
Streams.stream(PremiumListDao.loadAllPremiumEntries(premiumListName))
|
||||
PremiumListDao.loadAllPremiumEntries(premiumListName).stream()
|
||||
.map(PremiumEntry::toString)
|
||||
.collect(ImmutableSortedSet.toImmutableSortedSet(String::compareTo));
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.FeesMismatchException}
|
||||
* @error {@link DomainFlowUtils.FeesRequiredDuringEarlyAccessProgramException}
|
||||
* @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException}
|
||||
* @error {@link DomainFlowUtils.InvalidDsRecordException}
|
||||
* @error {@link DomainFlowUtils.InvalidIdnDomainLabelException}
|
||||
* @error {@link DomainFlowUtils.InvalidPunycodeException}
|
||||
* @error {@link DomainFlowUtils.InvalidTcnIdChecksumException}
|
||||
|
||||
@@ -129,6 +129,7 @@ import google.registry.model.tld.label.ReservedList;
|
||||
import google.registry.model.tmch.ClaimsListDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.tldconfig.idn.IdnLabelValidator;
|
||||
import google.registry.tools.DigestType;
|
||||
import google.registry.util.Idn;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
@@ -144,6 +145,7 @@ import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.xbill.DNS.DNSSEC.Algorithm;
|
||||
|
||||
/** Static utility functions for domain flows. */
|
||||
public class DomainFlowUtils {
|
||||
@@ -293,13 +295,46 @@ public class DomainFlowUtils {
|
||||
|
||||
/** Check that the DS data that will be set on a domain is valid. */
|
||||
static void validateDsData(Set<DelegationSignerData> dsData) throws EppException {
|
||||
if (dsData != null && dsData.size() > MAX_DS_RECORDS_PER_DOMAIN) {
|
||||
throw new TooManyDsRecordsException(
|
||||
String.format(
|
||||
"A maximum of %s DS records are allowed per domain.", MAX_DS_RECORDS_PER_DOMAIN));
|
||||
if (dsData != null) {
|
||||
if (dsData.size() > MAX_DS_RECORDS_PER_DOMAIN) {
|
||||
throw new TooManyDsRecordsException(
|
||||
String.format(
|
||||
"A maximum of %s DS records are allowed per domain.", MAX_DS_RECORDS_PER_DOMAIN));
|
||||
}
|
||||
// TODO(sarahbot@): Add signature length verification
|
||||
ImmutableList<DelegationSignerData> invalidAlgorithms =
|
||||
dsData.stream()
|
||||
.filter(ds -> !validateAlgorithm(ds.getAlgorithm()))
|
||||
.collect(toImmutableList());
|
||||
if (!invalidAlgorithms.isEmpty()) {
|
||||
throw new InvalidDsRecordException(
|
||||
String.format(
|
||||
"Domain contains DS record(s) with an invalid algorithm wire value: %s",
|
||||
invalidAlgorithms));
|
||||
}
|
||||
ImmutableList<DelegationSignerData> invalidDigestTypes =
|
||||
dsData.stream()
|
||||
.filter(ds -> !DigestType.fromWireValue(ds.getDigestType()).isPresent())
|
||||
.collect(toImmutableList());
|
||||
if (!invalidDigestTypes.isEmpty()) {
|
||||
throw new InvalidDsRecordException(
|
||||
String.format(
|
||||
"Domain contains DS record(s) with an invalid digest type: %s",
|
||||
invalidDigestTypes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean validateAlgorithm(int alg) {
|
||||
if (alg > 255 || alg < 0) {
|
||||
return false;
|
||||
}
|
||||
// Algorithms that are reserved or unassigned will just return a string representation of their
|
||||
// integer wire value.
|
||||
String algorithm = Algorithm.string(alg);
|
||||
return !algorithm.equals(Integer.toString(alg));
|
||||
}
|
||||
|
||||
/** We only allow specifying years in a period. */
|
||||
static Period verifyUnitIsYears(Period period) throws EppException {
|
||||
if (!checkNotNull(period).getUnit().equals(Period.Unit.YEARS)) {
|
||||
@@ -1217,6 +1252,13 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain has an invalid DS record. */
|
||||
static class InvalidDsRecordException extends ParameterValuePolicyErrorException {
|
||||
public InvalidDsRecordException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain name is under tld which doesn't exist. */
|
||||
static class TldDoesNotExistException extends ParameterValueRangeErrorException {
|
||||
public TldDoesNotExistException(String tld) {
|
||||
|
||||
@@ -114,6 +114,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainFlowUtils.EmptySecDnsUpdateException}
|
||||
* @error {@link DomainFlowUtils.FeesMismatchException}
|
||||
* @error {@link DomainFlowUtils.FeesRequiredForNonFreeOperationException}
|
||||
* @error {@link DomainFlowUtils.InvalidDsRecordException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourcesDoNotExistException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
|
||||
* @error {@link DomainFlowUtils.MaxSigLifeChangeNotSupportedException}
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
package google.registry.loadtest;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.QueueConstants.maxTasksPerAdd;
|
||||
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.Lists.partition;
|
||||
@@ -24,16 +22,20 @@ import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.cloud.tasks.v2.Task;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.protobuf.Timestamp;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
import google.registry.util.TaskQueueUtils;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -62,6 +64,7 @@ public class LoadTestAction implements Runnable {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final int NUM_QUEUES = 10;
|
||||
private static final int MAX_TASKS_PER_LOAD = 100;
|
||||
private static final int ARBITRARY_VALID_HOST_LENGTH = 40;
|
||||
private static final int MAX_CONTACT_LENGTH = 13;
|
||||
private static final int MAX_DOMAIN_LABEL_LENGTH = 63;
|
||||
@@ -146,7 +149,7 @@ public class LoadTestAction implements Runnable {
|
||||
@Parameter("hostInfos")
|
||||
int hostInfosPerSecond;
|
||||
|
||||
@Inject TaskQueueUtils taskQueueUtils;
|
||||
@Inject CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
private final String xmlContactCreateTmpl;
|
||||
private final String xmlContactCreateFail;
|
||||
@@ -208,7 +211,7 @@ public class LoadTestAction implements Runnable {
|
||||
ImmutableList<String> contactNames = contactNamesBuilder.build();
|
||||
ImmutableList<String> hostPrefixes = hostPrefixesBuilder.build();
|
||||
|
||||
ImmutableList.Builder<TaskOptions> tasks = new ImmutableList.Builder<>();
|
||||
ImmutableList.Builder<Task> tasks = new ImmutableList.Builder<>();
|
||||
for (int offsetSeconds = 0; offsetSeconds < runSeconds; offsetSeconds++) {
|
||||
DateTime startSecond = initialStartSecond.plusSeconds(offsetSeconds);
|
||||
// The first "failed" creates might actually succeed if the object doesn't already exist, but
|
||||
@@ -254,7 +257,7 @@ public class LoadTestAction implements Runnable {
|
||||
.collect(toImmutableList()),
|
||||
startSecond));
|
||||
}
|
||||
ImmutableList<TaskOptions> taskOptions = tasks.build();
|
||||
ImmutableList<Task> taskOptions = tasks.build();
|
||||
enqueue(taskOptions);
|
||||
logger.atInfo().log("Added %d total load test tasks.", taskOptions.size());
|
||||
}
|
||||
@@ -322,28 +325,50 @@ public class LoadTestAction implements Runnable {
|
||||
return name.toString();
|
||||
}
|
||||
|
||||
private List<TaskOptions> createTasks(List<String> xmls, DateTime start) {
|
||||
ImmutableList.Builder<TaskOptions> tasks = new ImmutableList.Builder<>();
|
||||
private List<Task> createTasks(List<String> xmls, DateTime start) {
|
||||
ImmutableList.Builder<Task> tasks = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < xmls.size(); i++) {
|
||||
// Space tasks evenly within across a second.
|
||||
int offsetMillis = (int) (1000.0 / xmls.size() * i);
|
||||
Instant scheduleTime =
|
||||
Instant.ofEpochMilli(start.plusMillis((int) (1000.0 / xmls.size() * i)).getMillis());
|
||||
tasks.add(
|
||||
TaskOptions.Builder.withUrl("/_dr/epptool")
|
||||
.etaMillis(start.getMillis() + offsetMillis)
|
||||
.header(X_CSRF_TOKEN, xsrfToken)
|
||||
.param("clientId", registrarId)
|
||||
.param("superuser", Boolean.FALSE.toString())
|
||||
.param("dryRun", Boolean.FALSE.toString())
|
||||
.param("xml", xmls.get(i)));
|
||||
Task.newBuilder()
|
||||
.setAppEngineHttpRequest(
|
||||
CloudTasksUtils.createPostTask(
|
||||
"/_dr/epptool",
|
||||
Service.TOOLS.toString(),
|
||||
ImmutableMultimap.of(
|
||||
"clientId",
|
||||
registrarId,
|
||||
"superuser",
|
||||
Boolean.FALSE.toString(),
|
||||
"dryRun",
|
||||
Boolean.FALSE.toString(),
|
||||
"xml",
|
||||
xmls.get(i)))
|
||||
.toBuilder()
|
||||
.getAppEngineHttpRequest()
|
||||
.toBuilder()
|
||||
// instead of adding the X_CSRF_TOKEN to params, this remains as part of
|
||||
// headers because of the existing setup for authentication in {@link
|
||||
// google.registry.request.auth.LegacyAuthenticationMechanism}
|
||||
.putHeaders(X_CSRF_TOKEN, xsrfToken)
|
||||
.build())
|
||||
.setScheduleTime(
|
||||
Timestamp.newBuilder()
|
||||
.setSeconds(scheduleTime.getEpochSecond())
|
||||
.setNanos(scheduleTime.getNano())
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
return tasks.build();
|
||||
}
|
||||
|
||||
private void enqueue(List<TaskOptions> tasks) {
|
||||
List<List<TaskOptions>> chunks = partition(tasks, maxTasksPerAdd());
|
||||
private void enqueue(List<Task> tasks) {
|
||||
List<List<Task>> chunks = partition(tasks, MAX_TASKS_PER_LOAD);
|
||||
// Farm out tasks to multiple queues to work around queue qps quotas.
|
||||
for (int i = 0; i < chunks.size(); i++) {
|
||||
taskQueueUtils.enqueue(getQueue("load" + (i % NUM_QUEUES)), chunks.get(i));
|
||||
cloudTasksUtils.enqueue("load" + (i % NUM_QUEUES), chunks.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
@@ -380,13 +381,13 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
|
||||
@Override
|
||||
public EppResource load(VKey<? extends EppResource> key) {
|
||||
return tm().doTransactionless(() -> tm().loadByKey(key));
|
||||
return replicaTm().doTransactionless(() -> replicaTm().loadByKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<VKey<? extends EppResource>, EppResource> loadAll(
|
||||
Iterable<? extends VKey<? extends EppResource>> keys) {
|
||||
return tm().doTransactionless(() -> tm().loadByKeys(keys));
|
||||
return replicaTm().doTransactionless(() -> replicaTm().loadByKeys(keys));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ import javax.persistence.AccessType;
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "Host")
|
||||
@javax.persistence.Table(
|
||||
name = "Host",
|
||||
indexes = {@javax.persistence.Index(columnList = "hostName")})
|
||||
@ExternalMessagingName("host")
|
||||
@WithStringVKey
|
||||
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
|
||||
|
||||
@@ -27,6 +27,7 @@ import google.registry.model.EppResource;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.replay.DatastoreOnlyEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
|
||||
/** An index that allows for quick enumeration of all EppResource entities (e.g. via map reduce). */
|
||||
@ReportedOn
|
||||
@@ -66,8 +67,9 @@ public class EppResourceIndex extends BackupGroupRoot implements DatastoreOnlyEn
|
||||
EppResourceIndex instance = instantiate(EppResourceIndex.class);
|
||||
instance.reference = resourceKey;
|
||||
instance.kind = resourceKey.getKind();
|
||||
// TODO(b/207368050): figure out if this value has ever been used other than test cases
|
||||
instance.id = resourceKey.getString(); // creates a web-safe key string
|
||||
// creates a web-safe key string, this value is never used
|
||||
// TODO(b/211785379): remove unused id
|
||||
instance.id = VKey.from(resourceKey).stringify();
|
||||
instance.bucket = bucket;
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static google.registry.config.RegistryConfig.getEppResourceCachingDuratio
|
||||
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.entriesToImmutableMap;
|
||||
import static google.registry.util.TypeUtils.instantiate;
|
||||
@@ -51,6 +52,7 @@ import google.registry.model.host.HostResource;
|
||||
import google.registry.model.replay.DatastoreOnlyEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
@@ -198,7 +200,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> load(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
return loadIndexesFromStore(clazz, foreignKeys, true).entrySet().stream()
|
||||
return loadIndexesFromStore(clazz, foreignKeys, true, false).entrySet().stream()
|
||||
.filter(e -> now.isBefore(e.getValue().getDeletionTime()))
|
||||
.collect(entriesToImmutableMap());
|
||||
}
|
||||
@@ -217,7 +219,10 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
*/
|
||||
private static <E extends EppResource>
|
||||
ImmutableMap<String, ForeignKeyIndex<E>> loadIndexesFromStore(
|
||||
Class<E> clazz, Collection<String> foreignKeys, boolean inTransaction) {
|
||||
Class<E> clazz,
|
||||
Collection<String> foreignKeys,
|
||||
boolean inTransaction,
|
||||
boolean useReplicaJpaTm) {
|
||||
if (tm().isOfy()) {
|
||||
Class<ForeignKeyIndex<E>> fkiClass = mapToFkiClass(clazz);
|
||||
return ImmutableMap.copyOf(
|
||||
@@ -226,17 +231,18 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
: tm().doTransactionless(() -> auditedOfy().load().type(fkiClass).ids(foreignKeys)));
|
||||
} else {
|
||||
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
|
||||
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
|
||||
ImmutableList<ForeignKeyIndex<E>> indexes =
|
||||
tm().transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.criteriaQuery(
|
||||
CriteriaQueryBuilder.create(clazz)
|
||||
.whereFieldIsIn(property, foreignKeys)
|
||||
.build())
|
||||
.getResultStream()
|
||||
.map(e -> ForeignKeyIndex.create(e, e.getDeletionTime()))
|
||||
.collect(toImmutableList()));
|
||||
jpaTmToUse.transact(
|
||||
() ->
|
||||
jpaTmToUse
|
||||
.criteriaQuery(
|
||||
CriteriaQueryBuilder.create(clazz)
|
||||
.whereFieldIsIn(property, foreignKeys)
|
||||
.build())
|
||||
.getResultStream()
|
||||
.map(e -> ForeignKeyIndex.create(e, e.getDeletionTime()))
|
||||
.collect(toImmutableList()));
|
||||
// We need to find and return the entities with the maximum deletionTime for each foreign key.
|
||||
return Multimaps.index(indexes, ForeignKeyIndex::getForeignKey).asMap().entrySet().stream()
|
||||
.map(
|
||||
@@ -260,7 +266,8 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
loadIndexesFromStore(
|
||||
RESOURCE_CLASS_TO_FKI_CLASS.inverse().get(key.getKind()),
|
||||
ImmutableSet.of(foreignKey),
|
||||
false)
|
||||
false,
|
||||
true)
|
||||
.get(foreignKey));
|
||||
}
|
||||
|
||||
@@ -276,7 +283,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
Streams.stream(keys).map(v -> v.getSqlKey().toString()).collect(toImmutableSet());
|
||||
ImmutableSet<VKey<ForeignKeyIndex<?>>> typedKeys = ImmutableSet.copyOf(keys);
|
||||
ImmutableMap<String, ? extends ForeignKeyIndex<? extends EppResource>> existingFkis =
|
||||
loadIndexesFromStore(resourceClass, foreignKeys, false);
|
||||
loadIndexesFromStore(resourceClass, foreignKeys, false, true);
|
||||
// ofy omits keys that don't have values in Datastore, so re-add them in
|
||||
// here with Optional.empty() values.
|
||||
return Maps.asMap(
|
||||
@@ -336,7 +343,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
// Safe to cast VKey<FKI<E>> to VKey<FKI<?>>
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableList<VKey<ForeignKeyIndex<?>>> fkiVKeys =
|
||||
Streams.stream(foreignKeys)
|
||||
foreignKeys.stream()
|
||||
.map(fk -> (VKey<ForeignKeyIndex<?>>) VKey.create(fkiClass, fk))
|
||||
.collect(toImmutableList());
|
||||
try {
|
||||
|
||||
@@ -21,7 +21,6 @@ import static com.google.common.hash.Funnels.stringFunnel;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.hash.BloomFilter;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
@@ -86,9 +85,8 @@ public final class PremiumList extends BaseDomainLabelList<BigDecimal, PremiumEn
|
||||
*/
|
||||
public synchronized ImmutableMap<String, BigDecimal> getLabelsToPrices() {
|
||||
if (labelsToPrices == null) {
|
||||
Iterable<PremiumEntry> entries = PremiumListDao.loadAllPremiumEntries(name);
|
||||
labelsToPrices =
|
||||
Streams.stream(entries)
|
||||
PremiumListDao.loadAllPremiumEntries(name).stream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
PremiumEntry::getDomainLabel,
|
||||
|
||||
@@ -28,8 +28,8 @@ import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.model.tld.label.PremiumList.PremiumEntry;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import java.math.BigDecimal;
|
||||
@@ -56,8 +56,7 @@ public class PremiumListDao {
|
||||
* <p>This is cached for a shorter duration because we need to periodically reload this entity to
|
||||
* check if a new revision has been published, and if so, then use that.
|
||||
*
|
||||
* <p>We also cache the absence of premium lists with a given name to avoid unnecessary pointless
|
||||
* lookups. Note that this cache is only applicable to PremiumList objects stored in SQL.
|
||||
* <p>We also cache the absence of premium lists with a given name to avoid pointless lookups.
|
||||
*/
|
||||
@NonFinalForTesting
|
||||
static LoadingCache<String, Optional<PremiumList>> premiumListCache =
|
||||
@@ -170,11 +169,10 @@ public class PremiumListDao {
|
||||
|
||||
if (!isNullOrEmpty(premiumList.getLabelsToPrices())) {
|
||||
ImmutableSet.Builder<PremiumEntry> entries = new ImmutableSet.Builder<>();
|
||||
premiumList.getLabelsToPrices().entrySet().stream()
|
||||
premiumList
|
||||
.getLabelsToPrices()
|
||||
.forEach(
|
||||
entry ->
|
||||
entries.add(
|
||||
PremiumEntry.create(revisionId, entry.getValue(), entry.getKey())));
|
||||
(key, value) -> entries.add(PremiumEntry.create(revisionId, value, key)));
|
||||
jpaTm().insertAll(entries.build());
|
||||
}
|
||||
});
|
||||
@@ -217,7 +215,7 @@ public class PremiumListDao {
|
||||
*
|
||||
* <p>This is an expensive operation and should only be used when the entire list is required.
|
||||
*/
|
||||
public static Iterable<PremiumEntry> loadPremiumEntries(PremiumList premiumList) {
|
||||
public static List<PremiumEntry> loadPremiumEntries(PremiumList premiumList) {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
@@ -254,15 +252,14 @@ public class PremiumListDao {
|
||||
*
|
||||
* <p>This is an expensive operation and should only be used when the entire list is required.
|
||||
*/
|
||||
public static Iterable<PremiumEntry> loadAllPremiumEntries(String premiumListName) {
|
||||
public static ImmutableList<PremiumEntry> loadAllPremiumEntries(String premiumListName) {
|
||||
PremiumList premiumList =
|
||||
getLatestRevision(premiumListName)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format("No premium list with name %s.", premiumListName)));
|
||||
Iterable<PremiumEntry> entries = loadPremiumEntries(premiumList);
|
||||
return Streams.stream(entries)
|
||||
return loadPremiumEntries(premiumList).stream()
|
||||
.map(
|
||||
premiumEntry ->
|
||||
new PremiumEntry.Builder()
|
||||
|
||||
+8
-10
@@ -14,12 +14,10 @@
|
||||
|
||||
package google.registry.model.translators;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.persistence.transaction.Transaction;
|
||||
import java.util.Date;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -47,13 +45,13 @@ public class CreateAutoTimestampTranslatorFactory
|
||||
/** Save a timestamp, setting it to the current time if it did not have a previous value. */
|
||||
@Override
|
||||
public Date saveValue(CreateAutoTimestamp pojoValue) {
|
||||
|
||||
// Don't do this if we're in the course of transaction serialization.
|
||||
if (Transaction.inSerializationMode()) {
|
||||
return pojoValue.getTimestamp() == null ? null : pojoValue.getTimestamp().toDate();
|
||||
}
|
||||
|
||||
return firstNonNull(pojoValue.getTimestamp(), ofyTm().getTransactionTime()).toDate();
|
||||
// Note that we use the current transaction manager -- we need to do this under JPA when we
|
||||
// serialize the entity from a Transaction object, but we need to use the JPA transaction
|
||||
// manager in that case.
|
||||
return (pojoValue.getTimestamp() == null
|
||||
? tm().getTransactionTime()
|
||||
: pojoValue.getTimestamp())
|
||||
.toDate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+11
-2
@@ -14,6 +14,8 @@
|
||||
|
||||
package google.registry.model.translators;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
@@ -48,9 +50,16 @@ public class UpdateAutoTimestampTranslatorFactory
|
||||
@Override
|
||||
public Date saveValue(UpdateAutoTimestamp pojoValue) {
|
||||
|
||||
// Don't do this if we're in the course of transaction serialization.
|
||||
// If we're in the course of Transaction serialization, we have to use the transaction time
|
||||
// here and the JPA transaction manager which is what will ultimately be saved during the
|
||||
// commit.
|
||||
// Note that this branch doesn't respect "auto update disabled", as this state is
|
||||
// specifically to address replay, so we add a runtime check for this.
|
||||
if (Transaction.inSerializationMode()) {
|
||||
return pojoValue.getTimestamp() == null ? null : pojoValue.getTimestamp().toDate();
|
||||
checkState(
|
||||
UpdateAutoTimestamp.autoUpdateEnabled(),
|
||||
"Auto-update disabled during transaction serialization.");
|
||||
return jpaTm().getTransactionTime().toDate();
|
||||
}
|
||||
|
||||
return UpdateAutoTimestamp.autoUpdateEnabled()
|
||||
|
||||
@@ -30,6 +30,7 @@ import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.module.pubapi.PubApiRequestComponent.PubApiRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.request.Modules.Jackson2Module;
|
||||
import google.registry.request.Modules.NetHttpTransportModule;
|
||||
@@ -56,6 +57,7 @@ import javax.inject.Singleton;
|
||||
KeyringModule.class,
|
||||
KmsModule.class,
|
||||
NetHttpTransportModule.class,
|
||||
PersistenceModule.class,
|
||||
PubApiRequestComponentModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.module.tools;
|
||||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import dagger.Component;
|
||||
import dagger.Lazy;
|
||||
import google.registry.config.CloudTasksUtilsModule;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.export.DriveModule;
|
||||
@@ -49,6 +50,7 @@ import javax.inject.Singleton;
|
||||
ConfigModule.class,
|
||||
CredentialModule.class,
|
||||
CustomLogicFactoryModule.class,
|
||||
CloudTasksUtilsModule.class,
|
||||
DatastoreServiceModule.class,
|
||||
DirectoryModule.class,
|
||||
DummyKeyringModule.class,
|
||||
|
||||
@@ -19,6 +19,7 @@ import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.persistence.PersistenceModule.AppEngineJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.ReadOnlyReplicaJpaTm;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.util.UtilsModule;
|
||||
@@ -40,4 +41,7 @@ public interface PersistenceComponent {
|
||||
|
||||
@AppEngineJpaTm
|
||||
JpaTransactionManager appEngineJpaTransactionManager();
|
||||
|
||||
@ReadOnlyReplicaJpaTm
|
||||
JpaTransactionManager readOnlyReplicaJpaTransactionManager();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import static google.registry.config.RegistryConfig.getHibernateHikariConnection
|
||||
import static google.registry.config.RegistryConfig.getHibernateHikariIdleTimeout;
|
||||
import static google.registry.config.RegistryConfig.getHibernateHikariMaximumPoolSize;
|
||||
import static google.registry.config.RegistryConfig.getHibernateHikariMinimumIdle;
|
||||
import static google.registry.config.RegistryConfig.getHibernateJdbcBatchSize;
|
||||
import static google.registry.config.RegistryConfig.getHibernateJdbcFetchSize;
|
||||
import static google.registry.config.RegistryConfig.getHibernateLogSqlQueries;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
@@ -76,15 +78,9 @@ public abstract class PersistenceModule {
|
||||
public static final String HIKARI_DS_CLOUD_SQL_INSTANCE =
|
||||
"hibernate.hikari.dataSource.cloudSqlInstance";
|
||||
|
||||
/**
|
||||
* Postgresql-specific: driver default fetch size is 0, which disables streaming result sets. Here
|
||||
* we set a small default geared toward Nomulus server transactions. Large queries can override
|
||||
* the defaults using {@link JpaTransactionManager#setQueryFetchSize}.
|
||||
*/
|
||||
public static final String JDBC_BATCH_SIZE = "hibernate.jdbc.batch_size";
|
||||
public static final String JDBC_FETCH_SIZE = "hibernate.jdbc.fetch_size";
|
||||
|
||||
private static final int DEFAULT_SERVER_FETCH_SIZE = 20;
|
||||
|
||||
@VisibleForTesting
|
||||
@Provides
|
||||
@DefaultHibernateConfigs
|
||||
@@ -111,7 +107,8 @@ public abstract class PersistenceModule {
|
||||
properties.put(HIKARI_MAXIMUM_POOL_SIZE, getHibernateHikariMaximumPoolSize());
|
||||
properties.put(HIKARI_IDLE_TIMEOUT, getHibernateHikariIdleTimeout());
|
||||
properties.put(Environment.DIALECT, NomulusPostgreSQLDialect.class.getName());
|
||||
properties.put(JDBC_FETCH_SIZE, Integer.toString(DEFAULT_SERVER_FETCH_SIZE));
|
||||
properties.put(JDBC_BATCH_SIZE, getHibernateJdbcBatchSize());
|
||||
properties.put(JDBC_FETCH_SIZE, getHibernateJdbcFetchSize());
|
||||
return properties.build();
|
||||
}
|
||||
|
||||
@@ -122,8 +119,11 @@ public abstract class PersistenceModule {
|
||||
@Config("cloudSqlJdbcUrl") String jdbcUrl,
|
||||
@Config("cloudSqlInstanceConnectionName") String instanceConnectionName,
|
||||
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs) {
|
||||
return createPartialSqlConfigs(
|
||||
jdbcUrl, instanceConnectionName, defaultConfigs, Optional.empty());
|
||||
HashMap<String, String> overrides = Maps.newHashMap(defaultConfigs);
|
||||
overrides.put(Environment.URL, jdbcUrl);
|
||||
overrides.put(HIKARI_DS_SOCKET_FACTORY, "com.google.cloud.sql.postgres.SocketFactory");
|
||||
overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, instanceConnectionName);
|
||||
return ImmutableMap.copyOf(overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,22 +184,6 @@ public abstract class PersistenceModule {
|
||||
return ImmutableMap.copyOf(overrides);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static ImmutableMap<String, String> createPartialSqlConfigs(
|
||||
String jdbcUrl,
|
||||
String instanceConnectionName,
|
||||
ImmutableMap<String, String> defaultConfigs,
|
||||
Optional<Provider<TransactionIsolationLevel>> isolationOverride) {
|
||||
HashMap<String, String> overrides = Maps.newHashMap(defaultConfigs);
|
||||
overrides.put(Environment.URL, jdbcUrl);
|
||||
overrides.put(HIKARI_DS_SOCKET_FACTORY, "com.google.cloud.sql.postgres.SocketFactory");
|
||||
overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, instanceConnectionName);
|
||||
isolationOverride
|
||||
.map(Provider::get)
|
||||
.ifPresent(override -> overrides.put(Environment.ISOLATION, override.name()));
|
||||
return ImmutableMap.copyOf(overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link Supplier} of single-use JDBC {@link Connection connections} that can manage
|
||||
* the database DDL schema.
|
||||
@@ -280,6 +264,40 @@ public abstract class PersistenceModule {
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ReadOnlyReplicaJpaTm
|
||||
static JpaTransactionManager provideReadOnlyReplicaJpaTm(
|
||||
SqlCredentialStore credentialStore,
|
||||
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs,
|
||||
@Config("cloudSqlReplicaInstanceConnectionName")
|
||||
Optional<String> replicaInstanceConnectionName,
|
||||
Clock clock) {
|
||||
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
|
||||
setSqlCredential(credentialStore, new RobotUser(RobotId.NOMULUS), overrides);
|
||||
replicaInstanceConnectionName.ifPresent(
|
||||
name -> overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name));
|
||||
overrides.put(
|
||||
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_READ_COMMITTED.name());
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@BeamReadOnlyReplicaJpaTm
|
||||
static JpaTransactionManager provideBeamReadOnlyReplicaJpaTm(
|
||||
@BeamPipelineCloudSqlConfigs ImmutableMap<String, String> beamCloudSqlConfigs,
|
||||
@Config("cloudSqlReplicaInstanceConnectionName")
|
||||
Optional<String> replicaInstanceConnectionName,
|
||||
Clock clock) {
|
||||
HashMap<String, String> overrides = Maps.newHashMap(beamCloudSqlConfigs);
|
||||
replicaInstanceConnectionName.ifPresent(
|
||||
name -> overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name));
|
||||
overrides.put(
|
||||
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_READ_COMMITTED.name());
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||
}
|
||||
|
||||
/** Constructs the {@link EntityManagerFactory} instance. */
|
||||
@VisibleForTesting
|
||||
static EntityManagerFactory create(
|
||||
@@ -357,7 +375,12 @@ public abstract class PersistenceModule {
|
||||
* The {@link JpaTransactionManager} optimized for bulk loading multi-level JPA entities. Please
|
||||
* see {@link google.registry.model.bulkquery.BulkQueryEntities} for more information.
|
||||
*/
|
||||
BULK_QUERY
|
||||
BULK_QUERY,
|
||||
/**
|
||||
* The {@link JpaTransactionManager} that uses the read-only Postgres replica if configured, or
|
||||
* the standard DB if not.
|
||||
*/
|
||||
READ_ONLY_REPLICA
|
||||
}
|
||||
|
||||
/** Dagger qualifier for JDBC {@link Connection} with schema management privilege. */
|
||||
@@ -383,6 +406,22 @@ public abstract class PersistenceModule {
|
||||
@Documented
|
||||
public @interface BeamBulkQueryJpaTm {}
|
||||
|
||||
/**
|
||||
* Dagger qualifier for {@link JpaTransactionManager} used inside BEAM pipelines that uses the
|
||||
* read-only Postgres replica if one is configured (otherwise it uses the standard DB).
|
||||
*/
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface BeamReadOnlyReplicaJpaTm {}
|
||||
|
||||
/**
|
||||
* Dagger qualifier for {@link JpaTransactionManager} that uses the read-only Postgres replica if
|
||||
* one is configured (otherwise it uses the standard DB).
|
||||
*/
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface ReadOnlyReplicaJpaTm {}
|
||||
|
||||
/** Dagger qualifier for {@link JpaTransactionManager} used for Nomulus tool. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
|
||||
@@ -144,7 +144,7 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||
*/
|
||||
public static <T> VKey<T> create(String keyString) {
|
||||
if (!keyString.startsWith(CLASS_TYPE + KV_SEPARATOR)) {
|
||||
// to handle the existing ofy key string
|
||||
// handle the existing ofy key string
|
||||
return fromWebsafeKey(keyString);
|
||||
} else {
|
||||
ImmutableMap<String, String> kvs =
|
||||
@@ -307,6 +307,7 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||
if (maybeGetSqlKey().isPresent()) {
|
||||
key += DELIMITER + SQL_LOOKUP_KEY + KV_SEPARATOR + SerializeUtils.stringify(getSqlKey());
|
||||
}
|
||||
// getString() method returns a Base64 encoded web safe of ofy key
|
||||
if (maybeGetOfyKey().isPresent()) {
|
||||
key += DELIMITER + OFY_LOOKUP_KEY + KV_SEPARATOR + getOfyKey().getString();
|
||||
}
|
||||
|
||||
+14
-12
@@ -18,7 +18,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.Collection;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Expression;
|
||||
@@ -42,12 +41,14 @@ public class CriteriaQueryBuilder<T> {
|
||||
|
||||
private final CriteriaQuery<T> query;
|
||||
private final Root<?> root;
|
||||
private final JpaTransactionManager jpaTm;
|
||||
private final ImmutableList.Builder<Predicate> predicates = new ImmutableList.Builder<>();
|
||||
private final ImmutableList.Builder<Order> orders = new ImmutableList.Builder<>();
|
||||
|
||||
private CriteriaQueryBuilder(CriteriaQuery<T> query, Root<?> root) {
|
||||
private CriteriaQueryBuilder(CriteriaQuery<T> query, Root<?> root, JpaTransactionManager jpaTm) {
|
||||
this.query = query;
|
||||
this.root = root;
|
||||
this.jpaTm = jpaTm;
|
||||
}
|
||||
|
||||
/** Adds a WHERE clause to the query, given the specified operation, field, and value. */
|
||||
@@ -75,18 +76,18 @@ public class CriteriaQueryBuilder<T> {
|
||||
*/
|
||||
public <V> CriteriaQueryBuilder<T> whereFieldContains(String fieldName, Object value) {
|
||||
return where(
|
||||
jpaTm().getEntityManager().getCriteriaBuilder().isMember(value, root.get(fieldName)));
|
||||
jpaTm.getEntityManager().getCriteriaBuilder().isMember(value, root.get(fieldName)));
|
||||
}
|
||||
|
||||
/** Orders the result by the given field ascending. */
|
||||
public CriteriaQueryBuilder<T> orderByAsc(String fieldName) {
|
||||
orders.add(jpaTm().getEntityManager().getCriteriaBuilder().asc(root.get(fieldName)));
|
||||
orders.add(jpaTm.getEntityManager().getCriteriaBuilder().asc(root.get(fieldName)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Orders the result by the given field descending. */
|
||||
public CriteriaQueryBuilder<T> orderByDesc(String fieldName) {
|
||||
orders.add(jpaTm().getEntityManager().getCriteriaBuilder().desc(root.get(fieldName)));
|
||||
orders.add(jpaTm.getEntityManager().getCriteriaBuilder().desc(root.get(fieldName)));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -103,23 +104,24 @@ public class CriteriaQueryBuilder<T> {
|
||||
|
||||
/** Creates a query builder that will SELECT from the given class. */
|
||||
public static <T> CriteriaQueryBuilder<T> create(Class<T> clazz) {
|
||||
return create(jpaTm().getEntityManager(), clazz);
|
||||
return create(jpaTm(), clazz);
|
||||
}
|
||||
|
||||
/** Creates a query builder for the given entity manager. */
|
||||
public static <T> CriteriaQueryBuilder<T> create(EntityManager em, Class<T> clazz) {
|
||||
CriteriaQuery<T> query = em.getCriteriaBuilder().createQuery(clazz);
|
||||
public static <T> CriteriaQueryBuilder<T> create(JpaTransactionManager jpaTm, Class<T> clazz) {
|
||||
CriteriaQuery<T> query = jpaTm.getEntityManager().getCriteriaBuilder().createQuery(clazz);
|
||||
Root<T> root = query.from(clazz);
|
||||
query = query.select(root);
|
||||
return new CriteriaQueryBuilder<>(query, root);
|
||||
return new CriteriaQueryBuilder<>(query, root, jpaTm);
|
||||
}
|
||||
|
||||
/** Creates a "count" query for the table for the class. */
|
||||
public static <T> CriteriaQueryBuilder<Long> createCount(EntityManager em, Class<T> clazz) {
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
public static <T> CriteriaQueryBuilder<Long> createCount(
|
||||
JpaTransactionManager jpaTm, Class<T> clazz) {
|
||||
CriteriaBuilder builder = jpaTm.getEntityManager().getCriteriaBuilder();
|
||||
CriteriaQuery<Long> query = builder.createQuery(Long.class);
|
||||
Root<T> root = query.from(clazz);
|
||||
query = query.select(builder.count(root));
|
||||
return new CriteriaQueryBuilder<>(query, root);
|
||||
return new CriteriaQueryBuilder<>(query, root, jpaTm);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -141,14 +141,15 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
// Postgresql-specific: 'set transaction' command must be called inside a transaction
|
||||
assertInTransaction();
|
||||
|
||||
EntityManager entityManager = getEntityManager();
|
||||
ReadOnlyCheckingEntityManager entityManager =
|
||||
(ReadOnlyCheckingEntityManager) getEntityManager();
|
||||
// Isolation is hardcoded to REPEATABLE READ, as specified by parent's Javadoc.
|
||||
entityManager
|
||||
.createNativeQuery("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
|
||||
.executeUpdate();
|
||||
.executeUpdateIgnoringReadOnly();
|
||||
entityManager
|
||||
.createNativeQuery(String.format("SET TRANSACTION SNAPSHOT '%s'", snapshotId))
|
||||
.executeUpdate();
|
||||
.executeUpdateIgnoringReadOnly();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1130,7 +1131,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
|
||||
private TypedQuery<T> buildQuery() {
|
||||
CriteriaQueryBuilder<T> queryBuilder =
|
||||
CriteriaQueryBuilder.create(getEntityManager(), entityClass);
|
||||
CriteriaQueryBuilder.create(JpaTransactionManagerImpl.this, entityClass);
|
||||
return addCriteria(queryBuilder);
|
||||
}
|
||||
|
||||
@@ -1177,7 +1178,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
@Override
|
||||
public long count() {
|
||||
CriteriaQueryBuilder<Long> queryBuilder =
|
||||
CriteriaQueryBuilder.createCount(getEntityManager(), entityClass);
|
||||
CriteriaQueryBuilder.createCount(JpaTransactionManagerImpl.this, entityClass);
|
||||
return addCriteria(queryBuilder).getSingleResult();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -206,7 +206,7 @@ public class ReadOnlyCheckingEntityManager implements EntityManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createNativeQuery(String sqlString) {
|
||||
public ReadOnlyCheckingQuery createNativeQuery(String sqlString) {
|
||||
return new ReadOnlyCheckingQuery(delegate.createNativeQuery(sqlString));
|
||||
}
|
||||
|
||||
|
||||
+40
-3
@@ -14,8 +14,8 @@
|
||||
|
||||
package google.registry.persistence.transaction;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.appengine.api.utils.SystemProperty;
|
||||
@@ -47,6 +47,10 @@ public final class TransactionManagerFactory {
|
||||
private static Supplier<JpaTransactionManager> jpaTm =
|
||||
Suppliers.memoize(TransactionManagerFactory::createJpaTransactionManager);
|
||||
|
||||
@NonFinalForTesting
|
||||
private static Supplier<JpaTransactionManager> replicaJpaTm =
|
||||
Suppliers.memoize(TransactionManagerFactory::createReplicaJpaTransactionManager);
|
||||
|
||||
private static boolean onBeam = false;
|
||||
|
||||
private TransactionManagerFactory() {}
|
||||
@@ -61,6 +65,14 @@ public final class TransactionManagerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private static JpaTransactionManager createReplicaJpaTransactionManager() {
|
||||
if (isInAppEngine()) {
|
||||
return DaggerPersistenceComponent.create().readOnlyReplicaJpaTransactionManager();
|
||||
} else {
|
||||
return DummyJpaTransactionManager.create();
|
||||
}
|
||||
}
|
||||
|
||||
private static DatastoreTransactionManager createTransactionManager() {
|
||||
return new DatastoreTransactionManager(null);
|
||||
}
|
||||
@@ -108,6 +120,21 @@ public final class TransactionManagerFactory {
|
||||
return jpaTm.get();
|
||||
}
|
||||
|
||||
/** Returns a read-only {@link JpaTransactionManager} instance if configured. */
|
||||
public static JpaTransactionManager replicaJpaTm() {
|
||||
return replicaJpaTm.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TransactionManager} that uses a replica database if one exists.
|
||||
*
|
||||
* <p>In Datastore mode, this is unchanged from the regular transaction manager. In SQL mode,
|
||||
* however, this will be a reference to the read-only replica database if one is configured.
|
||||
*/
|
||||
public static TransactionManager replicaTm() {
|
||||
return tm().isOfy() ? tm() : replicaJpaTm();
|
||||
}
|
||||
|
||||
/** Returns {@link DatastoreTransactionManager} instance. */
|
||||
@VisibleForTesting
|
||||
public static DatastoreTransactionManager ofyTm() {
|
||||
@@ -116,7 +143,7 @@ public final class TransactionManagerFactory {
|
||||
|
||||
/** Sets the return of {@link #jpaTm()} to the given instance of {@link JpaTransactionManager}. */
|
||||
public static void setJpaTm(Supplier<JpaTransactionManager> jpaTmSupplier) {
|
||||
checkNotNull(jpaTmSupplier, "jpaTmSupplier");
|
||||
checkArgumentNotNull(jpaTmSupplier, "jpaTmSupplier");
|
||||
checkState(
|
||||
RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
||||
|| RegistryToolEnvironment.get() != null,
|
||||
@@ -124,13 +151,23 @@ public final class TransactionManagerFactory {
|
||||
jpaTm = Suppliers.memoize(jpaTmSupplier::get);
|
||||
}
|
||||
|
||||
/** Sets the value of {@link #replicaJpaTm()} to the given {@link JpaTransactionManager}. */
|
||||
public static void setReplicaJpaTm(Supplier<JpaTransactionManager> replicaJpaTmSupplier) {
|
||||
checkArgumentNotNull(replicaJpaTmSupplier, "replicaJpaTmSupplier");
|
||||
checkState(
|
||||
RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
||||
|| RegistryToolEnvironment.get() != null,
|
||||
"setReplicaJpaTm() should only be called by tools and tests.");
|
||||
replicaJpaTm = Suppliers.memoize(replicaJpaTmSupplier::get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes {@link #jpaTm()} return the {@link JpaTransactionManager} instance provided by {@code
|
||||
* jpaTmSupplier} from now on. This method should only be called by an implementor of {@link
|
||||
* org.apache.beam.sdk.harness.JvmInitializer}.
|
||||
*/
|
||||
public static void setJpaTmOnBeamWorker(Supplier<JpaTransactionManager> jpaTmSupplier) {
|
||||
checkNotNull(jpaTmSupplier, "jpaTmSupplier");
|
||||
checkArgumentNotNull(jpaTmSupplier, "jpaTmSupplier");
|
||||
jpaTm = Suppliers.memoize(jpaTmSupplier::get);
|
||||
onBeam = true;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
@@ -91,7 +91,9 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
@Inject @Parameter("name") Optional<String> nameParam;
|
||||
@Inject @Parameter("nsLdhName") Optional<String> nsLdhNameParam;
|
||||
@Inject @Parameter("nsIp") Optional<String> nsIpParam;
|
||||
@Inject public RdapDomainSearchAction() {
|
||||
|
||||
@Inject
|
||||
public RdapDomainSearchAction() {
|
||||
super("domain search", EndpointType.DOMAINS);
|
||||
}
|
||||
|
||||
@@ -223,13 +225,13 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
resultSet = getMatchingResources(query, true, querySizeLimit);
|
||||
} else {
|
||||
resultSet =
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
CriteriaBuilder criteriaBuilder =
|
||||
jpaTm().getEntityManager().getCriteriaBuilder();
|
||||
replicaJpaTm().getEntityManager().getCriteriaBuilder();
|
||||
CriteriaQueryBuilder<DomainBase> queryBuilder =
|
||||
CriteriaQueryBuilder.create(DomainBase.class)
|
||||
CriteriaQueryBuilder.create(replicaJpaTm(), DomainBase.class)
|
||||
.where(
|
||||
"fullyQualifiedDomainName",
|
||||
criteriaBuilder::like,
|
||||
@@ -270,7 +272,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
resultSet = getMatchingResources(query, true, querySizeLimit);
|
||||
} else {
|
||||
resultSet =
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
CriteriaQueryBuilder<DomainBase> builder =
|
||||
@@ -354,7 +356,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
.map(VKey::from)
|
||||
.collect(toImmutableSet());
|
||||
} else {
|
||||
return jpaTm()
|
||||
return replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
CriteriaQueryBuilder<HostResource> builder =
|
||||
@@ -368,11 +370,12 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
builder =
|
||||
builder.where(
|
||||
"currentSponsorClientId",
|
||||
jpaTm().getEntityManager().getCriteriaBuilder()::equal,
|
||||
replicaJpaTm().getEntityManager().getCriteriaBuilder()::equal,
|
||||
desiredRegistrar.get());
|
||||
}
|
||||
return getMatchingResourcesSql(builder, true, maxNameserversInFirstStage)
|
||||
.resources().stream()
|
||||
.resources()
|
||||
.stream()
|
||||
.map(HostResource::createVKey)
|
||||
.collect(toImmutableSet());
|
||||
});
|
||||
@@ -509,11 +512,11 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
parameters.put("desiredRegistrar", desiredRegistrar.get());
|
||||
}
|
||||
hostKeys =
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
javax.persistence.Query query =
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery(queryBuilder.toString())
|
||||
.setMaxResults(maxNameserversInFirstStage);
|
||||
@@ -568,16 +571,16 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
}
|
||||
stream.forEach(domainSetBuilder::add);
|
||||
} else {
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
for (VKey<HostResource> hostKey : hostKeys) {
|
||||
CriteriaQueryBuilder<DomainBase> queryBuilder =
|
||||
CriteriaQueryBuilder.create(DomainBase.class)
|
||||
CriteriaQueryBuilder.create(replicaJpaTm(), DomainBase.class)
|
||||
.whereFieldContains("nsHosts", hostKey)
|
||||
.orderByAsc("fullyQualifiedDomainName");
|
||||
CriteriaBuilder criteriaBuilder =
|
||||
jpaTm().getEntityManager().getCriteriaBuilder();
|
||||
replicaJpaTm().getEntityManager().getCriteriaBuilder();
|
||||
if (!shouldIncludeDeleted()) {
|
||||
queryBuilder =
|
||||
queryBuilder.where(
|
||||
@@ -590,7 +593,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
criteriaBuilder::greaterThan,
|
||||
cursorString.get());
|
||||
}
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.criteriaQuery(queryBuilder.build())
|
||||
.getResultStream()
|
||||
.filter(this::isAuthorized)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||
import static google.registry.rdap.RdapUtils.getRegistrarByIanaIdentifier;
|
||||
@@ -277,7 +277,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
resultSet = getMatchingResources(query, false, rdapResultSetMaxSize + 1);
|
||||
} else {
|
||||
resultSet =
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
CriteriaQueryBuilder<ContactResource> builder =
|
||||
@@ -399,7 +399,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
querySizeLimit);
|
||||
} else {
|
||||
contactResultSet =
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
getMatchingResourcesSql(
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
@@ -233,7 +233,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
return makeSearchResults(
|
||||
getMatchingResources(query, shouldIncludeDeleted(), querySizeLimit), CursorType.NAME);
|
||||
} else {
|
||||
return jpaTm()
|
||||
return replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
CriteriaQueryBuilder<HostResource> queryBuilder =
|
||||
@@ -290,11 +290,11 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
}
|
||||
queryBuilder.append(" ORDER BY repo_id ASC");
|
||||
rdapResultSet =
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
javax.persistence.Query query =
|
||||
jpaTm()
|
||||
replicaJpaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery(queryBuilder.toString(), HostResource.class)
|
||||
.setMaxResults(querySizeLimit);
|
||||
|
||||
@@ -16,7 +16,7 @@ package google.registry.rdap;
|
||||
|
||||
import static com.google.common.base.Charsets.UTF_8;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -193,16 +193,17 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
*/
|
||||
<T extends EppResource> RdapResultSet<T> getMatchingResourcesSql(
|
||||
CriteriaQueryBuilder<T> builder, boolean checkForVisibility, int querySizeLimit) {
|
||||
jpaTm().assertInTransaction();
|
||||
replicaJpaTm().assertInTransaction();
|
||||
Optional<String> desiredRegistrar = getDesiredRegistrar();
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
builder =
|
||||
builder.where(
|
||||
"currentSponsorClientId", jpaTm().getEntityManager().getCriteriaBuilder()::equal,
|
||||
"currentSponsorClientId",
|
||||
replicaJpaTm().getEntityManager().getCriteriaBuilder()::equal,
|
||||
desiredRegistrar.get());
|
||||
}
|
||||
List<T> queryResult =
|
||||
jpaTm().criteriaQuery(builder.build()).setMaxResults(querySizeLimit).getResultList();
|
||||
replicaJpaTm().criteriaQuery(builder.build()).setMaxResults(querySizeLimit).getResultList();
|
||||
if (checkForVisibility) {
|
||||
return filterResourcesByVisibility(queryResult, querySizeLimit);
|
||||
} else {
|
||||
@@ -395,7 +396,7 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
RdapSearchPattern partialStringQuery,
|
||||
Optional<String> cursorString,
|
||||
DeletedItemHandling deletedItemHandling) {
|
||||
jpaTm().assertInTransaction();
|
||||
replicaJpaTm().assertInTransaction();
|
||||
if (partialStringQuery.getInitialString().length()
|
||||
< RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
|
||||
throw new UnprocessableEntityException(
|
||||
@@ -403,8 +404,8 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
"Initial search string must be at least %d characters",
|
||||
RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
|
||||
}
|
||||
CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder();
|
||||
CriteriaQueryBuilder<T> builder = CriteriaQueryBuilder.create(clazz);
|
||||
CriteriaBuilder criteriaBuilder = replicaJpaTm().getEntityManager().getCriteriaBuilder();
|
||||
CriteriaQueryBuilder<T> builder = CriteriaQueryBuilder.create(replicaJpaTm(), clazz);
|
||||
if (partialStringQuery.getHasWildcard()) {
|
||||
builder =
|
||||
builder.where(
|
||||
@@ -493,9 +494,9 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
"Initial search string must be at least %d characters",
|
||||
RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
|
||||
}
|
||||
jpaTm().assertInTransaction();
|
||||
CriteriaQueryBuilder<T> builder = CriteriaQueryBuilder.create(clazz);
|
||||
CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder();
|
||||
replicaJpaTm().assertInTransaction();
|
||||
CriteriaQueryBuilder<T> builder = CriteriaQueryBuilder.create(replicaJpaTm(), clazz);
|
||||
CriteriaBuilder criteriaBuilder = replicaJpaTm().getEntityManager().getCriteriaBuilder();
|
||||
builder = builder.where(filterField, criteriaBuilder::equal, queryString);
|
||||
if (cursorString.isPresent()) {
|
||||
if (cursorField.isPresent()) {
|
||||
@@ -544,7 +545,7 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
RdapSearchPattern partialStringQuery,
|
||||
Optional<String> cursorString,
|
||||
DeletedItemHandling deletedItemHandling) {
|
||||
jpaTm().assertInTransaction();
|
||||
replicaJpaTm().assertInTransaction();
|
||||
return queryItemsSql(clazz, "repoId", partialStringQuery, cursorString, deletedItemHandling);
|
||||
}
|
||||
|
||||
@@ -553,7 +554,9 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
if (!Objects.equals(deletedItemHandling, DeletedItemHandling.INCLUDE)) {
|
||||
builder =
|
||||
builder.where(
|
||||
"deletionTime", jpaTm().getEntityManager().getCriteriaBuilder()::equal, END_OF_TIME);
|
||||
"deletionTime",
|
||||
replicaJpaTm().getEntityManager().getCriteriaBuilder()::equal,
|
||||
END_OF_TIME);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.model.rde.RdeNamingUtils;
|
||||
import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
@@ -86,7 +87,13 @@ public final class BrdaCopyAction implements Runnable {
|
||||
}
|
||||
|
||||
private void copyAsRyde() throws IOException {
|
||||
String nameWithoutPrefix = RdeNamingUtils.makeRydeFilename(tld, watermark, THIN, 1, 0);
|
||||
// TODO(b/217772483): consider guarding this action with a lock and check if there is work.
|
||||
// Not urgent since file writes on GCS are atomic.
|
||||
int revision =
|
||||
RdeRevision.getCurrentRevision(tld, watermark, THIN)
|
||||
.orElseThrow(
|
||||
() -> new IllegalStateException("RdeRevision was not set on generated deposit"));
|
||||
String nameWithoutPrefix = RdeNamingUtils.makeRydeFilename(tld, watermark, THIN, 1, revision);
|
||||
String name = prefix.orElse("") + nameWithoutPrefix;
|
||||
BlobId xmlFilename = BlobId.of(stagingBucket, name + ".xml.ghostryde");
|
||||
BlobId xmlLengthFilename = BlobId.of(stagingBucket, name + ".xml.length");
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static google.registry.request.RequestParameters.extractBooleanParameter;
|
||||
import static google.registry.request.RequestParameters.extractOptionalIntParameter;
|
||||
import static google.registry.request.RequestParameters.extractOptionalParameter;
|
||||
@@ -22,7 +21,6 @@ import static google.registry.request.RequestParameters.extractRequiredDatetimeP
|
||||
import static google.registry.request.RequestParameters.extractSetOfDatetimeParameters;
|
||||
import static google.registry.request.RequestParameters.extractSetOfParameters;
|
||||
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.jcraft.jsch.SftpProgressMonitor;
|
||||
import dagger.Binds;
|
||||
@@ -30,7 +28,6 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.request.Parameter;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Named;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -110,12 +107,6 @@ public abstract class RdeModule {
|
||||
return extractOptionalParameter(req, PARAM_PREFIX);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("rde-report")
|
||||
static Queue provideQueueRdeReport() {
|
||||
return getQueue("rde-report");
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract SftpProgressMonitor provideSftpProgressMonitor(
|
||||
LoggingSftpProgressMonitor loggingSftpProgressMonitor);
|
||||
|
||||
@@ -56,6 +56,7 @@ import google.registry.model.host.HostResource;
|
||||
import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.PersistenceModule.JpaTransactionManagerType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.Parameter;
|
||||
@@ -340,6 +341,9 @@ public final class RdeStagingAction implements Runnable {
|
||||
.encode(stagingKeyBytes))
|
||||
.put("registryEnvironment", RegistryEnvironment.get().name())
|
||||
.put("workerMachineType", machineType)
|
||||
.put(
|
||||
"jpaTransactionManagerType",
|
||||
JpaTransactionManagerType.READ_ONLY_REPLICA.toString())
|
||||
// TODO (jianglai): Investigate turning off public IPs (for which
|
||||
// there is a quota) in order to increase the total number of
|
||||
// workers allowed (also under quota).
|
||||
@@ -387,9 +391,6 @@ public final class RdeStagingAction implements Runnable {
|
||||
if (revision.isPresent()) {
|
||||
throw new BadRequestException("Revision parameter not allowed in standard operation");
|
||||
}
|
||||
if (beam) {
|
||||
throw new BadRequestException("Beam parameter not allowed in standard operation");
|
||||
}
|
||||
|
||||
return ImmutableSetMultimap.copyOf(
|
||||
Multimaps.filterValues(
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static google.registry.model.common.Cursor.getCursorTimeOrStartOfTime;
|
||||
@@ -26,6 +24,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import com.google.appengine.tools.mapreduce.Reducer;
|
||||
import com.google.appengine.tools.mapreduce.ReducerInput;
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
@@ -36,10 +35,11 @@ import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.rde.RdeNamingUtils;
|
||||
import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.lock.LockHandler;
|
||||
import google.registry.tldconfig.idn.IdnTableEnum;
|
||||
import google.registry.util.TaskQueueUtils;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.xjc.rdeheader.XjcRdeHeader;
|
||||
import google.registry.xjc.rdeheader.XjcRdeHeaderElement;
|
||||
import google.registry.xml.ValidationMode;
|
||||
@@ -65,7 +65,7 @@ public final class RdeStagingReducer extends Reducer<PendingDeposit, DepositFrag
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final TaskQueueUtils taskQueueUtils;
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
private final LockHandler lockHandler;
|
||||
private final String bucket;
|
||||
private final Duration lockTimeout;
|
||||
@@ -74,14 +74,14 @@ public final class RdeStagingReducer extends Reducer<PendingDeposit, DepositFrag
|
||||
private final GcsUtils gcsUtils;
|
||||
|
||||
RdeStagingReducer(
|
||||
TaskQueueUtils taskQueueUtils,
|
||||
CloudTasksUtils cloudTasksUtils,
|
||||
LockHandler lockHandler,
|
||||
String bucket,
|
||||
Duration lockTimeout,
|
||||
byte[] stagingKeyBytes,
|
||||
ValidationMode validationMode,
|
||||
GcsUtils gcsUtils) {
|
||||
this.taskQueueUtils = taskQueueUtils;
|
||||
this.cloudTasksUtils = cloudTasksUtils;
|
||||
this.lockHandler = lockHandler;
|
||||
this.bucket = bucket;
|
||||
this.lockTimeout = lockTimeout;
|
||||
@@ -226,23 +226,35 @@ public final class RdeStagingReducer extends Reducer<PendingDeposit, DepositFrag
|
||||
logger.atInfo().log(
|
||||
"Rolled forward %s on %s cursor to %s.", key.cursor(), tld, newPosition);
|
||||
RdeRevision.saveRevision(tld, watermark, mode, revision);
|
||||
// Enqueueing a task is a side effect that is not undone if the transaction rolls
|
||||
// back. So this may result in multiple copies of the same task being processed. This
|
||||
// is fine because the RdeUploadAction is guarded by a lock and tracks progress by
|
||||
// cursor. The BrdaCopyAction writes a file to GCS, which is an atomic action.
|
||||
if (mode == RdeMode.FULL) {
|
||||
taskQueueUtils.enqueue(
|
||||
getQueue("rde-upload"),
|
||||
withUrl(RdeUploadAction.PATH).param(RequestParameters.PARAM_TLD, tld));
|
||||
cloudTasksUtils.enqueue(
|
||||
"rde-upload",
|
||||
CloudTasksUtils.createPostTask(
|
||||
RdeUploadAction.PATH,
|
||||
Service.BACKEND.toString(),
|
||||
ImmutableMultimap.of(RequestParameters.PARAM_TLD, tld)));
|
||||
} else {
|
||||
taskQueueUtils.enqueue(
|
||||
getQueue("brda"),
|
||||
withUrl(BrdaCopyAction.PATH)
|
||||
.param(RequestParameters.PARAM_TLD, tld)
|
||||
.param(RdeModule.PARAM_WATERMARK, watermark.toString()));
|
||||
cloudTasksUtils.enqueue(
|
||||
"brda",
|
||||
CloudTasksUtils.createPostTask(
|
||||
BrdaCopyAction.PATH,
|
||||
Service.BACKEND.toString(),
|
||||
ImmutableMultimap.of(
|
||||
RequestParameters.PARAM_TLD,
|
||||
tld,
|
||||
RdeModule.PARAM_WATERMARK,
|
||||
watermark.toString())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Injectible factory for creating {@link RdeStagingReducer}. */
|
||||
static class Factory {
|
||||
@Inject TaskQueueUtils taskQueueUtils;
|
||||
@Inject CloudTasksUtils cloudTasksUtils;
|
||||
@Inject LockHandler lockHandler;
|
||||
@Inject @Config("rdeBucket") String bucket;
|
||||
@Inject @Config("rdeStagingLockTimeout") Duration lockTimeout;
|
||||
@@ -252,7 +264,7 @@ public final class RdeStagingReducer extends Reducer<PendingDeposit, DepositFrag
|
||||
|
||||
RdeStagingReducer create(ValidationMode validationMode, GcsUtils gcsUtils) {
|
||||
return new RdeStagingReducer(
|
||||
taskQueueUtils,
|
||||
cloudTasksUtils,
|
||||
lockHandler,
|
||||
bucket,
|
||||
lockTimeout,
|
||||
|
||||
@@ -40,6 +40,8 @@ public class ReportingModule {
|
||||
|
||||
public static final String BEAM_QUEUE = "beam-reporting";
|
||||
|
||||
/** The amount of time expected for the Dataflow jobs to complete. */
|
||||
public static final int ENQUEUE_DELAY_MINUTES = 10;
|
||||
/**
|
||||
* The request parameter name used by reporting actions that takes a year/month parameter, which
|
||||
* defaults to the last month.
|
||||
|
||||
@@ -1,38 +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.reporting;
|
||||
|
||||
import com.google.appengine.api.taskqueue.QueueFactory;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import java.util.Map;
|
||||
import org.joda.time.Duration;
|
||||
import org.joda.time.YearMonth;
|
||||
|
||||
/** Static methods common to various reporting tasks. */
|
||||
public class ReportingUtils {
|
||||
|
||||
private static final int ENQUEUE_DELAY_MINUTES = 10;
|
||||
|
||||
/** Enqueues a task that takes a Beam jobId and the {@link YearMonth} as parameters. */
|
||||
public static void enqueueBeamReportingTask(String path, Map<String, String> parameters) {
|
||||
TaskOptions publishTask =
|
||||
TaskOptions.Builder.withUrl(path)
|
||||
.method(TaskOptions.Method.POST)
|
||||
// Dataflow jobs tend to take about 10 minutes to complete.
|
||||
.countdownMillis(Duration.standardMinutes(ENQUEUE_DELAY_MINUTES).getMillis());
|
||||
parameters.forEach(publishTask::param);
|
||||
QueueFactory.getQueue(ReportingModule.BEAM_QUEUE).add(publishTask);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ package google.registry.reporting.billing;
|
||||
import static google.registry.beam.BeamUtils.createJobName;
|
||||
import static google.registry.model.common.DatabaseMigrationStateSchedule.PrimaryDatabase.CLOUD_SQL;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.reporting.ReportingUtils.enqueueBeamReportingTask;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
@@ -27,21 +26,25 @@ 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.ImmutableMultimap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.PrimaryDatabase;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.Duration;
|
||||
import org.joda.time.YearMonth;
|
||||
|
||||
/**
|
||||
@@ -75,6 +78,7 @@ public class GenerateInvoicesAction implements Runnable {
|
||||
private final Response response;
|
||||
private final Dataflow dataflow;
|
||||
private final PrimaryDatabase database;
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
@Inject
|
||||
GenerateInvoicesAction(
|
||||
@@ -87,6 +91,7 @@ public class GenerateInvoicesAction implements Runnable {
|
||||
@Parameter(RequestParameters.PARAM_DATABASE) PrimaryDatabase database,
|
||||
YearMonth yearMonth,
|
||||
BillingEmailUtils emailUtils,
|
||||
CloudTasksUtils cloudTasksUtils,
|
||||
Clock clock,
|
||||
Response response,
|
||||
Dataflow dataflow) {
|
||||
@@ -104,6 +109,7 @@ public class GenerateInvoicesAction implements Runnable {
|
||||
this.database = database;
|
||||
this.yearMonth = yearMonth;
|
||||
this.emailUtils = emailUtils;
|
||||
this.cloudTasksUtils = cloudTasksUtils;
|
||||
this.clock = clock;
|
||||
this.response = response;
|
||||
this.dataflow = dataflow;
|
||||
@@ -120,17 +126,16 @@ public class GenerateInvoicesAction implements Runnable {
|
||||
.setContainerSpecGcsPath(
|
||||
String.format("%s/%s_metadata.json", stagingBucketUrl, PIPELINE_NAME))
|
||||
.setParameters(
|
||||
ImmutableMap.of(
|
||||
"yearMonth",
|
||||
yearMonth.toString("yyyy-MM"),
|
||||
"invoiceFilePrefix",
|
||||
invoiceFilePrefix,
|
||||
"database",
|
||||
database.name(),
|
||||
"billingBucketUrl",
|
||||
billingBucketUrl,
|
||||
"registryEnvironment",
|
||||
RegistryEnvironment.get().name()));
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.put("yearMonth", yearMonth.toString("yyyy-MM"))
|
||||
.put("invoiceFilePrefix", invoiceFilePrefix)
|
||||
.put("database", database.name())
|
||||
.put("billingBucketUrl", billingBucketUrl)
|
||||
.put("registryEnvironment", RegistryEnvironment.get().name())
|
||||
.put(
|
||||
"jpaTransactionManagerType",
|
||||
PersistenceModule.JpaTransactionManagerType.READ_ONLY_REPLICA.toString())
|
||||
.build());
|
||||
LaunchFlexTemplateResponse launchResponse =
|
||||
dataflow
|
||||
.projects()
|
||||
@@ -144,13 +149,18 @@ public class GenerateInvoicesAction implements Runnable {
|
||||
logger.atInfo().log("Got response: %s", launchResponse.getJob().toPrettyString());
|
||||
String jobId = launchResponse.getJob().getId();
|
||||
if (shouldPublish) {
|
||||
Map<String, String> beamTaskParameters =
|
||||
ImmutableMap.of(
|
||||
ReportingModule.PARAM_JOB_ID,
|
||||
jobId,
|
||||
ReportingModule.PARAM_YEAR_MONTH,
|
||||
yearMonth.toString());
|
||||
enqueueBeamReportingTask(PublishInvoicesAction.PATH, beamTaskParameters);
|
||||
cloudTasksUtils.enqueue(
|
||||
ReportingModule.BEAM_QUEUE,
|
||||
CloudTasksUtils.createPostTask(
|
||||
PublishInvoicesAction.PATH,
|
||||
Service.BACKEND.toString(),
|
||||
ImmutableMultimap.of(
|
||||
ReportingModule.PARAM_JOB_ID,
|
||||
jobId,
|
||||
ReportingModule.PARAM_YEAR_MONTH,
|
||||
yearMonth.toString()),
|
||||
clock,
|
||||
Duration.standardMinutes(ReportingModule.ENQUEUE_DELAY_MINUTES)));
|
||||
}
|
||||
response.setStatus(SC_OK);
|
||||
response.setPayload(String.format("Launched invoicing pipeline: %s", jobId));
|
||||
|
||||
+13
-8
@@ -21,9 +21,6 @@ import static google.registry.request.Action.Method.POST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.appengine.api.taskqueue.QueueFactory;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions.Method;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -33,9 +30,12 @@ import google.registry.bigquery.BigqueryJobFailureException;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.reporting.icann.IcannReportingModule.ReportType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.SendEmailService;
|
||||
@@ -86,6 +86,8 @@ public final class IcannReportingStagingAction implements Runnable {
|
||||
@Inject @Config("gSuiteOutgoingEmailAddress") InternetAddress sender;
|
||||
@Inject @Config("alertRecipientEmailAddress") InternetAddress recipient;
|
||||
@Inject SendEmailService emailService;
|
||||
@Inject Clock clock;
|
||||
@Inject CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
@Inject IcannReportingStagingAction() {}
|
||||
|
||||
@@ -119,11 +121,14 @@ public final class IcannReportingStagingAction implements Runnable {
|
||||
response.setPayload("Completed staging action.");
|
||||
|
||||
logger.atInfo().log("Enqueueing report upload.");
|
||||
TaskOptions uploadTask =
|
||||
TaskOptions.Builder.withUrl(IcannReportingUploadAction.PATH)
|
||||
.method(Method.POST)
|
||||
.countdownMillis(Duration.standardMinutes(2).getMillis());
|
||||
QueueFactory.getQueue(CRON_QUEUE).add(uploadTask);
|
||||
cloudTasksUtils.enqueue(
|
||||
CRON_QUEUE,
|
||||
CloudTasksUtils.createPostTask(
|
||||
IcannReportingUploadAction.PATH,
|
||||
Service.BACKEND.toString(),
|
||||
null,
|
||||
clock,
|
||||
Duration.standardMinutes(2)));
|
||||
return null;
|
||||
},
|
||||
BigqueryJobFailureException.class);
|
||||
|
||||
+20
-7
@@ -16,7 +16,6 @@ package google.registry.reporting.spec11;
|
||||
|
||||
import static google.registry.beam.BeamUtils.createJobName;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.reporting.ReportingUtils.enqueueBeamReportingTask;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
@@ -26,6 +25,7 @@ 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.ImmutableMultimap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
@@ -34,14 +34,16 @@ import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.PrimaryDatabase;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.Duration;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
/**
|
||||
@@ -73,6 +75,7 @@ public class GenerateSpec11ReportAction implements Runnable {
|
||||
private final Dataflow dataflow;
|
||||
private final PrimaryDatabase database;
|
||||
private final boolean sendEmail;
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
@Inject
|
||||
GenerateSpec11ReportAction(
|
||||
@@ -86,7 +89,8 @@ public class GenerateSpec11ReportAction implements Runnable {
|
||||
@Parameter(ReportingModule.SEND_EMAIL) boolean sendEmail,
|
||||
Clock clock,
|
||||
Response response,
|
||||
Dataflow dataflow) {
|
||||
Dataflow dataflow,
|
||||
CloudTasksUtils cloudTasksUtils) {
|
||||
this.projectId = projectId;
|
||||
this.jobRegion = jobRegion;
|
||||
this.stagingBucketUrl = stagingBucketUrl;
|
||||
@@ -101,6 +105,7 @@ public class GenerateSpec11ReportAction implements Runnable {
|
||||
this.response = response;
|
||||
this.dataflow = dataflow;
|
||||
this.sendEmail = sendEmail;
|
||||
this.cloudTasksUtils = cloudTasksUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,11 +141,19 @@ public class GenerateSpec11ReportAction implements Runnable {
|
||||
.execute();
|
||||
logger.atInfo().log("Got response: %s", launchResponse.getJob().toPrettyString());
|
||||
String jobId = launchResponse.getJob().getId();
|
||||
Map<String, String> beamTaskParameters =
|
||||
ImmutableMap.of(
|
||||
ReportingModule.PARAM_JOB_ID, jobId, ReportingModule.PARAM_DATE, date.toString());
|
||||
if (sendEmail) {
|
||||
enqueueBeamReportingTask(PublishSpec11ReportAction.PATH, beamTaskParameters);
|
||||
cloudTasksUtils.enqueue(
|
||||
ReportingModule.BEAM_QUEUE,
|
||||
CloudTasksUtils.createPostTask(
|
||||
PublishSpec11ReportAction.PATH,
|
||||
Service.BACKEND.toString(),
|
||||
ImmutableMultimap.of(
|
||||
ReportingModule.PARAM_JOB_ID,
|
||||
jobId,
|
||||
ReportingModule.PARAM_DATE,
|
||||
date.toString()),
|
||||
clock,
|
||||
Duration.standardMinutes(ReportingModule.ENQUEUE_DELAY_MINUTES)));
|
||||
}
|
||||
response.setStatus(SC_OK);
|
||||
response.setPayload(String.format("Launched Spec11 pipeline: %s", jobId));
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Enumerates the DNSSEC digest types for use with Delegation Signer records.
|
||||
*
|
||||
* <p>This also enforces the set of types that are valid for use with Cloud DNS. Customers cannot
|
||||
* create DS records containing any other digest type.
|
||||
*
|
||||
* <p>The complete list can be found here:
|
||||
* https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
|
||||
*/
|
||||
public enum DigestType {
|
||||
SHA1(1),
|
||||
SHA256(2),
|
||||
// Algorithm number 3 is GOST R 34.11-94 and is deliberately NOT SUPPORTED.
|
||||
// This algorithm was reviewed by ise-crypto and deemed academically broken (b/207029800).
|
||||
// In addition, RFC 8624 specifies that this algorithm MUST NOT be used for DNSSEC delegations.
|
||||
// TODO(sarhabot@): Add note in Cloud DNS code to notify the Registry of any new changes to
|
||||
// supported digest types.
|
||||
SHA384(4);
|
||||
|
||||
private final int wireValue;
|
||||
|
||||
DigestType(int wireValue) {
|
||||
this.wireValue = wireValue;
|
||||
}
|
||||
|
||||
/** Fetches a DigestType enumeration constant by its IANA assigned value. */
|
||||
public static Optional<DigestType> fromWireValue(int wireValue) {
|
||||
for (DigestType alg : DigestType.values()) {
|
||||
if (alg.getWireValue() == wireValue) {
|
||||
return Optional.of(alg);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/** Fetches a value in the range [0, 255] that encodes this DS digest type on the wire. */
|
||||
public int getWireValue() {
|
||||
return wireValue;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
import com.google.auto.value.AutoValue;
|
||||
@@ -25,6 +26,7 @@ import com.google.common.base.Splitter;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.template.soy.data.SoyListData;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.flows.domain.DomainFlowUtils;
|
||||
import java.util.List;
|
||||
|
||||
@AutoValue
|
||||
@@ -46,6 +48,15 @@ abstract class DsRecord {
|
||||
"digest should be even-lengthed hex, but is %s (length %s)",
|
||||
digest,
|
||||
digest.length());
|
||||
checkArgumentPresent(
|
||||
DigestType.fromWireValue(digestType),
|
||||
String.format("DS record uses an unrecognized digest type: %d", digestType));
|
||||
|
||||
if (!DomainFlowUtils.validateAlgorithm(alg)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("DS record uses an unrecognized algorithm: %d", alg));
|
||||
}
|
||||
|
||||
return new AutoValue_DsRecord(keyTag, alg, digestType, digest);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.tools.params.PathParameter;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -46,11 +47,20 @@ class EncryptEscrowDepositCommand implements CommandWithRemoteApi {
|
||||
validateWith = PathParameter.OutputDirectory.class)
|
||||
private Path outdir = Paths.get(".");
|
||||
|
||||
@Inject
|
||||
EscrowDepositEncryptor encryptor;
|
||||
@Parameter(
|
||||
names = {"-m", "--mode"},
|
||||
description = "Specify the escrow mode, FULL for RDE and THIN for BRDA.")
|
||||
private RdeMode mode = RdeMode.FULL;
|
||||
|
||||
@Parameter(
|
||||
names = {"-r", "--revision"},
|
||||
description = "Specify the revision.")
|
||||
private int revision = 0;
|
||||
|
||||
@Inject EscrowDepositEncryptor encryptor;
|
||||
|
||||
@Override
|
||||
public final void run() throws Exception {
|
||||
encryptor.encrypt(canonicalizeDomainName(tld), input, outdir);
|
||||
encryptor.encrypt(mode, canonicalizeDomainName(tld), revision, input, outdir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import static google.registry.model.rde.RdeMode.FULL;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.rde.RdeNamingUtils;
|
||||
import google.registry.rde.RdeUtil;
|
||||
import google.registry.rde.RydeEncoder;
|
||||
@@ -42,26 +43,44 @@ final class EscrowDepositEncryptor {
|
||||
|
||||
@Inject @Key("rdeSigningKey") Provider<PGPKeyPair> rdeSigningKey;
|
||||
@Inject @Key("rdeReceiverKey") Provider<PGPPublicKey> rdeReceiverKey;
|
||||
|
||||
@Inject
|
||||
@Key("brdaSigningKey")
|
||||
Provider<PGPKeyPair> brdaSigningKey;
|
||||
|
||||
@Inject
|
||||
@Key("brdaReceiverKey")
|
||||
Provider<PGPPublicKey> brdaReceiverKey;
|
||||
|
||||
@Inject EscrowDepositEncryptor() {}
|
||||
|
||||
/** Creates a {@code .ryde} and {@code .sig} file, provided an XML deposit file. */
|
||||
void encrypt(String tld, Path xmlFile, Path outdir)
|
||||
void encrypt(RdeMode mode, String tld, Integer revision, Path xmlFile, Path outdir)
|
||||
throws IOException, XmlException {
|
||||
try (InputStream xmlFileInput = Files.newInputStream(xmlFile);
|
||||
BufferedInputStream xmlInput = new BufferedInputStream(xmlFileInput, PEEK_BUFFER_SIZE)) {
|
||||
DateTime watermark = RdeUtil.peekWatermark(xmlInput);
|
||||
String name = RdeNamingUtils.makeRydeFilename(tld, watermark, FULL, 1, 0);
|
||||
String name = RdeNamingUtils.makeRydeFilename(tld, watermark, mode, 1, revision);
|
||||
Path rydePath = outdir.resolve(name + ".ryde");
|
||||
Path sigPath = outdir.resolve(name + ".sig");
|
||||
Path pubPath = outdir.resolve(tld + ".pub");
|
||||
PGPKeyPair signingKey = rdeSigningKey.get();
|
||||
PGPKeyPair signingKey;
|
||||
PGPPublicKey receiverKey;
|
||||
if (mode == FULL) {
|
||||
signingKey = rdeSigningKey.get();
|
||||
receiverKey = rdeReceiverKey.get();
|
||||
} else {
|
||||
signingKey = brdaSigningKey.get();
|
||||
receiverKey = brdaReceiverKey.get();
|
||||
}
|
||||
try (OutputStream rydeOutput = Files.newOutputStream(rydePath);
|
||||
OutputStream sigOutput = Files.newOutputStream(sigPath);
|
||||
RydeEncoder rydeEncoder = new RydeEncoder.Builder()
|
||||
.setRydeOutput(rydeOutput, rdeReceiverKey.get())
|
||||
.setSignatureOutput(sigOutput, signingKey)
|
||||
.setFileMetadata(name, Files.size(xmlFile), watermark)
|
||||
.build()) {
|
||||
RydeEncoder rydeEncoder =
|
||||
new RydeEncoder.Builder()
|
||||
.setRydeOutput(rydeOutput, receiverKey)
|
||||
.setSignatureOutput(sigOutput, signingKey)
|
||||
.setFileMetadata(name, Files.size(xmlFile), watermark)
|
||||
.build()) {
|
||||
ByteStreams.copy(xmlInput, rydeEncoder);
|
||||
}
|
||||
try (OutputStream pubOutput = Files.newOutputStream(pubPath);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
|
||||
import static google.registry.model.tld.Registries.assertTldsExist;
|
||||
import static google.registry.rde.RdeModule.PARAM_BEAM;
|
||||
import static google.registry.rde.RdeModule.PARAM_DIRECTORY;
|
||||
@@ -23,23 +22,22 @@ import static google.registry.rde.RdeModule.PARAM_MANUAL;
|
||||
import static google.registry.rde.RdeModule.PARAM_MODE;
|
||||
import static google.registry.rde.RdeModule.PARAM_REVISION;
|
||||
import static google.registry.rde.RdeModule.PARAM_WATERMARKS;
|
||||
import static google.registry.rde.RdeModule.RDE_REPORT_QUEUE;
|
||||
import static google.registry.request.RequestParameters.PARAM_TLDS;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.rde.RdeStagingAction;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.tools.params.DateTimeParameter;
|
||||
import google.registry.util.AppEngineServiceUtils;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -94,15 +92,7 @@ final class GenerateEscrowDepositCommand implements CommandWithRemoteApi {
|
||||
|
||||
@Inject AppEngineServiceUtils appEngineServiceUtils;
|
||||
|
||||
@Inject
|
||||
@Named("rde-report")
|
||||
Queue queue;
|
||||
|
||||
// ETA is a required property for TaskOptions but we let the service to set it when submitting the
|
||||
// task to the task queue. However, the local test service doesn't do that for us during the unit
|
||||
// test, so we add this field here to let the unit test be able to inject the ETA to pass the
|
||||
// test.
|
||||
@VisibleForTesting Optional<Long> maybeEtaMillis = Optional.empty();
|
||||
@Inject CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -126,27 +116,25 @@ final class GenerateEscrowDepositCommand implements CommandWithRemoteApi {
|
||||
throw new ParameterException("Output subdirectory must not be empty");
|
||||
}
|
||||
|
||||
// Unlike many tool commands, this command is actually invoking an action on the backend module
|
||||
// (because it's a mapreduce). So we invoke it in a different way.
|
||||
String hostname = appEngineServiceUtils.getCurrentVersionHostname("backend");
|
||||
TaskOptions opts =
|
||||
withUrl(RdeStagingAction.PATH)
|
||||
.header("Host", hostname)
|
||||
.param(PARAM_MANUAL, String.valueOf(true))
|
||||
.param(PARAM_MODE, mode.toString())
|
||||
.param(PARAM_DIRECTORY, outdir)
|
||||
.param(PARAM_LENIENT, Boolean.toString(lenient))
|
||||
.param(PARAM_BEAM, Boolean.toString(beam))
|
||||
.param(PARAM_TLDS, tlds.stream().collect(Collectors.joining(",")))
|
||||
.param(
|
||||
ImmutableMultimap.Builder<String, String> paramsBuilder =
|
||||
new ImmutableMultimap.Builder<String, String>()
|
||||
.put(PARAM_MANUAL, String.valueOf(true))
|
||||
.put(PARAM_MODE, mode.toString())
|
||||
.put(PARAM_DIRECTORY, outdir)
|
||||
.put(PARAM_LENIENT, Boolean.toString(lenient))
|
||||
.put(PARAM_BEAM, Boolean.toString(beam))
|
||||
.put(PARAM_TLDS, tlds.stream().collect(Collectors.joining(",")))
|
||||
.put(
|
||||
PARAM_WATERMARKS,
|
||||
watermarks.stream().map(DateTime::toString).collect(Collectors.joining(",")));
|
||||
|
||||
if (revision != null) {
|
||||
opts = opts.param(PARAM_REVISION, String.valueOf(revision));
|
||||
paramsBuilder.put(PARAM_REVISION, String.valueOf(revision));
|
||||
}
|
||||
if (maybeEtaMillis.isPresent()) {
|
||||
opts = opts.etaMillis(maybeEtaMillis.get());
|
||||
}
|
||||
queue.add(opts);
|
||||
cloudTasksUtils.enqueue(
|
||||
RDE_REPORT_QUEUE,
|
||||
CloudTasksUtils.createPostTask(
|
||||
RdeStagingAction.PATH, Service.BACKEND.toString(), paramsBuilder.build()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ abstract class GetEppResourceCommand implements CommandWithRemoteApi {
|
||||
? String.format(
|
||||
"%s\n\nWebsafe key: %s",
|
||||
expand ? resource.get().toHydratedString() : resource.get(),
|
||||
resource.get().createVKey().getOfyKey().getString())
|
||||
resource.get().createVKey().stringify())
|
||||
: String.format("%s '%s' does not exist or is deleted\n", resourceType, uniqueId));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.tools;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.model.tld.label.PremiumList;
|
||||
import google.registry.model.tld.label.PremiumList.PremiumEntry;
|
||||
import google.registry.model.tld.label.PremiumListDao;
|
||||
@@ -40,7 +39,7 @@ public class GetPremiumListCommand implements CommandWithRemoteApi {
|
||||
System.out.printf(
|
||||
"%s:\n%s\n",
|
||||
premiumListName,
|
||||
Streams.stream(PremiumListDao.loadAllPremiumEntries(premiumListName))
|
||||
PremiumListDao.loadAllPremiumEntries(premiumListName).stream()
|
||||
.sorted(Comparator.comparing(PremiumEntry::getDomainLabel))
|
||||
.map(premiumEntry -> premiumEntry.toString(premiumList.get().getCurrency()))
|
||||
.collect(Collectors.joining("\n")));
|
||||
|
||||
@@ -27,9 +27,9 @@ import google.registry.model.tld.Registries;
|
||||
class LoadTestCommand extends ConfirmingCommand
|
||||
implements CommandWithConnection, CommandWithRemoteApi {
|
||||
|
||||
// This is a mostly arbitrary value, roughly an hour and a quarter. It served as a generous
|
||||
// This is a mostly arbitrary value, roughly two and a half hours. It served as a generous
|
||||
// timespan for initial backup/restore testing, but has no other special significance.
|
||||
private static final int DEFAULT_RUN_SECONDS = 4600;
|
||||
private static final int DEFAULT_RUN_SECONDS = 9200;
|
||||
|
||||
@Parameter(
|
||||
names = {"--tld"},
|
||||
|
||||
@@ -15,13 +15,8 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.tools.javascrap.BackfillRegistryLocksCommand;
|
||||
import google.registry.tools.javascrap.BackfillSpec11ThreatMatchesCommand;
|
||||
import google.registry.tools.javascrap.DeleteContactByRoidCommand;
|
||||
import google.registry.tools.javascrap.CompareEscrowDepositsCommand;
|
||||
import google.registry.tools.javascrap.HardDeleteHostCommand;
|
||||
import google.registry.tools.javascrap.PopulateNullRegistrarFieldsCommand;
|
||||
import google.registry.tools.javascrap.RemoveIpAddressCommand;
|
||||
import google.registry.tools.javascrap.ResaveAllTldsCommand;
|
||||
|
||||
/** Container class to create and run remote commands against a Datastore instance. */
|
||||
public final class RegistryTool {
|
||||
@@ -35,11 +30,10 @@ public final class RegistryTool {
|
||||
public static final ImmutableMap<String, Class<? extends Command>> COMMAND_MAP =
|
||||
new ImmutableMap.Builder<String, Class<? extends Command>>()
|
||||
.put("ack_poll_messages", AckPollMessagesCommand.class)
|
||||
.put("backfill_registry_locks", BackfillRegistryLocksCommand.class)
|
||||
.put("backfill_spec11_threat_matches", BackfillSpec11ThreatMatchesCommand.class)
|
||||
.put("canonicalize_labels", CanonicalizeLabelsCommand.class)
|
||||
.put("check_domain", CheckDomainCommand.class)
|
||||
.put("check_domain_claims", CheckDomainClaimsCommand.class)
|
||||
.put("compare_escrow_deposits", CompareEscrowDepositsCommand.class)
|
||||
.put("convert_idn", ConvertIdnCommand.class)
|
||||
.put("count_domains", CountDomainsCommand.class)
|
||||
.put("create_anchor_tenant", CreateAnchorTenantCommand.class)
|
||||
@@ -55,7 +49,6 @@ public final class RegistryTool {
|
||||
.put("curl", CurlCommand.class)
|
||||
.put("dedupe_one_time_billing_event_ids", DedupeOneTimeBillingEventIdsCommand.class)
|
||||
.put("delete_allocation_tokens", DeleteAllocationTokensCommand.class)
|
||||
.put("delete_contact_by_roid", DeleteContactByRoidCommand.class)
|
||||
.put("delete_domain", DeleteDomainCommand.class)
|
||||
.put("delete_host", DeleteHostCommand.class)
|
||||
.put("delete_premium_list", DeletePremiumListCommand.class)
|
||||
@@ -105,12 +98,9 @@ public final class RegistryTool {
|
||||
.put("login", LoginCommand.class)
|
||||
.put("logout", LogoutCommand.class)
|
||||
.put("pending_escrow", PendingEscrowCommand.class)
|
||||
.put("populate_null_registrar_fields", PopulateNullRegistrarFieldsCommand.class)
|
||||
.put("registrar_contact", RegistrarContactCommand.class)
|
||||
.put("remove_ip_address", RemoveIpAddressCommand.class)
|
||||
.put("remove_registry_one_key", RemoveRegistryOneKeyCommand.class)
|
||||
.put("renew_domain", RenewDomainCommand.class)
|
||||
.put("resave_all_tlds", ResaveAllTldsCommand.class)
|
||||
.put("resave_entities", ResaveEntitiesCommand.class)
|
||||
.put("resave_environment_entities", ResaveEnvironmentEntitiesCommand.class)
|
||||
.put("resave_epp_resource", ResaveEppResourceCommand.class)
|
||||
|
||||
@@ -19,6 +19,7 @@ import dagger.Component;
|
||||
import dagger.Lazy;
|
||||
import google.registry.batch.BatchModule;
|
||||
import google.registry.bigquery.BigqueryModule;
|
||||
import google.registry.config.CloudTasksUtilsModule;
|
||||
import google.registry.config.CredentialModule.LocalCredentialJson;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
@@ -41,8 +42,7 @@ import google.registry.request.Modules.URLFetchServiceModule;
|
||||
import google.registry.request.Modules.UrlFetchTransportModule;
|
||||
import google.registry.request.Modules.UserServiceModule;
|
||||
import google.registry.tools.AuthModule.LocalCredentialModule;
|
||||
import google.registry.tools.javascrap.BackfillRegistryLocksCommand;
|
||||
import google.registry.tools.javascrap.DeleteContactByRoidCommand;
|
||||
import google.registry.tools.javascrap.CompareEscrowDepositsCommand;
|
||||
import google.registry.tools.javascrap.HardDeleteHostCommand;
|
||||
import google.registry.util.UtilsModule;
|
||||
import google.registry.whois.NonCachingWhoisModule;
|
||||
@@ -64,6 +64,7 @@ import javax.inject.Singleton;
|
||||
BigqueryModule.class,
|
||||
ConfigModule.class,
|
||||
CloudDnsWriterModule.class,
|
||||
CloudTasksUtilsModule.class,
|
||||
DatastoreAdminModule.class,
|
||||
DatastoreServiceModule.class,
|
||||
DummyKeyringModule.class,
|
||||
@@ -87,12 +88,12 @@ import javax.inject.Singleton;
|
||||
interface RegistryToolComponent {
|
||||
void inject(AckPollMessagesCommand command);
|
||||
|
||||
void inject(BackfillRegistryLocksCommand command);
|
||||
|
||||
void inject(CheckDomainClaimsCommand command);
|
||||
|
||||
void inject(CheckDomainCommand command);
|
||||
|
||||
void inject(CompareEscrowDepositsCommand command);
|
||||
|
||||
void inject(CountDomainsCommand command);
|
||||
|
||||
void inject(CreateAnchorTenantCommand command);
|
||||
@@ -107,8 +108,6 @@ interface RegistryToolComponent {
|
||||
|
||||
void inject(CreateTldCommand command);
|
||||
|
||||
void inject(DeleteContactByRoidCommand command);
|
||||
|
||||
void inject(EncryptEscrowDepositCommand command);
|
||||
|
||||
void inject(EnqueuePollMessageCommand command);
|
||||
|
||||
@@ -15,21 +15,15 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.tld.label.PremiumListUtils.parseToPremiumList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.util.ListNamingUtils.convertFilePathToName;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.model.tld.label.PremiumList;
|
||||
import google.registry.model.tld.label.PremiumList.PremiumEntry;
|
||||
import google.registry.model.tld.label.PremiumListDao;
|
||||
import google.registry.model.tld.label.PremiumListUtils;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Command to safely update {@link PremiumList} in Database for a given TLD. */
|
||||
@@ -43,46 +37,12 @@ class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
|
||||
checkArgument(
|
||||
list.isPresent(),
|
||||
String.format("Could not update premium list %s because it doesn't exist.", name));
|
||||
List<String> existingEntry = getExistingPremiumEntry(list.get()).asList();
|
||||
inputData = Files.readAllLines(inputFile, UTF_8);
|
||||
checkArgument(!inputData.isEmpty(), "New premium list data cannot be empty");
|
||||
currency = list.get().getCurrency();
|
||||
// reconstructing existing premium list to bypass Hibernate lazy initialization exception
|
||||
PremiumList existingPremiumList = parseToPremiumList(name, currency, existingEntry);
|
||||
PremiumList updatedPremiumList = parseToPremiumList(name, currency, inputData);
|
||||
|
||||
PremiumList updatedPremiumList = PremiumListUtils.parseToPremiumList(name, currency, inputData);
|
||||
return String.format(
|
||||
"Update premium list for %s?\n Old List: %s\n New List: %s",
|
||||
name, existingPremiumList, updatedPremiumList);
|
||||
}
|
||||
|
||||
/*
|
||||
To get premium list content as a set of string. This is a workaround to avoid dealing with
|
||||
Hibernate.LazyInitizationException error. It occurs when trying to access data of the
|
||||
latest revision of an existing premium list.
|
||||
"Cannot evaluate google.registry.model.tld.label.PremiumList.toString()'".
|
||||
Ideally, the following should be the way to verify info in latest revision of a premium list:
|
||||
|
||||
PremiumList existingPremiumList =
|
||||
PremiumListSqlDao.getLatestRevision(name)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format(
|
||||
"Could not update premium list %s because it doesn't exist.", name)));
|
||||
assertThat(persistedList.getLabelsToPrices()).containsEntry("foo", new BigDecimal("9000.00"));
|
||||
assertThat(persistedList.size()).isEqualTo(1);
|
||||
*/
|
||||
protected ImmutableSet<String> getExistingPremiumEntry(PremiumList list) {
|
||||
|
||||
Iterable<PremiumEntry> sqlListEntries =
|
||||
jpaTm().transact(() -> PremiumListDao.loadPremiumEntries(list));
|
||||
return Streams.stream(sqlListEntries)
|
||||
.map(
|
||||
premiumEntry ->
|
||||
String.format(
|
||||
"%s,%s %s",
|
||||
premiumEntry.getDomainLabel(), list.getCurrency(), premiumEntry.getValue()))
|
||||
.collect(toImmutableSet());
|
||||
name, list, updatedPremiumList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,157 +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.tools.javascrap;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
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.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.model.tld.RegistryLockDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.tools.CommandWithRemoteApi;
|
||||
import google.registry.tools.ConfirmingCommand;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.StringGenerator;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Scrap tool to backfill {@link RegistryLock}s for domains previously locked.
|
||||
*
|
||||
* <p>This will save new objects for all existing domains that are locked but don't have any
|
||||
* corresponding lock objects already in the database.
|
||||
*/
|
||||
@Parameters(
|
||||
separators = " =",
|
||||
commandDescription =
|
||||
"Backfills RegistryLock objects for specified domain resource IDs that are locked but don't"
|
||||
+ " already have a corresponding RegistryLock object.")
|
||||
public class BackfillRegistryLocksCommand extends ConfirmingCommand
|
||||
implements CommandWithRemoteApi {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private static final int VERIFICATION_CODE_LENGTH = 32;
|
||||
|
||||
@Parameter(
|
||||
names = {"--domain_roids"},
|
||||
description = "Comma-separated list of domain roids to check")
|
||||
protected List<String> roids;
|
||||
|
||||
// Inject here so that we can create the command automatically for tests
|
||||
@Inject Clock clock;
|
||||
|
||||
@Inject
|
||||
@Config("registryAdminClientId")
|
||||
String registryAdminClientId;
|
||||
|
||||
@Inject
|
||||
@Named("base58StringGenerator")
|
||||
StringGenerator stringGenerator;
|
||||
|
||||
private ImmutableList<DomainBase> lockedDomains;
|
||||
|
||||
@Override
|
||||
protected String prompt() {
|
||||
checkArgument(
|
||||
roids != null && !roids.isEmpty(), "Must provide non-empty domain_roids argument");
|
||||
lockedDomains =
|
||||
jpaTm().transact(() -> getLockedDomainsWithoutLocks(jpaTm().getTransactionTime()));
|
||||
ImmutableList<String> lockedDomainNames =
|
||||
lockedDomains.stream().map(DomainBase::getDomainName).collect(toImmutableList());
|
||||
return String.format(
|
||||
"Locked domains for which there does not exist a RegistryLock object: %s",
|
||||
lockedDomainNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String execute() {
|
||||
ImmutableSet.Builder<String> failedDomainsBuilder = new ImmutableSet.Builder<>();
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
for (DomainBase domainBase : lockedDomains) {
|
||||
try {
|
||||
RegistryLockDao.save(
|
||||
new RegistryLock.Builder()
|
||||
.isSuperuser(true)
|
||||
.setRegistrarId(registryAdminClientId)
|
||||
.setRepoId(domainBase.getRepoId())
|
||||
.setDomainName(domainBase.getDomainName())
|
||||
.setLockCompletionTime(
|
||||
getLockCompletionTimestamp(domainBase, jpaTm().getTransactionTime()))
|
||||
.setVerificationCode(
|
||||
stringGenerator.createString(VERIFICATION_CODE_LENGTH))
|
||||
.build());
|
||||
} catch (Throwable t) {
|
||||
logger.atSevere().withCause(t).log(
|
||||
"Error when creating lock object for domain '%s'.",
|
||||
domainBase.getDomainName());
|
||||
failedDomainsBuilder.add(domainBase.getDomainName());
|
||||
}
|
||||
}
|
||||
});
|
||||
ImmutableSet<String> failedDomains = failedDomainsBuilder.build();
|
||||
if (failedDomains.isEmpty()) {
|
||||
return String.format(
|
||||
"Successfully created lock objects for %d domains.", lockedDomains.size());
|
||||
} else {
|
||||
return String.format(
|
||||
"Successfully created lock objects for %d domains. We failed to create locks "
|
||||
+ "for the following domains: %s",
|
||||
lockedDomains.size() - failedDomains.size(), failedDomains);
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime getLockCompletionTimestamp(DomainBase domainBase, DateTime now) {
|
||||
// Best-effort, if a domain was URS-locked we should use that time
|
||||
// If we can't find that, return now.
|
||||
return HistoryEntryDao.loadHistoryObjectsForResource(domainBase.createVKey()).stream()
|
||||
// sort by modification time descending so we get the most recent one if it was locked twice
|
||||
.sorted(Comparator.comparing(HistoryEntry::getModificationTime).reversed())
|
||||
.filter(entry -> "Uniform Rapid Suspension".equals(entry.getReason()))
|
||||
.findFirst()
|
||||
.map(HistoryEntry::getModificationTime)
|
||||
.orElse(now);
|
||||
}
|
||||
|
||||
private ImmutableList<DomainBase> getLockedDomainsWithoutLocks(DateTime now) {
|
||||
ImmutableList<VKey<DomainBase>> domainKeys =
|
||||
roids.stream().map(roid -> VKey.create(DomainBase.class, roid)).collect(toImmutableList());
|
||||
ImmutableCollection<DomainBase> domains =
|
||||
transactIfJpaTm(() -> tm().loadByKeys(domainKeys)).values();
|
||||
return domains.stream()
|
||||
.filter(d -> d.getDeletionTime().isAfter(now))
|
||||
.filter(d -> d.getStatusValues().containsAll(REGISTRY_LOCK_STATUSES))
|
||||
.filter(d -> !RegistryLockDao.getMostRecentByRepoId(d.getRepoId()).isPresent())
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
}
|
||||
-223
@@ -1,223 +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.tools.javascrap;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
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 com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.beam.spec11.ThreatMatch;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.reporting.Spec11ThreatMatch;
|
||||
import google.registry.model.reporting.Spec11ThreatMatch.ThreatType;
|
||||
import google.registry.model.reporting.Spec11ThreatMatchDao;
|
||||
import google.registry.persistence.transaction.QueryComposer;
|
||||
import google.registry.reporting.spec11.RegistrarThreatMatches;
|
||||
import google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParser;
|
||||
import google.registry.tools.CommandWithRemoteApi;
|
||||
import google.registry.tools.ConfirmingCommand;
|
||||
import google.registry.util.Clock;
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Scrap tool to backfill {@link Spec11ThreatMatch} objects from prior days.
|
||||
*
|
||||
* <p>This will load the previously-existing Spec11 files from GCS (looking back to 2019-01-01 (a
|
||||
* rough estimate of when we started using this format) and convert those RegistrarThreatMatches
|
||||
* objects into the new Spec11ThreatMatch format. It will then insert these entries into SQL.
|
||||
*
|
||||
* <p>Note that the script will attempt to find the corresponding {@link DomainBase} object for each
|
||||
* domain name on the day of the scan. It will fail if it cannot find a corresponding domain object,
|
||||
* or if the domain objects were not active at the time of the scan.
|
||||
*/
|
||||
@Parameters(
|
||||
commandDescription =
|
||||
"Backfills Spec11 threat match entries from the old and deprecated GCS JSON files to the "
|
||||
+ "Cloud SQL database.")
|
||||
public class BackfillSpec11ThreatMatchesCommand extends ConfirmingCommand
|
||||
implements CommandWithRemoteApi {
|
||||
|
||||
private static final LocalDate START_DATE = new LocalDate(2019, 1, 1);
|
||||
|
||||
@Parameter(
|
||||
names = {"-o", "--overwrite_existing_dates"},
|
||||
description =
|
||||
"Whether the command will overwrite data that already exists for dates that exist in the "
|
||||
+ "GCS bucket. Defaults to false.")
|
||||
private boolean overrideExistingDates;
|
||||
|
||||
@Inject Spec11RegistrarThreatMatchesParser threatMatchesParser;
|
||||
// Inject the clock for testing purposes
|
||||
@Inject Clock clock;
|
||||
|
||||
@Override
|
||||
protected String prompt() {
|
||||
return String.format("Backfill Spec11 results from %d files?", getDatesToBackfill().size());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String execute() {
|
||||
ImmutableList<LocalDate> dates = getDatesToBackfill();
|
||||
ImmutableListMultimap.Builder<LocalDate, RegistrarThreatMatches> threatMatchesBuilder =
|
||||
new ImmutableListMultimap.Builder<>();
|
||||
for (LocalDate date : dates) {
|
||||
try {
|
||||
// It's OK if the file doesn't exist for a particular date; the result will be empty.
|
||||
threatMatchesBuilder.putAll(date, threatMatchesParser.getRegistrarThreatMatches(date));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
String.format("Error parsing through file with date %s.", date), e);
|
||||
}
|
||||
}
|
||||
ImmutableListMultimap<LocalDate, RegistrarThreatMatches> threatMatches =
|
||||
threatMatchesBuilder.build();
|
||||
// Look up all possible DomainBases for these domain names, any of which can be in the past
|
||||
ImmutableListMultimap<String, DomainBase> domainsByDomainName =
|
||||
getDomainsByDomainName(threatMatches);
|
||||
|
||||
// For each date, convert all threat matches with the proper domain repo ID
|
||||
int totalNumThreats = 0;
|
||||
for (LocalDate date : threatMatches.keySet()) {
|
||||
ImmutableList.Builder<Spec11ThreatMatch> spec11ThreatsBuilder = new ImmutableList.Builder<>();
|
||||
for (RegistrarThreatMatches rtm : threatMatches.get(date)) {
|
||||
rtm.threatMatches().stream()
|
||||
.map(
|
||||
threatMatch ->
|
||||
threatMatchToCloudSqlObject(
|
||||
threatMatch, date, rtm.clientId(), domainsByDomainName))
|
||||
.forEach(spec11ThreatsBuilder::add);
|
||||
}
|
||||
ImmutableList<Spec11ThreatMatch> spec11Threats = spec11ThreatsBuilder.build();
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
Spec11ThreatMatchDao.deleteEntriesByDate(jpaTm(), date);
|
||||
jpaTm().putAll(spec11Threats);
|
||||
});
|
||||
totalNumThreats += spec11Threats.size();
|
||||
}
|
||||
return String.format(
|
||||
"Successfully parsed through %d files with %d threats.", dates.size(), totalNumThreats);
|
||||
}
|
||||
|
||||
/** Returns a per-domain list of possible DomainBase objects, starting with the most recent. */
|
||||
private ImmutableListMultimap<String, DomainBase> getDomainsByDomainName(
|
||||
ImmutableListMultimap<LocalDate, RegistrarThreatMatches> threatMatchesByDate) {
|
||||
return threatMatchesByDate.values().stream()
|
||||
.map(RegistrarThreatMatches::threatMatches)
|
||||
.flatMap(ImmutableList::stream)
|
||||
.map(ThreatMatch::fullyQualifiedDomainName)
|
||||
.distinct()
|
||||
.collect(
|
||||
flatteningToImmutableListMultimap(
|
||||
Function.identity(),
|
||||
(domainName) -> {
|
||||
ImmutableList<DomainBase> domains = loadDomainsForFqdn(domainName);
|
||||
checkState(
|
||||
!domains.isEmpty(),
|
||||
"Domain name %s had no associated DomainBase objects.",
|
||||
domainName);
|
||||
return domains.stream()
|
||||
.sorted(Comparator.comparing(DomainBase::getCreationTime).reversed());
|
||||
}));
|
||||
}
|
||||
|
||||
/** Loads in all {@link DomainBase} objects for a given FQDN. */
|
||||
private ImmutableList<DomainBase> loadDomainsForFqdn(String fullyQualifiedDomainName) {
|
||||
return transactIfJpaTm(
|
||||
() ->
|
||||
tm().createQueryComposer(DomainBase.class)
|
||||
.where(
|
||||
"fullyQualifiedDomainName",
|
||||
QueryComposer.Comparator.EQ,
|
||||
fullyQualifiedDomainName)
|
||||
.list());
|
||||
}
|
||||
|
||||
/** Converts the previous {@link ThreatMatch} object to {@link Spec11ThreatMatch}. */
|
||||
private Spec11ThreatMatch threatMatchToCloudSqlObject(
|
||||
ThreatMatch threatMatch,
|
||||
LocalDate date,
|
||||
String registrarId,
|
||||
ImmutableListMultimap<String, DomainBase> domainsByDomainName) {
|
||||
DomainBase domain =
|
||||
findDomainAsOfDateOrThrow(
|
||||
threatMatch.fullyQualifiedDomainName(), date, domainsByDomainName);
|
||||
return new Spec11ThreatMatch.Builder()
|
||||
.setThreatTypes(ImmutableSet.of(ThreatType.valueOf(threatMatch.threatType())))
|
||||
.setCheckDate(date)
|
||||
.setRegistrarId(registrarId)
|
||||
.setDomainName(threatMatch.fullyQualifiedDomainName())
|
||||
.setDomainRepoId(domain.getRepoId())
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Returns the DomainBase object as of the particular date, which is likely in the past. */
|
||||
private DomainBase findDomainAsOfDateOrThrow(
|
||||
String domainName,
|
||||
LocalDate date,
|
||||
ImmutableListMultimap<String, DomainBase> domainsByDomainName) {
|
||||
ImmutableList<DomainBase> domains = domainsByDomainName.get(domainName);
|
||||
for (DomainBase domain : domains) {
|
||||
// We only know the date (not datetime) of the threat scan, so we approximate
|
||||
LocalDate creationDate = domain.getCreationTime().toLocalDate();
|
||||
LocalDate deletionDate = domain.getDeletionTime().toLocalDate();
|
||||
if (!date.isBefore(creationDate) && !date.isAfter(deletionDate)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
String.format("Could not find a DomainBase valid for %s on day %s.", domainName, date));
|
||||
}
|
||||
|
||||
/** Returns the list of dates between {@link #START_DATE} and now (UTC), inclusive. */
|
||||
private ImmutableList<LocalDate> getDatesToBackfill() {
|
||||
ImmutableSet<LocalDate> datesToSkip =
|
||||
overrideExistingDates ? ImmutableSet.of() : getExistingDates();
|
||||
ImmutableList.Builder<LocalDate> result = new ImmutableList.Builder<>();
|
||||
LocalDate endDate = clock.nowUtc().toLocalDate();
|
||||
for (LocalDate currentDate = START_DATE;
|
||||
!currentDate.isAfter(endDate);
|
||||
currentDate = currentDate.plusDays(1)) {
|
||||
if (!datesToSkip.contains(currentDate)) {
|
||||
result.add(currentDate);
|
||||
}
|
||||
}
|
||||
return result.build();
|
||||
}
|
||||
|
||||
private ImmutableSet<LocalDate> getExistingDates() {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.query(
|
||||
"SELECT DISTINCT stm.checkDate FROM Spec11ThreatMatch stm", LocalDate.class)
|
||||
.getResultStream()
|
||||
.collect(toImmutableSet()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.tools.javascrap;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.keyring.api.Keyring;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.rde.Ghostryde;
|
||||
import google.registry.tools.Command;
|
||||
import google.registry.tools.params.PathParameter;
|
||||
import google.registry.xjc.XjcXmlTransformer;
|
||||
import google.registry.xjc.rde.XjcRdeDeposit;
|
||||
import google.registry.xjc.rdedomain.XjcRdeDomain;
|
||||
import google.registry.xjc.rderegistrar.XjcRdeRegistrar;
|
||||
import google.registry.xml.XmlException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.xml.bind.JAXBElement;
|
||||
|
||||
/**
|
||||
* Command to view and schema validate an XML RDE escrow deposit.
|
||||
*
|
||||
* <p>Note that this command only makes sure that both deposits contain the same registrars and
|
||||
* domains, regardless of the order. To verify that they are indeed equivalent one still needs to
|
||||
* verify internal consistency within each deposit (i.e. to check that all hosts and contacts
|
||||
* referenced by domains are included in the deposit) by calling {@code
|
||||
* google.registry.tools.ValidateEscrowDepositCommand}.
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
@Parameters(separators = " =", commandDescription = "Compare two XML escrow deposits.")
|
||||
public final class CompareEscrowDepositsCommand implements Command {
|
||||
|
||||
@Parameter(
|
||||
description =
|
||||
"Two XML escrow deposit files. Each may be a plain XML or an XML GhostRyDE file.",
|
||||
validateWith = PathParameter.InputFile.class)
|
||||
private List<Path> inputs;
|
||||
|
||||
@Inject Provider<Keyring> keyring;
|
||||
|
||||
private XjcRdeDeposit getDeposit(Path input) throws IOException, XmlException {
|
||||
InputStream fileStream = Files.newInputStream(input);
|
||||
InputStream inputStream = fileStream;
|
||||
if (input.toString().endsWith(".ghostryde")) {
|
||||
inputStream = Ghostryde.decoder(fileStream, keyring.get().getRdeStagingDecryptionKey());
|
||||
}
|
||||
return XjcXmlTransformer.unmarshal(XjcRdeDeposit.class, inputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
checkArgument(
|
||||
inputs.size() == 2,
|
||||
"Must supply 2 files to compare, but %s was/were supplied.",
|
||||
inputs.size());
|
||||
XjcRdeDeposit deposit1 = getDeposit(inputs.get(0));
|
||||
XjcRdeDeposit deposit2 = getDeposit(inputs.get(1));
|
||||
compareXmlDeposits(deposit1, deposit2);
|
||||
}
|
||||
|
||||
private static void process(XjcRdeDeposit deposit, Set<String> domains, Set<String> registrars) {
|
||||
for (JAXBElement<?> item : deposit.getContents().getContents()) {
|
||||
if (XjcRdeDomain.class.isAssignableFrom(item.getDeclaredType())) {
|
||||
XjcRdeDomain domain = (XjcRdeDomain) item.getValue();
|
||||
domains.add(checkNotNull(domain.getName()));
|
||||
} else if (XjcRdeRegistrar.class.isAssignableFrom(item.getDeclaredType())) {
|
||||
XjcRdeRegistrar registrar = (XjcRdeRegistrar) item.getValue();
|
||||
registrars.add(checkNotNull(registrar.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean printUniqueElements(
|
||||
Set<String> set1, Set<String> set2, String element, String deposit) {
|
||||
ImmutableList<String> uniqueElements = ImmutableList.copyOf(difference(set1, set2));
|
||||
if (!uniqueElements.isEmpty()) {
|
||||
System.out.printf(
|
||||
"%s only in %s:\n%s\n", element, deposit, Joiner.on("\n").join(uniqueElements));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void compareXmlDeposits(XjcRdeDeposit deposit1, XjcRdeDeposit deposit2) {
|
||||
Set<String> domains1 = new HashSet<>();
|
||||
Set<String> domains2 = new HashSet<>();
|
||||
Set<String> registrars1 = new HashSet<>();
|
||||
Set<String> registrars2 = new HashSet<>();
|
||||
process(deposit1, domains1, registrars1);
|
||||
process(deposit2, domains2, registrars2);
|
||||
boolean good = true;
|
||||
good &= printUniqueElements(domains1, domains2, "domains", "deposit1");
|
||||
good &= printUniqueElements(domains2, domains1, "domains", "deposit2");
|
||||
good &= printUniqueElements(registrars1, registrars2, "registrars", "deposit1");
|
||||
good &= printUniqueElements(registrars2, registrars1, "registrars", "deposit2");
|
||||
if (good) {
|
||||
System.out.println(
|
||||
"The two deposits contain the same domains and registrars. "
|
||||
+ "You still need to run validate_escrow_deposit to check reference consistency.");
|
||||
} else {
|
||||
System.out.println("The two deposits differ.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +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.tools.javascrap;
|
||||
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.tools.CommandWithRemoteApi;
|
||||
import google.registry.tools.ConfirmingCommand;
|
||||
import google.registry.util.SystemClock;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Deletes a {@link google.registry.model.contact.ContactResource} by its ROID.
|
||||
*
|
||||
* <p>This is a short-term tool for race condition clean up while the bug is being fixed.
|
||||
*/
|
||||
@Parameters(separators = " =", commandDescription = "Delete a contact by its ROID.")
|
||||
public class DeleteContactByRoidCommand extends ConfirmingCommand implements CommandWithRemoteApi {
|
||||
|
||||
@Parameter(names = "--roid", description = "The roid of the contact to be deleted.")
|
||||
String roid;
|
||||
|
||||
@Parameter(
|
||||
names = "--contact_id",
|
||||
description = "The user provided contactId, for verification purpose.")
|
||||
String contactId;
|
||||
|
||||
ImmutableList<Key<?>> toDelete;
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
System.out.printf("Deleting %s, which refers to %s.\n", roid, contactId);
|
||||
tm().transact(
|
||||
() -> {
|
||||
Key<ContactResource> targetKey = Key.create(ContactResource.class, roid);
|
||||
ContactResource targetContact = auditedOfy().load().key(targetKey).now();
|
||||
verify(
|
||||
Objects.equals(targetContact.getContactId(), contactId),
|
||||
"contactId does not match.");
|
||||
verify(
|
||||
Objects.equals(targetContact.getStatusValues(), ImmutableSet.of(StatusValue.OK)));
|
||||
System.out.println("Target contact has the expected contactId");
|
||||
String canonicalResource =
|
||||
ForeignKeyIndex.load(ContactResource.class, contactId, new SystemClock().nowUtc())
|
||||
.getResourceKey()
|
||||
.getOfyKey()
|
||||
.getName();
|
||||
verify(!Objects.equals(canonicalResource, roid), "Contact still in ForeignKeyIndex.");
|
||||
System.out.printf(
|
||||
"It is safe to delete %s, since the contactId is mapped to a different entry in"
|
||||
+ " the Foreign key index (%s).\n\n",
|
||||
roid, canonicalResource);
|
||||
|
||||
List<Object> ancestors =
|
||||
auditedOfy().load().ancestor(Key.create(ContactResource.class, roid)).list();
|
||||
|
||||
System.out.println("Ancestor query returns: ");
|
||||
for (Object entity : ancestors) {
|
||||
System.out.println(Key.create(entity));
|
||||
}
|
||||
|
||||
ImmutableSet<String> deletetableKinds =
|
||||
ImmutableSet.of("HistoryEntry", "ContactResource");
|
||||
toDelete =
|
||||
ancestors.stream()
|
||||
.map(Key::create)
|
||||
.filter(key -> deletetableKinds.contains(key.getKind()))
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
|
||||
EppResourceIndex eppResourceIndex =
|
||||
auditedOfy().load().entity(EppResourceIndex.create(targetKey)).now();
|
||||
verify(eppResourceIndex.getKey().equals(targetKey), "Wrong EppResource Index loaded");
|
||||
System.out.printf("\n\nEppResourceIndex found (%s).\n", Key.create(eppResourceIndex));
|
||||
|
||||
toDelete =
|
||||
new ImmutableList.Builder<Key<?>>()
|
||||
.addAll(toDelete)
|
||||
.add(Key.create(eppResourceIndex))
|
||||
.build();
|
||||
|
||||
System.out.printf("\n\nAbout to delete %s entities:\n", toDelete.size());
|
||||
toDelete.forEach(System.out::println);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String execute() {
|
||||
tm().transact(() -> auditedOfy().delete().keys(toDelete).now());
|
||||
return "Done";
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +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.tools.javascrap;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.tools.MutatingCommand;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Scrap tool to update Registrars with null registrarName or localizedAddress fields.
|
||||
*
|
||||
* <p>This sets a null registrarName to the key name, and null localizedAddress fields to fake data.
|
||||
*/
|
||||
@Parameters(
|
||||
separators = " =",
|
||||
commandDescription = "Populate previously null required registrar fields."
|
||||
)
|
||||
public class PopulateNullRegistrarFieldsCommand extends MutatingCommand {
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
for (Registrar registrar : Registrar.loadAll()) {
|
||||
Registrar.Builder changeBuilder = registrar.asBuilder();
|
||||
changeBuilder.setRegistrarName(
|
||||
firstNonNull(registrar.getRegistrarName(), registrar.getRegistrarId()));
|
||||
|
||||
RegistrarAddress address = registrar.getLocalizedAddress();
|
||||
if (address == null) {
|
||||
changeBuilder.setLocalizedAddress(
|
||||
new RegistrarAddress.Builder()
|
||||
.setCity("Fakington")
|
||||
.setCountryCode("US")
|
||||
.setState("FL")
|
||||
.setZip("12345")
|
||||
.setStreet(ImmutableList.of("123 Fake Street"))
|
||||
.build());
|
||||
} else {
|
||||
changeBuilder.setLocalizedAddress(
|
||||
new RegistrarAddress.Builder()
|
||||
.setCity(firstNonNull(address.getCity(), "Fakington"))
|
||||
.setCountryCode(firstNonNull(address.getCountryCode(), "US"))
|
||||
.setState(firstNonNull(address.getState(), "FL"))
|
||||
.setZip(firstNonNull(address.getZip(), "12345"))
|
||||
.setStreet(firstNonNull(address.getStreet(), ImmutableList.of("123 Fake Street")))
|
||||
.build());
|
||||
}
|
||||
Registrar changedRegistrar = changeBuilder.build();
|
||||
if (!Objects.equals(registrar, changedRegistrar)) {
|
||||
stageEntityChange(registrar, changedRegistrar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +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.tools.javascrap;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.tools.MutatingEppToolCommand;
|
||||
import google.registry.tools.params.PathParameter;
|
||||
import google.registry.tools.soy.RemoveIpAddressSoyInfo;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Command to remove external IP Addresses from HostResources identified by text file listing
|
||||
* resource ids, one per line.
|
||||
*
|
||||
* <p>Written for b/23757755 so we can clean up records with IP addresses that should always be
|
||||
* resolved by hostname.
|
||||
*
|
||||
* <p>The JSON file should contain a list of objects each of which has a "roid" attribute.
|
||||
*/
|
||||
@Parameters(separators = " =", commandDescription = "Remove all IP Addresses.")
|
||||
public class RemoveIpAddressCommand extends MutatingEppToolCommand {
|
||||
public static String registrarId = "CharlestonRoad";
|
||||
|
||||
@Parameter(names = "--roids_file",
|
||||
description = "Text file containing a list of HostResource roids to remove",
|
||||
required = true,
|
||||
validateWith = PathParameter.InputFile.class)
|
||||
private Path roidsFilePath;
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() throws Exception {
|
||||
List<String> roids = Files.readAllLines(roidsFilePath, UTF_8);
|
||||
|
||||
for (String roid : roids) {
|
||||
// Look up the HostResource from its roid.
|
||||
Optional<HostResource> host =
|
||||
transactIfJpaTm(() -> tm().loadByKeyIfPresent(VKey.create(HostResource.class, roid)));
|
||||
if (!host.isPresent()) {
|
||||
System.err.printf("Record for %s not found.\n", roid);
|
||||
continue;
|
||||
}
|
||||
|
||||
ArrayList<SoyMapData> ipAddresses = new ArrayList<>();
|
||||
for (InetAddress address : host.get().getInetAddresses()) {
|
||||
SoyMapData dataMap = new SoyMapData(
|
||||
"address", address.getHostAddress(),
|
||||
"version", address instanceof Inet6Address ? "v6" : "v4");
|
||||
ipAddresses.add(dataMap);
|
||||
}
|
||||
|
||||
// Build and execute the EPP command.
|
||||
setSoyTemplate(
|
||||
RemoveIpAddressSoyInfo.getInstance(), RemoveIpAddressSoyInfo.REMOVE_IP_ADDRESS);
|
||||
addSoyRecord(
|
||||
registrarId,
|
||||
new SoyMapData(
|
||||
"name", host.get().getHostName(),
|
||||
"ipAddresses", ipAddresses,
|
||||
"requestedByRegistrar", registrarId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +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.tools.javascrap;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.tools.CommandWithRemoteApi;
|
||||
|
||||
/** Scrap command to resave all Registry entities. */
|
||||
@Parameters(commandDescription = "Resave all TLDs")
|
||||
public class ResaveAllTldsCommand implements CommandWithRemoteApi {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
tm().transact(() -> tm().putAll(tm().loadAllOf(Registry.class)));
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,12 @@
|
||||
"regexes": [
|
||||
"^DATASTORE|CLOUD_SQL$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jpaTransactionManagerType",
|
||||
"label": "The type of JPA transaction manager to use if using SQL",
|
||||
"helpText": "The standard SQL instance or a read-only replica may be used",
|
||||
"regexes": ["^REGULAR|READ_ONLY_REPLICA$"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jpaTransactionManagerType",
|
||||
"label": "The type of JPA transaction manager to use",
|
||||
"helpText": "The standard SQL instance or a read-only replica may be used",
|
||||
"regexes": ["^REGULAR|READ_ONLY_REPLICA$"]
|
||||
},
|
||||
{
|
||||
"name": "pendings",
|
||||
"label": "The pendings deposits to generate.",
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "Validate Datastore with Cloud SQL",
|
||||
"description": "An Apache Beam batch pipeline that compares Datastore with the primary Cloud SQL database.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "registryEnvironment",
|
||||
"label": "The Registry environment.",
|
||||
"helpText": "The Registry environment.",
|
||||
"is_optional": false,
|
||||
"regexes": [
|
||||
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "isolationOverride",
|
||||
"label": "The desired SQL transaction isolation level.",
|
||||
"helpText": "The desired SQL transaction isolation level.",
|
||||
"is_optional": true,
|
||||
"regexes": [
|
||||
"^[0-9A-Z_]+$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sqlSnapshotId",
|
||||
"label": "The ID of an exported Cloud SQL (Postgresql) snapshot.",
|
||||
"helpText": "The ID of an exported Cloud SQL (Postgresql) snapshot.",
|
||||
"is_optional": true
|
||||
},
|
||||
{
|
||||
"name": "latestCommitLogTimestamp",
|
||||
"label": "Nomulus CommitLog start time",
|
||||
"helpText": "The latest entity update time allowed for inclusion in validation, in ISO8601 format.",
|
||||
"is_optional": false
|
||||
},
|
||||
{
|
||||
"name": "comparisonStartTimestamp",
|
||||
"label": "Only entities updated at or after this time are included for validation.",
|
||||
"helpText": "The earliest entity update time allowed for inclusion in validation, in ISO8601 format.",
|
||||
"is_optional": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Validate Cloud SQL with Datastore being primary",
|
||||
"description": "An Apache Beam batch pipeline that compares Cloud SQL with the primary Datastore.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "registryEnvironment",
|
||||
"label": "The Registry environment.",
|
||||
"helpText": "The Registry environment.",
|
||||
"is_optional": false,
|
||||
"regexes": [
|
||||
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "comparisonStartTimestamp",
|
||||
"label": "Only entities updated at or after this time are included for validation.",
|
||||
"helpText": "The earliest entity update time allowed for inclusion in validation, in ISO8601 format.",
|
||||
"is_optional": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public class AsyncTaskEnqueuerTest {
|
||||
.method("POST")
|
||||
.header("Host", "backend.hostname.fake")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.param(PARAM_RESOURCE_KEY, contact.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, contact.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, clock.nowUtc().toString())
|
||||
.etaDelta(
|
||||
standardDays(5).minus(standardSeconds(30)),
|
||||
@@ -125,7 +125,7 @@ public class AsyncTaskEnqueuerTest {
|
||||
.method("POST")
|
||||
.header("Host", "backend.hostname.fake")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.param(PARAM_RESOURCE_KEY, contact.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, contact.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, now.toString())
|
||||
.param(PARAM_RESAVE_TIMES, "2015-05-20T14:34:56.000Z,2015-05-21T15:34:56.000Z")
|
||||
.etaDelta(
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE;
|
||||
import static google.registry.batch.AsyncTaskMetrics.OperationResult.STALE;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
@@ -496,7 +497,7 @@ public class DeleteContactsAndHostsActionTest
|
||||
QUEUE_ASYNC_DELETE,
|
||||
new TaskMatcher()
|
||||
.etaDelta(standardHours(23), standardHours(25))
|
||||
.param("resourceKey", contactNotSaved.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, contactNotSaved.createVKey().stringify())
|
||||
.param("requestingClientId", "TheRegistrar")
|
||||
.param("clientTransactionId", "fakeClientTrid")
|
||||
.param("serverTransactionId", "fakeServerTrid")
|
||||
@@ -504,7 +505,7 @@ public class DeleteContactsAndHostsActionTest
|
||||
.param("requestedTime", timeBeforeRun.toString()),
|
||||
new TaskMatcher()
|
||||
.etaDelta(standardHours(23), standardHours(25))
|
||||
.param("resourceKey", hostNotSaved.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, hostNotSaved.createVKey().stringify())
|
||||
.param("requestingClientId", "TheRegistrar")
|
||||
.param("clientTransactionId", "fakeClientTrid")
|
||||
.param("serverTransactionId", "fakeServerTrid")
|
||||
|
||||
@@ -48,7 +48,6 @@ import google.registry.model.common.Cursor;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.ofy.Ofy;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
@@ -56,7 +55,6 @@ import google.registry.model.tld.Registry;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.InjectExtension;
|
||||
import google.registry.testing.ReplayExtension;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
@@ -78,11 +76,6 @@ public class ExpandRecurringBillingEventsActionTest
|
||||
private DateTime currentTestTime = DateTime.parse("1999-01-05T00:00:00Z");
|
||||
private final FakeClock clock = new FakeClock(currentTestTime);
|
||||
|
||||
@Order(Order.DEFAULT - 1)
|
||||
@RegisterExtension
|
||||
public final InjectExtension inject =
|
||||
new InjectExtension().withStaticFieldOverride(Ofy.class, "clock", clock);
|
||||
|
||||
@Order(Order.DEFAULT - 2)
|
||||
@RegisterExtension
|
||||
public final ReplayExtension replayExtension = ReplayExtension.createWithDoubleReplay(clock);
|
||||
|
||||
@@ -145,7 +145,7 @@ public class RefreshDnsOnHostRenameActionTest
|
||||
assertTasksEnqueued(
|
||||
QUEUE_ASYNC_HOST_RENAME,
|
||||
new TaskMatcher()
|
||||
.param(PARAM_HOST_KEY, host.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_HOST_KEY, host.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, timeEnqueued.toString()));
|
||||
verify(action.asyncTaskMetrics).recordDnsRefreshBatchSize(1L);
|
||||
verifyNoMoreInteractions(action.asyncTaskMetrics);
|
||||
@@ -237,7 +237,7 @@ public class RefreshDnsOnHostRenameActionTest
|
||||
QUEUE_ASYNC_HOST_RENAME,
|
||||
new TaskMatcher()
|
||||
.etaDelta(standardHours(23), standardHours(25))
|
||||
.param("hostKey", host.createVKey().getOfyKey().getString()));
|
||||
.param(PARAM_HOST_KEY, host.createVKey().stringify()));
|
||||
assertThat(acquireLock()).isPresent();
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ public class ResaveEntityActionTest {
|
||||
.method("POST")
|
||||
.header("Host", "backend.hostname.fake")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.param(PARAM_RESOURCE_KEY, resavedDomain.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, resavedDomain.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, requestedTime.toString())
|
||||
.etaDelta(
|
||||
standardDays(5).minus(standardSeconds(30)),
|
||||
|
||||
@@ -28,6 +28,7 @@ import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.InjectExtension;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import org.apache.beam.sdk.coders.SerializableCoder;
|
||||
import org.apache.beam.sdk.testing.PAssert;
|
||||
import org.apache.beam.sdk.values.PCollectionTuple;
|
||||
@@ -83,7 +84,8 @@ class DatastoreSnapshotsTest {
|
||||
setupHelper.commitLogDir.getAbsolutePath(),
|
||||
START_TIME,
|
||||
fakeClock.nowUtc().plusMillis(1),
|
||||
ImmutableSet.copyOf(DatastoreSetupHelper.ALL_KINDS));
|
||||
ImmutableSet.copyOf(DatastoreSetupHelper.ALL_KINDS),
|
||||
Optional.empty());
|
||||
PAssert.that(tuple.get(ValidateSqlUtils.createSqlEntityTupleTag(Registrar.class)))
|
||||
.containsInAnyOrder(setupHelper.registrar1, setupHelper.registrar2);
|
||||
PAssert.that(tuple.get(ValidateSqlUtils.createSqlEntityTupleTag(DomainHistory.class)))
|
||||
|
||||
@@ -88,6 +88,7 @@ class SqlSnapshotsTest {
|
||||
DomainHistory.class,
|
||||
ContactResource.class,
|
||||
HostResource.class),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
PAssert.that(sqlSnapshot.get(createSqlEntityTupleTag(Registry.class)))
|
||||
.containsInAnyOrder(setupHelper.registry);
|
||||
|
||||
@@ -98,11 +98,7 @@ class TldFanoutActionTest {
|
||||
}
|
||||
|
||||
private void assertTaskWithoutTld() {
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
QUEUE,
|
||||
new TaskMatcher()
|
||||
.url(ENDPOINT)
|
||||
.header("content-type", "application/x-www-form-urlencoded"));
|
||||
cloudTasksHelper.assertTasksEnqueued(QUEUE, new TaskMatcher().url(ENDPOINT));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.flows;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
@@ -146,7 +147,7 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
|
||||
TaskMatcher expected =
|
||||
new TaskMatcher()
|
||||
.etaDelta(Duration.standardSeconds(75), Duration.standardSeconds(105)) // expected: 90
|
||||
.param("resourceKey", resource.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, resource.createVKey().stringify())
|
||||
.param("requestingClientId", requestingClientId)
|
||||
.param("serverTransactionId", trid.getServerTransactionId())
|
||||
.param("isSuperuser", Boolean.toString(isSuperuser))
|
||||
|
||||
@@ -106,6 +106,7 @@ import google.registry.flows.domain.DomainFlowUtils.FeeDescriptionParseException
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeesRequiredDuringEarlyAccessProgramException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeesRequiredForPremiumNameException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.InvalidDsRecordException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.InvalidPunycodeException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.InvalidTcnIdChecksumException;
|
||||
@@ -346,6 +347,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
createBillingEvent));
|
||||
assertDnsTasksEnqueued(getUniqueIdFromCommand());
|
||||
assertEppResourceIndexEntityFor(domain);
|
||||
|
||||
replayExtension.expectUpdateFor(domain);
|
||||
|
||||
// Verify that all timestamps are correct after SQL -> DS replay.
|
||||
// Added to confirm that timestamps get updated correctly.
|
||||
replayExtension.enableDomainTimestampChecks();
|
||||
}
|
||||
|
||||
private void assertNoLordn() throws Exception {
|
||||
@@ -573,6 +580,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
ImmutableMap.of("DOMAIN", "otherexample.tld", "YEARS", "2"));
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "otherexample.tld")));
|
||||
replayExtension.expectUpdateFor(reloadResourceByForeignKey());
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -825,7 +833,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
@TestOfyAndSql
|
||||
void testSuccess_existedButWasDeleted() throws Exception {
|
||||
persistContactsAndHosts();
|
||||
persistDeletedDomain(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1));
|
||||
replayExtension.expectUpdateFor(
|
||||
persistDeletedDomain(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1)));
|
||||
clock.advanceOneMilli();
|
||||
doSuccessfulTest();
|
||||
}
|
||||
@@ -1002,6 +1011,22 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_secDnsInvalidDigestType() throws Exception {
|
||||
setEppInput("domain_create_dsdata_bad_digest_types.xml");
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_secDnsInvalidAlgorithm() throws Exception {
|
||||
setEppInput("domain_create_dsdata_bad_algorithms.xml");
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_wrongExtension() {
|
||||
setEppInput("domain_create_wrong_extension.xml");
|
||||
|
||||
@@ -321,7 +321,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
.method("POST")
|
||||
.header("Host", "backend.hostname.fake")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.param(PARAM_RESOURCE_KEY, domain.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, domain.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, clock.nowUtc().toString())
|
||||
.param(PARAM_RESAVE_TIMES, clock.nowUtc().plusDays(5).toString())
|
||||
.etaDelta(when.minus(standardSeconds(30)), when.plus(standardSeconds(30))));
|
||||
|
||||
@@ -521,7 +521,7 @@ class DomainTransferRequestFlowTest
|
||||
.method("POST")
|
||||
.header("Host", "backend.hostname.fake")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.param(PARAM_RESOURCE_KEY, domain.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_RESOURCE_KEY, domain.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, clock.nowUtc().toString())
|
||||
.etaDelta(
|
||||
registry.getAutomaticTransferLength().minus(standardSeconds(30)),
|
||||
|
||||
@@ -72,6 +72,7 @@ import google.registry.flows.domain.DomainFlowUtils.DuplicateContactForRoleExcep
|
||||
import google.registry.flows.domain.DomainFlowUtils.EmptySecDnsUpdateException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeesRequiredForNonFreeOperationException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.InvalidDsRecordException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.LinkedResourcesDoNotExistException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.MaxSigLifeChangeNotSupportedException;
|
||||
@@ -122,7 +123,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, DomainBase> {
|
||||
|
||||
private static final DelegationSignerData SOME_DSDATA =
|
||||
DelegationSignerData.create(1, 2, 3, base16().decode("0123"));
|
||||
DelegationSignerData.create(1, 2, 2, base16().decode("0123"));
|
||||
private static final ImmutableMap<String, String> OTHER_DSDATA_TEMPLATE_MAP =
|
||||
ImmutableMap.of(
|
||||
"KEY_TAG", "12346",
|
||||
@@ -201,6 +202,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
.setDomain(domain)
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
replayExtension.expectUpdateFor(domain);
|
||||
return domain;
|
||||
}
|
||||
|
||||
@@ -211,9 +213,11 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
private void doSuccessfulTest(String expectedXmlFilename) throws Exception {
|
||||
assertTransactionalFlow(true);
|
||||
runFlowAssertResponse(loadFile(expectedXmlFilename));
|
||||
DomainBase domain = reloadResourceByForeignKey();
|
||||
replayExtension.expectUpdateFor(domain);
|
||||
// Check that the domain was updated. These values came from the xml.
|
||||
assertAboutDomains()
|
||||
.that(reloadResourceByForeignKey())
|
||||
.that(domain)
|
||||
.hasStatusValue(StatusValue.CLIENT_HOLD)
|
||||
.and()
|
||||
.hasAuthInfoPwd("2BARfoo")
|
||||
@@ -228,6 +232,10 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
assertNoBillingEvents();
|
||||
assertDnsTasksEnqueued("example.tld");
|
||||
assertLastHistoryContainsResource(reloadResourceByForeignKey());
|
||||
|
||||
// Verify that all timestamps are correct after SQL -> DS replay.
|
||||
// Added to confirm that timestamps get updated correctly.
|
||||
replayExtension.enableDomainTimestampChecks();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -307,6 +315,8 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
}
|
||||
persistResource(
|
||||
reloadResourceByForeignKey().asBuilder().setNameservers(nameservers.build()).build());
|
||||
// Add a null update here so we don't compare.
|
||||
replayExtension.expectUpdateFor(null);
|
||||
clock.advanceOneMilli();
|
||||
}
|
||||
|
||||
@@ -536,7 +546,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
"domain_update_dsdata_add.xml",
|
||||
ImmutableSet.of(SOME_DSDATA),
|
||||
ImmutableSet.of(SOME_DSDATA),
|
||||
ImmutableMap.of("KEY_TAG", "1", "ALG", "2", "DIGEST_TYPE", "3", "DIGEST", "0123"));
|
||||
ImmutableMap.of("KEY_TAG", "1", "ALG", "2", "DIGEST_TYPE", "2", "DIGEST", "0123"));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -556,8 +566,8 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
"domain_update_dsdata_add.xml",
|
||||
ImmutableSet.of(SOME_DSDATA),
|
||||
ImmutableSet.of(
|
||||
SOME_DSDATA, DelegationSignerData.create(12346, 2, 3, base16().decode("0123"))),
|
||||
ImmutableMap.of("KEY_TAG", "12346", "ALG", "2", "DIGEST_TYPE", "3", "DIGEST", "0123"));
|
||||
SOME_DSDATA, DelegationSignerData.create(12346, 2, 2, base16().decode("0123"))),
|
||||
ImmutableMap.of("KEY_TAG", "12346", "ALG", "2", "DIGEST_TYPE", "2", "DIGEST", "0123"));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -565,8 +575,8 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
doSecDnsSuccessfulTest(
|
||||
"domain_update_dsdata_add.xml",
|
||||
ImmutableSet.of(SOME_DSDATA),
|
||||
ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create(1, 8, 3, base16().decode("0123"))),
|
||||
ImmutableMap.of("KEY_TAG", "1", "ALG", "8", "DIGEST_TYPE", "3", "DIGEST", "0123"));
|
||||
ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create(1, 8, 2, base16().decode("0123"))),
|
||||
ImmutableMap.of("KEY_TAG", "1", "ALG", "8", "DIGEST_TYPE", "2", "DIGEST", "0123"));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -583,15 +593,15 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
doSecDnsSuccessfulTest(
|
||||
"domain_update_dsdata_add.xml",
|
||||
ImmutableSet.of(SOME_DSDATA),
|
||||
ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create(1, 2, 3, base16().decode("4567"))),
|
||||
ImmutableMap.of("KEY_TAG", "1", "ALG", "2", "DIGEST_TYPE", "3", "DIGEST", "4567"));
|
||||
ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create(1, 2, 2, base16().decode("4567"))),
|
||||
ImmutableMap.of("KEY_TAG", "1", "ALG", "2", "DIGEST_TYPE", "2", "DIGEST", "4567"));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_secDnsAddToMaxRecords() throws Exception {
|
||||
ImmutableSet.Builder<DelegationSignerData> builder = new ImmutableSet.Builder<>();
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
builder.add(DelegationSignerData.create(i, 2, 3, new byte[] {0, 1, 2}));
|
||||
builder.add(DelegationSignerData.create(i, 2, 2, new byte[] {0, 1, 2}));
|
||||
}
|
||||
ImmutableSet<DelegationSignerData> commonDsData = builder.build();
|
||||
|
||||
@@ -643,7 +653,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
void testSuccess_secDnsAddRemoveToMaxRecords() throws Exception {
|
||||
ImmutableSet.Builder<DelegationSignerData> builder = new ImmutableSet.Builder<>();
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
builder.add(DelegationSignerData.create(i, 2, 3, new byte[] {0, 1, 2}));
|
||||
builder.add(DelegationSignerData.create(i, 2, 2, new byte[] {0, 1, 2}));
|
||||
}
|
||||
ImmutableSet<DelegationSignerData> commonDsData = builder.build();
|
||||
|
||||
@@ -813,11 +823,69 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
MaxSigLifeChangeNotSupportedException.class, "domain_update_maxsiglife.xml");
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_secDnsInvalidDigestType() throws Exception {
|
||||
setEppInput("domain_update_dsdata_add.xml", OTHER_DSDATA_TEMPLATE_MAP);
|
||||
persistResource(
|
||||
newDomainBase(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setDsData(ImmutableSet.of(DelegationSignerData.create(1, 2, 3, new byte[] {0, 1, 2})))
|
||||
.build());
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_secDnsMultipleInvalidDigestTypes() throws Exception {
|
||||
setEppInput("domain_update_dsdata_add.xml", OTHER_DSDATA_TEMPLATE_MAP);
|
||||
persistResource(
|
||||
newDomainBase(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setDsData(
|
||||
ImmutableSet.of(
|
||||
DelegationSignerData.create(1, 2, 3, new byte[] {0, 1, 2}),
|
||||
DelegationSignerData.create(2, 2, 6, new byte[] {0, 1, 2})))
|
||||
.build());
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains("digestType=3");
|
||||
assertThat(thrown).hasMessageThat().contains("digestType=6");
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_secDnsInvalidAlgorithm() throws Exception {
|
||||
setEppInput("domain_update_dsdata_add.xml", OTHER_DSDATA_TEMPLATE_MAP);
|
||||
persistResource(
|
||||
newDomainBase(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setDsData(ImmutableSet.of(DelegationSignerData.create(1, 99, 2, new byte[] {0, 1, 2})))
|
||||
.build());
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_secDnsMultipleInvalidAlgorithms() throws Exception {
|
||||
setEppInput("domain_update_dsdata_add.xml", OTHER_DSDATA_TEMPLATE_MAP);
|
||||
persistResource(
|
||||
newDomainBase(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setDsData(
|
||||
ImmutableSet.of(
|
||||
DelegationSignerData.create(1, 998, 2, new byte[] {0, 1, 2}),
|
||||
DelegationSignerData.create(2, 99, 2, new byte[] {0, 1, 2})))
|
||||
.build());
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains("algorithm=998");
|
||||
assertThat(thrown).hasMessageThat().contains("algorithm=99");
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_secDnsTooManyDsRecords() throws Exception {
|
||||
ImmutableSet.Builder<DelegationSignerData> builder = new ImmutableSet.Builder<>();
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
builder.add(DelegationSignerData.create(i, 2, 3, new byte[] {0, 1, 2}));
|
||||
builder.add(DelegationSignerData.create(i, 2, 2, new byte[] {0, 1, 2}));
|
||||
}
|
||||
|
||||
setEppInput("domain_update_dsdata_add.xml", OTHER_DSDATA_TEMPLATE_MAP);
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.flows.host;
|
||||
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_HOST_KEY;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_HOST_RENAME;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
@@ -230,7 +231,7 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
|
||||
assertTasksEnqueued(
|
||||
QUEUE_ASYNC_HOST_RENAME,
|
||||
new TaskMatcher()
|
||||
.param("hostKey", renamedHost.createVKey().getOfyKey().getString())
|
||||
.param(PARAM_HOST_KEY, renamedHost.createVKey().stringify())
|
||||
.param("requestedTime", clock.nowUtc().toString()));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.model.tld.label;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
@@ -24,14 +25,18 @@ import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.joda.time.Duration.standardDays;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.persistence.transaction.TransactionManagerUtil;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.TestCacheExtension;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.IntStream;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -41,6 +46,8 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
/** Unit tests for {@link PremiumListDao}. */
|
||||
public class PremiumListDaoTest {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final FakeClock fakeClock = new FakeClock();
|
||||
|
||||
@RegisterExtension
|
||||
@@ -260,6 +267,27 @@ public class PremiumListDaoTest {
|
||||
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSave_largeSize_savedQuickly() {
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
ImmutableMap<String, BigDecimal> prices =
|
||||
IntStream.range(0, 20000).boxed().collect(toImmutableMap(String::valueOf, BigDecimal::new));
|
||||
PremiumList list =
|
||||
new PremiumList.Builder()
|
||||
.setName("testname")
|
||||
.setCurrency(USD)
|
||||
.setLabelsToPrices(prices)
|
||||
.setCreationTimestamp(fakeClock.nowUtc())
|
||||
.build();
|
||||
PremiumListDao.save(list);
|
||||
long duration = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
|
||||
if (duration >= 6000) {
|
||||
// Don't fail directly since we can't rely on what sort of machines the test is running on
|
||||
logger.atSevere().log(
|
||||
"Expected premium list update to take 2-3 seconds but it took %d ms", duration);
|
||||
}
|
||||
}
|
||||
|
||||
private static Money moneyOf(CurrencyUnit unit, double amount) {
|
||||
return Money.of(unit, BigDecimal.valueOf(amount).setScale(unit.getDecimalPlaces()));
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.googlecode.objectify.annotation.Entity;
|
||||
import google.registry.model.billing.BillingEvent.OneTime;
|
||||
import google.registry.model.common.ClassPathManager;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.registrar.RegistrarContact;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||
@@ -207,6 +208,29 @@ class VKeyTest {
|
||||
.isEqualTo(VKey.createSql(TestObject.class, "foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreate_stringifiedVKey_resourceKeyFromTaskQueue() throws Exception {
|
||||
VKey<HostResource> vkeyFromNewWebsafeKey =
|
||||
VKey.create(
|
||||
"kind:HostResource@sql:rO0ABXQADzZCQjJGNDc2LUdPT0dMRQ@ofy:ahdzfm"
|
||||
+ "RvbWFpbi1yZWdpc3RyeS1hbHBoYXIhCxIMSG9zdFJlc291cmNlIg82QkIyRjQ3Ni1HT09HTEUM");
|
||||
|
||||
assertThat(vkeyFromNewWebsafeKey.getSqlKey()).isEqualTo("6BB2F476-GOOGLE");
|
||||
assertThat(vkeyFromNewWebsafeKey.getOfyKey().getString())
|
||||
.isEqualTo(
|
||||
"ahdzfmRvbWFpb"
|
||||
+ "i1yZWdpc3RyeS1hbHBoYXIhCxIMSG9zdFJlc291cmNlIg82QkIyRjQ3Ni1HT09HTEUM");
|
||||
|
||||
// the ofy portion of the new vkey string representation was the old vkey string representation
|
||||
VKey<HostResource> vkeyFromOldWebsafeString =
|
||||
VKey.fromWebsafeKey(
|
||||
"ahdzfmRvbW"
|
||||
+ "Fpbi1yZWdpc3RyeS1hbHBoYXIhCxIMSG9zdFJlc291cmNlIg82QkIyRjQ3Ni1HT09HTEUM");
|
||||
|
||||
// the following is assertion is ensure backwork compatibility
|
||||
assertThat(vkeyFromNewWebsafeKey).isEqualTo(vkeyFromOldWebsafeString);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreate_stringifedVKey_ofyOnlyVKeyString() {
|
||||
assertThat(VKey.create("kind:TestObject@ofy:agR0ZXN0chMLEgpUZXN0T2JqZWN0IgNmb28M"))
|
||||
|
||||
+3
@@ -217,11 +217,14 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft
|
||||
JpaTransactionManagerImpl txnManager = new JpaTransactionManagerImpl(emf, clock);
|
||||
cachedTm = TransactionManagerFactory.jpaTm();
|
||||
TransactionManagerFactory.setJpaTm(Suppliers.ofInstance(txnManager));
|
||||
TransactionManagerFactory.setReplicaJpaTm(
|
||||
Suppliers.ofInstance(new ReplicaSimulatingJpaTransactionManager(txnManager)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterEach(ExtensionContext context) {
|
||||
TransactionManagerFactory.setJpaTm(Suppliers.ofInstance(cachedTm));
|
||||
TransactionManagerFactory.setReplicaJpaTm(Suppliers.ofInstance(cachedTm));
|
||||
// Even though we didn't set this, reset it to make sure no other tests are affected
|
||||
JpaTransactionManagerImpl.removeReplaySqlToDsOverrideForTest();
|
||||
cachedTm = null;
|
||||
|
||||
+12
@@ -16,6 +16,7 @@ package google.registry.persistence.transaction;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.testing.DatabaseHelper.insertInDb;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@@ -62,6 +63,17 @@ public class JpaTransactionManagerExtensionTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReplicaJpaTm() {
|
||||
TestEntity testEntity = new TestEntity("foo", "bar");
|
||||
assertThat(
|
||||
assertThrows(
|
||||
PersistenceException.class,
|
||||
() -> replicaJpaTm().transact(() -> replicaJpaTm().put(testEntity))))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Error while committing the transaction");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExtraParameters() {
|
||||
// This test verifies that 1) withEntityClass() has registered TestEntity and 2) The table
|
||||
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.persistence.transaction;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A {@link JpaTransactionManager} that simulates a read-only replica SQL instance.
|
||||
*
|
||||
* <p>We accomplish this by delegating all calls to the standard transaction manager except for
|
||||
* calls that start transactions. For these, we create a transaction like normal but set it to READ
|
||||
* ONLY mode before doing any work. This is similar to how the read-only Postgres replica works; it
|
||||
* treats all transactions as read-only transactions.
|
||||
*/
|
||||
public class ReplicaSimulatingJpaTransactionManager implements JpaTransactionManager {
|
||||
|
||||
private final JpaTransactionManager delegate;
|
||||
|
||||
public ReplicaSimulatingJpaTransactionManager(JpaTransactionManager delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardown() {
|
||||
delegate.teardown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManager getStandaloneEntityManager() {
|
||||
return delegate.getStandaloneEntityManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManager getEntityManager() {
|
||||
return delegate.getEntityManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaTransactionManager setDatabaseSnapshot(String snapshotId) {
|
||||
return delegate.setDatabaseSnapshot(snapshotId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> query(String sqlString, Class<T> resultClass) {
|
||||
return delegate.query(sqlString, resultClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> criteriaQuery(CriteriaQuery<T> criteriaQuery) {
|
||||
return delegate.criteriaQuery(criteriaQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query query(String sqlString) {
|
||||
return delegate.query(sqlString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inTransaction() {
|
||||
return delegate.inTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertInTransaction() {
|
||||
delegate.assertInTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transact(Supplier<T> work) {
|
||||
if (delegate.inTransaction()) {
|
||||
return work.get();
|
||||
}
|
||||
return delegate.transact(
|
||||
() -> {
|
||||
delegate
|
||||
.getEntityManager()
|
||||
.createNativeQuery("SET TRANSACTION READ ONLY")
|
||||
.executeUpdate();
|
||||
return work.get();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transactWithoutBackup(Supplier<T> work) {
|
||||
return transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transactNoRetry(Supplier<T> work) {
|
||||
return transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transact(Runnable work) {
|
||||
transact(
|
||||
() -> {
|
||||
work.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transactNoRetry(Runnable work) {
|
||||
transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transactNew(Supplier<T> work) {
|
||||
return transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transactNew(Runnable work) {
|
||||
transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transactNewReadOnly(Supplier<T> work) {
|
||||
return transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transactNewReadOnly(Runnable work) {
|
||||
transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T doTransactionless(Supplier<T> work) {
|
||||
return delegate.doTransactionless(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateTime getTransactionTime() {
|
||||
return delegate.getTransactionTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(Object entity) {
|
||||
delegate.insert(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertAll(ImmutableCollection<?> entities) {
|
||||
delegate.insertAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertAll(ImmutableObject... entities) {
|
||||
delegate.insertAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertWithoutBackup(ImmutableObject entity) {
|
||||
delegate.insertWithoutBackup(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertAllWithoutBackup(ImmutableCollection<?> entities) {
|
||||
delegate.insertAllWithoutBackup(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object entity) {
|
||||
delegate.put(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(ImmutableObject... entities) {
|
||||
delegate.putAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(ImmutableCollection<?> entities) {
|
||||
delegate.putAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putWithoutBackup(ImmutableObject entity) {
|
||||
delegate.putWithoutBackup(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAllWithoutBackup(ImmutableCollection<?> entities) {
|
||||
delegate.putAllWithoutBackup(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Object entity) {
|
||||
delegate.update(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAll(ImmutableCollection<?> entities) {
|
||||
delegate.updateAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAll(ImmutableObject... entities) {
|
||||
delegate.updateAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateWithoutBackup(ImmutableObject entity) {
|
||||
delegate.updateWithoutBackup(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAllWithoutBackup(ImmutableCollection<?> entities) {
|
||||
delegate.updateAllWithoutBackup(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean exists(VKey<T> key) {
|
||||
return delegate.exists(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(Object entity) {
|
||||
return delegate.exists(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> loadByKeyIfPresent(VKey<T> key) {
|
||||
return delegate.loadByKeyIfPresent(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableMap<VKey<? extends T>, T> loadByKeysIfPresent(
|
||||
Iterable<? extends VKey<? extends T>> vKeys) {
|
||||
return delegate.loadByKeysIfPresent(vKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableList<T> loadByEntitiesIfPresent(Iterable<T> entities) {
|
||||
return delegate.loadByEntitiesIfPresent(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T loadByKey(VKey<T> key) {
|
||||
return delegate.loadByKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableMap<VKey<? extends T>, T> loadByKeys(
|
||||
Iterable<? extends VKey<? extends T>> vKeys) {
|
||||
return delegate.loadByKeys(vKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T loadByEntity(T entity) {
|
||||
return delegate.loadByEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableList<T> loadByEntities(Iterable<T> entities) {
|
||||
return delegate.loadByEntities(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableList<T> loadAllOf(Class<T> clazz) {
|
||||
return delegate.loadAllOf(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Stream<T> loadAllOfStream(Class<T> clazz) {
|
||||
return delegate.loadAllOfStream(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> loadSingleton(Class<T> clazz) {
|
||||
return delegate.loadSingleton(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(VKey<?> key) {
|
||||
delegate.delete(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Iterable<? extends VKey<?>> vKeys) {
|
||||
delegate.delete(vKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T delete(T entity) {
|
||||
return delegate.delete(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteWithoutBackup(VKey<?> key) {
|
||||
delegate.deleteWithoutBackup(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteWithoutBackup(Iterable<? extends VKey<?>> keys) {
|
||||
delegate.deleteWithoutBackup(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteWithoutBackup(Object entity) {
|
||||
delegate.deleteWithoutBackup(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> QueryComposer<T> createQueryComposer(Class<T> entity) {
|
||||
return delegate.createQueryComposer(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSessionCache() {
|
||||
delegate.clearSessionCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOfy() {
|
||||
return delegate.isOfy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIgnoringReadOnlyWithoutBackup(Object entity) {
|
||||
delegate.putIgnoringReadOnlyWithoutBackup(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteIgnoringReadOnlyWithoutBackup(VKey<?> key) {
|
||||
delegate.deleteIgnoringReadOnlyWithoutBackup(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void assertDelete(VKey<T> key) {
|
||||
delegate.assertDelete(key);
|
||||
}
|
||||
}
|
||||
@@ -93,39 +93,27 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
}
|
||||
|
||||
private JsonObject generateActualJson(RequestType requestType, String paramValue, String cursor) {
|
||||
action.requestPath = actionPath;
|
||||
action.requestMethod = POST;
|
||||
String requestTypeParam = null;
|
||||
String requestTypeParam;
|
||||
switch (requestType) {
|
||||
case NAME:
|
||||
action.nameParam = Optional.of(paramValue);
|
||||
action.nsLdhNameParam = Optional.empty();
|
||||
action.nsIpParam = Optional.empty();
|
||||
requestTypeParam = "name";
|
||||
break;
|
||||
case NS_LDH_NAME:
|
||||
action.nameParam = Optional.empty();
|
||||
action.nsLdhNameParam = Optional.of(paramValue);
|
||||
action.nsIpParam = Optional.empty();
|
||||
requestTypeParam = "nsLdhName";
|
||||
break;
|
||||
case NS_IP:
|
||||
action.nameParam = Optional.empty();
|
||||
action.nsLdhNameParam = Optional.empty();
|
||||
action.nsIpParam = Optional.of(paramValue);
|
||||
requestTypeParam = "nsIp";
|
||||
break;
|
||||
default:
|
||||
action.nameParam = Optional.empty();
|
||||
action.nsLdhNameParam = Optional.empty();
|
||||
action.nsIpParam = Optional.empty();
|
||||
requestTypeParam = "";
|
||||
break;
|
||||
}
|
||||
if (paramValue != null) {
|
||||
if (cursor == null) {
|
||||
action.parameterMap = ImmutableListMultimap.of(requestTypeParam, paramValue);
|
||||
action.cursorTokenParam = Optional.empty();
|
||||
} else {
|
||||
action.parameterMap =
|
||||
ImmutableListMultimap.of(requestTypeParam, paramValue, "cursor", cursor);
|
||||
@@ -381,6 +369,11 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
clock.nowUtc()));
|
||||
|
||||
action.requestMethod = POST;
|
||||
action.nameParam = Optional.empty();
|
||||
action.nsLdhNameParam = Optional.empty();
|
||||
action.nsIpParam = Optional.empty();
|
||||
action.cursorTokenParam = Optional.empty();
|
||||
action.requestPath = actionPath;
|
||||
}
|
||||
|
||||
private JsonObject generateExpectedJsonForTwoDomainsNsReply() {
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.rde;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.SystemInfo.hasCommand;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
@@ -27,6 +28,8 @@ import com.google.common.io.CharStreams;
|
||||
import com.google.common.io.Files;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.keyring.api.Keyring;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.BouncyCastleProviderExtension;
|
||||
import google.registry.testing.FakeKeyringModule;
|
||||
@@ -109,6 +112,10 @@ public class BrdaCopyActionTest {
|
||||
action.receiverKey = receiverKey;
|
||||
action.signingKey = signingKey;
|
||||
action.stagingDecryptionKey = decryptKey;
|
||||
tm().transact(
|
||||
() -> {
|
||||
RdeRevision.saveRevision("lol", DateTime.parse("2010-10-17TZ"), RdeMode.THIN, 0);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
||||
@@ -27,7 +27,6 @@ import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistResourceWithCommitLog;
|
||||
import static google.registry.testing.TaskQueueHelper.assertAtLeastOneTaskIsEnqueued;
|
||||
import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued;
|
||||
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
|
||||
import static google.registry.testing.TestDataHelper.loadFile;
|
||||
import static google.registry.tldconfig.idn.IdnTableEnum.EXTENDED_LATIN;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -50,17 +49,15 @@ import google.registry.model.ofy.Ofy;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.testing.CloudTasksHelper;
|
||||
import google.registry.testing.CloudTasksHelper.TaskMatcher;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeKeyringModule;
|
||||
import google.registry.testing.FakeLockHandler;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.InjectExtension;
|
||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||
import google.registry.testing.mapreduce.MapreduceTestCase;
|
||||
import google.registry.tldconfig.idn.IdnTableEnum;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.SystemSleeper;
|
||||
import google.registry.util.TaskQueueUtils;
|
||||
import google.registry.xjc.XjcXmlTransformer;
|
||||
import google.registry.xjc.rde.XjcRdeContentType;
|
||||
import google.registry.xjc.rde.XjcRdeDeposit;
|
||||
@@ -100,10 +97,19 @@ public class RdeStagingActionDatastoreTest extends MapreduceTestCase<RdeStagingA
|
||||
|
||||
@RegisterExtension public final InjectExtension inject = new InjectExtension();
|
||||
|
||||
private final FakeClock clock = new FakeClock();
|
||||
/**
|
||||
* Without autoIncrement mode, the fake clock won't advance between Mapper and Reducer
|
||||
* transactions when action is invoked, resulting in rolled back reducer transaction (due to
|
||||
* TimestampInversionException if both transactions are mapped to the same CommitLog bucket) and
|
||||
* multiple RdeUplaod/BrdaCopy tasks being enqueued (due to transaction retries, since Cloud Tasks
|
||||
* enqueuing is not transactional with Datastore transactions).
|
||||
*/
|
||||
private final FakeClock clock = new FakeClock().setAutoIncrementByOneMilli();
|
||||
|
||||
private final FakeResponse response = new FakeResponse();
|
||||
private final GcsUtils gcsUtils = new GcsUtils(LocalStorageHelper.getOptions());
|
||||
private final List<? super XjcRdeContentType> alreadyExtracted = new ArrayList<>();
|
||||
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
|
||||
|
||||
private static PGPPublicKey encryptKey;
|
||||
private static PGPPrivateKey decryptKey;
|
||||
@@ -124,7 +130,7 @@ public class RdeStagingActionDatastoreTest extends MapreduceTestCase<RdeStagingA
|
||||
action.mrRunner = makeDefaultRunner();
|
||||
action.lenient = false;
|
||||
action.reducerFactory = new RdeStagingReducer.Factory();
|
||||
action.reducerFactory.taskQueueUtils = new TaskQueueUtils(new Retrier(new SystemSleeper(), 1));
|
||||
action.reducerFactory.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
action.reducerFactory.lockHandler = new FakeLockHandler(true);
|
||||
action.reducerFactory.bucket = "rde-bucket";
|
||||
action.reducerFactory.lockTimeout = Duration.standardHours(1);
|
||||
@@ -467,11 +473,13 @@ public class RdeStagingActionDatastoreTest extends MapreduceTestCase<RdeStagingA
|
||||
clock.setTo(DateTime.parse("2000-01-04TZ")); // Tuesday
|
||||
action.run();
|
||||
executeTasksUntilEmpty("mapreduce", clock);
|
||||
assertTasksEnqueued("rde-upload",
|
||||
new TaskMatcher()
|
||||
.url(RdeUploadAction.PATH)
|
||||
.param(RequestParameters.PARAM_TLD, "lol"));
|
||||
assertTasksEnqueued("brda",
|
||||
// TODO(b/217773051): duplicate tasks are possible though unlikely. Consider if below calls are
|
||||
// appropriate since they don't allow duplicates.
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"rde-upload",
|
||||
new TaskMatcher().url(RdeUploadAction.PATH).param(RequestParameters.PARAM_TLD, "lol"));
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"brda",
|
||||
new TaskMatcher()
|
||||
.url(BrdaCopyAction.PATH)
|
||||
.param(RequestParameters.PARAM_TLD, "lol")
|
||||
|
||||
@@ -20,8 +20,6 @@ import static google.registry.model.rde.RdeMode.FULL;
|
||||
import static google.registry.model.rde.RdeMode.THIN;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued;
|
||||
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
|
||||
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -41,13 +39,10 @@ import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.CloudTasksHelper;
|
||||
import google.registry.testing.CloudTasksHelper.TaskMatcher;
|
||||
import google.registry.testing.FakeKeyringModule;
|
||||
import google.registry.testing.FakeLockHandler;
|
||||
import google.registry.testing.FakeSleeper;
|
||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.TaskQueueUtils;
|
||||
import google.registry.xml.ValidationMode;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
@@ -74,6 +69,7 @@ class RdeStagingReducerTest {
|
||||
private static final PGPPublicKey encryptionKey =
|
||||
new FakeKeyringModule().get().getRdeStagingEncryptionKey();
|
||||
private static final DateTime now = DateTime.parse("2000-01-01TZ");
|
||||
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
|
||||
|
||||
private Fragments brdaFragments =
|
||||
new Fragments(
|
||||
@@ -94,7 +90,7 @@ class RdeStagingReducerTest {
|
||||
|
||||
private RdeStagingReducer reducer =
|
||||
new RdeStagingReducer(
|
||||
new TaskQueueUtils(new Retrier(new FakeSleeper(new FakeClock()), 1)),
|
||||
cloudTasksHelper.getTestCloudTasksUtils(),
|
||||
new FakeLockHandler(true),
|
||||
GCS_BUCKET,
|
||||
Duration.ZERO,
|
||||
@@ -133,7 +129,7 @@ class RdeStagingReducerTest {
|
||||
assertThat(loadCursorTime(CursorType.BRDA))
|
||||
.isEquivalentAccordingToCompareTo(now.plus(Duration.standardDays(1)));
|
||||
assertThat(loadRevision(THIN)).isEqualTo(1);
|
||||
assertTasksEnqueued(
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"brda",
|
||||
new TaskMatcher()
|
||||
.url(BrdaCopyAction.PATH)
|
||||
@@ -159,7 +155,7 @@ class RdeStagingReducerTest {
|
||||
// No extra operations in manual mode.
|
||||
assertThat(loadCursorTime(CursorType.BRDA)).isEquivalentAccordingToCompareTo(now);
|
||||
assertThat(loadRevision(THIN)).isEqualTo(0);
|
||||
assertNoTasksEnqueued("brda");
|
||||
cloudTasksHelper.assertNoTasksEnqueued("brda");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,7 +175,7 @@ class RdeStagingReducerTest {
|
||||
assertThat(loadCursorTime(CursorType.RDE_STAGING))
|
||||
.isEquivalentAccordingToCompareTo(now.plus(Duration.standardDays(1)));
|
||||
assertThat(loadRevision(FULL)).isEqualTo(1);
|
||||
assertTasksEnqueued(
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"rde-upload",
|
||||
new TaskMatcher().url(RdeUploadAction.PATH).param(RequestParameters.PARAM_TLD, "soy"));
|
||||
}
|
||||
@@ -200,7 +196,7 @@ class RdeStagingReducerTest {
|
||||
// No extra operations in manual mode.
|
||||
assertThat(loadCursorTime(CursorType.RDE_STAGING)).isEquivalentAccordingToCompareTo(now);
|
||||
assertThat(loadRevision(FULL)).isEqualTo(0);
|
||||
assertNoTasksEnqueued("rde-upload");
|
||||
cloudTasksHelper.assertNoTasksEnqueued("rde-upload");
|
||||
}
|
||||
|
||||
private static void compareLength(String outputFile, String lengthFilename) throws IOException {
|
||||
|
||||
+24
-9
@@ -15,23 +15,27 @@
|
||||
package google.registry.reporting.billing;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued;
|
||||
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.cloud.tasks.v2.HttpMethod;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.protobuf.util.Timestamps;
|
||||
import google.registry.beam.BeamActionTestBase;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.PrimaryDatabase;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.CloudTasksHelper;
|
||||
import google.registry.testing.CloudTasksHelper.TaskMatcher;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import java.io.IOException;
|
||||
import org.joda.time.Duration;
|
||||
import org.joda.time.YearMonth;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
@@ -45,6 +49,8 @@ class GenerateInvoicesActionTest extends BeamActionTestBase {
|
||||
|
||||
private final BillingEmailUtils emailUtils = mock(BillingEmailUtils.class);
|
||||
private FakeClock clock = new FakeClock();
|
||||
private CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
|
||||
private CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
|
||||
private GenerateInvoicesAction action;
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -60,6 +66,7 @@ class GenerateInvoicesActionTest extends BeamActionTestBase {
|
||||
PrimaryDatabase.DATASTORE,
|
||||
new YearMonth(2017, 10),
|
||||
emailUtils,
|
||||
cloudTasksUtils,
|
||||
clock,
|
||||
response,
|
||||
dataflow);
|
||||
@@ -68,13 +75,19 @@ class GenerateInvoicesActionTest extends BeamActionTestBase {
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getPayload()).isEqualTo("Launched invoicing pipeline: jobid");
|
||||
|
||||
TaskMatcher matcher =
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"beam-reporting",
|
||||
new TaskMatcher()
|
||||
.url("/_dr/task/publishInvoices")
|
||||
.method("POST")
|
||||
.method(HttpMethod.POST)
|
||||
.param("jobId", "jobid")
|
||||
.param("yearMonth", "2017-10");
|
||||
assertTasksEnqueued("beam-reporting", matcher);
|
||||
.param("yearMonth", "2017-10")
|
||||
.scheduleTime(
|
||||
Timestamps.fromMillis(
|
||||
clock
|
||||
.nowUtc()
|
||||
.plus(Duration.standardMinutes(ReportingModule.ENQUEUE_DELAY_MINUTES))
|
||||
.getMillis())));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -90,6 +103,7 @@ class GenerateInvoicesActionTest extends BeamActionTestBase {
|
||||
PrimaryDatabase.DATASTORE,
|
||||
new YearMonth(2017, 10),
|
||||
emailUtils,
|
||||
cloudTasksUtils,
|
||||
clock,
|
||||
response,
|
||||
dataflow);
|
||||
@@ -97,7 +111,7 @@ class GenerateInvoicesActionTest extends BeamActionTestBase {
|
||||
assertThat(response.getContentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8);
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getPayload()).isEqualTo("Launched invoicing pipeline: jobid");
|
||||
assertNoTasksEnqueued("beam-reporting");
|
||||
cloudTasksHelper.assertNoTasksEnqueued("beam-reporting");
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -114,6 +128,7 @@ class GenerateInvoicesActionTest extends BeamActionTestBase {
|
||||
PrimaryDatabase.DATASTORE,
|
||||
new YearMonth(2017, 10),
|
||||
emailUtils,
|
||||
cloudTasksUtils,
|
||||
clock,
|
||||
response,
|
||||
dataflow);
|
||||
@@ -121,6 +136,6 @@ class GenerateInvoicesActionTest extends BeamActionTestBase {
|
||||
assertThat(response.getStatus()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
|
||||
assertThat(response.getPayload()).isEqualTo("Pipeline launch failed: Pipeline error");
|
||||
verify(emailUtils).sendAlertEmail("Pipeline Launch failed due to Pipeline error");
|
||||
assertNoTasksEnqueued("beam-reporting");
|
||||
cloudTasksHelper.assertNoTasksEnqueued("beam-reporting");
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user