1
0
mirror of https://github.com/google/nomulus synced 2026-07-07 00:27:12 +00:00

Compare commits

...

14 Commits

Author SHA1 Message Date
Michael Muller cecc1a6cc7 Update terraform files and instructions (#1402)
* Update terraform files and instructions

Update proxy terraform files based on current best practices and allow
exclusion of forwarding rules for HTTP endpoints.  Specifically:
-   Add a "public_web_whois" input to allow disabling the public HTTP
    whois forwarding.
-   Add "description" fields to all variables.
-   Move outputs of the top-level module into "outputs.tf".
-   Auto-reformat using hclfmt.
2021-10-29 09:10:23 -04:00
Rachel Guan 77bc072aac Add domain pa notification response to first delete domain poll message (#1400)
* Add domain pa notification response to first delete domain poll message

* Add test case for poll message

* Change time in response data to now
2021-10-28 15:45:50 -04:00
Weimin Yu 93a479837f Make entities serializable for DB validation (#1401)
* Make entities serializable for DB validation

Make entities that are asynchronously replicated between Datastore and
Cloud SQL serializable so that they may be used in BEAM pipeline based
comparison tool.

Introduced an UnsafeSerializable interface (extending Serializable) and
added to relevant classes. Implementing classes are allowed some
shortcuts as explained in the interface's Javadoc. Post migration we
will decide whether to revert this change or properly implement
serialization.

Verified with production data.
2021-10-28 12:19:09 -04:00
gbrodman 1e7aae26a3 Create a mechanism for storing / using locks explicitly only in SQL (#1392)
This is used for the replay locks so that Beam pipelines (which will be
used for database comparison) can acquire / release locks as necessary
to avoid database contention. If we're comparing contents of Datastore
and SQL databases, we shouldn't have replay actively running during the
comparison, so the pipeline will grab the locks.

Beam doesn't always play nicely with loading from / saving to Datastore,
so we need to make sure that we store the replay locks in SQL at all
times, even when Datastore is the primary DB.
2021-10-27 16:20:35 -04:00
Michael Muller 201b6e8e0b Re-enable replay tests for most environments (#1399)
* Re-enable replay tests for most environments

This enables the replay tests except in environments where
the NOMULUS_DISABLE_REPLAY_TESTS environment variable is set to "true".

* Add a check for null
2021-10-25 12:11:02 -04:00
Rachel Guan 43074ea32f Send expiring notification emails to admins if no tech emails are on file (#1387)
* Send emails to admin if tech emails are not present

* Improve test cases and comments
2021-10-21 12:59:31 -04:00
Weimin Yu 1a4a31569e Alt entity model for fast JPA bulk query (#1398)
* Alt entity model for fast JPA bulk query

Defined an alternative JPA entity model that allows fast bulk loading of
multi-level entities, DomainBase and DomainHistory. The idea is to bulk
the base table as well as the child tables separately, and assemble them
into the target entity in memory in a pipeline.

For DomainBase:

- Defined a DomainBaseLite class that models the "Domain" table only.

- Defined a DomainHost class that models the "DomainHost" table
  (nsHosts field).

- Exposed ID fields in GracePeriod so that they can be mapped to domains
  after being loaded into memory.

For DomainHistory:

- Defined a DomainHistoryLite class that models the "DomainHistory"
  table only.

- Defined a DomainHistoryHost class that models its namesake table.

- Exposed ID fields in GracePeriodHistory and DomainDsDataHistory
  classes so that they can be mapped to DomainHistory after being
  loaded into memory.

In PersistenceModule, provisioned a JpaTransactionManager that uses
the alternative entity model.

Also added a pipeline option that specifies which JpaTransactionManager
to use in a pipeline.
2021-10-20 16:48:56 -04:00
gbrodman c7f50dae92 Use READ_COMMITTED serialization level in CreateSyntheticHEA (#1395)
I observed an instance in which a couple queries from this action were,
for whatever reason, hanging around as idle for >30 minutes. Assuming
the behavior that we saw before where "an open idle serializable
transaction means all pg read-locks stick around forever" still holds,
that's the reason why the amount of read-locks in use spirals out of
control.

I'm not sure why those queries aren't timing out, but that's a separate
issue.
2021-10-19 11:36:15 -04:00
Michael Muller 7344c424d1 Fix problems with the format tasks (#1390)
* Fix problems with the format tasks

The format check is using python2, and if "python" doesn't exist on the path
(or isn't python 2, or there is any other error in the python code or in the
shell script...) the format check just succeeds.

This change:
- Refactors out the gradle code that finds a python3 executable and use it
  to get the python executable to be used for the format check.
- Upgrades google-java-format-diff.py to python3 and removes #! line.
- Fixes shell script to ensure that failures are propagated.
- Suppresses error output when checking for python commands.

Tested:
- verified that python errors cause the build to fail
- verified that introducing a bad format diff causes check to fail
- verified that javaIncrementalFormatDryRun shows the diffs that would be
  introduced.
- verified that javaIncrementalFormatApply reformats a file.
- verified that well formatted code passes the format check.
- verified that an invalid or missing PYTHON env var causes
  google-java-format-git-diff.sh to fail with the appropriate error.

* Fix presubmit issues

Omit the format presubmit when not in a git repo and remove unused "string"
import.
2021-10-18 08:10:09 -04:00
gbrodman 969fa2b68c Fix weird flake (#1394) 2021-10-15 18:00:46 -04:00
gbrodman 9a569198fb Ignore class visibility in EntityTest (#1389) 2021-10-15 17:08:51 -04:00
gbrodman 8a53edd57b Use multiple transactions in IcannReportingUploadAction (#1386)
Relevant error log message: https://pantheon.corp.google.com/logs/viewer?project=domain-registry&minLogLevel=0&expandAll=false&timestamp=2021-10-11T15:28:01.047783000Z&customFacets=&limitCustomFacetWidth=true&dateRangeEnd=2021-10-11T20:51:40.591Z&interval=PT1H&resource=gae_app&logName=projects%2Fdomain-registry%2Flogs%2Fappengine.googleapis.com%252Frequest_log&scrollTimestamp=2021-10-11T15:10:23.174336000Z&filters=text:icannReportingUpload&dateRangeUnbound=backwardInTime&advancedFilter=resource.type%3D%22gae_app%22%0AlogName%3D%22projects%2Fdomain-registry%2Flogs%2Fappengine.googleapis.com%252Frequest_log%22%0A%22icannReportingUpload%22%0Aoperation.id%3D%22616453df00ff02a873d26cedb40001737e646f6d61696e2d726567697374727900016261636b656e643a6e6f6d756c75732d76303233000100%22

note the "invalid handle" bit

From https://cloud.google.com/datastore/docs/concepts/transactions:
"Transactions expire after 270 seconds or if idle for 60 seconds."

From b/202309933: "There is a 60 second timeout on Datastore operations
after which they will automatically rollback and the handles become
invalid."

From the logs we can see that the action is lasting significantly longer
than 270 seconds -- roughly 480 seconds in the linked log (more or
less). My running theory is that ICANN is, for some reason, now being
significantly more slow to respond than they used to be. Some uploads in
the log linked above are taking upwards of 10 seconds, especially when
they have to retry. Because we have >=45 TLDs, it's not surprising that
the action is taking >400 seconds to run.

The fix here is to perform each per-TLD operation in its own
transaction. The only reason why we need the transactions is for the
cursors anyway, and we can just grab and store those at the beginning of
the transaction.
2021-10-15 15:38:37 -04:00
Lai Jiang d25d4073f5 Add a beam pipeline to create synthetic history entries in SQL (#1383)
* Add a beam pipeline to create synthetic history entries in SQL

The logic is mostly lifted from CreateSyntheticHistoryEntriesAction. We
do not need to test for the existence of an embedded EPP resource in the
history entry before create a synthetic one because after
InitSqlPipeline runs it is guaranteed that no embedded resource exists.
2021-10-15 14:51:01 -04:00
Ben McIlwain 6ffe84e93d Add a scrap command to hard-delete a host resource (#1391) 2021-10-15 12:28:18 -04:00
107 changed files with 2645 additions and 469 deletions
+39 -26
View File
@@ -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)
+5 -1
View File
@@ -705,7 +705,11 @@ createToolTask(
createToolTask(
'jpaDemoPipeline', 'google.registry.beam.common.JpaDemoPipeline')
'jpaDemoPipeline', 'google.registry.beam.common.JpaDemoPipeline')
createToolTask(
'createSyntheticHistoryEntries',
'google.registry.tools.javascrap.CreateSyntheticHistoryEntriesPipeline')
project.tasks.create('initSqlPipeline', JavaExec) {
main = 'google.registry.beam.initsql.InitSqlPipeline'
@@ -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.";
@@ -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);
@@ -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();
@@ -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
@@ -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();
}
@@ -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
@@ -189,6 +189,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())
@@ -216,6 +216,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;
@@ -314,6 +318,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 +399,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 +426,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,8 +22,10 @@ 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;
import google.registry.persistence.BillingVKey.BillingEventVKey;
import google.registry.persistence.BillingVKey.BillingRecurrenceVKey;
import google.registry.persistence.VKey;
@@ -202,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 {
public static class GracePeriodHistory extends GracePeriodBase implements SqlOnlyEntity {
@Id Long gracePeriodHistoryRevisionId;
/** ID for the associated {@link DomainHistory} entity. */
@@ -214,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,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.host.HostHistory.HostHistoryId;
import google.registry.model.replay.DatastoreEntity;
import google.registry.model.replay.SqlEntity;
@@ -60,7 +61,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
@@ -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;
@@ -15,7 +15,7 @@
package google.registry.model.tmch;
import google.registry.model.ImmutableObject;
import google.registry.model.replay.NonReplicatedEntity;
import google.registry.model.replay.SqlOnlyEntity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -30,7 +30,7 @@ import javax.persistence.Id;
* work.
*/
@Entity(name = "ClaimsEntry")
class ClaimsEntry extends ImmutableObject implements NonReplicatedEntity, Serializable {
class ClaimsEntry extends ImmutableObject implements SqlOnlyEntity, Serializable {
@Id private Long revisionId;
@Id private String domainLabel;
@@ -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
@@ -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);
@@ -14,7 +14,6 @@
package google.registry.reporting.icann;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
@@ -46,7 +45,6 @@ import google.registry.util.SendEmailService;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -78,6 +76,7 @@ public final class IcannReportingUploadAction implements Runnable {
static final String PATH = "/_dr/task/icannReportingUpload";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String LOCK_NAME = "IcannReportingUploadAction";
@Inject
@Config("reportingBucket")
@@ -98,48 +97,33 @@ public final class IcannReportingUploadAction implements Runnable {
@Override
public void run() {
Runnable transactional =
() -> {
ImmutableMap.Builder<String, Boolean> reportSummaryBuilder = new ImmutableMap.Builder<>();
ImmutableMap<Cursor, String> cursors = loadCursors();
// If cursor time is before now, upload the corresponding report
cursors.entrySet().stream()
.filter(entry -> entry.getKey().getCursorTime().isBefore(clock.nowUtc()))
.forEach(
entry -> {
DateTime cursorTime = entry.getKey().getCursorTime();
uploadReport(
cursorTime,
entry.getKey().getType(),
entry.getValue(),
reportSummaryBuilder);
});
// Send email of which reports were uploaded
emailUploadResults(reportSummaryBuilder.build());
response.setStatus(SC_OK);
response.setContentType(PLAIN_TEXT_UTF_8);
};
Callable<Void> lockRunner =
() -> {
tm().transact(transactional);
return null;
};
String lockname = "IcannReportingUploadAction";
if (!lockHandler.executeWithLocks(lockRunner, null, Duration.standardHours(2), lockname)) {
throw new ServiceUnavailableException("Lock for IcannReportingUploadAction already in use");
if (!lockHandler.executeWithLocks(
this::runWithLock, null, Duration.standardHours(2), LOCK_NAME)) {
throw new ServiceUnavailableException(String.format("Lock for %s already in use", LOCK_NAME));
}
}
private Void runWithLock() {
ImmutableMap.Builder<String, Boolean> reportSummaryBuilder = new ImmutableMap.Builder<>();
ImmutableMap<Cursor, String> cursors = tm().transact(this::loadCursors);
// If cursor time is before now, upload the corresponding report
cursors.entrySet().stream()
.filter(entry -> entry.getKey().getCursorTime().isBefore(clock.nowUtc()))
.forEach(entry -> uploadReport(entry.getKey(), entry.getValue(), reportSummaryBuilder));
// Send email of which reports were uploaded
emailUploadResults(reportSummaryBuilder.build());
response.setStatus(SC_OK);
response.setContentType(PLAIN_TEXT_UTF_8);
return null;
}
/** Uploads the report and rolls forward the cursor for that report. */
private void uploadReport(
DateTime cursorTime,
CursorType cursorType,
String tldStr,
ImmutableMap.Builder<String, Boolean> reportSummaryBuilder) {
Cursor cursor, String tldStr, ImmutableMap.Builder<String, Boolean> reportSummaryBuilder) {
DateTime cursorTime = cursor.getCursorTime();
CursorType cursorType = cursor.getType();
DateTime cursorTimeMinusMonth = cursorTime.withDayOfMonth(1).minusMonths(1);
String reportSubdir =
String.format(
@@ -150,17 +134,16 @@ public final class IcannReportingUploadAction implements Runnable {
BlobId.of(reportingBucket, String.format("%s/%s", reportSubdir, filename));
logger.atInfo().log("Reading ICANN report %s from bucket '%s'.", filename, reportingBucket);
// Check that the report exists
try {
verifyFileExists(gcsFilename);
} catch (IllegalArgumentException e) {
if (!gcsUtils.existsAndNotEmpty(gcsFilename)) {
String logMessage =
String.format(
"Could not upload %s report for %s because file %s did not exist.",
cursorType, tldStr, filename);
"Could not upload %s report for %s because file %s (object %s in bucket %s) did not"
+ " exist.",
cursorType, tldStr, filename, gcsFilename.getName(), gcsFilename.getBucket());
if (clock.nowUtc().dayOfMonth().get() == 1) {
logger.atInfo().withCause(e).log(logMessage + " This report may not have been staged yet.");
logger.atInfo().log(logMessage + " This report may not have been staged yet.");
} else {
logger.atSevere().withCause(e).log(logMessage);
logger.atSevere().log(logMessage);
}
reportSummaryBuilder.put(filename, false);
return;
@@ -179,7 +162,6 @@ public final class IcannReportingUploadAction implements Runnable {
} catch (RuntimeException e) {
logger.atWarning().withCause(e).log("Upload to %s failed.", gcsFilename);
}
reportSummaryBuilder.put(filename, success);
// Set cursor to first day of next month if the upload succeeded
if (success) {
@@ -188,8 +170,24 @@ public final class IcannReportingUploadAction implements Runnable {
cursorType,
cursorTime.withTimeAtStartOfDay().withDayOfMonth(1).plusMonths(1),
Registry.get(tldStr));
tm().put(newCursor);
// In order to keep the transactions short-lived, we load all of the cursors in a single
// transaction then later use per-cursor transactions when checking + saving the cursors. We
// run behind a lock so the cursors shouldn't be changed, but double check to be sure.
success =
tm().transact(
() -> {
Cursor fromDb = tm().transact(() -> tm().loadByEntity(cursor));
if (!cursor.equals(fromDb)) {
logger.atSevere().log(
"Expected previously-loaded cursor %s to equal current cursor %s",
cursor, fromDb);
return false;
}
tm().put(newCursor);
return true;
});
}
reportSummaryBuilder.put(filename, success);
}
private String getFileName(CursorType cursorType, DateTime cursorTime, String tld) {
@@ -303,13 +301,4 @@ public final class IcannReportingUploadAction implements Runnable {
return ByteStreams.toByteArray(gcsInput);
}
}
private void verifyFileExists(BlobId gcsFilename) {
checkArgument(
gcsUtils.existsAndNotEmpty(gcsFilename),
"Object %s in bucket %s not found",
gcsFilename.getName(),
gcsFilename.getBucket());
}
}
@@ -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;
@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableMap;
import google.registry.tools.javascrap.BackfillRegistryLocksCommand;
import google.registry.tools.javascrap.BackfillSpec11ThreatMatchesCommand;
import google.registry.tools.javascrap.DeleteContactByRoidCommand;
import google.registry.tools.javascrap.HardDeleteHostCommand;
import google.registry.tools.javascrap.PopulateNullRegistrarFieldsCommand;
import google.registry.tools.javascrap.RemoveIpAddressCommand;
import google.registry.tools.javascrap.ResaveAllTldsCommand;
@@ -86,6 +87,7 @@ public final class RegistryTool {
.put("get_sql_credential", GetSqlCredentialCommand.class)
.put("get_tld", GetTldCommand.class)
.put("ghostryde", GhostrydeCommand.class)
.put("hard_delete_host", HardDeleteHostCommand.class)
.put("hash_certificate", HashCertificateCommand.class)
.put("import_datastore", ImportDatastoreCommand.class)
.put("list_cursors", ListCursorsCommand.class)
@@ -43,6 +43,7 @@ import google.registry.request.Modules.UserServiceModule;
import google.registry.tools.AuthModule.LocalCredentialModule;
import google.registry.tools.javascrap.BackfillRegistryLocksCommand;
import google.registry.tools.javascrap.DeleteContactByRoidCommand;
import google.registry.tools.javascrap.HardDeleteHostCommand;
import google.registry.util.UtilsModule;
import google.registry.whois.NonCachingWhoisModule;
import javax.annotation.Nullable;
@@ -124,6 +125,8 @@ interface RegistryToolComponent {
void inject(GhostrydeCommand command);
void inject(HardDeleteHostCommand command);
void inject(ImportDatastoreCommand command);
void inject(ListCursorsCommand command);
@@ -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());
@@ -0,0 +1,135 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools.javascrap;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.collect.ImmutableList;
import dagger.Component;
import google.registry.beam.common.RegistryJpaIO;
import google.registry.beam.common.RegistryPipelineOptions;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.model.EppResource;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.UpdateAutoTimestamp.DisableAutoUpdateResource;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import java.io.Serializable;
import javax.inject.Singleton;
import javax.persistence.Entity;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.MapElements;
import org.apache.beam.sdk.values.TypeDescriptor;
/**
* Pipeline that creates a synthetic history entry for every {@link EppResource} in SQL at the
* current time.
*
* <p>The history entries in Datastore does not have the EPP resource embedded in them. Therefore
* after {@link google.registry.beam.initsql.InitSqlPipeline} runs, these fields will all be empty.
* This pipeline loads all EPP resources and for each of them creates a synthetic history entry that
* contains the resource and saves them back to SQL, so that they can be used in the RDE pipeline.
*
* <p>Note that this pipeline should only be run in a test environment right after the init SQL
* pipeline finishes, and no EPP update is being made to the system, otherwise there is no garuantee
* that the latest history entry for a given EPP resource does not already have the resource
* embedded within it.
*
* <p>To run the pipeline:
*
* <p><code>
* $ ./nom_build :core:cSHE --args="--region=us-central1
* --runner=DataflowRunner
* --registryEnvironment=CRASH
* --project={project-id}
* --workerMachineType=n2-standard-4"
* </code>
*
* @see google.registry.tools.javascrap.CreateSyntheticHistoryEntriesAction
*/
public class CreateSyntheticHistoryEntriesPipeline implements Serializable {
private static final ImmutableList<Class<? extends EppResource>> EPP_RESOURCE_CLASSES =
ImmutableList.of(DomainBase.class, ContactResource.class, HostResource.class);
private static final String HISTORY_REASON =
"Backfill EppResource history objects after initial backup to SQL";
static void setup(Pipeline pipeline, String registryAdminRegistrarId) {
for (Class<? extends EppResource> clazz : EPP_RESOURCE_CLASSES) {
pipeline
.apply(
String.format("Read all %s", clazz.getSimpleName()),
RegistryJpaIO.read(
"SELECT id FROM %entity%"
.replace("%entity%", clazz.getAnnotation(Entity.class).name()),
String.class,
repoId -> VKey.createSql(clazz, repoId)))
.apply(
String.format("Save a synthetic HistoryEntry for each %s", clazz),
MapElements.into(TypeDescriptor.of(Void.class))
.via(
(VKey<? extends EppResource> key) -> {
jpaTm()
.transact(
() -> {
EppResource eppResource = jpaTm().loadByKey(key);
try (DisableAutoUpdateResource disable =
UpdateAutoTimestamp.disableAutoUpdate()) {
jpaTm()
.put(
HistoryEntry.createBuilderForResource(eppResource)
.setRegistrarId(registryAdminRegistrarId)
.setBySuperuser(true)
.setRequestedByRegistrar(false)
.setModificationTime(jpaTm().getTransactionTime())
.setReason(HISTORY_REASON)
.setType(HistoryEntry.Type.SYNTHETIC)
.build());
}
});
return null;
}));
}
}
public static void main(String[] args) {
RegistryPipelineOptions options =
PipelineOptionsFactory.fromArgs(args).withValidation().as(RegistryPipelineOptions.class);
RegistryPipelineOptions.validateRegistryPipelineOptions(options);
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_READ_COMMITTED);
String registryAdminRegistrarId =
DaggerCreateSyntheticHistoryEntriesPipeline_ConfigComponent.create()
.getRegistryAdminRegistrarId();
Pipeline pipeline = Pipeline.create(options);
setup(pipeline, registryAdminRegistrarId);
pipeline.run();
}
@Singleton
@Component(modules = ConfigModule.class)
interface ConfigComponent {
@Config("registryAdminClientId")
String getRegistryAdminRegistrarId();
}
}
@@ -0,0 +1,99 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools.javascrap;
import static com.google.common.base.Verify.verify;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.Key;
import google.registry.model.host.HostResource;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.tools.CommandWithRemoteApi;
import google.registry.tools.ConfirmingCommand;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Deletes a {@link HostResource} by its ROID.
*
* <p>This deletes the host itself, everything in the same entity group including all {@link
* google.registry.model.reporting.HistoryEntry}s and {@link
* google.registry.model.poll.PollMessage}s, the {@link EppResourceIndex}, and the {@link
* ForeignKeyIndex} (if it exists).
*
* <p>DO NOT use this to hard-delete a host that is still in use on a domain. Bad things will
* happen.
*/
@Parameters(separators = " =", commandDescription = "Delete a host by its ROID.")
public class HardDeleteHostCommand extends ConfirmingCommand implements CommandWithRemoteApi {
@Parameter(names = "--roid", description = "The ROID of the host to be deleted.")
String roid;
@Parameter(names = "--hostname", description = "The hostname, for verification.")
String hostname;
private ImmutableList<Key<Object>> toDelete;
@Override
protected void init() {
ofyTm()
.transact(
() -> {
Key<HostResource> targetKey = Key.create(HostResource.class, roid);
HostResource host = auditedOfy().load().key(targetKey).now();
verify(Objects.equals(host.getHostName(), hostname), "Hostname does not match");
List<Key<Object>> objectsInEntityGroup =
auditedOfy().load().ancestor(host).keys().list();
Optional<ForeignKeyIndex<HostResource>> fki =
Optional.ofNullable(
auditedOfy().load().key(ForeignKeyIndex.createKey(host)).now());
if (!fki.isPresent()) {
System.out.println(
"No ForeignKeyIndex exists, likely because resource is soft-deleted."
+ " Continuing.");
}
EppResourceIndex eppResourceIndex =
auditedOfy().load().entity(EppResourceIndex.create(targetKey)).now();
verify(eppResourceIndex.getKey().equals(targetKey), "Wrong EppResource Index loaded");
ImmutableList.Builder<Key<Object>> toDeleteBuilder =
new ImmutableList.Builder<Key<Object>>()
.addAll(objectsInEntityGroup)
.add(Key.create(eppResourceIndex));
fki.ifPresent(f -> toDeleteBuilder.add(Key.create(f)));
toDelete = toDeleteBuilder.build();
System.out.printf("\n\nAbout to delete %d entities with keys:\n", toDelete.size());
toDelete.forEach(System.out::println);
});
}
@Override
protected String execute() {
tm().transact(() -> auditedOfy().delete().keys(toDelete).now());
return "Done.";
}
}
@@ -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),
@@ -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()));
}
}
@@ -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);
}
}
@@ -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");
@@ -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,7 @@ 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.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@@ -87,6 +88,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());
@@ -36,6 +36,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 HostHistory}. */
@DualDatabaseTest
@@ -61,6 +62,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");
@@ -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()))
@@ -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");
@@ -51,8 +51,7 @@ public class EntityTest {
@Test
void testSqlEntityPersistence() {
try (ScanResult scanResult =
new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry").scan()) {
try (ScanResult scanResult = scanForClasses()) {
// All javax.persistence entities must implement SqlEntity and vice versa
ImmutableSet<String> javaxPersistenceClasses =
getAllClassesWithAnnotation(scanResult, javax.persistence.Entity.class.getName());
@@ -75,8 +74,7 @@ public class EntityTest {
// For replication, we need to be able to convert from Key -> VKey for the relevant classes.
// This means that the relevant classes must have non-composite Objectify keys or must have a
// createVKey method
try (ScanResult scanResult =
new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry").scan()) {
try (ScanResult scanResult = scanForClasses()) {
ImmutableSet<Class<?>> datastoreEntityClasses =
getClasses(scanResult.getClassesImplementing(DatastoreEntity.class.getName()));
// some classes aren't converted so they aren't relevant
@@ -126,9 +124,12 @@ public class EntityTest {
return classInfoList.stream()
.filter(ClassInfo::isStandardClass)
.map(ClassInfo::loadClass)
.filter(clazz -> !clazz.isAnnotationPresent(EntityForTesting.class))
.filter(clazz -> !clazz.isAnnotationPresent(Embed.class))
.filter(clazz -> !NON_CONVERTED_CLASSES.contains(clazz))
.filter(
clazz ->
!clazz.isAnnotationPresent(EntityForTesting.class)
&& !clazz.isAnnotationPresent(Embed.class)
&& !NON_CONVERTED_CLASSES.contains(clazz)
&& !clazz.getName().contains("Test"))
.collect(toImmutableSet());
}
@@ -136,6 +137,14 @@ public class EntityTest {
return getClasses(classInfoList).stream().map(Class::getName).collect(toImmutableSet());
}
private ScanResult scanForClasses() {
return new ClassGraph()
.enableAnnotationInfo()
.ignoreClassVisibility()
.acceptPackages("google.registry")
.scan();
}
/** Entities that are solely used for testing, to avoid scanning them in {@link EntityTest}. */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@@ -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),
@@ -20,6 +20,7 @@ import static google.registry.model.server.Lock.LockState.FREE;
import static google.registry.model.server.Lock.LockState.IN_USE;
import static google.registry.model.server.Lock.LockState.OWNER_DIED;
import static google.registry.model.server.Lock.LockState.TIMED_OUT;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -28,8 +29,10 @@ import static org.mockito.Mockito.when;
import google.registry.model.EntityTestCase;
import google.registry.model.server.Lock.LockState;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.util.RequestStatusChecker;
import java.util.Optional;
import org.joda.time.Duration;
@@ -132,6 +135,19 @@ public class LockTest extends EntityTestCase {
assertThat(acquire("b", ONE_DAY, IN_USE)).isEmpty();
}
@TestOfyOnly
void testSqlLock_inOfyMode() {
Lock.lockMetrics = origLockMetrics;
Optional<Lock> lock = Lock.acquireSql(RESOURCE_NAME, null, ONE_DAY, requestStatusChecker, true);
assertThat(lock).isPresent();
assertThat(DatabaseHelper.loadAllOf(Lock.class)).isEmpty();
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(Lock.class))).containsExactly(lock.get());
lock.get().releaseSql();
assertThat(DatabaseHelper.loadAllOf(Lock.class)).isEmpty();
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(Lock.class))).isEmpty();
}
@TestOfyAndSql
void testFailure_emptyResourceName() {
IllegalArgumentException thrown =
@@ -30,7 +30,8 @@ import org.junit.jupiter.api.extension.RegisterExtension;
public class SignedMarkRevocationListTest {
@RegisterExtension
public final AppEngineExtension appEngine = AppEngineExtension.builder().withCloudSql().build();
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
private final FakeClock clock = new FakeClock(DateTime.parse("2013-01-01T00:00:00Z"));
@@ -47,6 +47,8 @@ import google.registry.testing.DatabaseHelper;
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 java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.Money;
@@ -87,6 +89,16 @@ public final class RegistryTest extends EntityTestCase {
.isEqualTo(Registry.get("tld"));
}
@TestSqlOnly
void testSerializable() {
ReservedList rl15 = persistReservedList("tld-reserved15", "potato,FULLY_BLOCKED");
Registry registry = Registry.get("tld").asBuilder().setReservedLists(rl15).build();
tm().transact(() -> tm().put(registry));
Registry persisted =
tm().transact(() -> tm().loadByKey(Registry.createVKey(registry.tldStrId)));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyAndSql
void testFailure_registryNotFound() {
createTld("foo");
@@ -45,7 +45,6 @@ import java.sql.Driver;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -172,24 +171,39 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft
exporter.export(getTestEntities(), tempSqlFile);
executeSql(new String(Files.readAllBytes(tempSqlFile.toPath()), StandardCharsets.UTF_8));
}
assertReasonableNumDbConnections();
emf = createEntityManagerFactory(getJpaProperties());
emfEntityHash = entityHash;
}
ImmutableMap<String, String> properties = PersistenceModule.provideDefaultDatabaseConfigs();
/**
* Returns the full set of properties for setting up JPA {@link EntityManagerFactory} to the test
* database. This allows creation of customized JPA by individual tests.
*
* <p>Test that create {@code EntityManagerFactory} instances are reponsible for tearing them
* down.
*/
public ImmutableMap<String, String> getJpaProperties() {
Map<String, String> mergedProperties =
Maps.newHashMap(PersistenceModule.provideDefaultDatabaseConfigs());
if (!userProperties.isEmpty()) {
// If there are user properties, create a new properties object with these added.
Map<String, String> mergedProperties = Maps.newHashMap();
mergedProperties.putAll(properties);
mergedProperties.putAll(userProperties);
properties = ImmutableMap.copyOf(mergedProperties);
}
mergedProperties.put(Environment.URL, getJdbcUrl());
mergedProperties.put(Environment.USER, database.getUsername());
mergedProperties.put(Environment.PASS, database.getPassword());
// Tell Postgresql JDBC driver to retry on errors caused by out-of-band schema change between
// tests while the connection pool stays open (e.g., "cached plan must not change result type").
// We don't set this property in production since it has performance impact, and production
// schema is always compatible with the binary (enforced by our release process).
mergedProperties.put("hibernate.hikari.dataSource.autosave", "conservative");
// Forbid Hibernate push to stay consistent with flyway-based schema management.
checkState(
Objects.equals(properties.get(Environment.HBM2DDL_AUTO), "none"),
Objects.equals(mergedProperties.get(Environment.HBM2DDL_AUTO), "none"),
"The HBM2DDL_AUTO property must be 'none'.");
assertReasonableNumDbConnections();
emf =
createEntityManagerFactory(
getJdbcUrl(), database.getUsername(), database.getPassword(), properties);
emfEntityHash = entityHash;
return ImmutableMap.copyOf(mergedProperties);
}
@Override
@@ -307,15 +321,7 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft
}
/** Constructs the {@link EntityManagerFactory} instance. */
private EntityManagerFactory createEntityManagerFactory(
String jdbcUrl, String username, String password, ImmutableMap<String, String> configs) {
HashMap<String, String> properties = Maps.newHashMap(configs);
properties.put(Environment.URL, jdbcUrl);
properties.put(Environment.USER, username);
properties.put(Environment.PASS, password);
// Tell Postgresql JDBC driver to expect out-of-band schema change.
properties.put("hibernate.hikari.dataSource.autosave", "conservative");
private EntityManagerFactory createEntityManagerFactory(ImmutableMap<String, String> properties) {
ParsedPersistenceXmlDescriptor descriptor =
PersistenceXmlUtility.getParsedPersistenceXmlDescriptor();
@@ -294,8 +294,9 @@ class IcannReportingUploadActionTest {
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.SEVERE,
"Could not upload ICANN_UPLOAD_ACTIVITY report for tld because file"
+ " tld-activity-200512.csv did not exist");
"Could not upload ICANN_UPLOAD_ACTIVITY report for tld because file "
+ "tld-activity-200512.csv (object icann/monthly/2005-12/tld-activity-200512.csv in"
+ " bucket basin) did not exist.");
}
@TestOfyAndSql
@@ -310,9 +311,9 @@ class IcannReportingUploadActionTest {
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Could not upload ICANN_UPLOAD_ACTIVITY report for foo because file"
+ " foo-activity-200607.csv did not exist. This report may not have been staged"
+ " yet.");
"Could not upload ICANN_UPLOAD_ACTIVITY report for foo because file "
+ "foo-activity-200607.csv (object icann/monthly/2006-07/foo-activity-200607.csv in"
+ " bucket basin) did not exist. This report may not have been staged yet.");
}
@TestOfyAndSql
@@ -40,7 +40,9 @@ final class LockHandlerImplTest {
private final FakeClock clock = new FakeClock(DateTime.parse("2001-08-29T12:20:00Z"));
@RegisterExtension final AppEngineExtension appEngine = AppEngineExtension.builder().build();
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
private static class CountingCallable implements Callable<Void> {
int numCalled = 0;
@@ -69,18 +71,13 @@ final class LockHandlerImplTest {
}
private boolean executeWithLocks(Callable<Void> callable, final @Nullable Lock acquiredLock) {
LockHandlerImpl lockHandler = new LockHandlerImpl(new RequestStatusCheckerImpl(), clock) {
private static final long serialVersionUID = 0L;
@Override
Optional<Lock> acquire(String resourceName, String tld, Duration leaseLength) {
assertThat(resourceName).isEqualTo("resourceName");
assertThat(tld).isEqualTo("tld");
assertThat(leaseLength).isEqualTo(ONE_DAY);
return Optional.ofNullable(acquiredLock);
}
};
return createTestLockHandler(acquiredLock)
.executeWithLocks(callable, "tld", ONE_DAY, "resourceName");
}
return lockHandler.executeWithLocks(callable, "tld", ONE_DAY, "resourceName");
private boolean executeWithSqlLocks(Callable<Void> callable, final @Nullable Lock acquiredLock) {
return createTestLockHandler(acquiredLock)
.executeWithSqlLocks(callable, "tld", ONE_DAY, "resourceName");
}
@Test
@@ -92,6 +89,15 @@ final class LockHandlerImplTest {
verify(lock, times(1)).release();
}
@Test
void testSqlLockSucceeds() {
Lock lock = mock(Lock.class);
CountingCallable countingCallable = new CountingCallable();
assertThat(executeWithSqlLocks(countingCallable, lock)).isTrue();
assertThat(countingCallable.numCalled).isEqualTo(1);
verify(lock, times(1)).release();
}
@Test
void testLockSucceeds_uncheckedException() {
Lock lock = mock(Lock.class);
@@ -140,4 +146,23 @@ final class LockHandlerImplTest {
assertThat(executeWithLocks(countingCallable, lock)).isFalse();
assertThat(countingCallable.numCalled).isEqualTo(0);
}
private LockHandler createTestLockHandler(@Nullable Lock acquiredLock) {
return new LockHandlerImpl(new RequestStatusCheckerImpl(), clock) {
private static final long serialVersionUID = 0L;
@Override
Optional<Lock> acquire(String resourceName, String tld, Duration leaseLength) {
assertThat(resourceName).isEqualTo("resourceName");
assertThat(tld).isEqualTo("tld");
assertThat(leaseLength).isEqualTo(ONE_DAY);
return Optional.ofNullable(acquiredLock);
}
@Override
Optional<Lock> acquireSql(String resourceName, String tld, Duration leaseLength) {
return acquire(resourceName, tld, leaseLength);
}
};
}
}
@@ -16,6 +16,7 @@ package google.registry.schema.registrar;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registrar.RegistrarContact.Type.WHOIS;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.SqlHelper.saveRegistrar;
@@ -27,6 +28,7 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.TmOverrideExtension;
import google.registry.util.SerializeUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@@ -74,4 +76,11 @@ class RegistrarContactTest {
insertInDb(testRegistrarPoc);
assertThat(loadByEntity(testRegistrarPoc)).isEqualTo(testRegistrarPoc);
}
@Test
void testSerializable_succeeds() {
insertInDb(testRegistrarPoc);
RegistrarContact persisted = jpaTm().transact(() -> jpaTm().loadByEntity(testRegistrarPoc));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
}
@@ -146,6 +146,10 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
private ImmutableList<Class<?>> ofyTestEntities;
private ImmutableList<Class<?>> jpaTestEntities;
public Optional<JpaIntegrationTestExtension> getJpaIntegrationTestExtension() {
return Optional.ofNullable(jpaIntegrationTestExtension);
}
/** Builder for {@link AppEngineExtension}. */
public static class Builder {
@@ -26,11 +26,11 @@ public class FakeLockHandler implements LockHandler {
private static final long serialVersionUID = 6437880915118738492L;
boolean lockSucceeds;
private final boolean lockSucceeds;
/**
* @param lockSucceeds if true - the lock acquisition will succeed and the callable will be
* called. If false, lock acquisition will fail and the caller isn't called.
* called. If false, lock acquisition will fail and the caller isn't called.
*/
public FakeLockHandler(boolean lockSucceeds) {
this.lockSucceeds = lockSucceeds;
@@ -38,10 +38,17 @@ public class FakeLockHandler implements LockHandler {
@Override
public boolean executeWithLocks(
final Callable<Void> callable,
@Nullable String tld,
Duration leaseLength,
String... lockNames) {
Callable<Void> callable, @Nullable String tld, Duration leaseLength, String... lockNames) {
return execute(callable);
}
@Override
public boolean executeWithSqlLocks(
Callable<Void> callable, @Nullable String tld, Duration leaseLength, String... lockNames) {
return execute(callable);
}
private boolean execute(Callable<Void> callable) {
if (!lockSucceeds) {
return false;
}
@@ -20,6 +20,7 @@ import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableO
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import com.google.common.base.Ascii;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -38,6 +39,7 @@ import google.registry.persistence.transaction.Transaction.Delete;
import google.registry.persistence.transaction.Transaction.Mutation;
import google.registry.persistence.transaction.Transaction.Update;
import google.registry.persistence.transaction.TransactionEntity;
import google.registry.util.RequestStatusChecker;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
@@ -45,6 +47,7 @@ import javax.annotation.Nullable;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.mockito.Mockito;
/**
* A JUnit extension that replays datastore transactions against postgresql.
@@ -77,17 +80,32 @@ public class ReplayExtension implements BeforeEachCallback, AfterEachCallback {
return new ReplayExtension(clock, true, null);
}
// This allows us to disable the replay tests from an environment variable in specific
// environments (namely kokoro) where we see flakiness of unknown origin.
//
// TODO(b/197534789): Remove this once we get to the bottom of test flakiness
public static boolean replayTestsEnabled() {
String disableReplayTests = System.getenv("NOMULUS_DISABLE_REPLAY_TESTS");
if (disableReplayTests == null) {
return true;
}
return !Ascii.toLowerCase(disableReplayTests).equals("true");
}
/**
* Create a replay extension that replays from SQL to cloud datastore when running in SQL mode.
*/
public static ReplayExtension createWithDoubleReplay(FakeClock clock) {
// TODO: use the proper double-replay extension when the tests are not flaky
// return new ReplayExtension(
// clock,
// true,
// new ReplicateToDatastoreAction(
// clock, Mockito.mock(RequestStatusChecker.class), new FakeResponse()));
return createWithCompare(clock);
if (replayTestsEnabled()) {
return new ReplayExtension(
clock,
true,
new ReplicateToDatastoreAction(
clock, Mockito.mock(RequestStatusChecker.class), new FakeResponse()));
} else {
return createWithCompare(clock);
}
}
@Override
@@ -0,0 +1,115 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools.javascrap;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.removeTmOverrideForTest;
import static google.registry.persistence.transaction.TransactionManagerFactory.setTmOverrideForTest;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.newDomainBase;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import com.google.common.collect.ImmutableList;
import google.registry.beam.TestPipelineExtension;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.host.HostHistory;
import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
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 CreateSyntheticHistoryEntriesPipeline}. */
public class CreateSyntheticHistoryEntriesPipelineTest {
FakeClock clock = new FakeClock();
@RegisterExtension
JpaIntegrationTestExtension jpaEextension =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@RegisterExtension
DatastoreEntityExtension datastoreEntityExtension =
new DatastoreEntityExtension().allThreads(true);
@RegisterExtension TestPipelineExtension pipeline = TestPipelineExtension.create();
DomainBase domain;
ContactResource contact;
HostResource host;
@BeforeEach
void beforeEach() {
setTmOverrideForTest(jpaTm());
persistNewRegistrar("TheRegistrar");
persistNewRegistrar("NewRegistrar");
createTld("tld");
host = persistActiveHost("external.com");
domain =
persistSimpleResource(
newDomainBase("example.tld").asBuilder().setNameservers(host.createVKey()).build());
contact = jpaTm().transact(() -> jpaTm().loadByKey(domain.getRegistrant()));
clock.advanceOneMilli();
}
@AfterEach
void afterEach() {
removeTmOverrideForTest();
}
@Test
void testSuccess() {
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(DomainHistory.class))).isEmpty();
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(ContactHistory.class))).isEmpty();
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(HostHistory.class))).isEmpty();
CreateSyntheticHistoryEntriesPipeline.setup(pipeline, "NewRegistrar");
pipeline.run().waitUntilFinish();
validateHistoryEntry(DomainHistory.class, domain);
validateHistoryEntry(ContactHistory.class, contact);
validateHistoryEntry(HostHistory.class, host);
}
private static <T extends EppResource> void validateHistoryEntry(
Class<? extends HistoryEntry> historyClazz, T resource) {
ImmutableList<? extends HistoryEntry> historyEntries =
jpaTm().transact(() -> jpaTm().loadAllOf(historyClazz));
assertThat(historyEntries.size()).isEqualTo(1);
HistoryEntry historyEntry = historyEntries.get(0);
assertThat(historyEntry.getType()).isEqualTo(HistoryEntry.Type.SYNTHETIC);
assertThat(historyEntry.getRegistrarId()).isEqualTo("NewRegistrar");
EppResource embeddedResource;
if (historyEntry instanceof DomainHistory) {
embeddedResource = ((DomainHistory) historyEntry).getDomainContent().get();
} else if (historyEntry instanceof ContactHistory) {
embeddedResource = ((ContactHistory) historyEntry).getContactBase().get();
} else {
embeddedResource = ((HostHistory) historyEntry).getHostBase().get();
}
assertAboutImmutableObjects().that(embeddedResource).hasFieldsEqualTo(resource);
}
}
@@ -0,0 +1,26 @@
<epp xmlns:domain="urn:ietf:params:xml:ns:domain-1.0" xmlns:contact="urn:ietf:params:xml:ns:contact-1.0" xmlns:fee="urn:ietf:params:xml:ns:fee-0.6" xmlns="urn:ietf:params:xml:ns:epp-1.0" xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0" xmlns:fee11="urn:ietf:params:xml:ns:fee-0.11" xmlns:fee12="urn:ietf:params:xml:ns:fee-0.12" xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1" xmlns:host="urn:ietf:params:xml:ns:host-1.0">
<response>
<result code="1301">
<msg>Command completed successfully; ack to dequeue</msg>
</result>
<msgQ count="1" id="1-3-EXAMPLE-5-6-2011">
<qDate>2011-01-02T01:01:01Z</qDate>
<msg>Domain test.example was deleted by registry administrator with final deletion effective: 2011-01-02T00:56:01.009Z</msg>
</msgQ>
<resData>
<domain:panData>
<domain:name paResult="1">test.example</domain:name>
<domain:paTRID>
<clTRID>ABC-12345</clTRID>
<svTRID>other-trid</svTRID>
</domain:paTRID>
<domain:paDate>2011-01-02T01:01:01Z</domain:paDate>
</domain:panData>
</resData>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -412,12 +412,12 @@
create table "DomainTransactionRecord" (
id bigserial not null,
domain_repo_id text,
history_revision_id int8,
report_amount int4 not null,
report_field text not null,
reporting_time timestamptz not null,
tld text not null,
domain_repo_id text,
history_revision_id int8,
primary key (id)
);
+3 -2
View File
@@ -105,13 +105,14 @@ mentioned above.
Navigate to `proxy/terraform`, create a folder called
`envs`, and inside it, create a folder for the environment that proxy is
deployed to ("alpha" for example). Copy `example_config.tf` to the environment
folder.
deployed to ("alpha" for example). Copy `example_config.tf` and `outputs.tf`
to the environment folder.
```bash
$ cd proxy/terraform
$ mkdir -p envs/alpha
$ cp example_config.tf envs/alpha/config.tf
$ cp outputs.tf envs/alpha
```
Now go to the environment folder, edit the `config.tf` file and replace
+3 -5
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2.7
#
#===- google-java-format-diff.py - google-java-format Diff Reformatter -----===#
#
@@ -24,7 +23,6 @@ For perforce users:
import argparse
import difflib
import re
import string
import subprocess
import io
import sys
@@ -99,7 +97,7 @@ def main():
base_command = [binary]
# Reformat files containing changes in place.
for filename, lines in lines_by_file.iteritems():
for filename, lines in lines_by_file.items():
if args.i and args.verbose:
print('Formatting ' + filename)
command = base_command[:]
@@ -120,11 +118,11 @@ def main():
if not args.i:
with open(filename) as f:
code = f.readlines()
formatted_code = io.BytesIO(stdout).readlines()
formatted_code = io.StringIO(stdout.decode()).readlines()
diff = difflib.unified_diff(code, formatted_code,
filename, filename,
'(before formatting)', '(after formatting)')
diff_string = string.join(diff, '')
diff_string = ''.join(diff)
if len(diff_string) > 0:
sys.stdout.write(diff_string)
+23 -5
View File
@@ -42,6 +42,16 @@ where:
SCRIPT_DIR="$(realpath $(dirname $0))"
JAR_NAME="google-java-format-1.8-all-deps.jar"
# Make sure we have a valid python interpreter.
if [ -z "$PYTHON" ]; then
echo "You must specify the name of a python3 interpreter in the PYTHON" \
"environment variable."
exit 1
elif ! "$PYTHON" -c ''; then
echo "Invalid python interpreter: $PYTHON"
exit 1
fi
# Locate the java binary.
if [ -n "$JAVA_HOME" ]; then
JAVA_BIN="$JAVA_HOME/bin/java"
@@ -69,10 +79,14 @@ function runGoogleJavaFormatAgainstDiffs() {
shift
git diff -U0 "$forkPoint" | \
${SCRIPT_DIR}/google-java-format-diff.py \
--java-binary "$JAVA_BIN" \
--google-java-format-jar "${SCRIPT_DIR}/${JAR_NAME}" \
-p1 "$@" | tee gjf.out
"${PYTHON}" "${SCRIPT_DIR}/google-java-format-diff.py" \
--java-binary "$JAVA_BIN" \
--google-java-format-jar "${SCRIPT_DIR}/${JAR_NAME}" \
-p1 "$@" | \
tee gjf.out
# If any of the commands in the last pipe failed, return false.
[[ ! "${PIPESTATUS[@]}" =~ [^0\ ] ]]
}
# Show the file names in a diff preceeded by a message.
@@ -96,7 +110,11 @@ function callGoogleJavaFormatDiff() {
local callResult
case "$1" in
"check")
local output=$(runGoogleJavaFormatAgainstDiffs "$forkPoint")
# We need to do explicit checks for an error and "exit 1" if there was
# one here (though not elsewhere), "set -e" doesn't catch this case,
# it's not clear why.
local output
output=$(runGoogleJavaFormatAgainstDiffs "$forkPoint") || exit 1
echo "$output" | showFileNames "\033[1mNeeds formatting: "
callResult=$(echo -n "$output" | wc -l)
;;

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