mirror of
https://github.com/google/nomulus
synced 2026-08-02 21:36:08 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d1a67b01b | ||
|
|
01d3932122 | ||
|
|
2eb8bb3996 | ||
|
|
2218663d55 | ||
|
|
e0dc2e43bb | ||
|
|
7fedd40739 | ||
|
|
f793ca5b68 | ||
|
|
395ed19601 | ||
|
|
cecc1a6cc7 | ||
|
|
77bc072aac | ||
|
|
93a479837f | ||
|
|
1e7aae26a3 | ||
|
|
201b6e8e0b | ||
|
|
43074ea32f | ||
|
|
1a4a31569e | ||
|
|
c7f50dae92 | ||
|
|
7344c424d1 |
+39
-26
@@ -200,11 +200,28 @@ rootProject.ext {
|
||||
pyver = { exe ->
|
||||
try {
|
||||
ext.execInBash(
|
||||
exe + " -c 'import sys; print(sys.hexversion)'", "/") as Integer
|
||||
exe + " -c 'import sys; print(sys.hexversion)' 2>/dev/null",
|
||||
"/") as Integer
|
||||
} catch (org.gradle.process.internal.ExecException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the path to a usable python3 executable.
|
||||
getPythonExecutable = {
|
||||
// Find a python version greater than 3.7.3 (this is somewhat arbitrary, we
|
||||
// know we'd like at least 3.6, but 3.7.3 is the latest that ships with
|
||||
// Debian so it seems like that should be available anywhere).
|
||||
def MIN_PY_VER = 0x3070300
|
||||
if (pyver('python') >= MIN_PY_VER) {
|
||||
return 'python'
|
||||
} else if (pyver('/usr/bin/python3') >= MIN_PY_VER) {
|
||||
return '/usr/bin/python3'
|
||||
} else {
|
||||
throw new GradleException("No usable Python version found (build " +
|
||||
"requires at least python 3.7.3)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task runPresubmits(type: Exec) {
|
||||
@@ -212,18 +229,7 @@ task runPresubmits(type: Exec) {
|
||||
args('config/presubmits.py')
|
||||
|
||||
doFirst {
|
||||
// Find a python version greater than 3.7.3 (this is somewhat arbitrary, we
|
||||
// know we'd like at least 3.6, but 3.7.3 is the latest that ships with
|
||||
// Debian so it seems like that should be available anywhere).
|
||||
def MIN_PY_VER = 0x3070300
|
||||
if (pyver('python') >= MIN_PY_VER) {
|
||||
executable 'python'
|
||||
} else if (pyver('/usr/bin/python3') >= MIN_PY_VER) {
|
||||
executable '/usr/bin/python3'
|
||||
} else {
|
||||
throw new GradleException("No usable Python version found (build " +
|
||||
"requires at least python 3.7.3)");
|
||||
}
|
||||
executable getPythonExecutable()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,9 +444,10 @@ rootProject.ext {
|
||||
? "${rootDir}/.."
|
||||
: rootDir
|
||||
def formatDiffScript = "${scriptDir}/google-java-format-git-diff.sh"
|
||||
def pythonExe = getPythonExecutable()
|
||||
|
||||
return ext.execInBash(
|
||||
"${formatDiffScript} ${action}", "${workingDir}")
|
||||
"PYTHON=${pythonExe} ${formatDiffScript} ${action}", "${workingDir}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,18 +455,23 @@ rootProject.ext {
|
||||
// Note that this task checks modified Java files in the entire repository.
|
||||
task javaIncrementalFormatCheck {
|
||||
doLast {
|
||||
def checkResult = invokeJavaDiffFormatScript("check")
|
||||
if (checkResult == 'true') {
|
||||
throw new IllegalStateException(
|
||||
"Some Java files need to be reformatted. You may use the "
|
||||
+ "'javaIncrementalFormatDryRun' task to review\n "
|
||||
+ "the changes, or the 'javaIncrementalFormatApply' task "
|
||||
+ "to reformat.")
|
||||
} else if (checkResult != 'false') {
|
||||
throw new RuntimeException(
|
||||
"Failed to invoke format check script:\n" + checkResult)
|
||||
// We can only do this in a git tree.
|
||||
if (new File("${rootDir}/.git").exists()) {
|
||||
def checkResult = invokeJavaDiffFormatScript("check")
|
||||
if (checkResult == 'true') {
|
||||
throw new IllegalStateException(
|
||||
"Some Java files need to be reformatted. You may use the "
|
||||
+ "'javaIncrementalFormatDryRun' task to review\n "
|
||||
+ "the changes, or the 'javaIncrementalFormatApply' task "
|
||||
+ "to reformat.")
|
||||
} else if (checkResult != 'false') {
|
||||
throw new RuntimeException(
|
||||
"Failed to invoke format check script:\n" + checkResult)
|
||||
}
|
||||
println("Incremental Java format check ok.")
|
||||
} else {
|
||||
println("Omitting format check: not in a git directory.")
|
||||
}
|
||||
println("Incremental Java format check ok.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,7 +501,8 @@ task javadoc(type: Javadoc) {
|
||||
options.addBooleanOption('Xdoclint:all,-missing', true)
|
||||
options.addBooleanOption("-allow-script-in-comments",true)
|
||||
options.tags = ["type:a:Generic Type",
|
||||
"error:a:Expected Error"]
|
||||
"error:a:Expected Error",
|
||||
"invariant:a:Guaranteed Property"]
|
||||
}
|
||||
|
||||
tasks.build.dependsOn(tasks.javadoc)
|
||||
|
||||
@@ -119,9 +119,10 @@ PRESUBMITS = {
|
||||
"AppEngineExtension.register(...) instead.",
|
||||
|
||||
# PostgreSQLContainer instantiation must specify docker tag
|
||||
# TODO(b/204572437): Fix the pattern to pass DatabaseSnapshotTest.java
|
||||
PresubmitCheck(
|
||||
r"[\s\S]*new\s+PostgreSQLContainer(<[\s\S]*>)?\(\s*\)[\s\S]*",
|
||||
"java", {}):
|
||||
"java", {"DatabaseSnapshotTest.java"}):
|
||||
"PostgreSQLContainer instantiation must specify docker tag.",
|
||||
|
||||
# Various Soy linting checks
|
||||
|
||||
@@ -112,7 +112,7 @@ public class ReplayCommitLogsToSqlAction implements Runnable {
|
||||
return;
|
||||
}
|
||||
Optional<Lock> lock =
|
||||
Lock.acquire(
|
||||
Lock.acquireSql(
|
||||
this.getClass().getSimpleName(), null, LEASE_LENGTH, requestStatusChecker, false);
|
||||
if (!lock.isPresent()) {
|
||||
String message = "Can't acquire SQL commit log replay lock, aborting.";
|
||||
|
||||
+31
-25
@@ -113,9 +113,11 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of registrars that should receive expiring notification emails. There are two
|
||||
* certificates that should be considered (the main certificate and failOver certificate). The
|
||||
* registrars should receive notifications if one of the certificate checks returns true.
|
||||
* Returns a list of registrars that should receive expiring notification emails.
|
||||
*
|
||||
* <p>There are two certificates that should be considered (the main certificate and failOver
|
||||
* certificate). The registrars should receive notifications if one of the certificate checks
|
||||
* returns true.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
ImmutableList<RegistrarInfo> getRegistrarsWithExpiringCertificates() {
|
||||
@@ -157,15 +159,17 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
}
|
||||
try {
|
||||
ImmutableSet<InternetAddress> recipients = getEmailAddresses(registrar, Type.TECH);
|
||||
ImmutableSet<InternetAddress> ccs = getEmailAddresses(registrar, Type.ADMIN);
|
||||
Date expirationDate = certificateChecker.getCertificate(certificate.get()).getNotAfter();
|
||||
logger.atInfo().log(
|
||||
"Registrar %s should receive an email that its %s SSL certificate will expire on %s.",
|
||||
registrar.getRegistrarName(),
|
||||
" %s SSL certificate of registrar '%s' will expire on %s.",
|
||||
certificateType.getDisplayName(),
|
||||
registrar.getRegistrarName(),
|
||||
expirationDate.toString());
|
||||
if (recipients.isEmpty()) {
|
||||
if (recipients.isEmpty() && ccs.isEmpty()) {
|
||||
logger.atWarning().log(
|
||||
"Registrar %s contains no email addresses to receive notification email.",
|
||||
"Registrar %s contains no TECH nor ADMIN email addresses to receive notification"
|
||||
+ " email.",
|
||||
registrar.getRegistrarName());
|
||||
return false;
|
||||
}
|
||||
@@ -180,7 +184,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
expirationDate,
|
||||
registrar.getRegistrarId()))
|
||||
.setRecipients(recipients)
|
||||
.setCcs(getEmailAddresses(registrar, Type.ADMIN))
|
||||
.setCcs(ccs)
|
||||
.build());
|
||||
/*
|
||||
* A duration time offset is used here to ensure that date comparison between two
|
||||
@@ -249,30 +253,32 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
/** Sends notification emails to registrars with expiring certificates. */
|
||||
@VisibleForTesting
|
||||
int sendNotificationEmails() {
|
||||
int emailsSent = 0;
|
||||
int numEmailsSent = 0;
|
||||
for (RegistrarInfo registrarInfo : getRegistrarsWithExpiringCertificates()) {
|
||||
Registrar registrar = registrarInfo.registrar();
|
||||
if (registrarInfo.isCertExpiring()) {
|
||||
sendNotificationEmail(
|
||||
registrar,
|
||||
registrar.getLastExpiringCertNotificationSentDate(),
|
||||
CertificateType.PRIMARY,
|
||||
registrar.getClientCertificate());
|
||||
emailsSent++;
|
||||
if (registrarInfo.isCertExpiring()
|
||||
&& sendNotificationEmail(
|
||||
registrar,
|
||||
registrar.getLastExpiringCertNotificationSentDate(),
|
||||
CertificateType.PRIMARY,
|
||||
registrar.getClientCertificate())) {
|
||||
numEmailsSent++;
|
||||
}
|
||||
if (registrarInfo.isFailOverCertExpiring()) {
|
||||
sendNotificationEmail(
|
||||
registrar,
|
||||
registrar.getLastExpiringFailoverCertNotificationSentDate(),
|
||||
CertificateType.FAILOVER,
|
||||
registrar.getFailoverClientCertificate());
|
||||
emailsSent++;
|
||||
if (registrarInfo.isFailOverCertExpiring()
|
||||
&& sendNotificationEmail(
|
||||
registrar,
|
||||
registrar.getLastExpiringFailoverCertNotificationSentDate(),
|
||||
CertificateType.FAILOVER,
|
||||
registrar.getFailoverClientCertificate())) {
|
||||
numEmailsSent++;
|
||||
}
|
||||
}
|
||||
return emailsSent;
|
||||
return numEmailsSent;
|
||||
}
|
||||
|
||||
/** Returns a list of email addresses of the registrar that should receive a notification email */
|
||||
/**
|
||||
* Returns a list of email addresses of the registrar that should receive a notification email.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
ImmutableSet<InternetAddress> getEmailAddresses(Registrar registrar, Type contactType) {
|
||||
ImmutableSortedSet<RegistrarContact> contacts = registrar.getContactsOfType(contactType);
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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.beam.common;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import java.util.List;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityTransaction;
|
||||
|
||||
/**
|
||||
* A database snapshot shareable by concurrent queries from multiple database clients. A snapshot is
|
||||
* uniquely identified by its {@link #getSnapshotId snapshotId}, and must stay open until all
|
||||
* concurrent queries to this snapshot have attached to it by calling {@link
|
||||
* google.registry.persistence.transaction.JpaTransactionManager#setDatabaseSnapshot}. However, it
|
||||
* can be closed before those queries complete.
|
||||
*
|
||||
* <p>This feature is <em>Postgresql-only</em>.
|
||||
*
|
||||
* <p>To support large queries, transaction isolation level is fixed at the REPEATABLE_READ to avoid
|
||||
* exhausting predicate locks at the SERIALIZABLE level.
|
||||
*/
|
||||
// TODO(b/193662898): vendor-independent support for richer transaction semantics.
|
||||
public class DatabaseSnapshot implements AutoCloseable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private String snapshotId;
|
||||
private EntityManager entityManager;
|
||||
private EntityTransaction transaction;
|
||||
|
||||
private DatabaseSnapshot() {}
|
||||
|
||||
public String getSnapshotId() {
|
||||
checkState(entityManager != null, "Snapshot not opened yet.");
|
||||
checkState(entityManager.isOpen(), "Snapshot already closed.");
|
||||
return snapshotId;
|
||||
}
|
||||
|
||||
private DatabaseSnapshot open() {
|
||||
entityManager = jpaTm().getStandaloneEntityManager();
|
||||
transaction = entityManager.getTransaction();
|
||||
transaction.setRollbackOnly();
|
||||
transaction.begin();
|
||||
|
||||
entityManager
|
||||
.createNativeQuery("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
|
||||
.executeUpdate();
|
||||
|
||||
List<?> snapshotIds =
|
||||
entityManager.createNativeQuery("SELECT pg_export_snapshot();").getResultList();
|
||||
checkState(snapshotIds.size() == 1, "Unexpected number of snapshots: %s", snapshotIds.size());
|
||||
snapshotId = (String) snapshotIds.get(0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (transaction != null && transaction.isActive()) {
|
||||
try {
|
||||
transaction.rollback();
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log("Failed to close a Database Snapshot");
|
||||
}
|
||||
}
|
||||
if (entityManager != null && entityManager.isOpen()) {
|
||||
entityManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static DatabaseSnapshot createSnapshot() {
|
||||
return new DatabaseSnapshot().open();
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,9 @@ public final class RegistryJpaIO {
|
||||
|
||||
abstract Coder<T> coder();
|
||||
|
||||
@Nullable
|
||||
abstract String snapshotId();
|
||||
|
||||
abstract Builder<R, T> toBuilder();
|
||||
|
||||
@Override
|
||||
@@ -145,7 +148,9 @@ public final class RegistryJpaIO {
|
||||
public PCollection<T> expand(PBegin input) {
|
||||
return input
|
||||
.apply("Starting " + name(), Create.of((Void) null))
|
||||
.apply("Run query for " + name(), ParDo.of(new QueryRunner<>(query(), resultMapper())))
|
||||
.apply(
|
||||
"Run query for " + name(),
|
||||
ParDo.of(new QueryRunner<>(query(), resultMapper(), snapshotId())))
|
||||
.setCoder(coder())
|
||||
.apply("Reshuffle", Reshuffle.viaRandomKey());
|
||||
}
|
||||
@@ -162,6 +167,18 @@ public final class RegistryJpaIO {
|
||||
return toBuilder().coder(coder).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the database snapshot to use for this query.
|
||||
*
|
||||
* <p>This feature is <em>Postgresql-only</em>. User is responsible for keeping the snapshot
|
||||
* available until all JVM workers have started using it by calling {@link
|
||||
* JpaTransactionManager#setDatabaseSnapshot}.
|
||||
*/
|
||||
// TODO(b/193662898): vendor-independent support for richer transaction semantics.
|
||||
public Read<R, T> withSnapshot(String snapshotId) {
|
||||
return toBuilder().snapshotId(snapshotId).build();
|
||||
}
|
||||
|
||||
static <R, T> Builder<R, T> builder() {
|
||||
return new AutoValue_RegistryJpaIO_Read.Builder<R, T>()
|
||||
.name(DEFAULT_NAME)
|
||||
@@ -179,6 +196,8 @@ public final class RegistryJpaIO {
|
||||
|
||||
abstract Builder<R, T> coder(Coder coder);
|
||||
|
||||
abstract Builder<R, T> snapshotId(@Nullable String sharedSnapshotId);
|
||||
|
||||
abstract Read<R, T> build();
|
||||
|
||||
Builder<R, T> criteriaQuery(CriteriaQuerySupplier<R> criteriaQuery) {
|
||||
@@ -201,17 +220,28 @@ public final class RegistryJpaIO {
|
||||
static class QueryRunner<R, T> extends DoFn<Void, T> {
|
||||
private final RegistryQuery<R> query;
|
||||
private final SerializableFunction<R, T> resultMapper;
|
||||
// java.util.Optional is not serializable. Use of Guava Optional is discouraged.
|
||||
@Nullable private final String snapshotId;
|
||||
|
||||
QueryRunner(RegistryQuery<R> query, SerializableFunction<R, T> resultMapper) {
|
||||
QueryRunner(
|
||||
RegistryQuery<R> query,
|
||||
SerializableFunction<R, T> resultMapper,
|
||||
@Nullable String snapshotId) {
|
||||
this.query = query;
|
||||
this.resultMapper = resultMapper;
|
||||
this.snapshotId = snapshotId;
|
||||
}
|
||||
|
||||
@ProcessElement
|
||||
public void processElement(OutputReceiver<T> outputReceiver) {
|
||||
jpaTm()
|
||||
.transactNoRetry(
|
||||
() -> query.stream().map(resultMapper::apply).forEach(outputReceiver::output));
|
||||
() -> {
|
||||
if (snapshotId != null) {
|
||||
jpaTm().setDatabaseSnapshot(snapshotId);
|
||||
}
|
||||
query.stream().map(resultMapper::apply).forEach(outputReceiver::output);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
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.TransactionIsolationLevel;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
@@ -45,9 +46,19 @@ public interface RegistryPipelineComponent {
|
||||
@Config("projectId")
|
||||
String getProjectId();
|
||||
|
||||
/** Returns the regular {@link JpaTransactionManager} for general use. */
|
||||
@BeamJpaTm
|
||||
Lazy<JpaTransactionManager> getJpaTransactionManager();
|
||||
|
||||
/**
|
||||
* Returns a {@link JpaTransactionManager} optimized for bulk loading multi-level JPA entities
|
||||
* ({@link google.registry.model.domain.DomainBase} and {@link
|
||||
* google.registry.model.domain.DomainHistory}). Please refer to {@link
|
||||
* google.registry.model.bulkquery.BulkQueryEntities} for more information.
|
||||
*/
|
||||
@BeamBulkQueryJpaTm
|
||||
Lazy<JpaTransactionManager> getBulkQueryJpaTransactionManager();
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.beam.common;
|
||||
|
||||
import google.registry.beam.common.RegistryJpaIO.Write;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.persistence.PersistenceModule.JpaTransactionManagerType;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -44,6 +45,12 @@ public interface RegistryPipelineOptions extends GcpOptions {
|
||||
|
||||
void setIsolationOverride(TransactionIsolationLevel isolationOverride);
|
||||
|
||||
@Description("The JPA Transaction Manager to use.")
|
||||
@Default.Enum(value = "REGULAR")
|
||||
JpaTransactionManagerType getJpaTransactionManagerType();
|
||||
|
||||
void setJpaTransactionManagerType(JpaTransactionManagerType jpaTransactionManagerType);
|
||||
|
||||
@Description("The number of entities to write to the SQL database in one operation.")
|
||||
@Default.Integer(20)
|
||||
int getSqlWriteBatchSize();
|
||||
|
||||
+12
-3
@@ -49,10 +49,19 @@ public class RegistryPipelineWorkerInitializer implements JvmInitializer {
|
||||
}
|
||||
logger.atInfo().log("Setting up RegistryEnvironment %s.", environment);
|
||||
environment.setup();
|
||||
Lazy<JpaTransactionManager> transactionManagerLazy =
|
||||
toRegistryPipelineComponent(registryOptions).getJpaTransactionManager();
|
||||
RegistryPipelineComponent registryPipelineComponent =
|
||||
toRegistryPipelineComponent(registryOptions);
|
||||
Lazy<JpaTransactionManager> transactionManagerLazy;
|
||||
switch (registryOptions.getJpaTransactionManagerType()) {
|
||||
case BULK_QUERY:
|
||||
transactionManagerLazy = registryPipelineComponent.getBulkQueryJpaTransactionManager();
|
||||
break;
|
||||
case REGULAR:
|
||||
default:
|
||||
transactionManagerLazy = registryPipelineComponent.getJpaTransactionManager();
|
||||
}
|
||||
TransactionManagerFactory.setJpaTmOnBeamWorker(transactionManagerLazy::get);
|
||||
// Masquarade all threads as App Engine threads so we can create Ofy keys in the pipeline. Also
|
||||
// Masquerade all threads as App Engine threads so we can create Ofy keys in the pipeline. Also
|
||||
// loads all ofy entities.
|
||||
new AppEngineEnvironment("Beam").setEnvironmentForAllThreads();
|
||||
// Set the system property so that we can call IdService.allocateId() without access to
|
||||
|
||||
@@ -262,8 +262,8 @@ public final class Transforms {
|
||||
|
||||
// Production data repair configs go below. See b/185954992.
|
||||
|
||||
// Prober domains in bad state, without associated contacts, hosts, billings, and history.
|
||||
// They can be safely ignored.
|
||||
// Prober domains in bad state, without associated contacts, hosts, billings, and non-synthesized
|
||||
// history. They can be safely ignored.
|
||||
private static final ImmutableSet<String> IGNORED_DOMAINS =
|
||||
ImmutableSet.of("6AF6D2-IQCANT", "2-IQANYT");
|
||||
|
||||
@@ -299,7 +299,7 @@ public final class Transforms {
|
||||
return !IGNORED_HOSTS.contains(roid);
|
||||
}
|
||||
if (entity.getKind().equals("HistoryEntry")) {
|
||||
// Remove production bad data: History of the contacts to be ignored:
|
||||
// Remove production bad data: Histories of ignored EPP resources:
|
||||
com.google.appengine.api.datastore.Key parentKey = entity.getKey().getParent();
|
||||
if (parentKey.getKind().equals("ContactResource")) {
|
||||
String contactRoid = parentKey.getName();
|
||||
@@ -309,6 +309,10 @@ public final class Transforms {
|
||||
String hostRoid = parentKey.getName();
|
||||
return !IGNORED_HOSTS.contains(hostRoid);
|
||||
}
|
||||
if (parentKey.getKind().equals("DomainBase")) {
|
||||
String domainRoid = parentKey.getName();
|
||||
return !IGNORED_DOMAINS.contains(domainRoid);
|
||||
}
|
||||
}
|
||||
// End of production-specific checks.
|
||||
|
||||
|
||||
@@ -371,6 +371,10 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
String.format(
|
||||
"Domain %s was deleted by registry administrator with final deletion effective: %s",
|
||||
existingDomain.getDomainName(), deletionTime))
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
DomainPendingActionNotificationResponse.create(
|
||||
existingDomain.getDomainName(), true, trid, now)))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,22 @@ public class AppEngineEnvironment {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Constructor for use by tests.
|
||||
*
|
||||
* <p>All test suites must use the same appId for environments, since when tearing down we do not
|
||||
* clear cached environments in spawned threads. See {@link #unsetEnvironmentForAllThreads} for
|
||||
* more information.
|
||||
*/
|
||||
public AppEngineEnvironment() {
|
||||
this("PlaceholderAppId");
|
||||
/**
|
||||
* Use AppEngineExtension's appId here so that ofy and sql entities can be compared with {@code
|
||||
* Objects#equals()}. The choice of this value does not impact functional correctness.
|
||||
*/
|
||||
this("test");
|
||||
}
|
||||
|
||||
/** Constructor for use by applications, e.g., BEAM pipelines. */
|
||||
public AppEngineEnvironment(String appId) {
|
||||
environment = createAppEngineEnvironment(appId);
|
||||
}
|
||||
@@ -65,7 +77,17 @@ public class AppEngineEnvironment {
|
||||
ApiProxy.clearEnvironmentForCurrentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets the test environment in all threads with best effort.
|
||||
*
|
||||
* <p>This method unsets the environment factory and clears the cached environment in the current
|
||||
* thread (the main test runner thread). We do not clear the cache in spawned threads, even though
|
||||
* they may be reused. This is not a problem as long as the appId stays the same: those threads
|
||||
* are used only in AppEngine or BEAM tests, and expect the presence of an environment.
|
||||
*/
|
||||
public void unsetEnvironmentForAllThreads() {
|
||||
unsetEnvironmentForCurrentThread();
|
||||
|
||||
try {
|
||||
Method method = ApiProxy.class.getDeclaredMethod("clearEnvironmentFactory");
|
||||
method.setAccessible(true);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.model;
|
||||
|
||||
import google.registry.util.PreconditionsUtils;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.AttributeOverride;
|
||||
@@ -30,7 +31,7 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
* that we can enforce strictly increasing timestamps.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class BackupGroupRoot extends ImmutableObject {
|
||||
public abstract class BackupGroupRoot extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/**
|
||||
* An automatically managed timestamp of when this object was last written to Datastore.
|
||||
@@ -49,4 +50,14 @@ public abstract class BackupGroupRoot extends ImmutableObject {
|
||||
public UpdateAutoTimestamp getUpdateTimestamp() {
|
||||
return updateTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies {@link #updateTimestamp} from another entity.
|
||||
*
|
||||
* <p>This method is for the few cases when {@code updateTimestamp} is copied between different
|
||||
* types of entities. Use {@link #clone} for same-type copying.
|
||||
*/
|
||||
protected void copyUpdateTimestamp(BackupGroupRoot other) {
|
||||
this.updateTimestamp = PreconditionsUtils.checkArgumentNotNull(other, "other").updateTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.joda.time.DateTime;
|
||||
*
|
||||
* @see CreateAutoTimestampTranslatorFactory
|
||||
*/
|
||||
public class CreateAutoTimestamp extends ImmutableObject {
|
||||
public class CreateAutoTimestamp extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
DateTime timestamp;
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@ public abstract class ImmutableObject implements Cloneable {
|
||||
@Target(FIELD)
|
||||
public @interface Insignificant {}
|
||||
|
||||
// Note: if this class is made to implement Serializable, this field must become 'transient' since
|
||||
// hashing is not stable across executions. Also note that @XmlTransient is forbidden on transient
|
||||
// fields and need to be removed if transient is added.
|
||||
@Ignore @XmlTransient protected Integer hashCode;
|
||||
|
||||
private boolean equalsImmutableObject(ImmutableObject other) {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Marker interface for Nomulus entities whose serialization are implemented in a fragile way. These
|
||||
* entities are made {@link Serializable} so that they can be passed between JVMs. The intended use
|
||||
* case is BEAM pipeline-based cross-database data validation between Datastore and Cloud SQL during
|
||||
* the migration. Note that only objects loaded from the SQL database need serialization support.
|
||||
* Objects exported from Datastore can already be serialized as protocol buffers.
|
||||
*
|
||||
* <p>All entities implementing this interface take advantage of the fact that all Java collection
|
||||
* classes we use, either directly or indirectly, including those in Java libraries, Guava,
|
||||
* Objectify, and Hibernate are {@code Serializable}.
|
||||
*
|
||||
* <p>The {@code serialVersionUID} field has also been omitted in the implementing classes, since
|
||||
* they are not used for persistence.
|
||||
*/
|
||||
// TODO(b/203609782): either remove this interface or fix implementors post migration.
|
||||
public interface UnsafeSerializable extends Serializable {}
|
||||
@@ -38,7 +38,7 @@ import org.joda.time.DateTime;
|
||||
* @see UpdateAutoTimestampTranslatorFactory
|
||||
*/
|
||||
@Embeddable
|
||||
public class UpdateAutoTimestamp extends ImmutableObject {
|
||||
public class UpdateAutoTimestamp extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
// When set to true, database converters/translators should do the auto update. When set to
|
||||
// false, auto update should be suspended (this exists to allow us to preserve the original value
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.googlecode.objectify.annotation.Parent;
|
||||
import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.common.TimeOfYear;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
@@ -72,7 +73,7 @@ import org.joda.time.DateTime;
|
||||
/** A billable event in a domain's lifecycle. */
|
||||
@MappedSuperclass
|
||||
public abstract class BillingEvent extends ImmutableObject
|
||||
implements Buildable, TransferServerApproveEntity {
|
||||
implements Buildable, TransferServerApproveEntity, UnsafeSerializable {
|
||||
|
||||
/** The reason for the bill, which maps 1:1 to skus in go/registry-billing-skus. */
|
||||
public enum Reason {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainContent;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.GracePeriod.GracePeriodHistory;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsDataHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
|
||||
/**
|
||||
* Utilities for managing an alternative JPA entity model optimized for bulk loading multi-level
|
||||
* entities such as {@link DomainBase} and {@link DomainHistory}.
|
||||
*
|
||||
* <p>In a bulk query for a multi-level JPA entity type, the JPA framework only generates a bulk
|
||||
* query (SELECT * FROM table) for the base table. Then, for each row in the base table, additional
|
||||
* queries are issued to load associated rows in child tables. This can be very slow when an entity
|
||||
* type has multiple child tables.
|
||||
*
|
||||
* <p>We have defined an alternative entity model for {@code DomainBase} and {@code DomainHistory},
|
||||
* where the base table as well as the child tables are mapped to single-level entity types. The
|
||||
* idea is to load each of these types using a bulk query, and assemble them into the target type in
|
||||
* memory in a pipeline. The main use case is Datastore-Cloud SQL validation during the Registry
|
||||
* database migration, where we will need the full database snapshots frequently.
|
||||
*/
|
||||
public class BulkQueryEntities {
|
||||
/**
|
||||
* The JPA entity classes in persistence.xml to replace when creating the {@link
|
||||
* JpaTransactionManager} for bulk query.
|
||||
*/
|
||||
public static final ImmutableMap<String, String> JPA_ENTITIES_REPLACEMENTS =
|
||||
ImmutableMap.of(
|
||||
DomainBase.class.getCanonicalName(),
|
||||
DomainBaseLite.class.getCanonicalName(),
|
||||
DomainHistory.class.getCanonicalName(),
|
||||
DomainHistoryLite.class.getCanonicalName());
|
||||
|
||||
/* The JPA entity classes that are not included in persistence.xml and need to be added to
|
||||
* the {@link JpaTransactionManager} for bulk query.*/
|
||||
public static final ImmutableList<String> JPA_ENTITIES_NEW =
|
||||
ImmutableList.of(
|
||||
DomainHost.class.getCanonicalName(), DomainHistoryHost.class.getCanonicalName());
|
||||
|
||||
public static DomainBase assembleDomainBase(
|
||||
DomainBaseLite domainBaseLite,
|
||||
ImmutableSet<GracePeriod> gracePeriods,
|
||||
ImmutableSet<DelegationSignerData> delegationSignerData,
|
||||
ImmutableSet<VKey<HostResource>> nsHosts) {
|
||||
DomainBase.Builder builder = new DomainBase.Builder();
|
||||
builder.copyFrom(domainBaseLite);
|
||||
builder.setGracePeriods(gracePeriods);
|
||||
builder.setDsData(delegationSignerData);
|
||||
builder.setNameservers(nsHosts);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static DomainHistory assembleDomainHistory(
|
||||
DomainHistoryLite domainHistoryLite,
|
||||
ImmutableSet<DomainDsDataHistory> dsDataHistories,
|
||||
ImmutableSet<VKey<HostResource>> domainHistoryHosts,
|
||||
ImmutableSet<GracePeriodHistory> gracePeriodHistories,
|
||||
ImmutableSet<DomainTransactionRecord> transactionRecords) {
|
||||
DomainHistory.Builder builder = new DomainHistory.Builder();
|
||||
builder.copyFrom(domainHistoryLite);
|
||||
DomainContent rawDomainContent = domainHistoryLite.domainContent;
|
||||
if (rawDomainContent != null) {
|
||||
DomainContent newDomainContent =
|
||||
domainHistoryLite
|
||||
.domainContent
|
||||
.asBuilder()
|
||||
.setNameservers(domainHistoryHosts)
|
||||
.setGracePeriods(
|
||||
gracePeriodHistories.stream()
|
||||
.map(GracePeriod::createFromHistory)
|
||||
.collect(toImmutableSet()))
|
||||
.setDsData(
|
||||
dsDataHistories.stream()
|
||||
.map(DelegationSignerData::create)
|
||||
.collect(toImmutableSet()))
|
||||
.build();
|
||||
builder.setDomain(newDomainContent);
|
||||
}
|
||||
return builder.buildAndAssemble(
|
||||
dsDataHistories, domainHistoryHosts, gracePeriodHistories, transactionRecords);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainContent;
|
||||
import google.registry.model.replay.SqlOnlyEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Entity;
|
||||
|
||||
/**
|
||||
* A 'light' version of {@link DomainBase} with only base table ("Domain") attributes, which allows
|
||||
* fast bulk loading. They are used in in-memory assembly of {@code DomainBase} instances along with
|
||||
* bulk-loaded child entities ({@code GracePeriod} etc). The in-memory assembly achieves much higher
|
||||
* performance than loading {@code DomainBase} directly.
|
||||
*
|
||||
* <p>Please refer to {@link BulkQueryEntities} for more information.
|
||||
*/
|
||||
@Entity(name = "Domain")
|
||||
@WithStringVKey
|
||||
@Access(AccessType.FIELD)
|
||||
public class DomainBaseLite extends DomainContent implements SqlOnlyEntity {
|
||||
|
||||
@Override
|
||||
@javax.persistence.Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
public String getRepoId() {
|
||||
return super.getRepoId();
|
||||
}
|
||||
|
||||
public static VKey<DomainBaseLite> createVKey(String repoId) {
|
||||
return VKey.createSql(DomainBaseLite.class, repoId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.replay.SqlOnlyEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
|
||||
/**
|
||||
* A name server host referenced by a {@link google.registry.model.domain.DomainHistory} record.
|
||||
* Please refer to {@link BulkQueryEntities} for usage.
|
||||
*/
|
||||
@Entity
|
||||
@Access(AccessType.FIELD)
|
||||
@IdClass(DomainHistoryHost.class)
|
||||
public class DomainHistoryHost implements Serializable, SqlOnlyEntity {
|
||||
|
||||
@Id private Long domainHistoryHistoryRevisionId;
|
||||
@Id private String domainHistoryDomainRepoId;
|
||||
@Id private String hostRepoId;
|
||||
|
||||
private DomainHistoryHost() {}
|
||||
|
||||
public DomainHistoryId getDomainHistoryId() {
|
||||
return new DomainHistoryId(domainHistoryDomainRepoId, domainHistoryHistoryRevisionId);
|
||||
}
|
||||
|
||||
public VKey<HostResource> getHostVKey() {
|
||||
return VKey.create(HostResource.class, hostRepoId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainContent;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.replay.SqlOnlyEntity;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.PostLoad;
|
||||
|
||||
/**
|
||||
* A 'light' version of {@link DomainHistory} with only base table ("DomainHistory") attributes,
|
||||
* which allows fast bulk loading. They are used in in-memory assembly of {@code DomainHistory}
|
||||
* instances along with bulk-loaded child entities ({@code GracePeriodHistory} etc). The in-memory
|
||||
* assembly achieves much higher performance than loading {@code DomainHistory} directly.
|
||||
*
|
||||
* <p>Please refer to {@link BulkQueryEntities} for more information.
|
||||
*
|
||||
* <p>This class is adapted from {@link DomainHistory} by removing the {@code dsDataHistories},
|
||||
* {@code gracePeriodHistories}, and {@code nsHosts} fields and associated methods.
|
||||
*/
|
||||
@Entity(name = "DomainHistory")
|
||||
@Access(AccessType.FIELD)
|
||||
@IdClass(DomainHistoryId.class)
|
||||
public class DomainHistoryLite extends HistoryEntry implements SqlOnlyEntity {
|
||||
|
||||
// Store DomainContent instead of DomainBase so we don't pick up its @Id
|
||||
// Nullable for the sake of pre-Registry-3.0 history objects
|
||||
@Nullable DomainContent domainContent;
|
||||
|
||||
@Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
public String getDomainRepoId() {
|
||||
// We need to handle null case here because Hibernate sometimes accesses this method before
|
||||
// parent gets initialized
|
||||
return parent == null ? null : parent.getName();
|
||||
}
|
||||
|
||||
/** This method is private because it is only used by Hibernate. */
|
||||
@SuppressWarnings("unused")
|
||||
private void setDomainRepoId(String domainRepoId) {
|
||||
parent = Key.create(DomainBase.class, domainRepoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@Access(AccessType.PROPERTY)
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "unit", column = @Column(name = "historyPeriodUnit")),
|
||||
@AttributeOverride(name = "value", column = @Column(name = "historyPeriodValue"))
|
||||
})
|
||||
public Period getPeriod() {
|
||||
return super.getPeriod();
|
||||
}
|
||||
|
||||
/**
|
||||
* For transfers, the id of the other registrar.
|
||||
*
|
||||
* <p>For requests and cancels, the other registrar is the losing party (because the registrar
|
||||
* sending the EPP transfer command is the gaining party). For approves and rejects, the other
|
||||
* registrar is the gaining party.
|
||||
*/
|
||||
@Nullable
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Column(name = "historyOtherRegistrarId")
|
||||
@Override
|
||||
public String getOtherRegistrarId() {
|
||||
return super.getOtherRegistrarId();
|
||||
}
|
||||
|
||||
@Id
|
||||
@Column(name = "historyRevisionId")
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Override
|
||||
public long getId() {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
/** The key to the {@link DomainBase} this is based off of. */
|
||||
public VKey<DomainBase> getParentVKey() {
|
||||
return VKey.create(DomainBase.class, getDomainRepoId());
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
if (domainContent == null) {
|
||||
return;
|
||||
}
|
||||
// See inline comments in DomainHistory.postLoad for reasons for the following lines.
|
||||
if (domainContent.getDomainName() == null) {
|
||||
domainContent = null;
|
||||
} else if (domainContent.getRepoId() == null) {
|
||||
domainContent.setRepoId(parent.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.replay.SqlOnlyEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
|
||||
/** A name server host of a domain. Please refer to {@link BulkQueryEntities} for usage. */
|
||||
@Entity
|
||||
@Access(AccessType.FIELD)
|
||||
@IdClass(DomainHost.class)
|
||||
public class DomainHost implements Serializable, SqlOnlyEntity {
|
||||
|
||||
@Id private String domainRepoId;
|
||||
|
||||
@Id private String hostRepoId;
|
||||
|
||||
DomainHost() {}
|
||||
|
||||
public String getDomainRepoId() {
|
||||
return domainRepoId;
|
||||
}
|
||||
|
||||
public VKey<HostResource> getHostVKey() {
|
||||
return VKey.create(HostResource.class, hostRepoId);
|
||||
}
|
||||
}
|
||||
@@ -27,13 +27,13 @@ import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.OnLoad;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import google.registry.model.common.Cursor.CursorId;
|
||||
import google.registry.model.replay.DatastoreAndSqlEntity;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Column;
|
||||
@@ -53,7 +53,7 @@ import org.joda.time.DateTime;
|
||||
@javax.persistence.Entity
|
||||
@IdClass(CursorId.class)
|
||||
@InCrossTld
|
||||
public class Cursor extends ImmutableObject implements DatastoreAndSqlEntity {
|
||||
public class Cursor extends ImmutableObject implements DatastoreAndSqlEntity, UnsafeSerializable {
|
||||
|
||||
/** The scope of a global cursor. A global cursor is a cursor that is not specific to one tld. */
|
||||
public static final String GLOBAL = "GLOBAL";
|
||||
@@ -283,7 +283,7 @@ public class Cursor extends ImmutableObject implements DatastoreAndSqlEntity {
|
||||
return cursorTime;
|
||||
}
|
||||
|
||||
static class CursorId extends ImmutableObject implements Serializable {
|
||||
public static class CursorId extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
public CursorType type;
|
||||
public String scope;
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.google.common.collect.Range;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import java.util.List;
|
||||
import javax.persistence.Embeddable;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -45,7 +46,7 @@ import org.joda.time.DateTime;
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class TimeOfYear extends ImmutableObject {
|
||||
public class TimeOfYear extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/**
|
||||
* The time as "month day millis" with all fields left-padded with zeroes so that lexographic
|
||||
|
||||
@@ -27,7 +27,9 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.googlecode.objectify.mapper.Mapper;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.util.TypeUtils;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
@@ -53,11 +55,12 @@ import org.joda.time.DateTime;
|
||||
* to use for storing the list of transitions. The user is given this choice of subclass so that the
|
||||
* field of the value type stored in the transition can be given a customized name.
|
||||
*/
|
||||
public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedTransition<V>>
|
||||
extends ForwardingMap<DateTime, T> {
|
||||
public class TimedTransitionProperty<
|
||||
V extends Serializable, T extends TimedTransitionProperty.TimedTransition<V>>
|
||||
extends ForwardingMap<DateTime, T> implements UnsafeSerializable {
|
||||
|
||||
/**
|
||||
* A transition to a value of type {@code V} at a certain time. This superclass only has a field
|
||||
* A transition to a value of type {@code V} at a certain time. This superclass only has a field
|
||||
* for the {@code DateTime}, which means that subclasses should supply the field of type {@code V}
|
||||
* and implementations of the abstract getter and setter methods to access that field. This design
|
||||
* is so that subclasses tagged with @Embed can define a custom field name for their value, for
|
||||
@@ -65,11 +68,12 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
|
||||
*
|
||||
* <p>The public visibility of this class exists only so that it can be subclassed; clients should
|
||||
* never call any methods on this class or attempt to access its members, but should instead treat
|
||||
* it as a customizable implementation detail of {@code TimedTransitionProperty}. However, note
|
||||
* it as a customizable implementation detail of {@code TimedTransitionProperty}. However, note
|
||||
* that subclasses must also have public visibility so that they can be instantiated via
|
||||
* reflection in a call to {@code fromValueMap}.
|
||||
*/
|
||||
public abstract static class TimedTransition<V> extends ImmutableObject {
|
||||
public abstract static class TimedTransition<V extends Serializable> extends ImmutableObject
|
||||
implements UnsafeSerializable {
|
||||
/** The time at which this value becomes the active value. */
|
||||
private DateTime transitionTime;
|
||||
|
||||
@@ -89,16 +93,16 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided value map into the equivalent transition map, using transition objects
|
||||
* of the given TimedTransition subclass. The value map must be sorted according to the natural
|
||||
* Converts the provided value map into the equivalent transition map, using transition objects of
|
||||
* the given TimedTransition subclass. The value map must be sorted according to the natural
|
||||
* ordering of its DateTime keys, and keys cannot be earlier than START_OF_TIME.
|
||||
*/
|
||||
// NB: The Class<T> parameter could be eliminated by getting the class via reflection, but then
|
||||
// the callsite cannot infer T, so unless you explicitly call this as .<V, T>fromValueMap() it
|
||||
// will default to using just TimedTransition<V>, which fails at runtime.
|
||||
private static <V, T extends TimedTransition<V>> NavigableMap<DateTime, T> makeTransitionMap(
|
||||
ImmutableSortedMap<DateTime, V> valueMap,
|
||||
final Class<T> timedTransitionSubclass) {
|
||||
private static <V extends Serializable, T extends TimedTransition<V>>
|
||||
NavigableMap<DateTime, T> makeTransitionMap(
|
||||
ImmutableSortedMap<DateTime, V> valueMap, final Class<T> timedTransitionSubclass) {
|
||||
checkArgument(
|
||||
Ordering.natural().equals(valueMap.comparator()),
|
||||
"Timed transition value map must have transition time keys in chronological order");
|
||||
@@ -121,9 +125,9 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
|
||||
*
|
||||
* <p>This method should be the normal method for constructing a {@link TimedTransitionProperty}.
|
||||
*/
|
||||
public static <V, T extends TimedTransition<V>> TimedTransitionProperty<V, T> fromValueMap(
|
||||
ImmutableSortedMap<DateTime, V> valueMap,
|
||||
final Class<T> timedTransitionSubclass) {
|
||||
public static <V extends Serializable, T extends TimedTransition<V>>
|
||||
TimedTransitionProperty<V, T> fromValueMap(
|
||||
ImmutableSortedMap<DateTime, V> valueMap, final Class<T> timedTransitionSubclass) {
|
||||
return new TimedTransitionProperty<>(ImmutableSortedMap.copyOf(
|
||||
makeTransitionMap(valueMap, timedTransitionSubclass)));
|
||||
}
|
||||
@@ -175,10 +179,10 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
|
||||
* @param allowedTransitions optional map of all possible state-to-state transitions
|
||||
* @param allowedTransitionMapName optional transition map description string for error messages
|
||||
* @param initialValue optional initial value; if present, the first transition must have this
|
||||
* value
|
||||
* value
|
||||
* @param badInitialValueErrorMessage option error message string if the initial value is wrong
|
||||
*/
|
||||
public static <V, T extends TimedTransitionProperty.TimedTransition<V>>
|
||||
public static <V extends Serializable, T extends TimedTransitionProperty.TimedTransition<V>>
|
||||
TimedTransitionProperty<V, T> make(
|
||||
ImmutableSortedMap<DateTime, V> newTransitions,
|
||||
Class<T> transitionClass,
|
||||
@@ -200,7 +204,7 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
|
||||
* Validates that a transition map is not null or empty, starts at START_OF_TIME, and has
|
||||
* transitions which move from one value to another in allowed ways.
|
||||
*/
|
||||
public static <V, T extends TimedTransitionProperty.TimedTransition<V>>
|
||||
public static <V extends Serializable, T extends TimedTransitionProperty.TimedTransition<V>>
|
||||
void validateTimedTransitionMap(
|
||||
@Nullable NavigableMap<DateTime, V> transitionMap,
|
||||
ImmutableMultimap<V, V> allowedTransitions,
|
||||
@@ -240,8 +244,9 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
|
||||
* annotation. The map for those fields must be mutable so that Objectify can load values from
|
||||
* Datastore into the map, but clients should still never mutate the field's map directly.
|
||||
*/
|
||||
public static <V, T extends TimedTransition<V>> TimedTransitionProperty<V, T> forMapify(
|
||||
ImmutableSortedMap<DateTime, V> valueMap, Class<T> timedTransitionSubclass) {
|
||||
public static <V extends Serializable, T extends TimedTransition<V>>
|
||||
TimedTransitionProperty<V, T> forMapify(
|
||||
ImmutableSortedMap<DateTime, V> valueMap, Class<T> timedTransitionSubclass) {
|
||||
return new TimedTransitionProperty<>(
|
||||
new TreeMap<>(makeTransitionMap(valueMap, timedTransitionSubclass)));
|
||||
}
|
||||
@@ -254,8 +259,9 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
|
||||
* annotation. The map for those fields must be mutable so that Objectify can load values from
|
||||
* Datastore into the map, but clients should still never mutate the field's map directly.
|
||||
*/
|
||||
public static <V, T extends TimedTransition<V>> TimedTransitionProperty<V, T> forMapify(
|
||||
V valueAtStartOfTime, Class<T> timedTransitionSubclass) {
|
||||
public static <V extends Serializable, T extends TimedTransition<V>>
|
||||
TimedTransitionProperty<V, T> forMapify(
|
||||
V valueAtStartOfTime, Class<T> timedTransitionSubclass) {
|
||||
return forMapify(
|
||||
ImmutableSortedMap.of(START_OF_TIME, valueAtStartOfTime), timedTransitionSubclass);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.contact.ContactHistory.ContactHistoryId;
|
||||
import google.registry.model.replay.DatastoreEntity;
|
||||
import google.registry.model.replay.SqlEntity;
|
||||
@@ -59,7 +60,7 @@ import javax.persistence.PostLoad;
|
||||
@EntitySubclass
|
||||
@Access(AccessType.FIELD)
|
||||
@IdClass(ContactHistoryId.class)
|
||||
public class ContactHistory extends HistoryEntry implements SqlEntity {
|
||||
public class ContactHistory extends HistoryEntry implements SqlEntity, UnsafeSerializable {
|
||||
|
||||
// Store ContactBase instead of ContactResource so we don't pick up its @Id
|
||||
// Nullable for the sake of pre-Registry-3.0 history objects
|
||||
|
||||
@@ -20,7 +20,9 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.eppcommon.PresenceMarker;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Embedded;
|
||||
@@ -31,7 +33,7 @@ import javax.xml.bind.annotation.XmlType;
|
||||
@Embed
|
||||
@Embeddable
|
||||
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
|
||||
public class Disclose extends ImmutableObject {
|
||||
public class Disclose extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
List<PostalInfoChoice> name;
|
||||
|
||||
@@ -78,7 +80,7 @@ public class Disclose extends ImmutableObject {
|
||||
|
||||
/** The "intLocType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@Embed
|
||||
public static class PostalInfoChoice extends ImmutableObject {
|
||||
public static class PostalInfoChoice extends ImmutableObject implements Serializable {
|
||||
@XmlAttribute
|
||||
PostalInfo.Type type;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.Buildable.Overlayable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EnumType;
|
||||
@@ -38,7 +39,8 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@Embed
|
||||
@Embeddable
|
||||
@XmlType(propOrder = {"name", "org", "address", "type"})
|
||||
public class PostalInfo extends ImmutableObject implements Overlayable<PostalInfo> {
|
||||
public class PostalInfo extends ImmutableObject
|
||||
implements Overlayable<PostalInfo>, UnsafeSerializable {
|
||||
|
||||
/** The type of the address, either localized or international. */
|
||||
public enum Type {
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.persistence.VKey;
|
||||
import javax.persistence.Embeddable;
|
||||
@@ -46,7 +47,7 @@ import javax.xml.bind.annotation.XmlEnumValue;
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class DesignatedContact extends ImmutableObject {
|
||||
public class DesignatedContact extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/**
|
||||
* XML type for contact types. This can be either: {@code "admin"}, {@code "billing"}, or
|
||||
|
||||
@@ -24,6 +24,7 @@ import google.registry.model.host.HostResource;
|
||||
import google.registry.model.replay.DatastoreAndSqlEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import java.util.Set;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
@@ -164,6 +165,11 @@ public class DomainBase extends DomainContent
|
||||
return cloneDomainProjectedAtTime(this, now);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSqlSaveOnReplay() {
|
||||
fullyQualifiedDomainName = DomainNameUtils.canonicalizeDomainName(fullyQualifiedDomainName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDatastoreSaveOnReplay() {
|
||||
saveIndexesToDatastore();
|
||||
@@ -189,6 +195,7 @@ public class DomainBase extends DomainContent
|
||||
}
|
||||
|
||||
public Builder copyFrom(DomainContent domainContent) {
|
||||
this.getInstance().copyUpdateTimestamp(domainContent);
|
||||
return this.setAuthInfo(domainContent.getAuthInfo())
|
||||
.setAutorenewPollMessage(domainContent.getAutorenewPollMessage())
|
||||
.setAutorenewBillingEvent(domainContent.getAutorenewBillingEvent())
|
||||
|
||||
@@ -33,6 +33,7 @@ import google.registry.model.replay.SqlEntity;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
@@ -216,6 +217,10 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
public DomainHistoryId getDomainHistoryId() {
|
||||
return new DomainHistoryId(getDomainRepoId(), getId());
|
||||
}
|
||||
|
||||
/** Returns keys to the {@link HostResource} that are the nameservers for the domain. */
|
||||
public Set<VKey<HostResource>> getNsHosts() {
|
||||
return nsHosts;
|
||||
@@ -299,6 +304,8 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
|
||||
public void beforeSqlSaveOnReplay() {
|
||||
if (domainContent == null) {
|
||||
domainContent = jpaTm().getEntityManager().find(DomainBase.class, getDomainRepoId());
|
||||
domainContent.fullyQualifiedDomainName =
|
||||
DomainNameUtils.canonicalizeDomainName(domainContent.fullyQualifiedDomainName);
|
||||
fillAuxiliaryFieldsFromDomain(this);
|
||||
}
|
||||
}
|
||||
@@ -314,6 +321,8 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
|
||||
nullToEmptyImmutableCopy(domainHistory.domainContent.getGracePeriods()).stream()
|
||||
.map(gracePeriod -> GracePeriodHistory.createFrom(domainHistory.id, gracePeriod))
|
||||
.collect(toImmutableSet());
|
||||
} else {
|
||||
domainHistory.nsHosts = ImmutableSet.of();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,8 +402,16 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
|
||||
if (domainContent == null) {
|
||||
return this;
|
||||
}
|
||||
// TODO(b/203609982): if actual type of domainContent is DomainBase, convert to DomainContent
|
||||
// Note: a DomainHistory fetched by JPA has DomainContent in this field. Allowing DomainBase
|
||||
// in the setter makes equality checks messy.
|
||||
getInstance().domainContent = domainContent;
|
||||
return super.setParent(domainContent);
|
||||
if (domainContent instanceof DomainBase) {
|
||||
super.setParent(domainContent);
|
||||
} else {
|
||||
super.setParent(Key.create(DomainBase.class, domainContent.getRepoId()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDomainRepoId(String domainRepoId) {
|
||||
@@ -412,5 +429,19 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
|
||||
fillAuxiliaryFieldsFromDomain(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public DomainHistory buildAndAssemble(
|
||||
ImmutableSet<DomainDsDataHistory> dsDataHistories,
|
||||
ImmutableSet<VKey<HostResource>> domainHistoryHosts,
|
||||
ImmutableSet<GracePeriodHistory> gracePeriodHistories,
|
||||
ImmutableSet<DomainTransactionRecord> transactionRecords) {
|
||||
DomainHistory instance = super.build();
|
||||
instance.dsDataHistories = dsDataHistories;
|
||||
instance.nsHosts = domainHistoryHosts;
|
||||
instance.gracePeriodHistories = gracePeriodHistories;
|
||||
instance.domainTransactionRecords = transactionRecords;
|
||||
instance.hashCode = null;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.replay.DatastoreAndSqlEntity;
|
||||
import google.registry.model.replay.SqlOnlyEntity;
|
||||
@@ -203,7 +204,7 @@ public class GracePeriod extends GracePeriodBase implements DatastoreAndSqlEntit
|
||||
/** Entity class to represent a historic {@link GracePeriod}. */
|
||||
@Entity(name = "GracePeriodHistory")
|
||||
@Table(indexes = @Index(columnList = "domainRepoId"))
|
||||
static class GracePeriodHistory extends GracePeriodBase implements SqlOnlyEntity {
|
||||
public static class GracePeriodHistory extends GracePeriodBase implements SqlOnlyEntity {
|
||||
@Id Long gracePeriodHistoryRevisionId;
|
||||
|
||||
/** ID for the associated {@link DomainHistory} entity. */
|
||||
@@ -215,6 +216,10 @@ public class GracePeriod extends GracePeriodBase implements DatastoreAndSqlEntit
|
||||
return super.getGracePeriodId();
|
||||
}
|
||||
|
||||
public DomainHistoryId getDomainHistoryId() {
|
||||
return new DomainHistoryId(getDomainRepoId(), domainHistoryRevisionId);
|
||||
}
|
||||
|
||||
static GracePeriodHistory createFrom(long historyRevisionId, GracePeriod gracePeriod) {
|
||||
GracePeriodHistory instance = new GracePeriodHistory();
|
||||
instance.gracePeriodHistoryRevisionId = allocateId();
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.model.domain;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.persistence.BillingVKey.BillingEventVKey;
|
||||
@@ -35,7 +36,7 @@ import org.joda.time.DateTime;
|
||||
@Embed
|
||||
@MappedSuperclass
|
||||
@Access(AccessType.FIELD)
|
||||
public class GracePeriodBase extends ImmutableObject {
|
||||
public class GracePeriodBase extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/** Unique id required for hibernate representation. */
|
||||
@Transient long gracePeriodId;
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.model.domain;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
@@ -25,7 +26,7 @@ import javax.xml.bind.annotation.XmlValue;
|
||||
/** The "periodType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
public class Period extends ImmutableObject {
|
||||
public class Period extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@XmlAttribute
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
@@ -39,7 +40,7 @@ import org.joda.time.DateTime;
|
||||
@Embed
|
||||
@XmlType(propOrder = {"noticeId", "expirationTime", "acceptedTime"})
|
||||
@javax.persistence.Embeddable
|
||||
public class LaunchNotice extends ImmutableObject {
|
||||
public class LaunchNotice extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/** An empty instance to use in place of null. */
|
||||
private static final NoticeIdType EMPTY_NOTICE_ID = new NoticeIdType();
|
||||
@@ -47,14 +48,13 @@ public class LaunchNotice extends ImmutableObject {
|
||||
/** An id with a validator-id attribute. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
public static class NoticeIdType extends ImmutableObject {
|
||||
public static class NoticeIdType extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/**
|
||||
* The Trademark Claims Notice ID from
|
||||
* {@link "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3"}.
|
||||
* The Trademark Claims Notice ID from <a
|
||||
* href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3">the RFC</a>.
|
||||
*/
|
||||
@XmlValue
|
||||
String tcnId;
|
||||
@XmlValue String tcnId;
|
||||
|
||||
/** The identifier of the TMDB provider to use, defaulting to the TMCH. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.model.domain.secdns;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
@@ -31,7 +32,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@Embed
|
||||
@MappedSuperclass
|
||||
@Access(AccessType.FIELD)
|
||||
public abstract class DomainDsDataBase extends ImmutableObject {
|
||||
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@Ignore @XmlTransient @Transient String domainRepoId;
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ package google.registry.model.domain.secdns;
|
||||
|
||||
import static google.registry.model.IdService.allocateId;
|
||||
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.replay.SqlOnlyEntity;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
@@ -26,7 +28,8 @@ import javax.persistence.Id;
|
||||
|
||||
/** Entity class to represent a historic {@link DelegationSignerData}. */
|
||||
@Entity
|
||||
public class DomainDsDataHistory extends DomainDsDataBase implements SqlOnlyEntity {
|
||||
public class DomainDsDataHistory extends DomainDsDataBase
|
||||
implements SqlOnlyEntity, UnsafeSerializable {
|
||||
|
||||
@Id Long dsDataHistoryRevisionId;
|
||||
|
||||
@@ -53,6 +56,10 @@ public class DomainDsDataHistory extends DomainDsDataBase implements SqlOnlyEnti
|
||||
return instance;
|
||||
}
|
||||
|
||||
public DomainHistory.DomainHistoryId getDomainHistoryId() {
|
||||
return new DomainHistoryId(getDomainRepoId(), domainHistoryRevisionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Access(AccessType.PROPERTY)
|
||||
public String getDomainRepoId() {
|
||||
|
||||
@@ -27,6 +27,7 @@ import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -56,7 +57,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@XmlTransient
|
||||
@Embeddable
|
||||
@MappedSuperclass
|
||||
public class Address extends ImmutableObject implements Jsonifiable {
|
||||
public class Address extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
|
||||
|
||||
/** The schema validation will enforce that this has 3 lines at most. */
|
||||
// TODO(b/177569726): Remove this field after migration. We need to figure out how to generate
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.model.eppcommon;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
@@ -35,7 +36,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@XmlTransient
|
||||
@Embeddable
|
||||
@MappedSuperclass
|
||||
public abstract class AuthInfo extends ImmutableObject {
|
||||
public abstract class AuthInfo extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@Embedded protected PasswordAuth pw;
|
||||
|
||||
@@ -47,7 +48,7 @@ public abstract class AuthInfo extends ImmutableObject {
|
||||
@Embed
|
||||
@XmlType(namespace = "urn:ietf:params:xml:ns:eppcom-1.0")
|
||||
@Embeddable
|
||||
public static class PasswordAuth extends ImmutableObject {
|
||||
public static class PasswordAuth extends ImmutableObject implements UnsafeSerializable {
|
||||
@XmlValue
|
||||
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
|
||||
String value;
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
@@ -49,7 +50,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@XmlTransient
|
||||
@Embeddable
|
||||
@MappedSuperclass
|
||||
public class PhoneNumber extends ImmutableObject {
|
||||
public class PhoneNumber extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@XmlValue
|
||||
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
|
||||
|
||||
@@ -18,6 +18,7 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
@@ -32,7 +33,7 @@ import javax.xml.bind.annotation.XmlType;
|
||||
@Embed
|
||||
@XmlType(propOrder = {"clientTransactionId", "serverTransactionId"})
|
||||
@javax.persistence.Embeddable
|
||||
public class Trid extends ImmutableObject {
|
||||
public class Trid extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/** The server transaction id. */
|
||||
@XmlElement(name = "svTRID", namespace = "urn:ietf:params:xml:ns:epp-1.0")
|
||||
|
||||
@@ -20,11 +20,13 @@ import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.host.HostHistory.HostHistoryId;
|
||||
import google.registry.model.replay.DatastoreEntity;
|
||||
import google.registry.model.replay.SqlEntity;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -60,7 +62,7 @@ import javax.persistence.PostLoad;
|
||||
@EntitySubclass
|
||||
@Access(AccessType.FIELD)
|
||||
@IdClass(HostHistoryId.class)
|
||||
public class HostHistory extends HistoryEntry implements SqlEntity {
|
||||
public class HostHistory extends HistoryEntry implements SqlEntity, UnsafeSerializable {
|
||||
|
||||
// Store HostBase instead of HostResource so we don't pick up its @Id
|
||||
// Nullable for the sake of pre-Registry-3.0 history objects
|
||||
@@ -140,6 +142,8 @@ public class HostHistory extends HistoryEntry implements SqlEntity {
|
||||
public void beforeSqlSaveOnReplay() {
|
||||
if (hostBase == null) {
|
||||
hostBase = jpaTm().getEntityManager().find(HostResource.class, getHostRepoId());
|
||||
hostBase.fullyQualifiedHostName =
|
||||
DomainNameUtils.canonicalizeDomainName(hostBase.fullyQualifiedHostName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.replay.DatastoreAndSqlEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
|
||||
@@ -51,6 +52,11 @@ public class HostResource extends HostBase
|
||||
return VKey.create(HostResource.class, getRepoId(), Key.create(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSqlSaveOnReplay() {
|
||||
fullyQualifiedHostName = DomainNameUtils.canonicalizeDomainName(fullyQualifiedHostName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDatastoreSaveOnReplay() {
|
||||
saveIndexesToDatastore();
|
||||
|
||||
+4
-2
@@ -17,6 +17,7 @@ package google.registry.model.poll;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseData;
|
||||
import javax.persistence.Embeddable;
|
||||
@@ -31,12 +32,13 @@ import org.joda.time.DateTime;
|
||||
/** The {@link ResponseData} returned when completing a pending action on a domain. */
|
||||
@XmlTransient
|
||||
@Embeddable
|
||||
public class PendingActionNotificationResponse extends ImmutableObject implements ResponseData {
|
||||
public class PendingActionNotificationResponse extends ImmutableObject
|
||||
implements ResponseData, UnsafeSerializable {
|
||||
|
||||
/** The inner name type that contains a name and the result boolean. */
|
||||
@Embed
|
||||
@Embeddable
|
||||
static class NameOrId extends ImmutableObject {
|
||||
static class NameOrId extends ImmutableObject implements UnsafeSerializable {
|
||||
@XmlValue
|
||||
String value;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
@@ -98,7 +99,7 @@ import org.joda.time.DateTime;
|
||||
@javax.persistence.Index(columnList = "eventTime")
|
||||
})
|
||||
public abstract class PollMessage extends ImmutableObject
|
||||
implements Buildable, DatastoreAndSqlEntity, TransferServerApproveEntity {
|
||||
implements Buildable, DatastoreAndSqlEntity, TransferServerApproveEntity, UnsafeSerializable {
|
||||
|
||||
/** Entity id. */
|
||||
@Id
|
||||
|
||||
@@ -72,6 +72,7 @@ import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
@@ -116,7 +117,7 @@ import org.joda.time.DateTime;
|
||||
})
|
||||
@InCrossTld
|
||||
public class Registrar extends ImmutableObject
|
||||
implements Buildable, DatastoreAndSqlEntity, Jsonifiable {
|
||||
implements Buildable, DatastoreAndSqlEntity, Jsonifiable, UnsafeSerializable {
|
||||
|
||||
/** Represents the type of a registrar entity. */
|
||||
public enum Type {
|
||||
@@ -404,7 +405,7 @@ public class Registrar extends ImmutableObject
|
||||
|
||||
/** A billing account entry for this registrar, consisting of a currency and an account Id. */
|
||||
@Embed
|
||||
public static class BillingAccountEntry extends ImmutableObject {
|
||||
public static class BillingAccountEntry extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
CurrencyUnit currency;
|
||||
String accountId;
|
||||
|
||||
@@ -46,6 +46,7 @@ import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.registrar.RegistrarContact.RegistrarPocId;
|
||||
@@ -82,7 +83,7 @@ import javax.persistence.Transient;
|
||||
@IdClass(RegistrarPocId.class)
|
||||
@InCrossTld
|
||||
public class RegistrarContact extends ImmutableObject
|
||||
implements DatastoreAndSqlEntity, Jsonifiable {
|
||||
implements DatastoreAndSqlEntity, Jsonifiable, UnsafeSerializable {
|
||||
|
||||
@Parent @Transient Key<Registrar> parent;
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ public class ReplicateToDatastoreAction implements Runnable {
|
||||
return;
|
||||
}
|
||||
Optional<Lock> lock =
|
||||
Lock.acquire(
|
||||
Lock.acquireSql(
|
||||
this.getClass().getSimpleName(), null, LEASE_LENGTH, requestStatusChecker, false);
|
||||
if (!lock.isPresent()) {
|
||||
String message = "Can't acquire ReplicateToDatastoreAction lock, aborting.";
|
||||
|
||||
@@ -22,6 +22,8 @@ import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.replay.DatastoreAndSqlEntity;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
@@ -45,11 +47,10 @@ import org.joda.time.DateTime;
|
||||
@Embed
|
||||
@Entity
|
||||
public class DomainTransactionRecord extends ImmutableObject
|
||||
implements Buildable, DatastoreAndSqlEntity {
|
||||
implements Buildable, DatastoreAndSqlEntity, UnsafeSerializable {
|
||||
|
||||
@Id
|
||||
@Ignore
|
||||
@ImmutableObject.DoNotCompare
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ImmutableObject.Insignificant
|
||||
Long id;
|
||||
@@ -58,6 +59,14 @@ public class DomainTransactionRecord extends ImmutableObject
|
||||
@Column(nullable = false)
|
||||
String tld;
|
||||
|
||||
// The following two fields are exposed in this entity to support bulk-loading in Cloud SQL by the
|
||||
// Datastore-SQL validation. They are excluded from equality check since they are not set in
|
||||
// Datastore.
|
||||
// TODO(b/203609782): post migration, decide whether to keep these two fields.
|
||||
@Ignore @ImmutableObject.Insignificant String domainRepoId;
|
||||
|
||||
@Ignore @ImmutableObject.Insignificant Long historyRevisionId;
|
||||
|
||||
/**
|
||||
* The time this Transaction takes effect (counting grace periods and other nuances).
|
||||
*
|
||||
@@ -174,6 +183,10 @@ public class DomainTransactionRecord extends ImmutableObject
|
||||
}
|
||||
}
|
||||
|
||||
public DomainHistoryId getDomainHistoryId() {
|
||||
return new DomainHistoryId(domainRepoId, historyRevisionId);
|
||||
}
|
||||
|
||||
public DateTime getReportingTime() {
|
||||
return reportingTime;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.contact.ContactBase;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
@@ -83,7 +84,8 @@ import org.joda.time.DateTime;
|
||||
@Entity
|
||||
@MappedSuperclass
|
||||
@Access(AccessType.FIELD)
|
||||
public class HistoryEntry extends ImmutableObject implements Buildable, DatastoreEntity {
|
||||
public class HistoryEntry extends ImmutableObject
|
||||
implements Buildable, DatastoreEntity, UnsafeSerializable {
|
||||
|
||||
/** Represents the type of history entry. */
|
||||
public enum Type {
|
||||
|
||||
@@ -32,6 +32,8 @@ import google.registry.model.annotations.NotBackedUp;
|
||||
import google.registry.model.annotations.NotBackedUp.Reason;
|
||||
import google.registry.model.replay.DatastoreAndSqlEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.transaction.TransactionManager;
|
||||
import google.registry.util.RequestStatusChecker;
|
||||
import google.registry.util.RequestStatusCheckerImpl;
|
||||
import java.io.Serializable;
|
||||
@@ -212,6 +214,47 @@ public class Lock extends ImmutableObject implements DatastoreAndSqlEntity, Seri
|
||||
Duration leaseLength,
|
||||
RequestStatusChecker requestStatusChecker,
|
||||
boolean checkThreadRunning) {
|
||||
return acquireWithTransactionManager(
|
||||
resourceName, tld, leaseLength, requestStatusChecker, checkThreadRunning, tm());
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to acquire a lock in SQL. Returns absent if it can't be acquired.
|
||||
*
|
||||
* <p>This method exists so that Beam pipelines can acquire / load / release locks.
|
||||
*/
|
||||
public static Optional<Lock> acquireSql(
|
||||
String resourceName,
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
RequestStatusChecker requestStatusChecker,
|
||||
boolean checkThreadRunning) {
|
||||
return acquireWithTransactionManager(
|
||||
resourceName, tld, leaseLength, requestStatusChecker, checkThreadRunning, jpaTm());
|
||||
}
|
||||
|
||||
/** Release the lock. */
|
||||
public void release() {
|
||||
releaseWithTransactionManager(tm());
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock from SQL.
|
||||
*
|
||||
* <p>This method exists so that Beam pipelines can acquire / load / release locks.
|
||||
*/
|
||||
public void releaseSql() {
|
||||
releaseWithTransactionManager(jpaTm());
|
||||
}
|
||||
|
||||
/** Try to acquire a lock. Returns absent if it can't be acquired. */
|
||||
private static Optional<Lock> acquireWithTransactionManager(
|
||||
String resourceName,
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
RequestStatusChecker requestStatusChecker,
|
||||
boolean checkThreadRunning,
|
||||
TransactionManager transactionManager) {
|
||||
String scope = (tld != null) ? tld : GLOBAL;
|
||||
String lockId = makeLockId(resourceName, scope);
|
||||
// It's important to use transactNew rather than transact, because a Lock can be used to control
|
||||
@@ -219,11 +262,12 @@ public class Lock extends ImmutableObject implements DatastoreAndSqlEntity, Seri
|
||||
// must be definitively acquired before it is used, even when called inside another transaction.
|
||||
Supplier<AcquireResult> lockAcquirer =
|
||||
() -> {
|
||||
DateTime now = tm().getTransactionTime();
|
||||
DateTime now = transactionManager.getTransactionTime();
|
||||
|
||||
// Checking if an unexpired lock still exists - if so, the lock can't be acquired.
|
||||
Lock lock =
|
||||
tm().loadByKeyIfPresent(
|
||||
transactionManager
|
||||
.loadByKeyIfPresent(
|
||||
VKey.create(
|
||||
Lock.class,
|
||||
new LockId(resourceName, scope),
|
||||
@@ -249,13 +293,15 @@ public class Lock extends ImmutableObject implements DatastoreAndSqlEntity, Seri
|
||||
create(resourceName, scope, requestStatusChecker.getLogId(), now, leaseLength);
|
||||
// Locks are not parented under an EntityGroupRoot (so as to avoid write
|
||||
// contention) and don't need to be backed up.
|
||||
tm().putIgnoringReadOnly(newLock);
|
||||
transactionManager.putIgnoringReadOnly(newLock);
|
||||
|
||||
return AcquireResult.create(now, lock, newLock, lockState);
|
||||
};
|
||||
// In ofy, backup is determined per-action, but in SQL it's determined per-transaction
|
||||
AcquireResult acquireResult =
|
||||
tm().isOfy() ? tm().transactNew(lockAcquirer) : jpaTm().transactWithoutBackup(lockAcquirer);
|
||||
transactionManager.isOfy()
|
||||
? transactionManager.transactNew(lockAcquirer)
|
||||
: ((JpaTransactionManager) transactionManager).transactWithoutBackup(lockAcquirer);
|
||||
|
||||
logAcquireResult(acquireResult);
|
||||
lockMetrics.recordAcquire(resourceName, scope, acquireResult.lockState());
|
||||
@@ -263,7 +309,7 @@ public class Lock extends ImmutableObject implements DatastoreAndSqlEntity, Seri
|
||||
}
|
||||
|
||||
/** Release the lock. */
|
||||
public void release() {
|
||||
private void releaseWithTransactionManager(TransactionManager transactionManager) {
|
||||
// Just use the default clock because we aren't actually doing anything that will use the clock.
|
||||
Supplier<Void> lockReleaser =
|
||||
() -> {
|
||||
@@ -274,15 +320,17 @@ public class Lock extends ImmutableObject implements DatastoreAndSqlEntity, Seri
|
||||
VKey<Lock> key =
|
||||
VKey.create(
|
||||
Lock.class, new LockId(resourceName, tld), Key.create(Lock.class, lockId));
|
||||
Lock loadedLock = tm().loadByKeyIfPresent(key).orElse(null);
|
||||
Lock loadedLock = transactionManager.loadByKeyIfPresent(key).orElse(null);
|
||||
if (Lock.this.equals(loadedLock)) {
|
||||
// Use deleteIgnoringReadOnly() so that we don't create a commit log entry for deleting
|
||||
// the lock.
|
||||
logger.atInfo().log("Deleting lock: %s", lockId);
|
||||
tm().deleteIgnoringReadOnly(key);
|
||||
transactionManager.deleteIgnoringReadOnly(key);
|
||||
|
||||
lockMetrics.recordRelease(
|
||||
resourceName, tld, new Duration(acquiredTime, tm().getTransactionTime()));
|
||||
resourceName,
|
||||
tld,
|
||||
new Duration(acquiredTime, transactionManager.getTransactionTime()));
|
||||
} else {
|
||||
logger.atSevere().log(
|
||||
"The lock we acquired was transferred to someone else before we"
|
||||
@@ -294,11 +342,12 @@ public class Lock extends ImmutableObject implements DatastoreAndSqlEntity, Seri
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// In ofy, backup is determined per-action, but in SQL it's determined per-transaction
|
||||
if (tm().isOfy()) {
|
||||
tm().transact(lockReleaser);
|
||||
if (transactionManager.isOfy()) {
|
||||
transactionManager.transact(lockReleaser);
|
||||
} else {
|
||||
jpaTm().transactWithoutBackup(lockReleaser);
|
||||
((JpaTransactionManager) transactionManager).transactWithoutBackup(lockReleaser);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
@@ -88,7 +89,8 @@ import org.joda.time.Duration;
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "Tld")
|
||||
@InCrossTld
|
||||
public class Registry extends ImmutableObject implements Buildable, DatastoreAndSqlEntity {
|
||||
public class Registry extends ImmutableObject
|
||||
implements Buildable, DatastoreAndSqlEntity, UnsafeSerializable {
|
||||
|
||||
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
|
||||
@@ -308,7 +310,7 @@ public class Registry extends ImmutableObject implements Buildable, DatastoreAnd
|
||||
* <p>There must be at least one entry in this set.
|
||||
*
|
||||
* <p>All entries of this list must be valid keys for the map of {@code DnsWriter}s injected by
|
||||
* <code>@Inject Map<String, DnsWriter></code>
|
||||
* {@code @Inject Map<String, DnsWriter>}
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
Set<String> dnsWriters;
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.model.transfer;
|
||||
|
||||
import google.registry.model.Buildable.GenericBuilder;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
@@ -27,7 +28,7 @@ import org.joda.time.DateTime;
|
||||
/** Fields common to {@link TransferData} and {@link TransferResponse}. */
|
||||
@XmlTransient
|
||||
@MappedSuperclass
|
||||
public abstract class BaseTransferObject extends ImmutableObject {
|
||||
public abstract class BaseTransferObject extends ImmutableObject implements UnsafeSerializable {
|
||||
/**
|
||||
* The status of the current or last transfer. Can be null if never transferred. Note that we
|
||||
* leave IgnoreSave off this field so that we can ensure that TransferData loaded from Objectify
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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.persistence;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.model.bulkquery.BulkQueryEntities;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
|
||||
import google.registry.util.Clock;
|
||||
import java.util.List;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
import org.hibernate.jpa.boot.spi.Bootstrap;
|
||||
|
||||
/**
|
||||
* Defines factory method for instantiating the bulk-query optimized {@link JpaTransactionManager}.
|
||||
*/
|
||||
public final class BulkQueryJpaFactory {
|
||||
|
||||
private BulkQueryJpaFactory() {}
|
||||
|
||||
static EntityManagerFactory createBulkQueryEntityManagerFactory(
|
||||
ImmutableMap<String, String> cloudSqlConfigs) {
|
||||
ParsedPersistenceXmlDescriptor descriptor =
|
||||
PersistenceXmlUtility.getParsedPersistenceXmlDescriptor();
|
||||
|
||||
List<String> updatedManagedClasses =
|
||||
Streams.concat(
|
||||
descriptor.getManagedClassNames().stream(),
|
||||
BulkQueryEntities.JPA_ENTITIES_NEW.stream())
|
||||
.map(
|
||||
name -> {
|
||||
if (BulkQueryEntities.JPA_ENTITIES_REPLACEMENTS.containsKey(name)) {
|
||||
return BulkQueryEntities.JPA_ENTITIES_REPLACEMENTS.get(name);
|
||||
}
|
||||
return name;
|
||||
})
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
|
||||
descriptor.getManagedClassNames().clear();
|
||||
descriptor.getManagedClassNames().addAll(updatedManagedClasses);
|
||||
|
||||
return Bootstrap.getEntityManagerFactoryBuilder(descriptor, cloudSqlConfigs).build();
|
||||
}
|
||||
|
||||
public static JpaTransactionManager createBulkQueryJpaTransactionManager(
|
||||
ImmutableMap<String, String> cloudSqlConfigs, Clock clock) {
|
||||
return new JpaTransactionManagerImpl(
|
||||
createBulkQueryEntityManagerFactory(cloudSqlConfigs), clock);
|
||||
}
|
||||
}
|
||||
@@ -152,13 +152,36 @@ public abstract class PersistenceModule {
|
||||
@Singleton
|
||||
@BeamPipelineCloudSqlConfigs
|
||||
static ImmutableMap<String, String> provideBeamPipelineCloudSqlConfigs(
|
||||
@Config("beamCloudSqlJdbcUrl") String jdbcUrl,
|
||||
@Config("beamCloudSqlInstanceConnectionName") String instanceConnectionName,
|
||||
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs,
|
||||
SqlCredentialStore credentialStore,
|
||||
@Config("instanceConnectionNameOverride")
|
||||
Optional<Provider<String>> instanceConnectionNameOverride,
|
||||
@Config("beamIsolationOverride")
|
||||
Optional<Provider<TransactionIsolationLevel>> isolationOverride) {
|
||||
return createPartialSqlConfigs(
|
||||
jdbcUrl, instanceConnectionName, defaultConfigs, isolationOverride);
|
||||
Optional<Provider<TransactionIsolationLevel>> isolationOverride,
|
||||
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs) {
|
||||
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
|
||||
// TODO(b/175700623): make sql username configurable from config file.
|
||||
SqlCredential credential = credentialStore.getCredential(new RobotUser(RobotId.NOMULUS));
|
||||
overrides.put(Environment.USER, credential.login());
|
||||
overrides.put(Environment.PASS, credential.password());
|
||||
// Override the default minimum which is tuned for the Registry server. A worker VM should
|
||||
// release all connections if it no longer interacts with the database.
|
||||
overrides.put(HIKARI_MINIMUM_IDLE, "0");
|
||||
/**
|
||||
* Disable Hikari's maxPoolSize limit check by setting it to an absurdly large number. The
|
||||
* effective (and desirable) limit is the number of pipeline threads on the pipeline worker,
|
||||
* which can be configured using pipeline options. See {@link RegistryPipelineOptions} for more
|
||||
* information.
|
||||
*/
|
||||
overrides.put(HIKARI_MAXIMUM_POOL_SIZE, String.valueOf(Integer.MAX_VALUE));
|
||||
instanceConnectionNameOverride
|
||||
.map(Provider::get)
|
||||
.ifPresent(
|
||||
instanceConnectionName ->
|
||||
overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, instanceConnectionName));
|
||||
isolationOverride
|
||||
.map(Provider::get)
|
||||
.ifPresent(isolation -> overrides.put(Environment.ISOLATION, isolation.name()));
|
||||
return ImmutableMap.copyOf(overrides);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -230,37 +253,17 @@ public abstract class PersistenceModule {
|
||||
@Singleton
|
||||
@BeamJpaTm
|
||||
static JpaTransactionManager provideBeamJpaTm(
|
||||
SqlCredentialStore credentialStore,
|
||||
@Config("instanceConnectionNameOverride")
|
||||
Optional<Provider<String>> instanceConnectionNameOverride,
|
||||
@Config("beamIsolationOverride")
|
||||
Optional<Provider<TransactionIsolationLevel>> isolationOverride,
|
||||
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs,
|
||||
Clock clock) {
|
||||
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
|
||||
// TODO(b/175700623): make sql username configurable from config file.
|
||||
SqlCredential credential = credentialStore.getCredential(new RobotUser(RobotId.NOMULUS));
|
||||
overrides.put(Environment.USER, credential.login());
|
||||
overrides.put(Environment.PASS, credential.password());
|
||||
// Override the default minimum which is tuned for the Registry server. A worker VM should
|
||||
// release all connections if it no longer interacts with the database.
|
||||
overrides.put(HIKARI_MINIMUM_IDLE, "0");
|
||||
/**
|
||||
* Disable Hikari's maxPoolSize limit check by setting it to an absurdly large number. The
|
||||
* effective (and desirable) limit is the number of pipeline threads on the pipeline worker,
|
||||
* which can be configured using pipeline options. See {@link RegistryPipelineOptions} for more
|
||||
* information.
|
||||
*/
|
||||
overrides.put(HIKARI_MAXIMUM_POOL_SIZE, String.valueOf(Integer.MAX_VALUE));
|
||||
instanceConnectionNameOverride
|
||||
.map(Provider::get)
|
||||
.ifPresent(
|
||||
instanceConnectionName ->
|
||||
overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, instanceConnectionName));
|
||||
isolationOverride
|
||||
.map(Provider::get)
|
||||
.ifPresent(isolation -> overrides.put(Environment.ISOLATION, isolation.name()));
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||
@BeamPipelineCloudSqlConfigs ImmutableMap<String, String> beamCloudSqlConfigs, Clock clock) {
|
||||
return new JpaTransactionManagerImpl(create(beamCloudSqlConfigs), clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@BeamBulkQueryJpaTm
|
||||
static JpaTransactionManager provideBeamBulkQueryJpaTm(
|
||||
@BeamPipelineCloudSqlConfigs ImmutableMap<String, String> beamCloudSqlConfigs, Clock clock) {
|
||||
return new JpaTransactionManagerImpl(
|
||||
BulkQueryJpaFactory.createBulkQueryEntityManagerFactory(beamCloudSqlConfigs), clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -346,6 +349,17 @@ public abstract class PersistenceModule {
|
||||
}
|
||||
}
|
||||
|
||||
/** Types of {@link JpaTransactionManager JpaTransactionManagers}. */
|
||||
public enum JpaTransactionManagerType {
|
||||
/** The regular {@link JpaTransactionManager} for general use. */
|
||||
REGULAR,
|
||||
/**
|
||||
* The {@link JpaTransactionManager} optimized for bulk loading multi-level JPA entities. Please
|
||||
* see {@link google.registry.model.bulkquery.BulkQueryEntities} for more information.
|
||||
*/
|
||||
BULK_QUERY
|
||||
}
|
||||
|
||||
/** Dagger qualifier for JDBC {@link Connection} with schema management privilege. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
@@ -357,11 +371,18 @@ public abstract class PersistenceModule {
|
||||
@interface AppEngineJpaTm {}
|
||||
|
||||
/** Dagger qualifier for {@link JpaTransactionManager} used inside BEAM pipelines. */
|
||||
// Note: @SocketFactoryJpaTm will be phased out in favor of this qualifier.
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface BeamJpaTm {}
|
||||
|
||||
/**
|
||||
* Dagger qualifier for {@link JpaTransactionManager} that uses an alternative entity model for
|
||||
* faster bulk queries.
|
||||
*/
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface BeamBulkQueryJpaTm {}
|
||||
|
||||
/** Dagger qualifier for {@link JpaTransactionManager} used for Nomulus tool. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
|
||||
@@ -18,10 +18,13 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.translators.VKeyTranslatorFactory;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -36,6 +39,15 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -5291472863840231240L;
|
||||
|
||||
// Info that's stored in in vkey string generated via stringify().
|
||||
private static final String SQL_LOOKUP_KEY = "sql";
|
||||
private static final String OFY_LOOKUP_KEY = "ofy";
|
||||
private static final String CLASS_TYPE = "kind";
|
||||
|
||||
// Web safe delimiters that won't be used in base 64.
|
||||
private static final String KV_SEPARATOR = ":";
|
||||
private static final String DELIMITER = "@";
|
||||
|
||||
// The SQL key for the referenced entity.
|
||||
Serializable sqlKey;
|
||||
|
||||
@@ -114,6 +126,47 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||
return new VKey<T>(kind, Key.create(kind, name), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link VKey} from the string representation of a vkey.
|
||||
*
|
||||
* <p>There are two types of string representations: 1) existing ofy key string handled by
|
||||
* fromWebsafeKey() and 2) string encoded via stringify() where @ separates the substrings and
|
||||
* each of the substrings contains a look up key, ":", and its corresponding value. The key info
|
||||
* is encoded via Base64. The string begins with "kind:" and it must contains at least ofy key or
|
||||
* sql key.
|
||||
*
|
||||
* <p>Example of a Vkey string by fromWebsafeKey(): "agR0ZXN0chYLEgpEb21haW5CYXNlIgZST0lELTEM"
|
||||
*
|
||||
* <p>Example of a vkey string by stringify(): "google.registry.testing.TestObject@sql:rO0ABX" +
|
||||
* "QAA2Zvbw@ofy:agR0ZXN0cjELEg9FbnRpdHlHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M",
|
||||
* where sql key and ofy key are values are encoded in Base64.
|
||||
*/
|
||||
public static <T> VKey<T> create(String keyString) throws Exception {
|
||||
if (!keyString.startsWith(CLASS_TYPE + KV_SEPARATOR)) {
|
||||
// to handle the existing ofy key string
|
||||
return fromWebsafeKey(keyString);
|
||||
} else {
|
||||
ImmutableMap<String, String> kvs =
|
||||
ImmutableMap.copyOf(
|
||||
Splitter.on(DELIMITER).withKeyValueSeparator(KV_SEPARATOR).split(keyString));
|
||||
Class classType = Class.forName(kvs.get(CLASS_TYPE));
|
||||
|
||||
if (kvs.containsKey(SQL_LOOKUP_KEY) && kvs.containsKey(OFY_LOOKUP_KEY)) {
|
||||
return VKey.create(
|
||||
classType,
|
||||
SerializeUtils.parse(Serializable.class, kvs.get(SQL_LOOKUP_KEY)),
|
||||
Key.create(kvs.get(OFY_LOOKUP_KEY)));
|
||||
} else if (kvs.containsKey(SQL_LOOKUP_KEY)) {
|
||||
return VKey.createSql(
|
||||
classType, SerializeUtils.parse(Serializable.class, kvs.get(SQL_LOOKUP_KEY)));
|
||||
} else if (kvs.containsKey(OFY_LOOKUP_KEY)) {
|
||||
return VKey.createOfy(classType, Key.create(kvs.get(OFY_LOOKUP_KEY)));
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("Cannot parse key string: %s", keyString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clone with an ofy key restored from {@code ancestors}.
|
||||
*
|
||||
@@ -233,4 +286,29 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||
public static <T> VKey<T> fromWebsafeKey(String ofyKeyRepr) {
|
||||
return from(Key.create(ofyKeyRepr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the string representation of a {@link VKey}.
|
||||
*
|
||||
* <p>The string representation of a vkey contains its type, and sql key or ofy key, or both. Each
|
||||
* of the keys is first serialized into a byte array then encoded via Base64 into a web safe
|
||||
* string.
|
||||
*
|
||||
* <p>The string representation of a vkey contains key values pairs separated by delimiter "@".
|
||||
* Another delimiter ":" is put in between each key and value. The following is the complete
|
||||
* format of the string: "kind:class_name@sql:encoded_sqlKey@ofy:encoded_ofyKey", where kind is
|
||||
* required. The string representation may contain an encoded ofy key, or an encoded sql key, or
|
||||
* both.
|
||||
*/
|
||||
public String stringify() {
|
||||
// class type is required to create a vkey
|
||||
String key = CLASS_TYPE + KV_SEPARATOR + getKind().getName();
|
||||
if (maybeGetSqlKey().isPresent()) {
|
||||
key += DELIMITER + SQL_LOOKUP_KEY + KV_SEPARATOR + SerializeUtils.stringify(getSqlKey());
|
||||
}
|
||||
if (maybeGetOfyKey().isPresent()) {
|
||||
key += DELIMITER + OFY_LOOKUP_KEY + KV_SEPARATOR + getOfyKey().getString();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.model.common.TimedTransitionProperty;
|
||||
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
|
||||
import google.registry.persistence.converter.StringMapDescriptor.StringMap;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeConverter;
|
||||
@@ -30,7 +31,8 @@ import org.joda.time.DateTime;
|
||||
* Base JPA converter for {@link TimedTransitionProperty} objects that are stored in a column with
|
||||
* data type of hstore in the database.
|
||||
*/
|
||||
public abstract class TimedTransitionPropertyConverterBase<K, V extends TimedTransition<K>>
|
||||
public abstract class TimedTransitionPropertyConverterBase<
|
||||
K extends Serializable, V extends TimedTransition<K>>
|
||||
implements AttributeConverter<TimedTransitionProperty<K, V>, StringMap> {
|
||||
|
||||
abstract Map.Entry<String, String> convertToDatabaseMapEntry(Map.Entry<DateTime, V> entry);
|
||||
|
||||
+27
-1
@@ -24,7 +24,33 @@ import javax.persistence.criteria.CriteriaQuery;
|
||||
/** Sub-interface of {@link TransactionManager} which defines JPA related methods. */
|
||||
public interface JpaTransactionManager extends TransactionManager {
|
||||
|
||||
/** Returns the {@link EntityManager} for the current request. */
|
||||
/**
|
||||
* Returns a long-lived {@link EntityManager} not bound to a particular transaction.
|
||||
*
|
||||
* <p>Caller is responsible for closing the returned instance.
|
||||
*/
|
||||
EntityManager getStandaloneEntityManager();
|
||||
|
||||
/**
|
||||
* Specifies a database snapshot exported by another transaction to use in the current
|
||||
* transaction.
|
||||
*
|
||||
* <p>This is a Postgresql-specific feature. This method must be called before any other SQL
|
||||
* commands in a transaction.
|
||||
*
|
||||
* <p>To support large queries, transaction isolation level is fixed at the REPEATABLE_READ to
|
||||
* avoid exhausting predicate locks at the SERIALIZABLE level.
|
||||
*
|
||||
* @see google.registry.beam.common.DatabaseSnapshot
|
||||
*/
|
||||
// TODO(b/193662898): vendor-independent support for richer transaction semantics.
|
||||
JpaTransactionManager setDatabaseSnapshot(String snapshotId);
|
||||
|
||||
/**
|
||||
* Returns the {@link EntityManager} for the current request.
|
||||
*
|
||||
* <p>The returned instance is closed when the current transaction completes.
|
||||
*/
|
||||
EntityManager getEntityManager();
|
||||
|
||||
/**
|
||||
|
||||
+21
@@ -120,6 +120,11 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
emf.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManager getStandaloneEntityManager() {
|
||||
return emf.createEntityManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManager getEntityManager() {
|
||||
EntityManager entityManager = transactionInfo.get().entityManager;
|
||||
@@ -131,6 +136,22 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
return entityManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaTransactionManager setDatabaseSnapshot(String snapshotId) {
|
||||
// Postgresql-specific: 'set transaction' command must be called inside a transaction
|
||||
assertInTransaction();
|
||||
|
||||
EntityManager entityManager = getEntityManager();
|
||||
// Isolation is hardcoded to REPEATABLE READ, as specified by parent's Javadoc.
|
||||
entityManager
|
||||
.createNativeQuery("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
|
||||
.executeUpdate();
|
||||
entityManager
|
||||
.createNativeQuery(String.format("SET TRANSACTION SNAPSHOT '%s'", snapshotId))
|
||||
.executeUpdate();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> query(String sqlString, Class<T> resultClass) {
|
||||
return new DetachingTypedQuery<>(getEntityManager().createQuery(sqlString, resultClass));
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.reporting;
|
||||
|
||||
import static google.registry.request.RequestParameters.extractOptionalBooleanParameter;
|
||||
import static google.registry.request.RequestParameters.extractOptionalParameter;
|
||||
import static google.registry.request.RequestParameters.extractRequiredParameter;
|
||||
|
||||
@@ -55,6 +56,12 @@ public class ReportingModule {
|
||||
/** The request parameter specifying the jobId for a running Dataflow pipeline. */
|
||||
public static final String PARAM_JOB_ID = "jobId";
|
||||
|
||||
/**
|
||||
* The request parameter name used by actions to indicate if an email should be sent. This
|
||||
* parameter defaults to true if not specified.
|
||||
*/
|
||||
public static final String SEND_EMAIL = "email";
|
||||
|
||||
/** Provides the Cloud Dataflow jobId for a pipeline. */
|
||||
@Provides
|
||||
@Parameter(PARAM_JOB_ID)
|
||||
@@ -62,6 +69,13 @@ public class ReportingModule {
|
||||
return extractRequiredParameter(req, PARAM_JOB_ID);
|
||||
}
|
||||
|
||||
/** Provides the boolean value indicating if emails should be sent. */
|
||||
@Provides
|
||||
@Parameter(SEND_EMAIL)
|
||||
static boolean provideSendEmail(HttpServletRequest req) {
|
||||
return extractOptionalBooleanParameter(req, SEND_EMAIL).orElse(true);
|
||||
}
|
||||
|
||||
/** Extracts an optional YearMonth in yyyy-MM format from the request. */
|
||||
@Provides
|
||||
@Parameter(PARAM_YEAR_MONTH)
|
||||
|
||||
@@ -35,14 +35,19 @@ public interface DnsCountQueryCoordinator {
|
||||
* <p>If your report query requires any additional parameters, add them here.
|
||||
*/
|
||||
class Params {
|
||||
|
||||
public BigqueryConnection bigquery;
|
||||
|
||||
/** The Google Cloud project id. */
|
||||
public String projectId;
|
||||
|
||||
public Params(BigqueryConnection bigquery, String projectId) {
|
||||
/** The BigQuery dataset from which to query. */
|
||||
public String icannReportingDataSet;
|
||||
|
||||
public Params(BigqueryConnection bigquery, String projectId, String icannReportingDataSet) {
|
||||
this.bigquery = bigquery;
|
||||
this.projectId = projectId;
|
||||
this.icannReportingDataSet = icannReportingDataSet;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.reporting.icann;
|
||||
|
||||
import static google.registry.reporting.icann.IcannReportingModule.ICANN_REPORTING_DATA_SET;
|
||||
import static google.registry.util.TypeUtils.getClassFromString;
|
||||
import static google.registry.util.TypeUtils.instantiate;
|
||||
|
||||
@@ -21,6 +22,7 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.bigquery.BigqueryConnection;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import javax.inject.Named;
|
||||
|
||||
/** Dagger module to provide the DnsCountQueryCoordinator. */
|
||||
@Module
|
||||
@@ -30,9 +32,10 @@ public class DnsCountQueryCoordinatorModule {
|
||||
static DnsCountQueryCoordinator provideDnsCountQueryCoordinator(
|
||||
@Config("dnsCountQueryCoordinatorClass") String customClass,
|
||||
BigqueryConnection bigquery,
|
||||
@Config("projectId") String projectId) {
|
||||
@Config("projectId") String projectId,
|
||||
@Named(ICANN_REPORTING_DATA_SET) String icannReportingDataSet) {
|
||||
DnsCountQueryCoordinator.Params params =
|
||||
new DnsCountQueryCoordinator.Params(bigquery, projectId);
|
||||
new DnsCountQueryCoordinator.Params(bigquery, projectId, icannReportingDataSet);
|
||||
return instantiate(getClassFromString(customClass, DnsCountQueryCoordinator.class), params);
|
||||
}
|
||||
}
|
||||
|
||||
+159
-80
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.reporting.icann;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.reporting.icann.IcannReportingModule.DATASTORE_EXPORT_DATA_SET;
|
||||
import static google.registry.reporting.icann.IcannReportingModule.ICANN_REPORTING_DATA_SET;
|
||||
import static google.registry.reporting.icann.QueryBuilderUtils.getQueryFromFile;
|
||||
@@ -71,102 +72,180 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
|
||||
DateTime latestReportTime = earliestReportTime.plusMonths(1).minusMillis(1);
|
||||
|
||||
ImmutableMap.Builder<String, String> queriesBuilder = ImmutableMap.builder();
|
||||
String registrarIanaIdQuery =
|
||||
SqlTemplate.create(getQueryFromFile("registrar_iana_id.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.build();
|
||||
String registrarIanaIdQuery;
|
||||
if (tm().isOfy()) {
|
||||
registrarIanaIdQuery =
|
||||
SqlTemplate.create(getQueryFromFile("registrar_iana_id.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.build();
|
||||
} else {
|
||||
registrarIanaIdQuery =
|
||||
SqlTemplate.create(getQueryFromFile("cloud_sql_registrar_iana_id.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.build();
|
||||
}
|
||||
queriesBuilder.put(getTableName(REGISTRAR_IANA_ID, yearMonth), registrarIanaIdQuery);
|
||||
|
||||
String totalDomainsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("total_domains.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("DOMAINBASE_TABLE", "DomainBase")
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.build();
|
||||
String totalDomainsQuery;
|
||||
if (tm().isOfy()) {
|
||||
totalDomainsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("total_domains.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("DOMAINBASE_TABLE", "DomainBase")
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.build();
|
||||
} else {
|
||||
totalDomainsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("cloud_sql_total_domains.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.build();
|
||||
}
|
||||
queriesBuilder.put(getTableName(TOTAL_DOMAINS, yearMonth), totalDomainsQuery);
|
||||
|
||||
DateTimeFormatter timestampFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
String totalNameserversQuery =
|
||||
SqlTemplate.create(getQueryFromFile("total_nameservers.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("HOSTRESOURCE_TABLE", "HostResource")
|
||||
.put("DOMAINBASE_TABLE", "DomainBase")
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
.build();
|
||||
String totalNameserversQuery;
|
||||
if (tm().isOfy()) {
|
||||
totalNameserversQuery =
|
||||
SqlTemplate.create(getQueryFromFile("total_nameservers.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("HOSTRESOURCE_TABLE", "HostResource")
|
||||
.put("DOMAINBASE_TABLE", "DomainBase")
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
.build();
|
||||
} else {
|
||||
totalNameserversQuery =
|
||||
SqlTemplate.create(getQueryFromFile("cloud_sql_total_nameservers.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
.build();
|
||||
}
|
||||
queriesBuilder.put(getTableName(TOTAL_NAMESERVERS, yearMonth), totalNameserversQuery);
|
||||
|
||||
String transactionCountsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("transaction_counts.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("HISTORYENTRY_TABLE", "HistoryEntry")
|
||||
.put("EARLIEST_REPORT_TIME", timestampFormatter.print(earliestReportTime))
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
.put("CLIENT_ID", "clientId")
|
||||
.put("OTHER_CLIENT_ID", "otherClientId")
|
||||
.put("TRANSFER_SUCCESS_FIELD", "TRANSFER_GAINING_SUCCESSFUL")
|
||||
.put("TRANSFER_NACKED_FIELD", "TRANSFER_GAINING_NACKED")
|
||||
.put("DEFAULT_FIELD", "field")
|
||||
.build();
|
||||
String transactionCountsQuery;
|
||||
if (tm().isOfy()) {
|
||||
transactionCountsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("transaction_counts.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("HISTORYENTRY_TABLE", "HistoryEntry")
|
||||
.put("EARLIEST_REPORT_TIME", timestampFormatter.print(earliestReportTime))
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
.put("CLIENT_ID", "clientId")
|
||||
.put("OTHER_CLIENT_ID", "otherClientId")
|
||||
.put("TRANSFER_SUCCESS_FIELD", "TRANSFER_GAINING_SUCCESSFUL")
|
||||
.put("TRANSFER_NACKED_FIELD", "TRANSFER_GAINING_NACKED")
|
||||
.put("DEFAULT_FIELD", "field")
|
||||
.build();
|
||||
} else {
|
||||
transactionCountsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("cloud_sql_transaction_counts.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("EARLIEST_REPORT_TIME", timestampFormatter.print(earliestReportTime))
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
.build();
|
||||
}
|
||||
queriesBuilder.put(getTableName(TRANSACTION_COUNTS, yearMonth), transactionCountsQuery);
|
||||
|
||||
String transactionTransferLosingQuery =
|
||||
SqlTemplate.create(getQueryFromFile("transaction_counts.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("HISTORYENTRY_TABLE", "HistoryEntry")
|
||||
.put("EARLIEST_REPORT_TIME", timestampFormatter.print(earliestReportTime))
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
// Roles are reversed for losing queries
|
||||
.put("CLIENT_ID", "otherClientId")
|
||||
.put("OTHER_CLIENT_ID", "clientId")
|
||||
.put("TRANSFER_SUCCESS_FIELD", "TRANSFER_LOSING_SUCCESSFUL")
|
||||
.put("TRANSFER_NACKED_FIELD", "TRANSFER_LOSING_NACKED")
|
||||
.put("DEFAULT_FIELD", "NULL")
|
||||
.build();
|
||||
String transactionTransferLosingQuery;
|
||||
if (tm().isOfy()) {
|
||||
transactionTransferLosingQuery =
|
||||
SqlTemplate.create(getQueryFromFile("transaction_counts.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("HISTORYENTRY_TABLE", "HistoryEntry")
|
||||
.put("EARLIEST_REPORT_TIME", timestampFormatter.print(earliestReportTime))
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
// Roles are reversed for losing queries
|
||||
.put("CLIENT_ID", "otherClientId")
|
||||
.put("OTHER_CLIENT_ID", "clientId")
|
||||
.put("TRANSFER_SUCCESS_FIELD", "TRANSFER_LOSING_SUCCESSFUL")
|
||||
.put("TRANSFER_NACKED_FIELD", "TRANSFER_LOSING_NACKED")
|
||||
.put("DEFAULT_FIELD", "NULL")
|
||||
.build();
|
||||
} else {
|
||||
transactionTransferLosingQuery =
|
||||
SqlTemplate.create(getQueryFromFile("cloud_sql_transaction_transfer_losing.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("EARLIEST_REPORT_TIME", timestampFormatter.print(earliestReportTime))
|
||||
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
|
||||
.build();
|
||||
}
|
||||
queriesBuilder.put(
|
||||
getTableName(TRANSACTION_TRANSFER_LOSING, yearMonth), transactionTransferLosingQuery);
|
||||
|
||||
// App Engine log table suffixes use YYYYMMDD format
|
||||
DateTimeFormatter logTableFormatter = DateTimeFormat.forPattern("yyyyMMdd");
|
||||
String attemptedAddsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("attempted_adds.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
|
||||
.put("REQUEST_TABLE", "appengine_googleapis_com_request_log_")
|
||||
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(earliestReportTime))
|
||||
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(latestReportTime))
|
||||
// All metadata logs for reporting come from google.registry.flows.FlowReporter.
|
||||
.put(
|
||||
"METADATA_LOG_PREFIX",
|
||||
"google.registry.flows.FlowReporter recordToLogs: FLOW-LOG-SIGNATURE-METADATA")
|
||||
.build();
|
||||
String attemptedAddsQuery;
|
||||
if (tm().isOfy()) {
|
||||
attemptedAddsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("attempted_adds.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
|
||||
.put("REQUEST_TABLE", "appengine_googleapis_com_request_log_")
|
||||
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(earliestReportTime))
|
||||
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(latestReportTime))
|
||||
// All metadata logs for reporting come from google.registry.flows.FlowReporter.
|
||||
.put(
|
||||
"METADATA_LOG_PREFIX",
|
||||
"google.registry.flows.FlowReporter recordToLogs: FLOW-LOG-SIGNATURE-METADATA")
|
||||
.build();
|
||||
} else {
|
||||
attemptedAddsQuery =
|
||||
SqlTemplate.create(getQueryFromFile("cloud_sql_attempted_adds.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
|
||||
.put("REQUEST_TABLE", "appengine_googleapis_com_request_log_")
|
||||
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(earliestReportTime))
|
||||
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(latestReportTime))
|
||||
// All metadata logs for reporting come from google.registry.flows.FlowReporter.
|
||||
.put(
|
||||
"METADATA_LOG_PREFIX",
|
||||
"google.registry.flows.FlowReporter recordToLogs: FLOW-LOG-SIGNATURE-METADATA")
|
||||
.build();
|
||||
}
|
||||
queriesBuilder.put(getTableName(ATTEMPTED_ADDS, yearMonth), attemptedAddsQuery);
|
||||
|
||||
String aggregateQuery =
|
||||
SqlTemplate.create(getQueryFromFile("transactions_report_aggregation.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRY_TABLE", "Registry")
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("REGISTRAR_IANA_ID_TABLE", getTableName(REGISTRAR_IANA_ID, yearMonth))
|
||||
.put("TOTAL_DOMAINS_TABLE", getTableName(TOTAL_DOMAINS, yearMonth))
|
||||
.put("TOTAL_NAMESERVERS_TABLE", getTableName(TOTAL_NAMESERVERS, yearMonth))
|
||||
.put("TRANSACTION_COUNTS_TABLE", getTableName(TRANSACTION_COUNTS, yearMonth))
|
||||
.put(
|
||||
"TRANSACTION_TRANSFER_LOSING_TABLE",
|
||||
getTableName(TRANSACTION_TRANSFER_LOSING, yearMonth))
|
||||
.put("ATTEMPTED_ADDS_TABLE", getTableName(ATTEMPTED_ADDS, yearMonth))
|
||||
.build();
|
||||
String aggregateQuery;
|
||||
if (tm().isOfy()) {
|
||||
aggregateQuery =
|
||||
SqlTemplate.create(getQueryFromFile("transactions_report_aggregation.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRY_TABLE", "Registry")
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("REGISTRAR_IANA_ID_TABLE", getTableName(REGISTRAR_IANA_ID, yearMonth))
|
||||
.put("TOTAL_DOMAINS_TABLE", getTableName(TOTAL_DOMAINS, yearMonth))
|
||||
.put("TOTAL_NAMESERVERS_TABLE", getTableName(TOTAL_NAMESERVERS, yearMonth))
|
||||
.put("TRANSACTION_COUNTS_TABLE", getTableName(TRANSACTION_COUNTS, yearMonth))
|
||||
.put(
|
||||
"TRANSACTION_TRANSFER_LOSING_TABLE",
|
||||
getTableName(TRANSACTION_TRANSFER_LOSING, yearMonth))
|
||||
.put("ATTEMPTED_ADDS_TABLE", getTableName(ATTEMPTED_ADDS, yearMonth))
|
||||
.build();
|
||||
} else {
|
||||
aggregateQuery =
|
||||
SqlTemplate.create(getQueryFromFile("cloud_sql_transactions_report_aggregation.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("REGISTRAR_IANA_ID_TABLE", getTableName(REGISTRAR_IANA_ID, yearMonth))
|
||||
.put("TOTAL_DOMAINS_TABLE", getTableName(TOTAL_DOMAINS, yearMonth))
|
||||
.put("TOTAL_NAMESERVERS_TABLE", getTableName(TOTAL_NAMESERVERS, yearMonth))
|
||||
.put("TRANSACTION_COUNTS_TABLE", getTableName(TRANSACTION_COUNTS, yearMonth))
|
||||
.put(
|
||||
"TRANSACTION_TRANSFER_LOSING_TABLE",
|
||||
getTableName(TRANSACTION_TRANSFER_LOSING, yearMonth))
|
||||
.put("ATTEMPTED_ADDS_TABLE", getTableName(ATTEMPTED_ADDS, yearMonth))
|
||||
.build();
|
||||
}
|
||||
queriesBuilder.put(getTableName(TRANSACTIONS_REPORT_AGGREGATION, yearMonth), aggregateQuery);
|
||||
|
||||
return queriesBuilder.build();
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#standardSQL
|
||||
-- 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.
|
||||
|
||||
-- Determine the number of attempted adds each registrar made.
|
||||
|
||||
-- Since the specification requests all 'attempted' adds, we regex the
|
||||
-- monthly App Engine logs, searching for all create commands and associating
|
||||
-- them with their corresponding registrars.
|
||||
|
||||
-- Example log generated by FlowReporter in App Engine logs:
|
||||
--google.registry.flows.FlowReporter
|
||||
-- recordToLogs: FLOW-LOG-SIGNATURE-METADATA:
|
||||
--{"serverTrid":"oNwL2J2eRya7bh7c9oHIzg==-2360a","clientId":"ipmirror"
|
||||
-- ,"commandType":"hello", "resourceType":"","flowClassName":"HelloFlow"
|
||||
-- ,"targetId":"","targetIds":[],"tld":"",
|
||||
-- "tlds":[],"icannActivityReportField":""}
|
||||
|
||||
-- This outer select just converts the registrar's clientId to their name.
|
||||
SELECT
|
||||
tld,
|
||||
registrar_table.registrar_name AS registrar_name,
|
||||
'ATTEMPTED_ADDS' AS metricName,
|
||||
count AS metricValue
|
||||
FROM (
|
||||
SELECT
|
||||
JSON_EXTRACT_SCALAR(json, '$.tld') AS tld,
|
||||
JSON_EXTRACT_SCALAR(json, '$.clientId') AS clientId,
|
||||
COUNT(json) AS count
|
||||
FROM (
|
||||
-- Extract JSON metadata package from monthly logs
|
||||
SELECT
|
||||
REGEXP_EXTRACT(logMessages, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
|
||||
AS json
|
||||
FROM (
|
||||
SELECT
|
||||
protoPayload.resource AS requestPath,
|
||||
ARRAY(
|
||||
SELECT logMessage
|
||||
FROM UNNEST(protoPayload.line)) AS logMessage
|
||||
FROM
|
||||
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%REQUEST_TABLE%*`
|
||||
WHERE _TABLE_SUFFIX
|
||||
BETWEEN '%FIRST_DAY_OF_MONTH%'
|
||||
AND '%LAST_DAY_OF_MONTH%')
|
||||
JOIN UNNEST(logMessage) AS logMessages
|
||||
-- Look for metadata logs from epp and registrar console requests
|
||||
WHERE requestPath IN ('/_dr/epp', '/_dr/epptool', '/registrar-xhr')
|
||||
AND STARTS_WITH(logMessages, "%METADATA_LOG_PREFIX%")
|
||||
-- Look for domain creates
|
||||
AND REGEXP_CONTAINS(
|
||||
logMessages, r'"commandType":"create","resourceType":"domain"')
|
||||
-- Filter prober data
|
||||
AND NOT REGEXP_CONTAINS(
|
||||
logMessages, r'"prober-[a-z]{2}-((any)|(canary))"') )
|
||||
GROUP BY tld, clientId ) AS logs_table
|
||||
JOIN
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT registrar_id, registrar_name FROM "Registrar";''') AS registrar_table
|
||||
ON
|
||||
logs_table.clientId = registrar_table.registrar_id
|
||||
ORDER BY tld, registrar_name
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#standardSQL
|
||||
-- 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.
|
||||
|
||||
-- Gather a list of all tld-registrar pairs, with their IANA IDs.
|
||||
|
||||
-- This establishes which registrars will appear in the reports.
|
||||
|
||||
SELECT
|
||||
allowedTlds AS tld,
|
||||
registrar_name,
|
||||
iana_identifier AS iana_id
|
||||
FROM
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT
|
||||
allowedTlds,
|
||||
registrar_name,
|
||||
iana_identifier
|
||||
FROM
|
||||
"Registrar" AS r,
|
||||
UNNEST(allowed_tlds) AS allowedTlds
|
||||
WHERE
|
||||
r.type='REAL' OR r.type='INTERNAL';''')
|
||||
-- Filter out prober data
|
||||
WHERE NOT ENDS_WITH(allowedTlds, "test")
|
||||
ORDER BY tld, registrar_name
|
||||
@@ -0,0 +1,43 @@
|
||||
#standardSQL
|
||||
-- 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.
|
||||
|
||||
-- Determine the number of domains each registrar sponsors per tld.
|
||||
|
||||
-- This is just the number of fullyQualifiedDomainNames under each
|
||||
-- tld-registrar pair.
|
||||
|
||||
SELECT
|
||||
tld,
|
||||
registrar_name,
|
||||
'TOTAL_DOMAINS' as metricName,
|
||||
COUNT(domain_name) as metricValue
|
||||
FROM
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT
|
||||
registrar_name,
|
||||
registrar_id
|
||||
FROM "Registrar" AS r
|
||||
WHERE r.type='REAL' OR r.type='INTERNAL';''')
|
||||
JOIN
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT
|
||||
tld,
|
||||
current_sponsor_registrar_id,
|
||||
domain_name
|
||||
FROM "Domain" AS d;''')
|
||||
ON
|
||||
current_sponsor_registrar_id = registrar_id
|
||||
GROUP BY tld, registrar_name
|
||||
ORDER BY tld, registrar_name
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
#standardSQL
|
||||
-- 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.
|
||||
|
||||
-- Determine the number of referenced nameservers for a registrar's domains.
|
||||
|
||||
-- We count the number of unique hosts under each tld-registrar combo by
|
||||
-- collecting all domains' listed hosts that were still valid at the
|
||||
-- end of the reporting month.
|
||||
|
||||
SELECT
|
||||
tld,
|
||||
registrar_name,
|
||||
'TOTAL_NAMESERVERS' AS metricName,
|
||||
COUNT(host_name) AS metricValue
|
||||
FROM
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT
|
||||
host_name,
|
||||
current_sponsor_registrar_id,
|
||||
creation_time AS host_creation_time,
|
||||
CASE
|
||||
WHEN deletion_time > to_timestamp('9999-12-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
THEN to_timestamp('9999-12-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
ELSE
|
||||
deletion_time
|
||||
END as host_deletion_time
|
||||
FROM "Host";''')
|
||||
JOIN (
|
||||
SELECT
|
||||
registrar_id,
|
||||
registrar_name
|
||||
FROM
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql", '''SELECT registrar_id, registrar_name FROM "Registrar" AS r WHERE r.type='REAL' OR r.type='INTERNAL';'''))
|
||||
ON current_sponsor_registrar_id = registrar_id
|
||||
JOIN (
|
||||
SELECT
|
||||
tld,
|
||||
referencedHostName
|
||||
FROM
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT
|
||||
repo_id,
|
||||
tld,
|
||||
creation_time AS domain_creation_time,
|
||||
CASE
|
||||
WHEN deletion_time > to_timestamp('9999-12-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
THEN to_timestamp('9999-12-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
ELSE
|
||||
deletion_time
|
||||
END as domain_deletion_time
|
||||
FROM "Domain";''')
|
||||
JOIN
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql", '''SELECT domain_repo_id, host_repo_id FROM "DomainHost";''')
|
||||
ON
|
||||
repo_id = domain_repo_id
|
||||
JOIN
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql", '''SELECT repo_id as host_table_repo_id, host_name AS referencedHostName FROM "Host";''')
|
||||
ON
|
||||
host_table_repo_id = host_repo_id
|
||||
WHERE
|
||||
domain_creation_time <= TIMESTAMP("%LATEST_REPORT_TIME%")
|
||||
AND domain_deletion_time > TIMESTAMP("%LATEST_REPORT_TIME%"))
|
||||
ON host_name = referencedHostName
|
||||
WHERE
|
||||
host_creation_time <= TIMESTAMP("%LATEST_REPORT_TIME%")
|
||||
AND host_deletion_time > TIMESTAMP("%LATEST_REPORT_TIME%")
|
||||
GROUP BY tld, registrar_name
|
||||
ORDER BY tld, registrar_name
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
#standardSQL
|
||||
-- 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.
|
||||
|
||||
-- Counts the number of mutating transactions each registrar made.
|
||||
|
||||
-- We populate the fields through explicit logging of
|
||||
-- DomainTransactionRecords, which contain all necessary information for
|
||||
-- reporting (such as reporting time, report field, report amount, etc.
|
||||
|
||||
SELECT
|
||||
tld,
|
||||
registrar_table.registrar_name AS registrar_name,
|
||||
metricName,
|
||||
metricValue
|
||||
FROM (
|
||||
SELECT
|
||||
tld,
|
||||
clientId,
|
||||
CASE WHEN field = 'TRANSFER_SUCCESSFUL' THEN 'TRANSFER_GAINING_SUCCESSFUL'
|
||||
WHEN field = 'TRANSFER_NACKED' THEN 'TRANSFER_GAINING_NACKED'
|
||||
ELSE field
|
||||
END AS metricName,
|
||||
SUM(amount) AS metricValue
|
||||
FROM (
|
||||
SELECT
|
||||
CASE
|
||||
-- Explicit transfer acks (approve) and nacks (reject) are done
|
||||
-- by the opposing registrar. Thus, for these specific actions,
|
||||
-- we swap the 'history_other_registrar_id' with the
|
||||
-- 'history_registrar_id' to properly account for this reversal.
|
||||
WHEN (history_type = 'DOMAIN_TRANSFER_APPROVE'
|
||||
OR history_type = 'DOMAIN_TRANSFER_REJECT')
|
||||
THEN history_other_registrar_id
|
||||
ELSE history_registrar_id
|
||||
END AS clientId,
|
||||
tld,
|
||||
report_field AS field,
|
||||
report_amount AS amount,
|
||||
reporting_time AS reportingTime
|
||||
FROM EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
''' SELECT history_type, history_other_registrar_id, history_registrar_id, domain_repo_id, history_revision_id FROM "DomainHistory";''') AS dh
|
||||
JOIN
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT domain_repo_id, history_revision_id, reporting_time, tld, report_field, report_amount FROM "DomainTransactionRecord";''') AS dtr
|
||||
ON
|
||||
dh.domain_repo_id = dtr.domain_repo_id AND dh.history_revision_id = dtr.history_revision_id
|
||||
)
|
||||
-- Only look at this month's data
|
||||
WHERE reportingTime
|
||||
BETWEEN TIMESTAMP('%EARLIEST_REPORT_TIME%')
|
||||
AND TIMESTAMP('%LATEST_REPORT_TIME%')
|
||||
GROUP BY
|
||||
tld,
|
||||
clientId,
|
||||
field ) AS counts_table
|
||||
JOIN
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT registrar_id, registrar_name FROM "Registrar";''') AS registrar_table
|
||||
ON
|
||||
counts_table.clientId = registrar_table.registrar_id
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
#standardSQL
|
||||
-- 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.
|
||||
|
||||
-- Counts the number of mutating transactions each registrar made.
|
||||
|
||||
-- We populate the fields through explicit logging of
|
||||
-- DomainTransactionRecords, which contain all necessary information for
|
||||
-- reporting (such as reporting time, report field, report amount, etc.
|
||||
|
||||
SELECT
|
||||
tld,
|
||||
registrar_table.registrar_name AS registrar_name,
|
||||
metricName,
|
||||
metricValue
|
||||
FROM (
|
||||
SELECT
|
||||
tld,
|
||||
clientId,
|
||||
CASE WHEN field = 'TRANSFER_SUCCESSFUL' THEN 'TRANSFER_LOSING_SUCCESSFUL'
|
||||
WHEN field = 'TRANSFER_NACKED' THEN 'TRANSFER_LOSING_NACKED'
|
||||
ELSE NULL
|
||||
END AS metricName,
|
||||
SUM(amount) AS metricValue
|
||||
FROM (
|
||||
SELECT
|
||||
CASE
|
||||
-- Explicit transfer acks (approve) and nacks (reject) are done
|
||||
-- by the opposing registrar. Thus, for these specific actions,
|
||||
-- we swap the 'history_other_registrar_id' with the
|
||||
-- 'history_registrar_id' to properly account for this reversal.
|
||||
WHEN (history_type = 'DOMAIN_TRANSFER_APPROVE'
|
||||
OR history_type = 'DOMAIN_TRANSFER_REJECT')
|
||||
THEN history_registrar_id
|
||||
ELSE history_other_registrar_id
|
||||
END AS clientId,
|
||||
tld,
|
||||
report_field AS field,
|
||||
report_amount AS amount,
|
||||
reporting_time AS reportingTime
|
||||
FROM EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
''' SELECT history_type, history_other_registrar_id, history_registrar_id, domain_repo_id, history_revision_id FROM "DomainHistory";''') AS dh
|
||||
JOIN
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT domain_repo_id, history_revision_id, reporting_time, tld, report_field, report_amount FROM "DomainTransactionRecord";''') AS dtr
|
||||
ON
|
||||
dh.domain_repo_id = dtr.domain_repo_id AND dh.history_revision_id = dtr.history_revision_id
|
||||
)
|
||||
-- Only look at this month's data
|
||||
WHERE reportingTime
|
||||
BETWEEN TIMESTAMP('%EARLIEST_REPORT_TIME%')
|
||||
AND TIMESTAMP('%LATEST_REPORT_TIME%')
|
||||
GROUP BY
|
||||
tld,
|
||||
clientId,
|
||||
field ) AS counts_table
|
||||
JOIN
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
|
||||
'''SELECT registrar_id, registrar_name FROM "Registrar";''') AS registrar_table
|
||||
ON
|
||||
counts_table.clientId = registrar_table.registrar_id
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
#standardSQL
|
||||
-- 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.
|
||||
|
||||
-- Construct the transaction reports' rows from the intermediary data views.
|
||||
|
||||
-- This query pulls from all intermediary tables to create the activity
|
||||
-- report csv, via a table transpose and sum over all activity report fields.
|
||||
|
||||
SELECT
|
||||
tlds.tld_name as tld,
|
||||
-- Surround registrar names with quotes to handle names containing a comma.
|
||||
FORMAT("\"%s\"", registrars.registrar_name) as registrar_name,
|
||||
registrars.iana_id as iana_id,
|
||||
SUM(IF(metrics.metricName = 'TOTAL_DOMAINS', metrics.metricValue, 0)) AS total_domains,
|
||||
SUM(IF(metrics.metricName = 'TOTAL_NAMESERVERS', metrics.metricValue, 0)) AS total_nameservers,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_1_YR', metrics.metricValue, 0)) AS net_adds_1_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_2_YR', metrics.metricValue, 0)) AS net_adds_2_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_3_YR', metrics.metricValue, 0)) AS net_adds_3_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_4_YR', metrics.metricValue, 0)) AS net_adds_4_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_5_YR', metrics.metricValue, 0)) AS net_adds_5_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_6_YR', metrics.metricValue, 0)) AS net_adds_6_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_7_YR', metrics.metricValue, 0)) AS net_adds_7_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_8_YR', metrics.metricValue, 0)) AS net_adds_8_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_9_YR', metrics.metricValue, 0)) AS net_adds_9_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_ADDS_10_Yr', metrics.metricValue, 0)) AS net_adds_10_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_1_YR', metrics.metricValue, 0)) AS net_renews_1_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_2_YR', metrics.metricValue, 0)) AS net_renews_2_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_3_YR', metrics.metricValue, 0)) AS net_renews_3_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_4_YR', metrics.metricValue, 0)) AS net_renews_4_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_5_YR', metrics.metricValue, 0)) AS net_renews_5_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_6_YR', metrics.metricValue, 0)) AS net_renews_6_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_7_YR', metrics.metricValue, 0)) AS net_renews_7_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_8_YR', metrics.metricValue, 0)) AS net_renews_8_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_9_YR', metrics.metricValue, 0)) AS net_renews_9_yr,
|
||||
SUM(IF(metrics.metricName = 'NET_RENEWS_10_YR', metrics.metricValue, 0)) AS net_renews_10_yr,
|
||||
SUM(IF(metrics.metricName = 'TRANSFER_GAINING_SUCCESSFUL', metrics.metricValue, 0)) AS transfer_gaining_successful,
|
||||
SUM(IF(metrics.metricName = 'TRANSFER_GAINING_NACKED', metrics.metricValue, 0)) AS transfer_gaining_nacked,
|
||||
SUM(IF(metrics.metricName = 'TRANSFER_LOSING_SUCCESSFUL', metrics.metricValue, 0)) AS transfer_losing_successful,
|
||||
SUM(IF(metrics.metricName = 'TRANSFER_LOSING_NACKED', metrics.metricValue, 0)) AS transfer_losing_nacked,
|
||||
-- We don't interact with transfer disputes
|
||||
0 AS transfer_disputed_won,
|
||||
0 AS transfer_disputed_lost,
|
||||
0 AS transfer_disputed_nodecision,
|
||||
SUM(IF(metrics.metricName = 'DELETED_DOMAINS_GRACE', metrics.metricValue, 0)) AS deleted_domains_grace,
|
||||
SUM(IF(metrics.metricName = 'DELETED_DOMAINS_NOGRACE', metrics.metricValue, 0)) AS deleted_domains_nograce,
|
||||
SUM(IF(metrics.metricName = 'RESTORED_DOMAINS', metrics.metricValue, 0)) AS restored_domains,
|
||||
-- We don't require restore reports
|
||||
0 AS restored_noreport,
|
||||
-- We don't enforce AGP limits right now
|
||||
0 AS agp_exemption_requests,
|
||||
0 AS agp_exemptions_granted,
|
||||
0 AS agp_exempted_domains,
|
||||
SUM(IF(metrics.metricName = 'ATTEMPTED_ADDS', metrics.metricValue, 0)) AS attempted_adds
|
||||
FROM
|
||||
-- Only produce reports for real TLDs
|
||||
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql", '''SELECT "tld_name" FROM "Tld" AS t WHERE t.tld_type='REAL';''') AS tlds
|
||||
JOIN
|
||||
(SELECT *
|
||||
FROM `%PROJECT_ID%.%ICANN_REPORTING_DATA_SET%.%REGISTRAR_IANA_ID_TABLE%`)
|
||||
AS registrars
|
||||
ON tlds.tld_name = registrars.tld
|
||||
-- We LEFT JOIN to produce reports even if the registrar made no transactions
|
||||
LEFT OUTER JOIN (
|
||||
-- Gather all intermediary data views
|
||||
SELECT *
|
||||
FROM `%PROJECT_ID%.%ICANN_REPORTING_DATA_SET%.%TOTAL_DOMAINS_TABLE%`
|
||||
UNION ALL
|
||||
SELECT *
|
||||
FROM `%PROJECT_ID%.%ICANN_REPORTING_DATA_SET%.%TOTAL_NAMESERVERS_TABLE%`
|
||||
UNION ALL
|
||||
SELECT *
|
||||
FROM `%PROJECT_ID%.%ICANN_REPORTING_DATA_SET%.%TRANSACTION_COUNTS_TABLE%`
|
||||
UNION ALL
|
||||
SELECT *
|
||||
FROM `%PROJECT_ID%.%ICANN_REPORTING_DATA_SET%.%TRANSACTION_TRANSFER_LOSING_TABLE%`
|
||||
UNION ALL
|
||||
SELECT *
|
||||
FROM `%PROJECT_ID%.%ICANN_REPORTING_DATA_SET%.%ATTEMPTED_ADDS_TABLE%` ) AS metrics
|
||||
-- Join on tld and registrar name
|
||||
ON registrars.tld = metrics.tld
|
||||
AND registrars.registrar_name = metrics.registrar_name
|
||||
GROUP BY
|
||||
tld, registrar_name, iana_id
|
||||
ORDER BY
|
||||
tld, registrar_name
|
||||
@@ -72,6 +72,7 @@ public class GenerateSpec11ReportAction implements Runnable {
|
||||
private final Response response;
|
||||
private final Dataflow dataflow;
|
||||
private final PrimaryDatabase database;
|
||||
private final boolean sendEmail;
|
||||
|
||||
@Inject
|
||||
GenerateSpec11ReportAction(
|
||||
@@ -82,6 +83,7 @@ public class GenerateSpec11ReportAction implements Runnable {
|
||||
@Key("safeBrowsingAPIKey") String apiKey,
|
||||
@Parameter(ReportingModule.PARAM_DATE) LocalDate date,
|
||||
@Parameter(RequestParameters.PARAM_DATABASE) PrimaryDatabase database,
|
||||
@Parameter(ReportingModule.SEND_EMAIL) boolean sendEmail,
|
||||
Clock clock,
|
||||
Response response,
|
||||
Dataflow dataflow) {
|
||||
@@ -98,6 +100,7 @@ public class GenerateSpec11ReportAction implements Runnable {
|
||||
this.clock = clock;
|
||||
this.response = response;
|
||||
this.dataflow = dataflow;
|
||||
this.sendEmail = sendEmail;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,7 +139,9 @@ public class GenerateSpec11ReportAction implements Runnable {
|
||||
Map<String, String> beamTaskParameters =
|
||||
ImmutableMap.of(
|
||||
ReportingModule.PARAM_JOB_ID, jobId, ReportingModule.PARAM_DATE, date.toString());
|
||||
enqueueBeamReportingTask(PublishSpec11ReportAction.PATH, beamTaskParameters);
|
||||
if (sendEmail) {
|
||||
enqueueBeamReportingTask(PublishSpec11ReportAction.PATH, beamTaskParameters);
|
||||
}
|
||||
response.setStatus(SC_OK);
|
||||
response.setPayload(String.format("Launched Spec11 pipeline: %s", jobId));
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -22,8 +22,8 @@ import org.joda.time.Duration;
|
||||
/**
|
||||
* Code execution locked on some shared resource.
|
||||
*
|
||||
* <p>Locks are either specific to a tld or global to the entire system, in which case a tld of
|
||||
* null is used.
|
||||
* <p>Locks are either specific to a tld or global to the entire system, in which case a tld of null
|
||||
* is used.
|
||||
*/
|
||||
public interface LockHandler extends Serializable {
|
||||
|
||||
@@ -42,4 +42,22 @@ public interface LockHandler extends Serializable {
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
String... lockNames);
|
||||
|
||||
/**
|
||||
* Acquire one or more locks using only Cloud SQL and execute a Void {@link Callable}.
|
||||
*
|
||||
* <p>Runs on a thread that will be killed if it doesn't complete before the lease expires.
|
||||
*
|
||||
* <p>Note that locks are specific either to a given tld or to the entire system (in which case
|
||||
* tld should be passed as null).
|
||||
*
|
||||
* <p>This method exists so that Beam pipelines can acquire / load / release locks.
|
||||
*
|
||||
* @return true if all locks were acquired and the callable was run; false otherwise.
|
||||
*/
|
||||
boolean executeWithSqlLocks(
|
||||
final Callable<Void> callable,
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
String... lockNames);
|
||||
}
|
||||
|
||||
@@ -73,12 +73,42 @@ public class LockHandlerImpl implements LockHandler {
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
String... lockNames) {
|
||||
return executeWithLockAcquirer(callable, tld, leaseLength, this::acquire, lockNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire one or more locks using only Cloud SQL and execute a Void {@link Callable}.
|
||||
*
|
||||
* <p>Thread will be killed if it doesn't complete before the lease expires.
|
||||
*
|
||||
* <p>Note that locks are specific either to a given tld or to the entire system (in which case
|
||||
* tld should be passed as null).
|
||||
*
|
||||
* <p>This method exists so that Beam pipelines can acquire / load / release locks.
|
||||
*
|
||||
* @return whether all locks were acquired and the callable was run.
|
||||
*/
|
||||
@Override
|
||||
public boolean executeWithSqlLocks(
|
||||
final Callable<Void> callable,
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
String... lockNames) {
|
||||
return executeWithLockAcquirer(callable, tld, leaseLength, this::acquireSql, lockNames);
|
||||
}
|
||||
|
||||
private boolean executeWithLockAcquirer(
|
||||
final Callable<Void> callable,
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
LockAcquirer lockAcquirer,
|
||||
String... lockNames) {
|
||||
DateTime startTime = clock.nowUtc();
|
||||
String sanitizedTld = Strings.emptyToNull(tld);
|
||||
try {
|
||||
return AppEngineTimeLimiter.create()
|
||||
.callWithTimeout(
|
||||
new LockingCallable(callable, sanitizedTld, leaseLength, lockNames),
|
||||
new LockingCallable(callable, lockAcquirer, sanitizedTld, leaseLength, lockNames),
|
||||
leaseLength.minus(LOCK_TIMEOUT_FUDGE).getMillis(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
} catch (ExecutionException | UncheckedExecutionException e) {
|
||||
@@ -108,17 +138,32 @@ public class LockHandlerImpl implements LockHandler {
|
||||
return Lock.acquire(lockName, tld, leaseLength, requestStatusChecker, true);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Optional<Lock> acquireSql(String lockName, @Nullable String tld, Duration leaseLength) {
|
||||
return Lock.acquireSql(lockName, tld, leaseLength, requestStatusChecker, true);
|
||||
}
|
||||
|
||||
private interface LockAcquirer {
|
||||
Optional<Lock> acquireLock(String lockName, @Nullable String tld, Duration leaseLength);
|
||||
}
|
||||
|
||||
/** A {@link Callable} that acquires and releases a lock around a delegate {@link Callable}. */
|
||||
private class LockingCallable implements Callable<Boolean> {
|
||||
private static class LockingCallable implements Callable<Boolean> {
|
||||
final Callable<Void> delegate;
|
||||
final LockAcquirer lockAcquirer;
|
||||
@Nullable final String tld;
|
||||
final Duration leaseLength;
|
||||
final Set<String> lockNames;
|
||||
|
||||
LockingCallable(
|
||||
Callable<Void> delegate, String tld, Duration leaseLength, String... lockNames) {
|
||||
Callable<Void> delegate,
|
||||
LockAcquirer lockAcquirer,
|
||||
String tld,
|
||||
Duration leaseLength,
|
||||
String... lockNames) {
|
||||
checkArgument(leaseLength.isLongerThan(LOCK_TIMEOUT_FUDGE));
|
||||
this.delegate = delegate;
|
||||
this.lockAcquirer = lockAcquirer;
|
||||
this.tld = tld;
|
||||
this.leaseLength = leaseLength;
|
||||
// Make sure we join locks in a fixed (lexicographical) order to avoid deadlock.
|
||||
@@ -130,7 +175,7 @@ public class LockHandlerImpl implements LockHandler {
|
||||
Set<Lock> acquiredLocks = new HashSet<>();
|
||||
try {
|
||||
for (String lockName : lockNames) {
|
||||
Optional<Lock> lock = acquire(lockName, tld, leaseLength);
|
||||
Optional<Lock> lock = lockAcquirer.acquireLock(lockName, tld, leaseLength);
|
||||
if (!lock.isPresent()) {
|
||||
logger.atInfo().log("Couldn't acquire lock named: %s for TLD %s.", lockName, tld);
|
||||
return false;
|
||||
|
||||
+10
@@ -108,6 +108,16 @@ public class CreateSyntheticHistoryEntriesAction implements Runnable {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
// Use READ COMMITTED isolation level so that any long-living queries don't cause
|
||||
// collection of predicate locks to spiral out of control (as would happen with a
|
||||
// SERIALIZABLE isolation level)
|
||||
//
|
||||
// NB: setting the isolation level inside the transaction only works for Postgres and
|
||||
// will be reverted to the default once the transaction is committed.
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")
|
||||
.executeUpdate();
|
||||
// The class we're searching from is based on which parent type (e.g. Domain) we have
|
||||
Class<? extends HistoryEntry> historyClass =
|
||||
getHistoryClassFromParent(resource.getClass());
|
||||
|
||||
@@ -34,6 +34,7 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -111,6 +112,7 @@ public class ReplayCommitLogsToSqlActionTest {
|
||||
DelegationSignerData.class,
|
||||
DomainBase.class,
|
||||
GracePeriod.class,
|
||||
Lock.class,
|
||||
PremiumList.class,
|
||||
PremiumEntry.class,
|
||||
RegistrarContact.class,
|
||||
@@ -135,6 +137,7 @@ public class ReplayCommitLogsToSqlActionTest {
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
inject.setStaticField(Ofy.class, "clock", fakeClock);
|
||||
lenient().when(requestStatusChecker.getLogId()).thenReturn("requestLogId");
|
||||
action.gcsUtils = gcsUtils;
|
||||
action.response = response;
|
||||
action.requestStatusChecker = requestStatusChecker;
|
||||
@@ -464,9 +467,10 @@ public class ReplayCommitLogsToSqlActionTest {
|
||||
}
|
||||
});
|
||||
runAndAssertSuccess(now.minusMinutes(1), 1, 1);
|
||||
// jpaTm()::putIgnoringReadOnly should only have been called with the checkpoint
|
||||
// jpaTm()::putIgnoringReadOnly should only have been called with the checkpoint and the lock
|
||||
verify(spy, times(2)).putIgnoringReadOnly(any(SqlReplayCheckpoint.class));
|
||||
verify(spy, times(2)).putIgnoringReadOnly(any());
|
||||
verify(spy).putIgnoringReadOnly(any(Lock.class));
|
||||
verify(spy, times(3)).putIgnoringReadOnly(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -506,7 +510,7 @@ public class ReplayCommitLogsToSqlActionTest {
|
||||
@Test
|
||||
void testFailure_cannotAcquireLock() {
|
||||
Truth8.assertThat(
|
||||
Lock.acquire(
|
||||
Lock.acquireSql(
|
||||
ReplayCommitLogsToSqlAction.class.getSimpleName(),
|
||||
null,
|
||||
Duration.standardHours(1),
|
||||
|
||||
+151
-96
@@ -142,7 +142,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void sendNotificationEmail_returnsTrue() throws Exception {
|
||||
void sendNotificationEmail_techEMailAsRecipient_returnsTrue() throws Exception {
|
||||
X509Certificate expiringCertificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
@@ -157,25 +157,64 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
.asBuilder()
|
||||
.setFailoverClientCertificate(cert.get(), clock.nowUtc())
|
||||
.build());
|
||||
ImmutableList<RegistrarContact> contacts =
|
||||
ImmutableList.of(
|
||||
new RegistrarContact.Builder()
|
||||
.setParent(registrar)
|
||||
.setName("Will Doe")
|
||||
.setEmailAddress("will@example-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551213")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
|
||||
.setVisibleInWhoisAsAdmin(true)
|
||||
.setVisibleInWhoisAsTech(false)
|
||||
.build());
|
||||
persistSimpleResources(contacts);
|
||||
persistResource(registrar);
|
||||
persistSampleContacts(registrar, Type.TECH);
|
||||
assertThat(
|
||||
action.sendNotificationEmail(registrar, START_OF_TIME, CertificateType.FAILOVER, cert))
|
||||
.isEqualTo(true);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void sendNotificationEmail_adminEMailAsRecipient_returnsTrue() throws Exception {
|
||||
X509Certificate expiringCertificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert();
|
||||
Optional<String> cert =
|
||||
Optional.of(certificateChecker.serializeCertificate(expiringCertificate));
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
makeRegistrar1()
|
||||
.asBuilder()
|
||||
.setFailoverClientCertificate(cert.get(), clock.nowUtc())
|
||||
.build());
|
||||
persistSampleContacts(registrar, Type.ADMIN);
|
||||
assertThat(
|
||||
action.sendNotificationEmail(registrar, START_OF_TIME, CertificateType.FAILOVER, cert))
|
||||
.isEqualTo(true);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void sendNotificationEmail_returnsFalse_unsupportedEmailType() throws Exception {
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
createRegistrar(
|
||||
"testId",
|
||||
"testName",
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert(),
|
||||
null)
|
||||
.build());
|
||||
persistSampleContacts(registrar, Type.LEGAL);
|
||||
assertThat(
|
||||
action.sendNotificationEmail(
|
||||
registrar,
|
||||
START_OF_TIME,
|
||||
CertificateType.FAILOVER,
|
||||
Optional.of(
|
||||
certificateChecker.serializeCertificate(
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert()))))
|
||||
.isEqualTo(false);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void sendNotificationEmail_returnsFalse_noEmailRecipients() throws Exception {
|
||||
X509Certificate expiringCertificate =
|
||||
@@ -247,93 +286,82 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void sendNotificationEmails_allEmailsBeingAttemptedToSend() throws Exception {
|
||||
X509Certificate expiringCertificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert();
|
||||
X509Certificate certificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-10-01T00:00:00Z"))
|
||||
.cert();
|
||||
int numOfRegistrars = 10;
|
||||
int numOfRegistrarsWithExpiringCertificates = 2;
|
||||
for (int i = 1; i <= numOfRegistrarsWithExpiringCertificates; i++) {
|
||||
persistResource(
|
||||
createRegistrar("oldcert" + i, "name" + i, expiringCertificate, null).build());
|
||||
void sendNotificationEmails_allEmailsBeingSent_onlyMainCertificates() throws Exception {
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
createRegistrar(
|
||||
"oldcert" + i,
|
||||
"name" + i,
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert(),
|
||||
null)
|
||||
.build());
|
||||
persistSampleContacts(registrar, Type.TECH);
|
||||
}
|
||||
for (int i = numOfRegistrarsWithExpiringCertificates; i <= numOfRegistrars; i++) {
|
||||
persistResource(createRegistrar("goodcert" + i, "name" + i, certificate, null).build());
|
||||
}
|
||||
assertThat(action.sendNotificationEmails()).isEqualTo(numOfRegistrarsWithExpiringCertificates);
|
||||
assertThat(action.sendNotificationEmails()).isEqualTo(10);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void sendNotificationEmails_allEmailsBeingAttemptedToSend_onlyMainCertificates()
|
||||
throws Exception {
|
||||
X509Certificate expiringCertificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert();
|
||||
int numOfRegistrars = 10;
|
||||
for (int i = 1; i <= numOfRegistrars; i++) {
|
||||
persistResource(
|
||||
createRegistrar("oldcert" + i, "name" + i, expiringCertificate, null).build());
|
||||
void sendNotificationEmails_allEmailsBeingSent_onlyFailOverCertificates() throws Exception {
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
createRegistrar(
|
||||
"oldcert" + i,
|
||||
"name" + i,
|
||||
null,
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert())
|
||||
.build());
|
||||
persistSampleContacts(registrar, Type.TECH);
|
||||
}
|
||||
assertThat(action.sendNotificationEmails()).isEqualTo(numOfRegistrars);
|
||||
assertThat(action.sendNotificationEmails()).isEqualTo(10);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void sendNotificationEmails_allEmailsBeingAttemptedToSend_onlyFailOverCertificates()
|
||||
throws Exception {
|
||||
void sendNotificationEmails_allEmailsBeingSent_mixedOfCertificates() throws Exception {
|
||||
X509Certificate expiringCertificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert();
|
||||
int numOfRegistrars = 10;
|
||||
for (int i = 1; i <= numOfRegistrars; i++) {
|
||||
persistResource(
|
||||
createRegistrar("oldcert" + i, "name" + i, null, expiringCertificate).build());
|
||||
}
|
||||
assertThat(action.sendNotificationEmails()).isEqualTo(numOfRegistrars);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void sendNotificationEmails_allEmailsBeingAttemptedToSend_mixedOfCertificates() throws Exception {
|
||||
X509Certificate expiringCertificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert();
|
||||
int numOfRegistrars = 10;
|
||||
int numOfExpiringFailOverOnly = 2;
|
||||
int numOfExpiringPrimaryOnly = 3;
|
||||
for (int i = 1; i <= numOfExpiringFailOverOnly; i++) {
|
||||
persistResource(
|
||||
createRegistrar("cl" + i, "expiringFailOverOnly" + i, null, expiringCertificate).build());
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
createRegistrar(
|
||||
"cl" + i, "regWIthexpiringFailOverOnly" + i, null, expiringCertificate)
|
||||
.build());
|
||||
persistSampleContacts(registrar, Type.TECH);
|
||||
}
|
||||
for (int i = 1; i <= numOfExpiringPrimaryOnly; i++) {
|
||||
persistResource(
|
||||
createRegistrar("cli" + i, "expiringPrimaryOnly" + i, expiringCertificate, null).build());
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
createRegistrar(
|
||||
"cli" + i, "regWithexpiringPrimaryOnly" + i, expiringCertificate, null)
|
||||
.build());
|
||||
persistSampleContacts(registrar, Type.TECH);
|
||||
}
|
||||
for (int i = numOfExpiringFailOverOnly + numOfExpiringPrimaryOnly + 1;
|
||||
i <= numOfRegistrars;
|
||||
i++) {
|
||||
persistResource(
|
||||
createRegistrar("client" + i, "regularReg" + i, expiringCertificate, expiringCertificate)
|
||||
.build());
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
createRegistrar(
|
||||
"client" + i,
|
||||
"regWithTwoExpiring" + i,
|
||||
expiringCertificate,
|
||||
expiringCertificate)
|
||||
.build());
|
||||
persistSampleContacts(registrar, Type.ADMIN);
|
||||
}
|
||||
assertThat(action.sendNotificationEmails())
|
||||
.isEqualTo(numOfRegistrars + numOfExpiringFailOverOnly + numOfExpiringPrimaryOnly);
|
||||
assertThat(action.sendNotificationEmails()).isEqualTo(16);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -649,17 +677,19 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
@TestOfyAndSql
|
||||
void run_sentEmails_responseStatusIs200() throws Exception {
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
persistResource(
|
||||
createRegistrar(
|
||||
"id_" + i,
|
||||
"name" + i,
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert(),
|
||||
null)
|
||||
.build());
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
createRegistrar(
|
||||
"id_" + i,
|
||||
"name" + i,
|
||||
SelfSignedCaCertificate.create(
|
||||
"www.example.tld",
|
||||
DateTime.parse("2020-09-02T00:00:00Z"),
|
||||
DateTime.parse("2021-06-01T00:00:00Z"))
|
||||
.cert(),
|
||||
null)
|
||||
.build());
|
||||
persistSampleContacts(registrar, Type.TECH);
|
||||
}
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
@@ -667,7 +697,9 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
.isEqualTo("Done. Sent 5 expiring certificate notification emails in total.");
|
||||
}
|
||||
|
||||
/** Returns a sample registrar with a customized registrar name, client id and certificate* */
|
||||
/**
|
||||
* Returns a sample registrar builder with a customized registrar name, client id and certificate.
|
||||
*/
|
||||
private Registrar.Builder createRegistrar(
|
||||
String registrarId,
|
||||
String registrarName,
|
||||
@@ -706,4 +738,27 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/** Returns persisted sample contacts with a customized contact email type. */
|
||||
private ImmutableList<RegistrarContact> persistSampleContacts(
|
||||
Registrar registrar, RegistrarContact.Type emailType) {
|
||||
return persistSimpleResources(
|
||||
ImmutableList.of(
|
||||
new RegistrarContact.Builder()
|
||||
.setParent(registrar)
|
||||
.setName("Will Doe")
|
||||
.setEmailAddress("will@example-registrar.tld")
|
||||
.setPhoneNumber("+1.0105551213")
|
||||
.setFaxNumber("+1.0105551213")
|
||||
.setTypes(ImmutableSet.of(emailType))
|
||||
.build(),
|
||||
new RegistrarContact.Builder()
|
||||
.setParent(registrar)
|
||||
.setName("Will Smith")
|
||||
.setEmailAddress("will@test-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551213")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setTypes(ImmutableSet.of(emailType))
|
||||
.build()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
// 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.beam.common;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.truth.Truth;
|
||||
import google.registry.beam.TestPipelineExtension;
|
||||
import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.persistence.NomulusPostgreSql;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
|
||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import javax.persistence.Persistence;
|
||||
import org.apache.beam.sdk.testing.PAssert;
|
||||
import org.apache.beam.sdk.values.PCollection;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
/** Unit tests for {@link DatabaseSnapshot}. */
|
||||
@Testcontainers
|
||||
public class DatabaseSnapshotTest {
|
||||
|
||||
/**
|
||||
* For reasons unknown, an EntityManagerFactory created by {@code JpaIntegrationTestExtension} or
|
||||
* {@code JpaUnitTestExtension} enters a bad state after exporting the first snapshot. Starting
|
||||
* with the second attempt, exports alternate between error ("cannot export a snapshot from a
|
||||
* subtransaction") and success. The {@link #createSnapshot_twiceNoRead} test below fails with
|
||||
* either extension. EntityManagerFactory created for production does not have this problem.
|
||||
*/
|
||||
@Container
|
||||
private static PostgreSQLContainer sqlContainer =
|
||||
new PostgreSQLContainer<>(NomulusPostgreSql.getDockerTag())
|
||||
.withInitScript("sql/schema/nomulus.golden.sql");
|
||||
|
||||
@RegisterExtension
|
||||
final transient TestPipelineExtension testPipeline =
|
||||
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);
|
||||
|
||||
static JpaTransactionManager origJpa;
|
||||
static JpaTransactionManager jpa;
|
||||
|
||||
static Registry registry;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
ImmutableMap<String, String> jpaProperties =
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.put(Environment.URL, sqlContainer.getJdbcUrl())
|
||||
.put(Environment.USER, sqlContainer.getUsername())
|
||||
.put(Environment.PASS, sqlContainer.getPassword())
|
||||
.putAll(PersistenceModule.provideDefaultDatabaseConfigs())
|
||||
.build();
|
||||
jpa =
|
||||
new JpaTransactionManagerImpl(
|
||||
Persistence.createEntityManagerFactory("nomulus", jpaProperties), new FakeClock());
|
||||
origJpa = jpaTm();
|
||||
TransactionManagerFactory.setJpaTm(() -> jpa);
|
||||
|
||||
Registry tld = DatabaseHelper.newRegistry("tld", "TLD");
|
||||
jpaTm().transact(() -> jpaTm().put(tld));
|
||||
registry = jpaTm().transact(() -> jpaTm().loadByEntity(tld));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
TransactionManagerFactory.setJpaTm(() -> origJpa);
|
||||
|
||||
if (jpa != null) {
|
||||
jpa.teardown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSnapshot_onceNoRead() {
|
||||
try (DatabaseSnapshot databaseSnapshot = DatabaseSnapshot.createSnapshot()) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSnapshot_twiceNoRead() {
|
||||
try (DatabaseSnapshot databaseSnapshot = DatabaseSnapshot.createSnapshot()) {}
|
||||
try (DatabaseSnapshot databaseSnapshot = DatabaseSnapshot.createSnapshot()) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readSnapshot() {
|
||||
try (DatabaseSnapshot databaseSnapshot = DatabaseSnapshot.createSnapshot()) {
|
||||
Registry snapshotRegistry =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.setDatabaseSnapshot(databaseSnapshot.getSnapshotId())
|
||||
.loadByEntity(registry));
|
||||
Truth.assertThat(snapshotRegistry).isEqualTo(registry);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readSnapshot_withSubsequentChange() {
|
||||
try (DatabaseSnapshot databaseSnapshot = DatabaseSnapshot.createSnapshot()) {
|
||||
Registry updated =
|
||||
registry
|
||||
.asBuilder()
|
||||
.setCreateBillingCost(registry.getStandardCreateCost().plus(1))
|
||||
.build();
|
||||
jpaTm().transact(() -> jpaTm().put(updated));
|
||||
|
||||
Registry persistedUpdate = jpaTm().transact(() -> jpaTm().loadByEntity(registry));
|
||||
Truth.assertThat(persistedUpdate).isNotEqualTo(registry);
|
||||
|
||||
Registry snapshotRegistry =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.setDatabaseSnapshot(databaseSnapshot.getSnapshotId())
|
||||
.loadByEntity(registry));
|
||||
Truth.assertThat(snapshotRegistry).isEqualTo(registry);
|
||||
} finally {
|
||||
// Revert change to registry in DB, which is shared by all test methods.
|
||||
jpaTm().transact(() -> jpaTm().put(registry));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithRegistryJpaIO() {
|
||||
try (DatabaseSnapshot databaseSnapshot = DatabaseSnapshot.createSnapshot()) {
|
||||
Registry updated =
|
||||
registry
|
||||
.asBuilder()
|
||||
.setCreateBillingCost(registry.getStandardCreateCost().plus(1))
|
||||
.build();
|
||||
jpaTm().transact(() -> jpaTm().put(updated));
|
||||
|
||||
Read<Registry, Registry> read =
|
||||
RegistryJpaIO.read(() -> CriteriaQueryBuilder.create(Registry.class).build(), x -> x)
|
||||
.withSnapshot(databaseSnapshot.getSnapshotId());
|
||||
PCollection<Registry> registries = testPipeline.apply(read);
|
||||
|
||||
// This assertion depends on Registry being Serializable, which may change if the
|
||||
// UnsafeSerializable interface is removed after migration.
|
||||
PAssert.that(registries).containsInAnyOrder(registry);
|
||||
testPipeline.run();
|
||||
} finally {
|
||||
// Revert change to registry in DB, which is shared by all test methods.
|
||||
jpaTm().transact(() -> jpaTm().put(registry));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import static google.registry.beam.common.RegistryPipelineOptions.validateRegist
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.persistence.PersistenceModule.JpaTransactionManagerType;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.testing.SystemPropertyExtension;
|
||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
@@ -123,4 +124,37 @@ class RegistryPipelineOptionsTest {
|
||||
validateRegistryPipelineOptions(options);
|
||||
assertThat(options.getProject()).isEqualTo("some-project");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jpaTransactionManagerType_default() {
|
||||
RegistryPipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(
|
||||
"--registryEnvironment=" + RegistryEnvironment.UNITTEST.name())
|
||||
.withValidation()
|
||||
.as(RegistryPipelineOptions.class);
|
||||
assertThat(options.getJpaTransactionManagerType()).isEqualTo(JpaTransactionManagerType.REGULAR);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jpaTransactionManagerType_regularJpa() {
|
||||
RegistryPipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(
|
||||
"--registryEnvironment=" + RegistryEnvironment.UNITTEST.name(),
|
||||
"--jpaTransactionManagerType=REGULAR")
|
||||
.withValidation()
|
||||
.as(RegistryPipelineOptions.class);
|
||||
assertThat(options.getJpaTransactionManagerType()).isEqualTo(JpaTransactionManagerType.REGULAR);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jpaTransactionManagerType_bulkQueryJpa() {
|
||||
RegistryPipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(
|
||||
"--registryEnvironment=" + RegistryEnvironment.UNITTEST.name(),
|
||||
"--jpaTransactionManagerType=BULK_QUERY")
|
||||
.withValidation()
|
||||
.as(RegistryPipelineOptions.class);
|
||||
assertThat(options.getJpaTransactionManagerType())
|
||||
.isEqualTo(JpaTransactionManagerType.BULK_QUERY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class InitSqlPipelineTest {
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT - 1)
|
||||
final transient DatastoreEntityExtension datastore =
|
||||
new DatastoreEntityExtension("test").allThreads(true);
|
||||
new DatastoreEntityExtension().allThreads(true);
|
||||
|
||||
@RegisterExtension final transient InjectExtension injectExtension = new InjectExtension();
|
||||
|
||||
|
||||
@@ -838,14 +838,19 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("domain_delete_response_pending.xml"));
|
||||
|
||||
HistoryEntry deleteHistoryEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_DELETE);
|
||||
DateTime now = clock.nowUtc();
|
||||
assertPollMessages(
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setParent(deleteHistoryEntry)
|
||||
.setEventTime(clock.nowUtc())
|
||||
.setEventTime(now)
|
||||
.setMsg(
|
||||
"Domain example.tld was deleted by registry administrator with final deletion"
|
||||
+ " effective: 2000-07-11T22:00:00.013Z")
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
DomainPendingActionNotificationResponse.create(
|
||||
"example.tld", true, deleteHistoryEntry.getTrid(), now)))
|
||||
.build(),
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId("TheRegistrar")
|
||||
|
||||
@@ -156,6 +156,30 @@ class PollRequestFlowTest extends FlowTestCase<PollRequestFlow> {
|
||||
runFlowAssertResponse(loadFile("poll_response_domain_pending_notification.xml"));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_domainPendingActionImmediateDelete() throws Exception {
|
||||
persistResource(
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId(getRegistrarIdForFlow())
|
||||
.setEventTime(clock.nowUtc())
|
||||
.setMsg(
|
||||
String.format(
|
||||
"Domain %s was deleted by registry administrator with final deletion"
|
||||
+ " effective: %s",
|
||||
domain.getDomainName(), clock.nowUtc().minusMinutes(5)))
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
DomainPendingActionNotificationResponse.create(
|
||||
domain.getDomainName(),
|
||||
true,
|
||||
Trid.create("ABC-12345", "other-trid"),
|
||||
clock.nowUtc())))
|
||||
.setParent(createHistoryEntryForEppResource(domain))
|
||||
.build());
|
||||
assertTransactionalFlow(false);
|
||||
runFlowAssertResponse(loadFile("poll_message_domain_pending_action_immediate_delete.xml"));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_domainAutorenewMessage() throws Exception {
|
||||
persistResource(
|
||||
|
||||
@@ -412,6 +412,10 @@ public final class ImmutableObjectSubject extends Subject {
|
||||
// don't use ImmutableMap or a stream->collect model since we can have nulls
|
||||
Map<Field, Object> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<Field, Object> entry : originalFields.entrySet()) {
|
||||
// TODO(b/203685960): filter by @DoNotCompare instead.
|
||||
if (entry.getKey().isAnnotationPresent(ImmutableObject.Insignificant.class)) {
|
||||
continue;
|
||||
}
|
||||
if (!ignoredFieldSet.contains(entry.getKey().getName())) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
@@ -426,7 +430,9 @@ public final class ImmutableObjectSubject extends Subject {
|
||||
// don't use ImmutableMap or a stream->collect model since we can have nulls
|
||||
Map<Field, Object> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<Field, Object> entry : originalFields.entrySet()) {
|
||||
if (!entry.getKey().isAnnotationPresent(annotation)) {
|
||||
// TODO(b/203685960): filter by @DoNotCompare instead.
|
||||
if (!entry.getKey().isAnnotationPresent(annotation)
|
||||
&& !entry.getKey().isAnnotationPresent(ImmutableObject.Insignificant.class)) {
|
||||
|
||||
// Perform any necessary substitutions.
|
||||
if (entry.getKey().isAnnotationPresent(ImmutableObject.EmptySetToNull.class)
|
||||
|
||||
@@ -25,6 +25,7 @@ import static google.registry.testing.DatabaseHelper.loadByKey;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.SerializeUtils.serializeDeserialize;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -46,6 +47,7 @@ import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -195,6 +197,20 @@ public class BillingEventTest extends EntityTestCase {
|
||||
ofyTmOrDoNothing(() -> assertThat(tm().loadByEntity(modification)).isEqualTo(modification));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
BillingEvent persisted = loadByEntity(oneTime);
|
||||
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
persisted = loadByEntity(oneTimeSynthetic);
|
||||
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
persisted = loadByEntity(recurring);
|
||||
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
persisted = loadByEntity(cancellationOneTime);
|
||||
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
persisted = loadByEntity(cancellationRecurring);
|
||||
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testParenting() {
|
||||
// Note that these are all tested separately because BillingEvent is an abstract base class that
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.GracePeriod.GracePeriodHistory;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsDataHistory;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.persistence.VKey;
|
||||
|
||||
/**
|
||||
* Helpers for bulk-loading {@link google.registry.model.domain.DomainBase} and {@link
|
||||
* google.registry.model.domain.DomainHistory} entities in <em>tests</em>.
|
||||
*/
|
||||
public class BulkQueryHelper {
|
||||
|
||||
static DomainBase loadAndAssembleDomainBase(String domainRepoId) {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
BulkQueryEntities.assembleDomainBase(
|
||||
jpaTm().loadByKey(DomainBaseLite.createVKey(domainRepoId)),
|
||||
jpaTm()
|
||||
.loadAllOfStream(GracePeriod.class)
|
||||
.filter(gracePeriod -> gracePeriod.getDomainRepoId().equals(domainRepoId))
|
||||
.collect(toImmutableSet()),
|
||||
jpaTm()
|
||||
.loadAllOfStream(DelegationSignerData.class)
|
||||
.filter(dsData -> dsData.getDomainRepoId().equals(domainRepoId))
|
||||
.collect(toImmutableSet()),
|
||||
jpaTm()
|
||||
.loadAllOfStream(DomainHost.class)
|
||||
.filter(domainHost -> domainHost.getDomainRepoId().equals(domainRepoId))
|
||||
.map(DomainHost::getHostVKey)
|
||||
.collect(toImmutableSet())));
|
||||
}
|
||||
|
||||
static DomainHistory loadAndAssembleDomainHistory(DomainHistoryId domainHistoryId) {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
BulkQueryEntities.assembleDomainHistory(
|
||||
jpaTm().loadByKey(VKey.createSql(DomainHistoryLite.class, domainHistoryId)),
|
||||
jpaTm()
|
||||
.loadAllOfStream(DomainDsDataHistory.class)
|
||||
.filter(
|
||||
domainDsDataHistory ->
|
||||
domainDsDataHistory.getDomainHistoryId().equals(domainHistoryId))
|
||||
.collect(toImmutableSet()),
|
||||
jpaTm()
|
||||
.loadAllOfStream(DomainHistoryHost.class)
|
||||
.filter(
|
||||
domainHistoryHost ->
|
||||
domainHistoryHost.getDomainHistoryId().equals(domainHistoryId))
|
||||
.map(DomainHistoryHost::getHostVKey)
|
||||
.collect(toImmutableSet()),
|
||||
jpaTm()
|
||||
.loadAllOfStream(GracePeriodHistory.class)
|
||||
.filter(
|
||||
gracePeriodHistory ->
|
||||
gracePeriodHistory.getDomainHistoryId().equals(domainHistoryId))
|
||||
.collect(toImmutableSet()),
|
||||
jpaTm()
|
||||
.loadAllOfStream(DomainTransactionRecord.class)
|
||||
.filter(x -> true)
|
||||
.collect(toImmutableSet())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Sets.SetView;
|
||||
import com.google.common.truth.Truth8;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for reading {@link DomainBaseLite}. */
|
||||
class DomainBaseLiteTest {
|
||||
|
||||
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
|
||||
|
||||
@RegisterExtension
|
||||
public final AppEngineExtension appEngine =
|
||||
AppEngineExtension.builder().withDatastoreAndCloudSql().withClock(fakeClock).build();
|
||||
|
||||
private final TestSetupHelper setupHelper = new TestSetupHelper(fakeClock);
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
setupHelper.initializeAllEntities();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
setupHelper.tearDownBulkQueryJpaTm();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDomainHost() {
|
||||
setupHelper.applyChangeToDomainAndHistory();
|
||||
setupHelper.setupBulkQueryJpaTm(appEngine);
|
||||
Truth8.assertThat(
|
||||
jpaTm().transact(() -> jpaTm().loadAllOf(DomainHost.class)).stream()
|
||||
.map(DomainHost::getHostVKey))
|
||||
.containsExactly(setupHelper.host.createVKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void domainBaseLiteAttributes_versusDomainBase() {
|
||||
Set<String> domainBaseAttributes =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.getMetamodel()
|
||||
.entity(DomainBase.class)
|
||||
.getAttributes())
|
||||
.stream()
|
||||
.map(Attribute::getName)
|
||||
.collect(Collectors.toSet());
|
||||
setupHelper.setupBulkQueryJpaTm(appEngine);
|
||||
Set<String> domainBaseLiteAttributes =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.getMetamodel()
|
||||
.entity(DomainBaseLite.class)
|
||||
.getAttributes())
|
||||
.stream()
|
||||
.map(Attribute::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
assertThat(domainBaseAttributes).containsAtLeastElementsIn(domainBaseLiteAttributes);
|
||||
|
||||
SetView<?> excludedFromDomainBase =
|
||||
Sets.difference(domainBaseAttributes, domainBaseLiteAttributes);
|
||||
assertThat(excludedFromDomainBase)
|
||||
.containsExactly("internalDelegationSignerData", "internalGracePeriods", "nsHosts");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDomainBaseLite_simple() {
|
||||
setupHelper.setupBulkQueryJpaTm(appEngine);
|
||||
assertThat(BulkQueryHelper.loadAndAssembleDomainBase(TestSetupHelper.DOMAIN_REPO_ID))
|
||||
.isEqualTo(setupHelper.domain);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDomainBaseLite_full() {
|
||||
setupHelper.applyChangeToDomainAndHistory();
|
||||
setupHelper.setupBulkQueryJpaTm(appEngine);
|
||||
assertThat(BulkQueryHelper.loadAndAssembleDomainBase(TestSetupHelper.DOMAIN_REPO_ID))
|
||||
.isEqualTo(setupHelper.domain);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Sets.SetView;
|
||||
import com.google.common.truth.Truth8;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for {@link DomainHistoryLite}. */
|
||||
public class DomainHistoryLiteTest {
|
||||
|
||||
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
|
||||
|
||||
@RegisterExtension
|
||||
public final AppEngineExtension appEngine =
|
||||
AppEngineExtension.builder().withDatastoreAndCloudSql().withClock(fakeClock).build();
|
||||
|
||||
private final TestSetupHelper setupHelper = new TestSetupHelper(fakeClock);
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
setupHelper.initializeAllEntities();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
setupHelper.tearDownBulkQueryJpaTm();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDomainHistoryHost() {
|
||||
setupHelper.applyChangeToDomainAndHistory();
|
||||
setupHelper.setupBulkQueryJpaTm(appEngine);
|
||||
Truth8.assertThat(
|
||||
jpaTm().transact(() -> jpaTm().loadAllOf(DomainHistoryHost.class)).stream()
|
||||
.map(DomainHistoryHost::getHostVKey))
|
||||
.containsExactly(setupHelper.host.createVKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void domainHistoryLiteAttributes_versusDomainHistory() {
|
||||
Set<String> domainHistoryAttributes =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.getMetamodel()
|
||||
.entity(DomainHistory.class)
|
||||
.getAttributes())
|
||||
.stream()
|
||||
.map(Attribute::getName)
|
||||
.collect(Collectors.toSet());
|
||||
setupHelper.setupBulkQueryJpaTm(appEngine);
|
||||
Set<String> domainHistoryLiteAttributes =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.getMetamodel()
|
||||
.entity(DomainHistoryLite.class)
|
||||
.getAttributes())
|
||||
.stream()
|
||||
.map(Attribute::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
assertThat(domainHistoryAttributes).containsAtLeastElementsIn(domainHistoryLiteAttributes);
|
||||
|
||||
SetView<?> excludedFromDomainHistory =
|
||||
Sets.difference(domainHistoryAttributes, domainHistoryLiteAttributes);
|
||||
assertThat(excludedFromDomainHistory)
|
||||
.containsExactly(
|
||||
"dsDataHistories",
|
||||
"gracePeriodHistories",
|
||||
"internalDomainTransactionRecords",
|
||||
"nsHosts");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDomainHistory_noContent() {
|
||||
setupHelper.setupBulkQueryJpaTm(appEngine);
|
||||
assertThat(
|
||||
BulkQueryHelper.loadAndAssembleDomainHistory(
|
||||
setupHelper.domainHistory.getDomainHistoryId()))
|
||||
.isEqualTo(setupHelper.domainHistory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDomainHistory_full() {
|
||||
setupHelper.applyChangeToDomainAndHistory();
|
||||
setupHelper.setupBulkQueryJpaTm(appEngine);
|
||||
assertThat(
|
||||
BulkQueryHelper.loadAndAssembleDomainHistory(
|
||||
setupHelper.domainHistory.getDomainHistoryId()))
|
||||
.isEqualTo(setupHelper.domainHistory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// 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.model.bulkquery;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.testing.SqlHelper.saveRegistrar;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DomainAuthInfo;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.launch.LaunchNotice;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.model.transfer.ContactTransferData;
|
||||
import google.registry.persistence.BulkQueryJpaFactory;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
|
||||
/** Entity creation utilities for domain-related tests. */
|
||||
class TestSetupHelper {
|
||||
|
||||
public static final String TLD = "tld";
|
||||
public static final String DOMAIN_REPO_ID = "4-TLD";
|
||||
public static final String DOMAIN_NAME = "example.tld";
|
||||
public static final String REGISTRAR_ID = "AnRegistrar";
|
||||
|
||||
private final FakeClock fakeClock;
|
||||
|
||||
Registry registry;
|
||||
Registrar registrar;
|
||||
ContactResource contact;
|
||||
DomainBase domain;
|
||||
DomainHistory domainHistory;
|
||||
HostResource host;
|
||||
|
||||
private JpaTransactionManager originalJpaTm;
|
||||
private JpaTransactionManager bulkQueryJpaTm;
|
||||
|
||||
TestSetupHelper(FakeClock fakeClock) {
|
||||
this.fakeClock = fakeClock;
|
||||
}
|
||||
|
||||
void initializeAllEntities() {
|
||||
registry = putInDb(DatabaseHelper.newRegistry(TLD, Ascii.toUpperCase(TLD)));
|
||||
registrar = saveRegistrar(REGISTRAR_ID);
|
||||
contact = putInDb(createContact(DOMAIN_REPO_ID, REGISTRAR_ID));
|
||||
domain = putInDb(createSimpleDomain(contact));
|
||||
domainHistory = putInDb(createHistoryWithoutContent(domain, fakeClock));
|
||||
host = putInDb(createHost());
|
||||
}
|
||||
|
||||
void applyChangeToDomainAndHistory() {
|
||||
domain = putInDb(createFullDomain(contact, host, fakeClock));
|
||||
domainHistory = putInDb(createFullHistory(domain, fakeClock));
|
||||
}
|
||||
|
||||
void setupBulkQueryJpaTm(AppEngineExtension appEngineExtension) {
|
||||
bulkQueryJpaTm =
|
||||
BulkQueryJpaFactory.createBulkQueryJpaTransactionManager(
|
||||
appEngineExtension
|
||||
.getJpaIntegrationTestExtension()
|
||||
.map(JpaIntegrationTestExtension::getJpaProperties)
|
||||
.orElseThrow(
|
||||
() -> new IllegalStateException("Expecting JpaIntegrationTestExtension.")),
|
||||
fakeClock);
|
||||
originalJpaTm = TransactionManagerFactory.jpaTm();
|
||||
TransactionManagerFactory.setJpaTm(() -> bulkQueryJpaTm);
|
||||
}
|
||||
|
||||
void tearDownBulkQueryJpaTm() {
|
||||
if (bulkQueryJpaTm != null) {
|
||||
bulkQueryJpaTm.teardown();
|
||||
TransactionManagerFactory.setJpaTm(() -> originalJpaTm);
|
||||
}
|
||||
}
|
||||
|
||||
static ContactResource createContact(String repoId, String registrarId) {
|
||||
return new ContactResource.Builder()
|
||||
.setRepoId(repoId)
|
||||
.setCreationRegistrarId(registrarId)
|
||||
.setTransferData(new ContactTransferData.Builder().build())
|
||||
.setPersistedCurrentSponsorRegistrarId(registrarId)
|
||||
.build();
|
||||
}
|
||||
|
||||
static DomainBase createSimpleDomain(ContactResource contact) {
|
||||
return DatabaseHelper.newDomainBase(DOMAIN_NAME, DOMAIN_REPO_ID, contact)
|
||||
.asBuilder()
|
||||
.setCreationRegistrarId(REGISTRAR_ID)
|
||||
.setPersistedCurrentSponsorRegistrarId(REGISTRAR_ID)
|
||||
.build();
|
||||
}
|
||||
|
||||
static DomainBase createFullDomain(
|
||||
ContactResource contact, HostResource host, FakeClock fakeClock) {
|
||||
return createSimpleDomain(contact)
|
||||
.asBuilder()
|
||||
.setDomainName(DOMAIN_NAME)
|
||||
.setRepoId(DOMAIN_REPO_ID)
|
||||
.setCreationRegistrarId(REGISTRAR_ID)
|
||||
.setLastEppUpdateTime(fakeClock.nowUtc())
|
||||
.setLastEppUpdateRegistrarId(REGISTRAR_ID)
|
||||
.setLastTransferTime(fakeClock.nowUtc())
|
||||
.setNameservers(host.createVKey())
|
||||
.setStatusValues(
|
||||
ImmutableSet.of(
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||
StatusValue.SERVER_DELETE_PROHIBITED,
|
||||
StatusValue.SERVER_TRANSFER_PROHIBITED,
|
||||
StatusValue.SERVER_UPDATE_PROHIBITED,
|
||||
StatusValue.SERVER_RENEW_PROHIBITED,
|
||||
StatusValue.SERVER_HOLD))
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(DesignatedContact.Type.ADMIN, contact.createVKey())))
|
||||
.setSubordinateHosts(ImmutableSet.of("ns1.example.com"))
|
||||
.setPersistedCurrentSponsorRegistrarId(REGISTRAR_ID)
|
||||
.setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1))
|
||||
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("password")))
|
||||
.setDsData(ImmutableSet.of(DelegationSignerData.create(1, 2, 3, new byte[] {0, 1, 2})))
|
||||
.setLaunchNotice(LaunchNotice.create("tcnid", "validatorId", START_OF_TIME, START_OF_TIME))
|
||||
.setSmdId("smdid")
|
||||
.addGracePeriod(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.ADD, DOMAIN_REPO_ID, END_OF_TIME, REGISTRAR_ID, null, 100L))
|
||||
.build();
|
||||
}
|
||||
|
||||
static HostResource createHost() {
|
||||
return new HostResource.Builder()
|
||||
.setRepoId("host1")
|
||||
.setHostName("ns1.example.com")
|
||||
.setCreationRegistrarId(REGISTRAR_ID)
|
||||
.setPersistedCurrentSponsorRegistrarId(REGISTRAR_ID)
|
||||
.build();
|
||||
}
|
||||
|
||||
static DomainTransactionRecord createDomainTransactionRecord(FakeClock fakeClock) {
|
||||
return new DomainTransactionRecord.Builder()
|
||||
.setTld(TLD)
|
||||
.setReportingTime(fakeClock.nowUtc())
|
||||
.setReportField(TransactionReportField.NET_ADDS_1_YR)
|
||||
.setReportAmount(1)
|
||||
.build();
|
||||
}
|
||||
|
||||
static DomainHistory createHistoryWithoutContent(DomainBase domain, FakeClock fakeClock) {
|
||||
return new DomainHistory.Builder()
|
||||
.setType(HistoryEntry.Type.DOMAIN_CREATE)
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(fakeClock.nowUtc())
|
||||
.setRegistrarId(REGISTRAR_ID)
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
.setReason("reason")
|
||||
.setRequestedByRegistrar(true)
|
||||
.setDomainRepoId(domain.getRepoId())
|
||||
.setOtherRegistrarId("otherClient")
|
||||
.setPeriod(Period.create(1, Period.Unit.YEARS))
|
||||
.build();
|
||||
}
|
||||
|
||||
static DomainHistory createFullHistory(DomainBase domain, FakeClock fakeClock) {
|
||||
return createHistoryWithoutContent(domain, fakeClock)
|
||||
.asBuilder()
|
||||
.setType(HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE)
|
||||
.setDomain(domain)
|
||||
.setDomainTransactionRecords(ImmutableSet.of(createDomainTransactionRecord(fakeClock)))
|
||||
.build();
|
||||
}
|
||||
|
||||
static <T> T putInDb(T entity) {
|
||||
jpaTm().transact(() -> jpaTm().put(entity));
|
||||
return jpaTm().transact(() -> jpaTm().loadByEntity(entity));
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
@@ -46,6 +48,15 @@ public class CursorTest extends EntityTestCase {
|
||||
fakeClock.setTo(DateTime.parse("2010-10-17TZ"));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
final DateTime time = DateTime.parse("2012-07-12T03:30:00.000Z");
|
||||
tm().transact(() -> tm().put(Cursor.createGlobal(RECURRING_BILLING, time)));
|
||||
Cursor persisted =
|
||||
tm().transact(() -> tm().loadByKey(Cursor.createGlobalVKey(RECURRING_BILLING)));
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_persistScopedCursor() {
|
||||
createTld("tld");
|
||||
|
||||
@@ -48,6 +48,7 @@ import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
/** Unit tests for {@link ContactResource}. */
|
||||
@@ -174,6 +175,14 @@ public class ContactResourceTest extends EntityTestCase {
|
||||
.hasValue(contactResource);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
ContactResource persisted =
|
||||
loadByForeignKey(ContactResource.class, contactResource.getForeignKey(), fakeClock.nowUtc())
|
||||
.get();
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testIndexing() throws Exception {
|
||||
verifyDatastoreIndexing(
|
||||
|
||||
@@ -56,6 +56,7 @@ import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import java.util.Arrays;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -354,6 +355,14 @@ public class DomainBaseSqlTest {
|
||||
});
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
createTld("com");
|
||||
insertInDb(contact, contact2, domain, host);
|
||||
DomainBase persisted = jpaTm().transact(() -> jpaTm().loadByEntity(domain));
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testUpdates() {
|
||||
createTld("com");
|
||||
|
||||
@@ -921,6 +921,19 @@ public class DomainBaseTest extends EntityTestCase {
|
||||
.containsExactly(EppResourceIndex.create(Key.create(domain)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBeforeSqlSaveOnReplay_canonicalName() {
|
||||
domain.fullyQualifiedDomainName = "EXAMPLE.COM";
|
||||
assertThat(domain.getDomainName()).isEqualTo("EXAMPLE.COM");
|
||||
domain.beforeSqlSaveOnReplay();
|
||||
assertThat(domain.getDomainName()).isEqualTo("example.com");
|
||||
|
||||
domain.fullyQualifiedDomainName = "kittyçat.com";
|
||||
assertThat(domain.getDomainName()).isEqualTo("kittyçat.com");
|
||||
domain.beforeSqlSaveOnReplay();
|
||||
assertThat(domain.getDomainName()).isEqualTo("xn--kittyat-yxa.com");
|
||||
}
|
||||
|
||||
static class BillEventInfo extends ImmutableObject {
|
||||
VKey<BillingEvent.Recurring> billingEventRecurring;
|
||||
Long billingEventRecurringHistoryId;
|
||||
|
||||
@@ -41,6 +41,8 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
@@ -93,6 +95,44 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||
assertThat(loadByEntity(singleUseToken)).isEqualTo(singleUseToken);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
AllocationToken unlimitedUseToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123Unlimited")
|
||||
.setTokenType(UNLIMITED_USE)
|
||||
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
|
||||
.setAllowedTlds(ImmutableSet.of("dev", "app"))
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar, NewRegistrar"))
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountPremiums(true)
|
||||
.setDiscountYears(3)
|
||||
.setTokenStatusTransitions(
|
||||
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
|
||||
.put(START_OF_TIME, NOT_STARTED)
|
||||
.put(DateTime.now(UTC), TokenStatus.VALID)
|
||||
.put(DateTime.now(UTC).plusWeeks(8), TokenStatus.ENDED)
|
||||
.build())
|
||||
.build());
|
||||
AllocationToken persisted = loadByEntity(unlimitedUseToken);
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
|
||||
DomainBase domain = persistActiveDomain("example.foo");
|
||||
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
|
||||
AllocationToken singleUseToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123Single")
|
||||
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
|
||||
.setDomainName("example.foo")
|
||||
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
|
||||
.setTokenType(SINGLE_USE)
|
||||
.build());
|
||||
persisted = loadByEntity(singleUseToken);
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testIndexing() throws Exception {
|
||||
DomainBase domain = persistActiveDomain("blahdomain.foo");
|
||||
|
||||
@@ -39,6 +39,7 @@ import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
|
||||
/** Tests for {@link ContactHistory}. */
|
||||
@DualDatabaseTest
|
||||
@@ -64,6 +65,18 @@ public class ContactHistoryTest extends EntityTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");
|
||||
insertInDb(contact);
|
||||
ContactResource contactFromDb = loadByEntity(contact);
|
||||
ContactHistory contactHistory = createContactHistory(contactFromDb);
|
||||
insertInDb(contactHistory);
|
||||
ContactHistory fromDatabase =
|
||||
jpaTm().transact(() -> jpaTm().loadByKey(contactHistory.createVKey()));
|
||||
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testLegacyPersistence_nullContactBase() {
|
||||
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");
|
||||
|
||||
@@ -55,6 +55,8 @@ import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -87,6 +89,16 @@ public class DomainHistoryTest extends EntityTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
DomainBase domain = addGracePeriodForSql(createDomainWithContactsAndHosts());
|
||||
DomainHistory domainHistory = createDomainHistory(domain);
|
||||
insertInDb(domainHistory);
|
||||
DomainHistory fromDatabase =
|
||||
jpaTm().transact(() -> jpaTm().loadByKey(domainHistory.createVKey()));
|
||||
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testLegacyPersistence_nullResource() {
|
||||
DomainBase domain = addGracePeriodForSql(createDomainWithContactsAndHosts());
|
||||
@@ -125,8 +137,7 @@ public class DomainHistoryTest extends EntityTestCase {
|
||||
DomainHistory domainHistory = createDomainHistory(domain);
|
||||
tm().transact(() -> tm().insert(domainHistory));
|
||||
|
||||
// retrieving a HistoryEntry or a DomainHistory with the same key should return the same
|
||||
// object
|
||||
// retrieving a HistoryEntry or a DomainHistory with the same key should return the same object
|
||||
// note: due to the @EntitySubclass annotation. all Keys for DomainHistory objects will have
|
||||
// type HistoryEntry
|
||||
VKey<DomainHistory> domainHistoryVKey = domainHistory.createVKey();
|
||||
@@ -221,6 +232,66 @@ public class DomainHistoryTest extends EntityTestCase {
|
||||
jpaTm().loadByEntity(historyWithoutResource).getDomainContent().get()));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testBeforeSqlSave_canonicalNameUncapitalized() throws Exception {
|
||||
Field domainNameField = DomainContent.class.getDeclaredField("fullyQualifiedDomainName");
|
||||
// reflection hacks to get around visibility issues
|
||||
domainNameField.setAccessible(true);
|
||||
DomainBase domain = createDomainWithContactsAndHosts();
|
||||
domainNameField.set(domain, "EXAMPLE.TLD");
|
||||
|
||||
DomainHistory historyWithoutResource =
|
||||
new DomainHistory.Builder()
|
||||
.setType(HistoryEntry.Type.DOMAIN_CREATE)
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(fakeClock.nowUtc())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
.setReason("reason")
|
||||
.setRequestedByRegistrar(true)
|
||||
.setDomainRepoId(domain.getRepoId())
|
||||
.setOtherRegistrarId("otherClient")
|
||||
.setPeriod(Period.create(1, Period.Unit.YEARS))
|
||||
.build();
|
||||
|
||||
DatabaseHelper.putInDb(domain, historyWithoutResource);
|
||||
jpaTm().transact(historyWithoutResource::beforeSqlSaveOnReplay);
|
||||
|
||||
assertThat(historyWithoutResource.getDomainContent().get().getDomainName())
|
||||
.isEqualTo("example.tld");
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testBeforeSqlSave_canonicalNameUtf8() throws Exception {
|
||||
Field domainNameField = DomainContent.class.getDeclaredField("fullyQualifiedDomainName");
|
||||
// reflection hacks to get around visibility issues
|
||||
domainNameField.setAccessible(true);
|
||||
DomainBase domain = createDomainWithContactsAndHosts();
|
||||
domainNameField.set(domain, "kittyçat.tld");
|
||||
|
||||
DomainHistory historyWithoutResource =
|
||||
new DomainHistory.Builder()
|
||||
.setType(HistoryEntry.Type.DOMAIN_CREATE)
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(fakeClock.nowUtc())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
.setReason("reason")
|
||||
.setRequestedByRegistrar(true)
|
||||
.setDomainRepoId(domain.getRepoId())
|
||||
.setOtherRegistrarId("otherClient")
|
||||
.setPeriod(Period.create(1, Period.Unit.YEARS))
|
||||
.build();
|
||||
|
||||
DatabaseHelper.putInDb(domain, historyWithoutResource);
|
||||
jpaTm().transact(historyWithoutResource::beforeSqlSaveOnReplay);
|
||||
|
||||
assertThat(historyWithoutResource.getDomainContent().get().getDomainName())
|
||||
.isEqualTo("xn--kittyat-yxa.tld");
|
||||
}
|
||||
|
||||
static DomainBase createDomainWithContactsAndHosts() {
|
||||
createTld("tld");
|
||||
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
|
||||
|
||||
@@ -33,9 +33,12 @@ import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/** Tests for {@link HostHistory}. */
|
||||
@DualDatabaseTest
|
||||
@@ -61,6 +64,17 @@ public class HostHistoryTest extends EntityTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
|
||||
insertInDb(host);
|
||||
HostResource hostFromDb = loadByEntity(host);
|
||||
HostHistory hostHistory = createHostHistory(hostFromDb);
|
||||
insertInDb(hostHistory);
|
||||
HostHistory fromDatabase = jpaTm().transact(() -> jpaTm().loadByKey(hostHistory.createVKey()));
|
||||
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testLegacyPersistence_nullHostBase() {
|
||||
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
|
||||
@@ -134,6 +148,58 @@ public class HostHistoryTest extends EntityTestCase {
|
||||
.hasFieldsEqualTo(jpaTm().loadByEntity(hostHistory).getHostBase().get()));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testBeforeSqlSave_canonicalNameUncapitalized() throws Exception {
|
||||
Field hostNameField = HostBase.class.getDeclaredField("fullyQualifiedHostName");
|
||||
// reflection hacks to get around visibility issues
|
||||
hostNameField.setAccessible(true);
|
||||
HostResource hostResource = newHostResource("ns1.example.tld");
|
||||
hostNameField.set(hostResource, "NS1.EXAMPLE.TLD");
|
||||
HostHistory hostHistory =
|
||||
new HostHistory.Builder()
|
||||
.setType(HistoryEntry.Type.HOST_CREATE)
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(fakeClock.nowUtc())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
.setReason("reason")
|
||||
.setRequestedByRegistrar(true)
|
||||
.setHostRepoId(hostResource.getRepoId())
|
||||
.build();
|
||||
|
||||
DatabaseHelper.putInDb(hostResource, hostHistory);
|
||||
jpaTm().transact(hostHistory::beforeSqlSaveOnReplay);
|
||||
|
||||
assertThat(hostHistory.getHostBase().get().getHostName()).isEqualTo("ns1.example.tld");
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testBeforeSqlSave_canonicalNameUtf8() throws Exception {
|
||||
Field hostNameField = HostBase.class.getDeclaredField("fullyQualifiedHostName");
|
||||
// reflection hacks to get around visibility issues
|
||||
hostNameField.setAccessible(true);
|
||||
HostResource hostResource = newHostResource("ns1.example.tld");
|
||||
hostNameField.set(hostResource, "ns1.kittyçat.tld");
|
||||
HostHistory hostHistory =
|
||||
new HostHistory.Builder()
|
||||
.setType(HistoryEntry.Type.HOST_CREATE)
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(fakeClock.nowUtc())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
.setReason("reason")
|
||||
.setRequestedByRegistrar(true)
|
||||
.setHostRepoId(hostResource.getRepoId())
|
||||
.build();
|
||||
|
||||
DatabaseHelper.putInDb(hostResource, hostHistory);
|
||||
jpaTm().transact(hostHistory::beforeSqlSaveOnReplay);
|
||||
|
||||
assertThat(hostHistory.getHostBase().get().getHostName()).isEqualTo("ns1.xn--kittyat-yxa.tld");
|
||||
}
|
||||
|
||||
private void assertHostHistoriesEqual(HostHistory one, HostHistory two) {
|
||||
assertAboutImmutableObjects().that(one).isEqualExceptFields(two, "hostBase");
|
||||
assertAboutImmutableObjects()
|
||||
|
||||
@@ -45,6 +45,8 @@ import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
@@ -111,6 +113,14 @@ class HostResourceTest extends EntityTestCase {
|
||||
.containsExactly(newHost);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
HostResource newHost = host.asBuilder().setRepoId("NEWHOST").build();
|
||||
tm().transact(() -> tm().insert(newHost));
|
||||
HostResource persisted = tm().transact(() -> tm().loadByEntity(newHost));
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testLoadingByForeignKey() {
|
||||
assertThat(loadByForeignKey(HostResource.class, host.getForeignKey(), fakeClock.nowUtc()))
|
||||
@@ -309,4 +319,17 @@ class HostResourceTest extends EntityTestCase {
|
||||
assertThat(ofyTm().loadAllOf(EppResourceIndex.class))
|
||||
.containsExactly(EppResourceIndex.create(Key.create(host)));
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testBeforeSqlSaveOnReplay_canonicalName() {
|
||||
host.fullyQualifiedHostName = "NS1.EXAMPLE.COM";
|
||||
assertThat(host.getHostName()).isEqualTo("NS1.EXAMPLE.COM");
|
||||
host.beforeSqlSaveOnReplay();
|
||||
assertThat(host.getHostName()).isEqualTo("ns1.example.com");
|
||||
|
||||
host.fullyQualifiedHostName = "ns1.kittyçat.com";
|
||||
assertThat(host.getHostName()).isEqualTo("ns1.kittyçat.com");
|
||||
host.beforeSqlSaveOnReplay();
|
||||
assertThat(host.getHostName()).isEqualTo("ns1.xn--kittyat-yxa.com");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
/** Unit tests for {@link PollMessage}. */
|
||||
@@ -118,6 +119,20 @@ public class PollMessageTest extends EntityTestCase {
|
||||
assertThat(tm().transact(() -> tm().loadByEntity(pollMessage))).isEqualTo(pollMessage);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializableOneTime() {
|
||||
PollMessage.OneTime pollMessage =
|
||||
persistResource(
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(fakeClock.nowUtc())
|
||||
.setMsg("Test poll message")
|
||||
.setParent(historyEntry)
|
||||
.build());
|
||||
PollMessage persisted = tm().transact(() -> tm().loadByEntity(pollMessage));
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testPersistenceAutorenew() {
|
||||
PollMessage.Autorenew pollMessage =
|
||||
@@ -133,6 +148,22 @@ public class PollMessageTest extends EntityTestCase {
|
||||
assertThat(tm().transact(() -> tm().loadByEntity(pollMessage))).isEqualTo(pollMessage);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializableAutorenew() {
|
||||
PollMessage.Autorenew pollMessage =
|
||||
persistResource(
|
||||
new PollMessage.Autorenew.Builder()
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(fakeClock.nowUtc())
|
||||
.setMsg("Test poll message")
|
||||
.setParent(historyEntry)
|
||||
.setAutorenewEndTime(fakeClock.nowUtc().plusDays(365))
|
||||
.setTargetId("foobar.foo")
|
||||
.build());
|
||||
PollMessage persisted = tm().transact(() -> tm().loadByEntity(pollMessage));
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testIndexingAutorenew() throws Exception {
|
||||
PollMessage.Autorenew pollMessage =
|
||||
|
||||
@@ -45,7 +45,9 @@ import google.registry.model.tld.Registries;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
@@ -135,6 +137,12 @@ class RegistrarTest extends EntityTestCase {
|
||||
assertThat(tm().transact(() -> tm().loadByKey(registrar.createVKey()))).isEqualTo(registrar);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testSerializable() {
|
||||
Registrar persisted = tm().transact(() -> tm().loadByKey(registrar.createVKey()));
|
||||
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testIndexing() throws Exception {
|
||||
verifyDatastoreIndexing(registrar, "registrarName", "ianaIdentifier");
|
||||
|
||||
+38
-14
@@ -42,6 +42,7 @@ import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.InjectExtension;
|
||||
import google.registry.testing.ReplayExtension;
|
||||
import google.registry.testing.TestObject;
|
||||
import google.registry.util.RequestStatusChecker;
|
||||
import java.util.List;
|
||||
@@ -49,7 +50,8 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junitpioneer.jupiter.RetryingTest;
|
||||
@@ -73,8 +75,7 @@ public class ReplicateToDatastoreActionTest {
|
||||
private ReplicateToDatastoreAction action;
|
||||
private FakeResponse response;
|
||||
|
||||
// TODO(b/197534789): fix these tests and re-add the @BeforeEach
|
||||
// @BeforeEach
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
resetAction();
|
||||
injectExtension.setStaticField(Ofy.class, "clock", fakeClock);
|
||||
@@ -88,16 +89,18 @@ public class ReplicateToDatastoreActionTest {
|
||||
TestObject.beforeDatastoreSaveCallCount = 0;
|
||||
}
|
||||
|
||||
// TODO(b/197534789): fix these tests and re-add the @AfterEach
|
||||
// @AfterEach
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||
fakeClock.disableAutoIncrement();
|
||||
}
|
||||
|
||||
@RetryingTest(4)
|
||||
@Disabled("b/197534789")
|
||||
void testReplication() {
|
||||
if (!ReplayExtension.replayTestsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TestObject foo = TestObject.create("foo");
|
||||
TestObject bar = TestObject.create("bar");
|
||||
TestObject baz = TestObject.create("baz");
|
||||
@@ -122,8 +125,11 @@ public class ReplicateToDatastoreActionTest {
|
||||
}
|
||||
|
||||
@RetryingTest(4)
|
||||
@Disabled("b/197534789")
|
||||
void testReplayFromLastTxn() {
|
||||
if (!ReplayExtension.replayTestsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TestObject foo = TestObject.create("foo");
|
||||
TestObject bar = TestObject.create("bar");
|
||||
|
||||
@@ -145,8 +151,11 @@ public class ReplicateToDatastoreActionTest {
|
||||
}
|
||||
|
||||
@RetryingTest(4)
|
||||
@Disabled("b/197534789")
|
||||
void testUnintentionalConcurrency() {
|
||||
if (!ReplayExtension.replayTestsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TestObject foo = TestObject.create("foo");
|
||||
TestObject bar = TestObject.create("bar");
|
||||
|
||||
@@ -181,8 +190,11 @@ public class ReplicateToDatastoreActionTest {
|
||||
}
|
||||
|
||||
@RetryingTest(4)
|
||||
@Disabled("b/197534789")
|
||||
void testMissingTransactions() {
|
||||
if (!ReplayExtension.replayTestsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write a transaction (should have a transaction id of 1).
|
||||
TestObject foo = TestObject.create("foo");
|
||||
insertInDb(foo);
|
||||
@@ -199,8 +211,11 @@ public class ReplicateToDatastoreActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("b/197534789")
|
||||
void testMissingTransactions_fullTask() {
|
||||
if (!ReplayExtension.replayTestsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write a transaction (should have a transaction id of 1).
|
||||
TestObject foo = TestObject.create("foo");
|
||||
insertInDb(foo);
|
||||
@@ -218,8 +233,11 @@ public class ReplicateToDatastoreActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("b/197534789")
|
||||
void testBeforeDatastoreSaveCallback() {
|
||||
if (!ReplayExtension.replayTestsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TestObject testObject = TestObject.create("foo");
|
||||
insertInDb(testObject);
|
||||
action.run();
|
||||
@@ -228,8 +246,11 @@ public class ReplicateToDatastoreActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("b/197534789")
|
||||
void testNotInMigrationState_doesNothing() {
|
||||
if (!ReplayExtension.replayTestsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set a schedule that backtracks the current status to DATASTORE_PRIMARY
|
||||
DateTime now = fakeClock.nowUtc();
|
||||
jpaTm()
|
||||
@@ -265,12 +286,15 @@ public class ReplicateToDatastoreActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("b/197534789")
|
||||
void testFailure_cannotAcquireLock() {
|
||||
if (!ReplayExtension.replayTestsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RequestStatusChecker requestStatusChecker = mock(RequestStatusChecker.class);
|
||||
when(requestStatusChecker.getLogId()).thenReturn("logId");
|
||||
Truth8.assertThat(
|
||||
Lock.acquire(
|
||||
Lock.acquireSql(
|
||||
ReplicateToDatastoreAction.class.getSimpleName(),
|
||||
null,
|
||||
Duration.standardHours(1),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user