mirror of
https://github.com/google/nomulus
synced 2026-08-09 08:46:07 +00:00
Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43074ea32f | ||
|
|
1a4a31569e | ||
|
|
c7f50dae92 | ||
|
|
7344c424d1 | ||
|
|
969fa2b68c | ||
|
|
9a569198fb | ||
|
|
8a53edd57b | ||
|
|
d25d4073f5 | ||
|
|
6ffe84e93d | ||
|
|
a451524010 | ||
|
|
bb8988ee4e | ||
|
|
2aff72b3b6 | ||
|
|
35fd61f771 | ||
|
|
13cb17e9a4 | ||
|
|
4f1c317bbc | ||
|
|
c8aa32ef05 | ||
|
|
95a1bbf66a | ||
|
|
23aa16469e | ||
|
|
0277c5c25a | ||
|
|
b1b0589281 | ||
|
|
28628564cc | ||
|
|
835f93f555 | ||
|
|
276c188e9d | ||
|
|
34ecc6fbe7 | ||
|
|
0f4156c563 | ||
|
|
e1827ab939 | ||
|
|
51b2887709 | ||
|
|
62eb8801c5 | ||
|
|
f6920454f6 | ||
|
|
9103216a46 | ||
|
|
c6705d1956 | ||
|
|
737f65bd33 | ||
|
|
c8caa8f80b | ||
|
|
65ef18052b | ||
|
|
f7938e80f7 | ||
|
|
d8b3a30a20 | ||
|
|
93715c6f9e | ||
|
|
90cf4519c5 | ||
|
|
3a177f36b1 |
+40
-25
@@ -200,28 +200,37 @@ 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) {
|
||||
|
||||
// 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)");
|
||||
}
|
||||
args('config/presubmits.py')
|
||||
|
||||
doFirst {
|
||||
executable getPythonExecutable()
|
||||
}
|
||||
}
|
||||
|
||||
def javadocSource = []
|
||||
@@ -435,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}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,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.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ PRESUBMITS = {
|
||||
{"src/test", "ActivityReportingQueryBuilder.java",
|
||||
# This class contains helper method to make queries in Beam.
|
||||
"RegistryJpaIO.java",
|
||||
"CreateSyntheticHistoryEntriesAction.java",
|
||||
# TODO(b/179158393): Remove everything below, which should be done
|
||||
# using Criteria
|
||||
"JpaTransactionManager.java",
|
||||
|
||||
+14
-6
@@ -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'
|
||||
@@ -783,27 +787,27 @@ createUberJar(
|
||||
// User should install gcloud and login to GCP before invoking this tasks.
|
||||
if (environment == 'alpha') {
|
||||
def pipelines = [
|
||||
InitSql :
|
||||
initSql :
|
||||
[
|
||||
mainClass: 'google.registry.beam.initsql.InitSqlPipeline',
|
||||
metaData : 'google/registry/beam/init_sql_pipeline_metadata.json'
|
||||
],
|
||||
BulkDeleteDatastore:
|
||||
bulkDeleteDatastore:
|
||||
[
|
||||
mainClass: 'google.registry.beam.datastore.BulkDeleteDatastorePipeline',
|
||||
metaData : 'google/registry/beam/bulk_delete_datastore_pipeline_metadata.json'
|
||||
],
|
||||
Spec11 :
|
||||
spec11 :
|
||||
[
|
||||
mainClass: 'google.registry.beam.spec11.Spec11Pipeline',
|
||||
metaData : 'google/registry/beam/spec11_pipeline_metadata.json'
|
||||
],
|
||||
Invoicing :
|
||||
invoicing :
|
||||
[
|
||||
mainClass: 'google.registry.beam.invoicing.InvoicingPipeline',
|
||||
metaData : 'google/registry/beam/invoicing_pipeline_metadata.json'
|
||||
],
|
||||
Rde :
|
||||
rde :
|
||||
[
|
||||
mainClass: 'google.registry.beam.rde.RdePipeline',
|
||||
metaData : 'google/registry/beam/rde_pipeline_metadata.json'
|
||||
@@ -1111,6 +1115,10 @@ test {
|
||||
// TODO(weiminyu): Remove dependency on sqlIntegrationTest
|
||||
}.dependsOn(fragileTest, outcastTest, standardTest, registryToolIntegrationTest, sqlIntegrationTest)
|
||||
|
||||
// When we override tests, we also break the cleanTest command.
|
||||
cleanTest.dependsOn(cleanFragileTest, cleanOutcastTest, cleanStandardTest,
|
||||
cleanRegistryToolIntegrationTest, cleanSqlIntegrationTest)
|
||||
|
||||
project.build.dependsOn devtool
|
||||
project.build.dependsOn buildToolImage
|
||||
project.build.dependsOn ':stage'
|
||||
|
||||
@@ -55,10 +55,9 @@ public final class CommitLogImports {
|
||||
* represents the changes in one transaction. The {@code CommitLogManifest} contains deleted
|
||||
* entity keys, whereas each {@code CommitLogMutation} contains one whole entity.
|
||||
*/
|
||||
public static ImmutableList<ImmutableList<VersionedEntity>> loadEntitiesByTransaction(
|
||||
static ImmutableList<ImmutableList<VersionedEntity>> loadEntitiesByTransaction(
|
||||
InputStream inputStream) {
|
||||
try (AppEngineEnvironment appEngineEnvironment = new AppEngineEnvironment();
|
||||
InputStream input = new BufferedInputStream(inputStream)) {
|
||||
try (InputStream input = new BufferedInputStream(inputStream)) {
|
||||
Iterator<ImmutableObject> commitLogs = createDeserializingIterator(input, false);
|
||||
checkState(commitLogs.hasNext());
|
||||
checkState(commitLogs.next() instanceof CommitLogCheckpoint);
|
||||
@@ -105,7 +104,7 @@ public final class CommitLogImports {
|
||||
* represents the changes in one transaction. The {@code CommitLogManifest} contains deleted
|
||||
* entity keys, whereas each {@code CommitLogMutation} contains one whole entity.
|
||||
*/
|
||||
public static ImmutableList<VersionedEntity> loadEntities(InputStream inputStream) {
|
||||
static ImmutableList<VersionedEntity> loadEntities(InputStream inputStream) {
|
||||
return loadEntitiesByTransaction(inputStream).stream()
|
||||
.flatMap(ImmutableList::stream)
|
||||
.collect(toImmutableList());
|
||||
|
||||
@@ -105,29 +105,29 @@ public abstract class VersionedEntity implements Serializable {
|
||||
* VersionedEntity VersionedEntities}. See {@link CommitLogImports#loadEntities} for more
|
||||
* information.
|
||||
*/
|
||||
public static Stream<VersionedEntity> fromManifest(CommitLogManifest manifest) {
|
||||
static Stream<VersionedEntity> fromManifest(CommitLogManifest manifest) {
|
||||
long commitTimeMillis = manifest.getCommitTime().getMillis();
|
||||
return manifest.getDeletions().stream()
|
||||
.map(com.googlecode.objectify.Key::getRaw)
|
||||
.map(key -> builder().commitTimeMills(commitTimeMillis).key(key).build());
|
||||
.map(key -> newBuilder().commitTimeMills(commitTimeMillis).key(key).build());
|
||||
}
|
||||
|
||||
/* Converts a {@link CommitLogMutation} to a {@link VersionedEntity}. */
|
||||
public static VersionedEntity fromMutation(CommitLogMutation mutation) {
|
||||
static VersionedEntity fromMutation(CommitLogMutation mutation) {
|
||||
return from(
|
||||
com.googlecode.objectify.Key.create(mutation).getParent().getId(),
|
||||
mutation.getEntityProtoBytes());
|
||||
}
|
||||
|
||||
public static VersionedEntity from(long commitTimeMillis, byte[] entityProtoBytes) {
|
||||
return builder()
|
||||
return newBuilder()
|
||||
.entityProtoBytes(entityProtoBytes)
|
||||
.key(EntityTranslator.createFromPbBytes(entityProtoBytes).getKey())
|
||||
.commitTimeMills(commitTimeMillis)
|
||||
.build();
|
||||
}
|
||||
|
||||
static Builder builder() {
|
||||
private static Builder newBuilder() {
|
||||
return new AutoValue_VersionedEntity.Builder();
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public abstract class VersionedEntity implements Serializable {
|
||||
|
||||
public abstract VersionedEntity build();
|
||||
|
||||
public Builder entityProtoBytes(byte[] bytes) {
|
||||
Builder entityProtoBytes(byte[] bytes) {
|
||||
return entityProtoBytes(new ImmutableBytes(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
+39
-28
@@ -55,6 +55,7 @@ import org.joda.time.format.DateTimeFormatter;
|
||||
path = SendExpiringCertificateNotificationEmailAction.PATH,
|
||||
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
|
||||
public class SendExpiringCertificateNotificationEmailAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/sendExpiringCertificateNotificationEmail";
|
||||
/**
|
||||
* Used as an offset when storing the last notification email sent date.
|
||||
@@ -96,8 +97,13 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
public void run() {
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
try {
|
||||
sendNotificationEmails();
|
||||
int numEmailsSent = sendNotificationEmails();
|
||||
String message =
|
||||
String.format(
|
||||
"Done. Sent %d expiring certificate notification emails in total.", numEmailsSent);
|
||||
logger.atInfo().log(message);
|
||||
response.setStatus(SC_OK);
|
||||
response.setPayload(message);
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log(
|
||||
"Exception thrown when sending expiring certificate notification emails.");
|
||||
@@ -107,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() {
|
||||
@@ -151,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;
|
||||
}
|
||||
@@ -174,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
|
||||
@@ -243,32 +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++;
|
||||
}
|
||||
}
|
||||
logger.atInfo().log(
|
||||
"Attempted to send %d expiring certificate notification emails.", emailsSent);
|
||||
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);
|
||||
@@ -327,6 +337,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
|
||||
@AutoValue
|
||||
public abstract static class RegistrarInfo {
|
||||
|
||||
static RegistrarInfo create(
|
||||
Registrar registrar, boolean isCertExpiring, boolean isFailOverCertExpiring) {
|
||||
return new AutoValue_SendExpiringCertificateNotificationEmailAction_RegistrarInfo(
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.batch;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
|
||||
import static org.apache.http.HttpStatus.SC_OK;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An action that wipes out Personal Identifiable Information (PII) fields of {@link ContactHistory}
|
||||
* entities.
|
||||
*
|
||||
* <p>ContactHistory entities should be retained in the database for only certain amount of time.
|
||||
* This periodic wipe out action only applies to SQL.
|
||||
*/
|
||||
@Action(
|
||||
service = Service.BACKEND,
|
||||
path = WipeOutContactHistoryPiiAction.PATH,
|
||||
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
|
||||
public class WipeOutContactHistoryPiiAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/wipeOutContactHistoryPii";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private final Clock clock;
|
||||
private final Response response;
|
||||
private final int minMonthsBeforeWipeOut;
|
||||
private final int wipeOutQueryBatchSize;
|
||||
|
||||
@Inject
|
||||
public WipeOutContactHistoryPiiAction(
|
||||
Clock clock,
|
||||
@Config("minMonthsBeforeWipeOut") int minMonthsBeforeWipeOut,
|
||||
@Config("wipeOutQueryBatchSize") int wipeOutQueryBatchSize,
|
||||
Response response) {
|
||||
this.clock = clock;
|
||||
this.response = response;
|
||||
this.minMonthsBeforeWipeOut = minMonthsBeforeWipeOut;
|
||||
this.wipeOutQueryBatchSize = wipeOutQueryBatchSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
try {
|
||||
int totalNumOfWipedEntities = 0;
|
||||
DateTime wipeOutTime = clock.nowUtc().minusMonths(minMonthsBeforeWipeOut);
|
||||
logger.atInfo().log(
|
||||
"About to wipe out all PII of contact history entities prior to %s.", wipeOutTime);
|
||||
|
||||
int numOfWipedEntities = 0;
|
||||
do {
|
||||
numOfWipedEntities =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
wipeOutContactHistoryData(
|
||||
getNextContactHistoryEntitiesWithPiiBatch(wipeOutTime)));
|
||||
totalNumOfWipedEntities += numOfWipedEntities;
|
||||
} while (numOfWipedEntities > 0);
|
||||
String msg =
|
||||
String.format(
|
||||
"Done. Wiped out PII of %d ContactHistory entities in total.",
|
||||
totalNumOfWipedEntities);
|
||||
logger.atInfo().log(msg);
|
||||
response.setPayload(msg);
|
||||
response.setStatus(SC_OK);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Exception thrown during the process of wiping out contact history PII.");
|
||||
response.setStatus(SC_INTERNAL_SERVER_ERROR);
|
||||
response.setPayload(
|
||||
String.format(
|
||||
"Exception thrown during the process of wiping out contact history PII with cause"
|
||||
+ ": %s",
|
||||
e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of up to {@link #wipeOutQueryBatchSize} {@link ContactHistory} entities
|
||||
* containing PII that are prior to @param wipeOutTime.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
Stream<ContactHistory> getNextContactHistoryEntitiesWithPiiBatch(DateTime wipeOutTime) {
|
||||
// email is one of the required fields in EPP, meaning it's initially not null.
|
||||
// Therefore, checking if it's null is one way to avoid processing contact history entities
|
||||
// that have been processed previously. Refer to RFC 5733 for more information.
|
||||
return jpaTm()
|
||||
.query(
|
||||
"FROM ContactHistory WHERE modificationTime < :wipeOutTime " + "AND email IS NOT NULL",
|
||||
ContactHistory.class)
|
||||
.setParameter("wipeOutTime", wipeOutTime)
|
||||
.setMaxResults(wipeOutQueryBatchSize)
|
||||
.getResultStream();
|
||||
}
|
||||
|
||||
/** Wipes out the PII of each of the {@link ContactHistory} entities in the stream. */
|
||||
@VisibleForTesting
|
||||
int wipeOutContactHistoryData(Stream<ContactHistory> contactHistoryEntities) {
|
||||
AtomicInteger numOfEntities = new AtomicInteger(0);
|
||||
contactHistoryEntities.forEach(
|
||||
contactHistoryEntity -> {
|
||||
jpaTm().update(contactHistoryEntity.asBuilder().wipeOutPii().build());
|
||||
numOfEntities.incrementAndGet();
|
||||
});
|
||||
logger.atInfo().log(
|
||||
"Wiped out all PII fields of %d ContactHistory entities.", numOfEntities.get());
|
||||
return numOfEntities.get();
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,12 @@ public class WipeoutDatastoreAction implements Runnable {
|
||||
.setJobName(createJobName("bulk-delete-datastore-", clock))
|
||||
.setContainerSpecGcsPath(
|
||||
String.format("%s/%s_metadata.json", stagingBucketUrl, PIPELINE_NAME))
|
||||
.setParameters(ImmutableMap.of("kindsToDelete", "*"));
|
||||
.setParameters(
|
||||
ImmutableMap.of(
|
||||
"kindsToDelete",
|
||||
"*",
|
||||
"registryEnvironment",
|
||||
RegistryEnvironment.get().name()));
|
||||
LaunchFlexTemplateResponse launchResponse =
|
||||
dataflow
|
||||
.projects()
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.beam.common;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.backup.AppEngineEnvironment;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
@@ -59,18 +58,16 @@ public class JpaDemoPipeline implements Serializable {
|
||||
public void processElement() {
|
||||
// AppEngineEnvironment is needed as long as JPA entity classes still depends
|
||||
// on Objectify.
|
||||
try (AppEngineEnvironment allowOfyEntity = new AppEngineEnvironment()) {
|
||||
int result =
|
||||
(Integer)
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery("select 1;")
|
||||
.getSingleResult());
|
||||
verify(result == 1, "Expecting 1, got %s.", result);
|
||||
}
|
||||
int result =
|
||||
(Integer)
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery("select 1;")
|
||||
.getSingleResult());
|
||||
verify(result == 1, "Expecting 1, got %s.", result);
|
||||
counter.inc();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -20,11 +20,9 @@ import static org.apache.beam.sdk.values.TypeDescriptors.integers;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.backup.AppEngineEnvironment;
|
||||
import google.registry.beam.common.RegistryQuery.CriteriaQuerySupplier;
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
import google.registry.model.UpdateAutoTimestamp.DisableAutoUpdateResource;
|
||||
import google.registry.model.ofy.ObjectifyService;
|
||||
import google.registry.model.replay.SqlEntity;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||
@@ -211,14 +209,9 @@ public final class RegistryJpaIO {
|
||||
|
||||
@ProcessElement
|
||||
public void processElement(OutputReceiver<T> outputReceiver) {
|
||||
// AppEngineEnvironment is need for handling VKeys, which involve Ofy keys. Unlike
|
||||
// SqlBatchWriter, it is unnecessary to initialize ObjectifyService in this class.
|
||||
try (AppEngineEnvironment env = new AppEngineEnvironment()) {
|
||||
// TODO(b/187210388): JpaTransactionManager should support non-transactional query.
|
||||
jpaTm()
|
||||
.transactNoRetry(
|
||||
() -> query.stream().map(resultMapper::apply).forEach(outputReceiver::output));
|
||||
}
|
||||
jpaTm()
|
||||
.transactNoRetry(
|
||||
() -> query.stream().map(resultMapper::apply).forEach(outputReceiver::output));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,16 +357,6 @@ public final class RegistryJpaIO {
|
||||
this.withAutoTimestamp = withAutoTimestamp;
|
||||
}
|
||||
|
||||
@Setup
|
||||
public void setup() {
|
||||
// AppEngineEnvironment is needed as long as Objectify keys are still involved in the handling
|
||||
// of SQL entities (e.g., in VKeys). ObjectifyService needs to be initialized when conversion
|
||||
// between Ofy entity and Datastore entity is needed.
|
||||
try (AppEngineEnvironment env = new AppEngineEnvironment()) {
|
||||
ObjectifyService.initOfy();
|
||||
}
|
||||
}
|
||||
|
||||
@ProcessElement
|
||||
public void processElement(@Element KV<ShardedKey<Integer>, Iterable<T>> kv) {
|
||||
if (withAutoTimestamp) {
|
||||
@@ -386,19 +369,17 @@ public final class RegistryJpaIO {
|
||||
}
|
||||
|
||||
private void actuallyProcessElement(@Element KV<ShardedKey<Integer>, Iterable<T>> kv) {
|
||||
try (AppEngineEnvironment env = new AppEngineEnvironment()) {
|
||||
ImmutableList<Object> entities =
|
||||
Streams.stream(kv.getValue())
|
||||
.map(this.jpaConverter::apply)
|
||||
// TODO(b/177340730): post migration delete the line below.
|
||||
.filter(Objects::nonNull)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
try {
|
||||
jpaTm().transact(() -> jpaTm().putAll(entities));
|
||||
counter.inc(entities.size());
|
||||
} catch (RuntimeException e) {
|
||||
processSingly(entities);
|
||||
}
|
||||
ImmutableList<Object> entities =
|
||||
Streams.stream(kv.getValue())
|
||||
.map(this.jpaConverter::apply)
|
||||
// TODO(b/177340730): post migration delete the line below.
|
||||
.filter(Objects::nonNull)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
try {
|
||||
jpaTm().transact(() -> jpaTm().putAll(entities));
|
||||
counter.inc(entities.size());
|
||||
} catch (RuntimeException e) {
|
||||
processSingly(entities);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -34,7 +35,6 @@ import org.apache.beam.sdk.options.Description;
|
||||
public interface RegistryPipelineOptions extends GcpOptions {
|
||||
|
||||
@Description("The Registry environment.")
|
||||
@Nullable
|
||||
RegistryEnvironment getRegistryEnvironment();
|
||||
|
||||
void setRegistryEnvironment(RegistryEnvironment environment);
|
||||
@@ -45,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();
|
||||
|
||||
+22
-3
@@ -20,6 +20,8 @@ import com.google.auto.service.AutoService;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import dagger.Lazy;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.config.SystemPropertySetter;
|
||||
import google.registry.model.AppEngineEnvironment;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||
import org.apache.beam.sdk.harness.JvmInitializer;
|
||||
@@ -35,18 +37,35 @@ import org.apache.beam.sdk.options.PipelineOptions;
|
||||
@AutoService(JvmInitializer.class)
|
||||
public class RegistryPipelineWorkerInitializer implements JvmInitializer {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
public static final String PROPERTY = "google.registry.beam";
|
||||
|
||||
@Override
|
||||
public void beforeProcessing(PipelineOptions options) {
|
||||
RegistryPipelineOptions registryOptions = options.as(RegistryPipelineOptions.class);
|
||||
RegistryEnvironment environment = registryOptions.getRegistryEnvironment();
|
||||
if (environment == null || environment.equals(RegistryEnvironment.UNITTEST)) {
|
||||
return;
|
||||
throw new RuntimeException(
|
||||
"A registry environment must be specified in the pipeline options.");
|
||||
}
|
||||
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);
|
||||
// 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
|
||||
// datastore.
|
||||
SystemPropertySetter.PRODUCTION_IMPL.setProperty(PROPERTY, "true");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.datastore.v1.Entity;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
@@ -308,6 +309,11 @@ public class BulkDeleteDatastorePipeline {
|
||||
|
||||
public interface BulkDeletePipelineOptions extends GcpOptions {
|
||||
|
||||
@Description("The Registry environment.")
|
||||
RegistryEnvironment getRegistryEnvironment();
|
||||
|
||||
void setRegistryEnvironment(RegistryEnvironment environment);
|
||||
|
||||
@Description(
|
||||
"The Datastore KINDs to be deleted. The format may be:\n"
|
||||
+ "\t- The list of kinds to be deleted as a comma-separated string, or\n"
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.backup.AppEngineEnvironment;
|
||||
import google.registry.backup.VersionedEntity;
|
||||
import google.registry.beam.common.RegistryJpaIO;
|
||||
import google.registry.beam.initsql.Transforms.RemoveDomainBaseForeignKeys;
|
||||
@@ -230,9 +229,7 @@ public class InitSqlPipeline implements Serializable {
|
||||
}
|
||||
|
||||
private static ImmutableList<String> toKindStrings(Collection<Class<?>> entityClasses) {
|
||||
try (AppEngineEnvironment env = new AppEngineEnvironment()) {
|
||||
return entityClasses.stream().map(Key::getKind).collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
return entityClasses.stream().map(Key::getKind).collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import static google.registry.beam.initsql.BackupPaths.getExportFilePatterns;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.apache.beam.sdk.values.TypeDescriptors.kvs;
|
||||
import static org.apache.beam.sdk.values.TypeDescriptors.strings;
|
||||
@@ -277,7 +278,7 @@ public final class Transforms {
|
||||
|
||||
// Prober contacts referencing phantom registrars. They and their associated history entries can
|
||||
// be safely ignored.
|
||||
private static final ImmutableSet IGNORED_CONTACTS =
|
||||
private static final ImmutableSet<String> IGNORED_CONTACTS =
|
||||
ImmutableSet.of(
|
||||
"1_WJ0TEST-GOOGLE", "1_WJ1TEST-GOOGLE", "1_WJ2TEST-GOOGLE", "1_WJ3TEST-GOOGLE");
|
||||
|
||||
@@ -320,7 +321,8 @@ public final class Transforms {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Entity repairBadData(Entity entity) {
|
||||
@VisibleForTesting
|
||||
static Entity repairBadData(Entity entity) {
|
||||
if (entity.getKind().equals("Cancellation")
|
||||
&& Objects.equals(entity.getProperty("reason"), "AUTO_RENEW")) {
|
||||
// AUTO_RENEW has been moved from 'reason' to flags. Change reason to RENEW and add the
|
||||
@@ -328,6 +330,15 @@ public final class Transforms {
|
||||
// instead of append. See b/185954992.
|
||||
entity.setUnindexedProperty("reason", Reason.RENEW.name());
|
||||
entity.setUnindexedProperty("flags", ImmutableList.of(Flag.AUTO_RENEW.name()));
|
||||
} else if (entity.getKind().equals("DomainBase")) {
|
||||
// Canonicalize old domain/host names from 2016 and earlier before we were enforcing this.
|
||||
entity.setIndexedProperty(
|
||||
"fullyQualifiedDomainName",
|
||||
canonicalizeDomainName((String) entity.getProperty("fullyQualifiedDomainName")));
|
||||
} else if (entity.getKind().equals("HostResource")) {
|
||||
entity.setIndexedProperty(
|
||||
"fullyQualifiedHostName",
|
||||
canonicalizeDomainName((String) entity.getProperty("fullyQualifiedHostName")));
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
@@ -365,7 +376,8 @@ public final class Transforms {
|
||||
* Returns a {@link PTransform} that produces a {@link PCollection} containing all elements in the
|
||||
* given {@link Iterable}.
|
||||
*/
|
||||
static PTransform<PBegin, PCollection<String>> toStringPCollection(Iterable<String> strings) {
|
||||
private static PTransform<PBegin, PCollection<String>> toStringPCollection(
|
||||
Iterable<String> strings) {
|
||||
return Create.of(strings).withCoder(StringUtf8Coder.of());
|
||||
}
|
||||
|
||||
@@ -373,7 +385,7 @@ public final class Transforms {
|
||||
* Returns a {@link PTransform} from file {@link Metadata} to {@link VersionedEntity} using
|
||||
* caller-provided {@code transformer}.
|
||||
*/
|
||||
static PTransform<PCollection<Metadata>, PCollection<VersionedEntity>> processFiles(
|
||||
private static PTransform<PCollection<Metadata>, PCollection<VersionedEntity>> processFiles(
|
||||
DoFn<ReadableFile, VersionedEntity> transformer) {
|
||||
return new PTransform<PCollection<Metadata>, PCollection<VersionedEntity>>() {
|
||||
@Override
|
||||
@@ -389,7 +401,7 @@ public final class Transforms {
|
||||
private final DateTime fromTime;
|
||||
private final DateTime toTime;
|
||||
|
||||
public FilterCommitLogFileByTime(DateTime fromTime, DateTime toTime) {
|
||||
FilterCommitLogFileByTime(DateTime fromTime, DateTime toTime) {
|
||||
checkNotNull(fromTime, "fromTime");
|
||||
checkNotNull(toTime, "toTime");
|
||||
checkArgument(
|
||||
|
||||
@@ -19,10 +19,13 @@ import static com.google.common.base.Verify.verify;
|
||||
import static google.registry.model.common.Cursor.getCursorTimeOrStartOfTime;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||
import static google.registry.rde.RdeModule.BRDA_QUEUE;
|
||||
import static google.registry.rde.RdeModule.RDE_UPLOAD_QUEUE;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.keyring.api.PgpHelper;
|
||||
@@ -31,14 +34,20 @@ import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.rde.RdeNamingUtils;
|
||||
import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.rde.BrdaCopyAction;
|
||||
import google.registry.rde.DepositFragment;
|
||||
import google.registry.rde.Ghostryde;
|
||||
import google.registry.rde.PendingDeposit;
|
||||
import google.registry.rde.RdeCounter;
|
||||
import google.registry.rde.RdeMarshaller;
|
||||
import google.registry.rde.RdeModule;
|
||||
import google.registry.rde.RdeResourceType;
|
||||
import google.registry.rde.RdeUploadAction;
|
||||
import google.registry.rde.RdeUtil;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.tldconfig.idn.IdnTableEnum;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.xjc.rdeheader.XjcRdeHeader;
|
||||
import google.registry.xjc.rdeheader.XjcRdeHeaderElement;
|
||||
import google.registry.xml.ValidationMode;
|
||||
@@ -68,6 +77,8 @@ public class RdeIO {
|
||||
|
||||
abstract GcsUtils gcsUtils();
|
||||
|
||||
abstract CloudTasksUtils cloudTasksUtils();
|
||||
|
||||
abstract String rdeBucket();
|
||||
|
||||
// It's OK to return a primitive array because we are only using it to construct the
|
||||
@@ -83,7 +94,9 @@ public class RdeIO {
|
||||
|
||||
@AutoValue.Builder
|
||||
abstract static class Builder {
|
||||
abstract Builder setGcsUtils(GcsUtils gcsUtils);
|
||||
abstract Builder setGcsUtils(GcsUtils value);
|
||||
|
||||
abstract Builder setCloudTasksUtils(CloudTasksUtils value);
|
||||
|
||||
abstract Builder setRdeBucket(String value);
|
||||
|
||||
@@ -100,7 +113,8 @@ public class RdeIO {
|
||||
.apply(
|
||||
"Write to GCS",
|
||||
ParDo.of(new RdeWriter(gcsUtils(), rdeBucket(), stagingKeyBytes(), validationMode())))
|
||||
.apply("Update cursors", ParDo.of(new CursorUpdater()));
|
||||
.apply("Update cursors", ParDo.of(new CursorUpdater()))
|
||||
.apply("Enqueue upload action", ParDo.of(new UploadEnqueuer(cloudTasksUtils())));
|
||||
return PDone.in(input.getPipeline());
|
||||
}
|
||||
}
|
||||
@@ -236,11 +250,12 @@ public class RdeIO {
|
||||
}
|
||||
}
|
||||
|
||||
private static class CursorUpdater extends DoFn<KV<PendingDeposit, Integer>, Void> {
|
||||
private static class CursorUpdater extends DoFn<KV<PendingDeposit, Integer>, PendingDeposit> {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@ProcessElement
|
||||
public void processElement(@Element KV<PendingDeposit, Integer> input) {
|
||||
public void processElement(
|
||||
@Element KV<PendingDeposit, Integer> input, OutputReceiver<PendingDeposit> outputReceiver) {
|
||||
tm().transact(
|
||||
() -> {
|
||||
PendingDeposit key = input.getKey();
|
||||
@@ -268,6 +283,45 @@ public class RdeIO {
|
||||
"Rolled forward %s on %s cursor to %s.", key.cursor(), key.tld(), newPosition);
|
||||
RdeRevision.saveRevision(key.tld(), key.watermark(), key.mode(), revision);
|
||||
});
|
||||
outputReceiver.output(input.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
private static class UploadEnqueuer extends DoFn<PendingDeposit, Void> {
|
||||
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
private UploadEnqueuer(CloudTasksUtils cloudTasksUtils) {
|
||||
this.cloudTasksUtils = cloudTasksUtils;
|
||||
}
|
||||
|
||||
@ProcessElement
|
||||
public void processElement(@Element PendingDeposit input, PipelineOptions options) {
|
||||
if (input.mode() == RdeMode.FULL) {
|
||||
cloudTasksUtils.enqueue(
|
||||
RDE_UPLOAD_QUEUE,
|
||||
CloudTasksUtils.createPostTask(
|
||||
RdeUploadAction.PATH,
|
||||
Service.BACKEND.getServiceId(),
|
||||
ImmutableMultimap.of(
|
||||
RequestParameters.PARAM_TLD,
|
||||
input.tld(),
|
||||
RdeModule.PARAM_PREFIX,
|
||||
options.getJobName() + '/')));
|
||||
} else {
|
||||
cloudTasksUtils.enqueue(
|
||||
BRDA_QUEUE,
|
||||
CloudTasksUtils.createPostTask(
|
||||
BrdaCopyAction.PATH,
|
||||
Service.BACKEND.getServiceId(),
|
||||
ImmutableMultimap.of(
|
||||
RequestParameters.PARAM_TLD,
|
||||
input.tld(),
|
||||
RdeModule.PARAM_WATERMARK,
|
||||
input.watermark().toString(),
|
||||
RdeModule.PARAM_PREFIX,
|
||||
options.getJobName() + '/')));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.google.common.io.BaseEncoding;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Component;
|
||||
import google.registry.beam.common.RegistryJpaIO;
|
||||
import google.registry.config.CloudTasksUtilsModule;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
@@ -44,6 +45,8 @@ import google.registry.rde.PendingDeposit;
|
||||
import google.registry.rde.PendingDeposit.PendingDepositCoder;
|
||||
import google.registry.rde.RdeFragmenter;
|
||||
import google.registry.rde.RdeMarshaller;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.util.UtilsModule;
|
||||
import google.registry.xml.ValidationMode;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -66,7 +69,6 @@ import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
import org.apache.beam.sdk.transforms.FlatMapElements;
|
||||
import org.apache.beam.sdk.transforms.Flatten;
|
||||
import org.apache.beam.sdk.transforms.GroupByKey;
|
||||
import org.apache.beam.sdk.transforms.Reshuffle;
|
||||
import org.apache.beam.sdk.values.KV;
|
||||
import org.apache.beam.sdk.values.PCollection;
|
||||
import org.apache.beam.sdk.values.PCollectionList;
|
||||
@@ -93,6 +95,7 @@ public class RdePipeline implements Serializable {
|
||||
private final String rdeBucket;
|
||||
private final byte[] stagingKeyBytes;
|
||||
private final GcsUtils gcsUtils;
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
// Registrars to be excluded from data escrow. Not including the sandbox-only OTE type so that
|
||||
// if sneaks into production we would get an extra signal.
|
||||
@@ -111,13 +114,14 @@ public class RdePipeline implements Serializable {
|
||||
}
|
||||
|
||||
@Inject
|
||||
RdePipeline(RdePipelineOptions options, GcsUtils gcsUtils) {
|
||||
RdePipeline(RdePipelineOptions options, GcsUtils gcsUtils, CloudTasksUtils cloudTasksUtils) {
|
||||
this.options = options;
|
||||
this.mode = ValidationMode.valueOf(options.getValidationMode());
|
||||
this.pendings = decodePendings(options.getPendings());
|
||||
this.rdeBucket = options.getGcsBucket();
|
||||
this.rdeBucket = options.getRdeStagingBucket();
|
||||
this.stagingKeyBytes = BaseEncoding.base64Url().decode(options.getStagingKey());
|
||||
this.gcsUtils = gcsUtils;
|
||||
this.cloudTasksUtils = cloudTasksUtils;
|
||||
}
|
||||
|
||||
PipelineResult run() {
|
||||
@@ -140,10 +144,11 @@ public class RdePipeline implements Serializable {
|
||||
|
||||
void persistData(PCollection<KV<PendingDeposit, Iterable<DepositFragment>>> input) {
|
||||
input.apply(
|
||||
"Write to GCS and update cursors",
|
||||
"Write to GCS, update cursors, and enqueue upload tasks",
|
||||
RdeIO.Write.builder()
|
||||
.setRdeBucket(rdeBucket)
|
||||
.setGcsUtils(gcsUtils)
|
||||
.setCloudTasksUtils(cloudTasksUtils)
|
||||
.setValidationMode(mode)
|
||||
.setStagingKeyBytes(stagingKeyBytes)
|
||||
.build());
|
||||
@@ -177,18 +182,13 @@ public class RdePipeline implements Serializable {
|
||||
}));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Reshuffle is still recommended by Dataflow.
|
||||
<T extends EppResource>
|
||||
PCollection<KV<PendingDeposit, DepositFragment>> processNonRegistrarEntities(
|
||||
Pipeline pipeline, Class<T> clazz) {
|
||||
return createInputs(pipeline, clazz)
|
||||
.apply("Marshal " + clazz.getSimpleName() + " into DepositFragment", mapToFragments(clazz))
|
||||
.setCoder(KvCoder.of(PendingDepositCoder.of(), SerializableCoder.of(DepositFragment.class)))
|
||||
.apply(
|
||||
"Reshuffle KV<PendingDeposit, DepositFragment> of "
|
||||
+ clazz.getSimpleName()
|
||||
+ " to prevent fusion",
|
||||
Reshuffle.of());
|
||||
.setCoder(
|
||||
KvCoder.of(PendingDepositCoder.of(), SerializableCoder.of(DepositFragment.class)));
|
||||
}
|
||||
|
||||
<T extends EppResource> PCollection<VKey<T>> createInputs(Pipeline pipeline, Class<T> clazz) {
|
||||
@@ -202,7 +202,7 @@ public class RdePipeline implements Serializable {
|
||||
String.class,
|
||||
// TODO: consider adding coders for entities and pass them directly instead of using
|
||||
// VKeys.
|
||||
x -> VKey.create(clazz, x)));
|
||||
x -> VKey.createSql(clazz, x)));
|
||||
}
|
||||
|
||||
<T extends EppResource>
|
||||
@@ -270,7 +270,7 @@ public class RdePipeline implements Serializable {
|
||||
* Encodes the TLD to pending deposit map in an URL safe string that is sent to the pipeline
|
||||
* worker by the pipeline launcher as a pipeline option.
|
||||
*/
|
||||
static String encodePendings(ImmutableSetMultimap<String, PendingDeposit> pendings)
|
||||
public static String encodePendings(ImmutableSetMultimap<String, PendingDeposit> pendings)
|
||||
throws IOException {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||
@@ -282,13 +282,24 @@ public class RdePipeline implements Serializable {
|
||||
|
||||
public static void main(String[] args) throws IOException, ClassNotFoundException {
|
||||
PipelineOptionsFactory.register(RdePipelineOptions.class);
|
||||
RdePipelineOptions options = PipelineOptionsFactory.fromArgs(args).as(RdePipelineOptions.class);
|
||||
RdePipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(args).withValidation().as(RdePipelineOptions.class);
|
||||
// RegistryPipelineWorkerInitializer only initializes before pipeline executions, after the
|
||||
// main() function constructed the graph. We need the registry environment set up so that we
|
||||
// can create a CloudTasksUtils which uses the environment-dependent config file.
|
||||
options.getRegistryEnvironment().setup();
|
||||
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_READ_COMMITTED);
|
||||
DaggerRdePipeline_RdePipelineComponent.builder().options(options).build().rdePipeline().run();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {CredentialModule.class, ConfigModule.class})
|
||||
@Component(
|
||||
modules = {
|
||||
CredentialModule.class,
|
||||
ConfigModule.class,
|
||||
CloudTasksUtilsModule.class,
|
||||
UtilsModule.class
|
||||
})
|
||||
interface RdePipelineComponent {
|
||||
RdePipeline rdePipeline();
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ public interface RdePipelineOptions extends RegistryPipelineOptions {
|
||||
void setValidationMode(String value);
|
||||
|
||||
@Description("The GCS bucket where the encrypted RDE deposits will be uploaded to.")
|
||||
String getGcsBucket();
|
||||
String getRdeStagingBucket();
|
||||
|
||||
void setGcsBucket(String value);
|
||||
void setRdeStagingBucket(String value);
|
||||
|
||||
@Description("The Base64-encoded PGP public key to encrypt the deposits.")
|
||||
String getStagingKey();
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.beam.spec11;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.beam.BeamUtils.getQueryFromFile;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -30,6 +31,7 @@ import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.reporting.Spec11ThreatMatch;
|
||||
import google.registry.model.reporting.Spec11ThreatMatch.ThreatType;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.SqlTemplate;
|
||||
import google.registry.util.UtilsModule;
|
||||
@@ -41,6 +43,7 @@ import org.apache.beam.sdk.coders.SerializableCoder;
|
||||
import org.apache.beam.sdk.io.TextIO;
|
||||
import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO;
|
||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
import org.apache.beam.sdk.transforms.DoFn;
|
||||
import org.apache.beam.sdk.transforms.GroupByKey;
|
||||
import org.apache.beam.sdk.transforms.MapElements;
|
||||
import org.apache.beam.sdk.transforms.ParDo;
|
||||
@@ -113,15 +116,43 @@ public class Spec11Pipeline implements Serializable {
|
||||
}
|
||||
|
||||
static PCollection<DomainNameInfo> readFromCloudSql(Pipeline pipeline) {
|
||||
Read<Object[], DomainNameInfo> read =
|
||||
Read<Object[], KV<String, String>> read =
|
||||
RegistryJpaIO.read(
|
||||
"select d, r.emailAddress from Domain d join Registrar r on"
|
||||
+ " d.currentSponsorClientId = r.clientIdentifier where r.type = 'REAL'"
|
||||
+ " and d.deletionTime > now()",
|
||||
"select d.repoId, r.emailAddress from Domain d join Registrar r on"
|
||||
+ " d.currentSponsorClientId = r.clientIdentifier where r.type = 'REAL' and"
|
||||
+ " d.deletionTime > now()",
|
||||
false,
|
||||
Spec11Pipeline::parseRow);
|
||||
|
||||
return pipeline.apply("Read active domains from Cloud SQL", read);
|
||||
return pipeline
|
||||
.apply("Read active domains from Cloud SQL", read)
|
||||
.apply(
|
||||
"Build DomainNameInfo",
|
||||
ParDo.of(
|
||||
new DoFn<KV<String, String>, DomainNameInfo>() {
|
||||
@ProcessElement
|
||||
public void processElement(
|
||||
@Element KV<String, String> input, OutputReceiver<DomainNameInfo> output) {
|
||||
DomainBase domainBase =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.loadByKey(
|
||||
VKey.createSql(DomainBase.class, input.getKey())));
|
||||
String emailAddress = input.getValue();
|
||||
if (emailAddress == null) {
|
||||
emailAddress = "";
|
||||
}
|
||||
DomainNameInfo domainNameInfo =
|
||||
DomainNameInfo.create(
|
||||
domainBase.getDomainName(),
|
||||
domainBase.getRepoId(),
|
||||
domainBase.getCurrentSponsorRegistrarId(),
|
||||
emailAddress);
|
||||
output.output(domainNameInfo);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
static PCollection<DomainNameInfo> readFromBigQuery(
|
||||
@@ -142,17 +173,8 @@ public class Spec11Pipeline implements Serializable {
|
||||
.withTemplateCompatibility());
|
||||
}
|
||||
|
||||
private static DomainNameInfo parseRow(Object[] row) {
|
||||
DomainBase domainBase = (DomainBase) row[0];
|
||||
String emailAddress = (String) row[1];
|
||||
if (emailAddress == null) {
|
||||
emailAddress = "";
|
||||
}
|
||||
return DomainNameInfo.create(
|
||||
domainBase.getDomainName(),
|
||||
domainBase.getRepoId(),
|
||||
domainBase.getCurrentSponsorRegistrarId(),
|
||||
emailAddress);
|
||||
private static KV<String, String> parseRow(Object[] row) {
|
||||
return KV.of((String) row[0], (String) row[1]);
|
||||
}
|
||||
|
||||
static void saveToSql(
|
||||
|
||||
@@ -22,10 +22,13 @@ import dagger.Provides;
|
||||
import google.registry.config.CredentialModule.DefaultCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.util.CloudTasksUtils.GcpCloudTasksClient;
|
||||
import google.registry.util.CloudTasksUtils.SerializableCloudTasksClient;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import google.registry.util.Retrier;
|
||||
import java.io.IOException;
|
||||
import javax.inject.Provider;
|
||||
import java.io.Serializable;
|
||||
import java.util.function.Supplier;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
@@ -42,24 +45,35 @@ public abstract class CloudTasksUtilsModule {
|
||||
public static CloudTasksUtils provideCloudTasksUtils(
|
||||
@Config("projectId") String projectId,
|
||||
@Config("locationId") String locationId,
|
||||
// Use a provider so that we can use try-with-resources with the client, which implements
|
||||
// Autocloseable.
|
||||
Provider<CloudTasksClient> clientProvider,
|
||||
SerializableCloudTasksClient client,
|
||||
Retrier retrier) {
|
||||
return new CloudTasksUtils(retrier, projectId, locationId, clientProvider);
|
||||
return new CloudTasksUtils(retrier, projectId, locationId, client);
|
||||
}
|
||||
|
||||
// Provides a supplier instead of using a Dagger @Provider because the latter is not serializable.
|
||||
@Provides
|
||||
public static Supplier<CloudTasksClient> provideCloudTasksClientSupplier(
|
||||
@DefaultCredential GoogleCredentialsBundle credentials) {
|
||||
return (Supplier<CloudTasksClient> & Serializable)
|
||||
() -> {
|
||||
CloudTasksClient client;
|
||||
try {
|
||||
client =
|
||||
CloudTasksClient.create(
|
||||
CloudTasksSettings.newBuilder()
|
||||
.setCredentialsProvider(
|
||||
FixedCredentialsProvider.create(credentials.getGoogleCredentials()))
|
||||
.build());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return client;
|
||||
};
|
||||
}
|
||||
|
||||
@Provides
|
||||
public static CloudTasksClient provideCloudTasksClient(
|
||||
@DefaultCredential GoogleCredentialsBundle credentials) {
|
||||
try {
|
||||
return CloudTasksClient.create(
|
||||
CloudTasksSettings.newBuilder()
|
||||
.setCredentialsProvider(
|
||||
FixedCredentialsProvider.create(credentials.getGoogleCredentials()))
|
||||
.build());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
public static SerializableCloudTasksClient provideSerializableCloudTasksClient(
|
||||
final Supplier<CloudTasksClient> clientSupplier) {
|
||||
return new GcpCloudTasksClient(clientSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1306,6 +1306,18 @@ public final class RegistryConfig {
|
||||
public static ImmutableSet<String> provideAllowedEcdsaCurves(RegistryConfigSettings config) {
|
||||
return ImmutableSet.copyOf(config.sslCertificateValidation.allowedEcdsaCurves);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("minMonthsBeforeWipeOut")
|
||||
public static int provideMinMonthsBeforeWipeOut(RegistryConfigSettings config) {
|
||||
return config.contactHistory.minMonthsBeforeWipeOut;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("wipeOutQueryBatchSize")
|
||||
public static int provideWipeOutQueryBatchSize(RegistryConfigSettings config) {
|
||||
return config.contactHistory.wipeOutQueryBatchSize;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the App Engine project ID, which is based off the environment name. */
|
||||
|
||||
@@ -41,6 +41,7 @@ public class RegistryConfigSettings {
|
||||
public Keyring keyring;
|
||||
public RegistryTool registryTool;
|
||||
public SslCertificateValidation sslCertificateValidation;
|
||||
public ContactHistory contactHistory;
|
||||
|
||||
/** Configuration options that apply to the entire App Engine project. */
|
||||
public static class AppEngine {
|
||||
@@ -234,4 +235,10 @@ public class RegistryConfigSettings {
|
||||
public String expirationWarningEmailBodyText;
|
||||
public String expirationWarningEmailSubjectText;
|
||||
}
|
||||
|
||||
/** Configuration for contact history. */
|
||||
public static class ContactHistory {
|
||||
public int minMonthsBeforeWipeOut;
|
||||
public int wipeOutQueryBatchSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,6 +442,13 @@ registryTool:
|
||||
# OAuth client secret used by the tool.
|
||||
clientSecret: YOUR_CLIENT_SECRET
|
||||
|
||||
# Configuration options for handling contact history.
|
||||
contactHistory:
|
||||
# The number of months that a ContactHistory entity should be stored in the database.
|
||||
minMonthsBeforeWipeOut: 18
|
||||
# The batch size for querying ContactHistory table in the database.
|
||||
wipeOutQueryBatchSize: 500
|
||||
|
||||
# Configuration options for checking SSL certificates.
|
||||
sslCertificateValidation:
|
||||
# A map specifying the maximum amount of days the certificate can be valid.
|
||||
|
||||
@@ -391,6 +391,13 @@
|
||||
<url-pattern>/_dr/task/relockDomain</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- Background action to wipe out PII fields of ContactHistory entities that
|
||||
have been in the database for a certain period of time. -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>backend-servlet</servlet-name>
|
||||
<url-pattern>/_dr/task/wipeOutContactHistoryPii</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- Action to wipeout Cloud SQL data -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>backend-servlet</servlet-name>
|
||||
|
||||
@@ -348,4 +348,14 @@
|
||||
<schedule>every 3 minutes</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>every monday 15:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
</cronentries>
|
||||
|
||||
@@ -246,4 +246,14 @@
|
||||
<schedule>every 3 minutes</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>every monday 15:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
</cronentries>
|
||||
|
||||
@@ -229,6 +229,15 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
|
||||
private DomainHistory buildDomainHistory(
|
||||
DomainBase newDomain, DateTime now, Period period, Duration renewGracePeriod) {
|
||||
Optional<MetadataExtension> metadataExtensionOpt =
|
||||
eppInput.getSingleExtension(MetadataExtension.class);
|
||||
if (metadataExtensionOpt.isPresent()) {
|
||||
MetadataExtension metadataExtension = metadataExtensionOpt.get();
|
||||
if (metadataExtension.getReason() != null) {
|
||||
historyBuilder.setReason(metadataExtension.getReason());
|
||||
}
|
||||
historyBuilder.setRequestedByRegistrar(metadataExtension.getRequestedByRegistrar());
|
||||
}
|
||||
return historyBuilder
|
||||
.setType(DOMAIN_RENEW)
|
||||
.setPeriod(period)
|
||||
|
||||
+27
-13
@@ -12,12 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.backup;
|
||||
package google.registry.model;
|
||||
|
||||
import com.google.apphosting.api.ApiProxy;
|
||||
import com.google.apphosting.api.ApiProxy.Environment;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.Closeable;
|
||||
import google.registry.model.ofy.ObjectifyService;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
@@ -38,26 +39,39 @@ import java.lang.reflect.Proxy;
|
||||
* <p>Note that conversion from Objectify objects to Datastore {@code Entities} still requires the
|
||||
* Datastore service.
|
||||
*/
|
||||
public class AppEngineEnvironment implements Closeable {
|
||||
public class AppEngineEnvironment {
|
||||
|
||||
private boolean isPlaceHolderNeeded;
|
||||
private Environment environment;
|
||||
|
||||
public AppEngineEnvironment() {
|
||||
this("PlaceholderAppId");
|
||||
}
|
||||
|
||||
public AppEngineEnvironment(String appId) {
|
||||
isPlaceHolderNeeded = ApiProxy.getCurrentEnvironment() == null;
|
||||
// isPlaceHolderNeeded may be true when we are invoked in a test with AppEngineExtension.
|
||||
if (isPlaceHolderNeeded) {
|
||||
ApiProxy.setEnvironmentForCurrentThread(createAppEngineEnvironment(appId));
|
||||
}
|
||||
environment = createAppEngineEnvironment(appId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (isPlaceHolderNeeded) {
|
||||
ApiProxy.setEnvironmentForCurrentThread(null);
|
||||
public void setEnvironmentForCurrentThread() {
|
||||
ApiProxy.setEnvironmentForCurrentThread(environment);
|
||||
ObjectifyService.initOfy();
|
||||
}
|
||||
|
||||
public void setEnvironmentForAllThreads() {
|
||||
setEnvironmentForCurrentThread();
|
||||
ApiProxy.setEnvironmentFactory(() -> environment);
|
||||
}
|
||||
|
||||
public void unsetEnvironmentForCurrentThread() {
|
||||
ApiProxy.clearEnvironmentForCurrentThread();
|
||||
}
|
||||
|
||||
public void unsetEnvironmentForAllThreads() {
|
||||
try {
|
||||
Method method = ApiProxy.class.getDeclaredMethod("clearEnvironmentFactory");
|
||||
method.setAccessible(true);
|
||||
method.invoke(null);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.appengine.api.datastore.DatastoreServiceFactory;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import google.registry.beam.common.RegistryPipelineWorkerInitializer;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Allocates a globally unique {@link Long} number to use as an Ofy {@code @Id}.
|
||||
*
|
||||
* <p>In non-test environments the Id is generated by Datastore, whereas in tests it's from an
|
||||
* <p>In non-test, non-beam environments the Id is generated by Datastore, otherwise it's from an
|
||||
* atomic long number that's incremented every time this method is called.
|
||||
*/
|
||||
public final class IdService {
|
||||
@@ -35,13 +36,25 @@ public final class IdService {
|
||||
*/
|
||||
private static final String APP_WIDE_ALLOCATION_KIND = "common";
|
||||
|
||||
/** Counts of used ids for use in unit tests. Outside tests this is never used. */
|
||||
private static final AtomicLong nextTestId = new AtomicLong(1); // ids cannot be zero
|
||||
/**
|
||||
* Counts of used ids for use in unit tests or Beam.
|
||||
*
|
||||
* <p>Note that one should only use self-allocate Ids in Beam for entities whose Ids are not
|
||||
* important and are not persisted back to the database, i. e. nowhere the uniqueness of the ID is
|
||||
* required.
|
||||
*/
|
||||
private static final AtomicLong nextSelfAllocatedId = new AtomicLong(1); // ids cannot be zero
|
||||
|
||||
private static final boolean isSelfAllocated() {
|
||||
return RegistryEnvironment.UNITTEST.equals(RegistryEnvironment.get())
|
||||
|| "true".equals(System.getProperty(RegistryPipelineWorkerInitializer.PROPERTY, "false"));
|
||||
}
|
||||
|
||||
/** Allocates an id. */
|
||||
// TODO(b/201547855): Find a way to allocate a unique ID without datastore.
|
||||
public static long allocateId() {
|
||||
return RegistryEnvironment.UNITTEST.equals(RegistryEnvironment.get())
|
||||
? nextTestId.getAndIncrement()
|
||||
return isSelfAllocated()
|
||||
? nextSelfAllocatedId.getAndIncrement()
|
||||
: DatastoreServiceFactory.getDatastoreService()
|
||||
.allocateIds(APP_WIDE_ALLOCATION_KIND, 1)
|
||||
.iterator()
|
||||
@@ -49,13 +62,11 @@ public final class IdService {
|
||||
.getId();
|
||||
}
|
||||
|
||||
/** Resets the global test id counter (i.e. sets the next id to 1). */
|
||||
/** Resets the global self-allocated id counter (i.e. sets the next id to 1). */
|
||||
@VisibleForTesting
|
||||
public static void resetNextTestId() {
|
||||
public static void resetSelfAllocatedId() {
|
||||
checkState(
|
||||
RegistryEnvironment.UNITTEST.equals(RegistryEnvironment.get()),
|
||||
"Can't call resetTestIdCounts() from RegistryEnvironment.%s",
|
||||
RegistryEnvironment.get());
|
||||
nextTestId.set(1); // ids cannot be zero
|
||||
isSelfAllocated(), "Can only call resetSelfAllocatedId() in unit tests or Beam pipelines");
|
||||
nextSelfAllocatedId.set(1); // ids cannot be zero
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,8 @@
|
||||
package google.registry.model;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.flogger.StackSize;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.OnLoad;
|
||||
import google.registry.model.translators.UpdateAutoTimestampTranslatorFactory;
|
||||
@@ -44,12 +40,10 @@ import org.joda.time.DateTime;
|
||||
@Embeddable
|
||||
public class UpdateAutoTimestamp extends ImmutableObject {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
// 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
|
||||
// during a replay).
|
||||
private static ThreadLocal<Boolean> autoUpdateEnabled = ThreadLocal.withInitial(() -> true);
|
||||
private static final ThreadLocal<Boolean> autoUpdateEnabled = ThreadLocal.withInitial(() -> true);
|
||||
|
||||
@Transient DateTime timestamp;
|
||||
|
||||
@@ -63,16 +57,7 @@ public class UpdateAutoTimestamp extends ImmutableObject {
|
||||
@PrePersist
|
||||
@PreUpdate
|
||||
void setTimestamp() {
|
||||
// On the off chance that this is called outside of a transaction, log it instead of failing
|
||||
// with an exception from attempting to call jpaTm().getTransactionTime(), and then fall back
|
||||
// to DateTime.now(UTC).
|
||||
if (!jpaTm().inTransaction()) {
|
||||
logger.atSevere().withStackTrace(StackSize.MEDIUM).log(
|
||||
"Failed to update automatic timestamp because this wasn't called in a JPA transaction%s.",
|
||||
ofyTm().inTransaction() ? " (but there is an open Ofy transaction)" : "");
|
||||
timestamp = DateTime.now(UTC);
|
||||
lastUpdateTime = DateTimeUtils.toZonedDateTime(timestamp);
|
||||
} else if (autoUpdateEnabled() || lastUpdateTime == null) {
|
||||
if (autoUpdateEnabled() || lastUpdateTime == null) {
|
||||
timestamp = jpaTm().getTransactionTime();
|
||||
lastUpdateTime = DateTimeUtils.toZonedDateTime(timestamp);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -227,5 +227,11 @@ public class ContactHistory extends HistoryEntry implements SqlEntity {
|
||||
getInstance().parent = Key.create(ContactResource.class, contactRepoId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder wipeOutPii() {
|
||||
getInstance().contactBase =
|
||||
getInstance().getContactBase().get().asBuilder().wipeOut().build();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -865,7 +865,8 @@ public class DomainContent extends EppResource
|
||||
public B setDomainName(String domainName) {
|
||||
checkArgument(
|
||||
domainName.equals(canonicalizeDomainName(domainName)),
|
||||
"Domain name must be in puny-coded, lower-case form");
|
||||
"Domain name %s not in puny-coded, lower-case form",
|
||||
domainName);
|
||||
getInstance().fullyQualifiedDomainName = domainName;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@@ -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.secdns;
|
||||
import static google.registry.model.IdService.allocateId;
|
||||
|
||||
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;
|
||||
@@ -53,6 +54,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() {
|
||||
|
||||
@@ -196,7 +196,8 @@ public class HostBase extends EppResource {
|
||||
public B setHostName(String hostName) {
|
||||
checkArgument(
|
||||
hostName.equals(canonicalizeDomainName(hostName)),
|
||||
"Host name must be in puny-coded, lower-case form");
|
||||
"Host name %s not in puny-coded, lower-case form",
|
||||
hostName);
|
||||
getInstance().fullyQualifiedHostName = hostName;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ 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.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.replay.DatastoreAndSqlEntity;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
@@ -49,7 +50,6 @@ public class DomainTransactionRecord extends ImmutableObject
|
||||
|
||||
@Id
|
||||
@Ignore
|
||||
@ImmutableObject.DoNotCompare
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ImmutableObject.Insignificant
|
||||
Long id;
|
||||
@@ -58,6 +58,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 +182,10 @@ public class DomainTransactionRecord extends ImmutableObject
|
||||
}
|
||||
}
|
||||
|
||||
public DomainHistoryId getDomainHistoryId() {
|
||||
return new DomainHistoryId(domainRepoId, historyRevisionId);
|
||||
}
|
||||
|
||||
public DateTime getReportingTime() {
|
||||
return reportingTime;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import google.registry.batch.ResaveAllEppResourcesAction;
|
||||
import google.registry.batch.ResaveEntityAction;
|
||||
import google.registry.batch.SendExpiringCertificateNotificationEmailAction;
|
||||
import google.registry.batch.WipeOutCloudSqlAction;
|
||||
import google.registry.batch.WipeOutContactHistoryPiiAction;
|
||||
import google.registry.batch.WipeoutDatastoreAction;
|
||||
import google.registry.cron.CommitLogFanoutAction;
|
||||
import google.registry.cron.CronModule;
|
||||
@@ -219,6 +220,8 @@ interface BackendRequestComponent {
|
||||
|
||||
WipeoutDatastoreAction wipeoutDatastoreAction();
|
||||
|
||||
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
abstract class Builder implements RequestComponentBuilder<BackendRequestComponent> {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import static google.registry.config.RegistryConfig.getHibernateHikariIdleTimeou
|
||||
import static google.registry.config.RegistryConfig.getHibernateHikariMaximumPoolSize;
|
||||
import static google.registry.config.RegistryConfig.getHibernateHikariMinimumIdle;
|
||||
import static google.registry.config.RegistryConfig.getHibernateLogSqlQueries;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.api.client.auth.oauth2.Credential;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
@@ -34,6 +35,7 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.persistence.transaction.CloudSqlCredentialSupplier;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
|
||||
import google.registry.persistence.transaction.TransactionManager;
|
||||
import google.registry.privileges.secretmanager.SqlCredential;
|
||||
import google.registry.privileges.secretmanager.SqlCredentialStore;
|
||||
import google.registry.privileges.secretmanager.SqlUser;
|
||||
@@ -150,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
|
||||
@@ -206,6 +231,12 @@ public abstract class PersistenceModule {
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static TransactionManager provideTransactionManager() {
|
||||
return tm();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@AppEngineJpaTm
|
||||
@@ -222,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
|
||||
@@ -338,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
|
||||
@@ -349,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
|
||||
|
||||
+4
-2
@@ -98,14 +98,16 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
// EntityManagerFactory is thread safe.
|
||||
private final EntityManagerFactory emf;
|
||||
private final Clock clock;
|
||||
|
||||
// TODO(b/177588434): Investigate alternatives for managing transaction information. ThreadLocal
|
||||
// adds an unnecessary restriction that each request has to be processed by one thread
|
||||
// synchronously.
|
||||
private final ThreadLocal<TransactionInfo> transactionInfo =
|
||||
private static final ThreadLocal<TransactionInfo> transactionInfo =
|
||||
ThreadLocal.withInitial(TransactionInfo::new);
|
||||
|
||||
// If this value is present, use it to determine whether or not to replay SQL transactions to
|
||||
// Datastore, rather than using the schedule stored in Datastore.
|
||||
private static ThreadLocal<Optional<Boolean>> replaySqlToDatastoreOverrideForTest =
|
||||
private static final ThreadLocal<Optional<Boolean>> replaySqlToDatastoreOverrideForTest =
|
||||
ThreadLocal.withInitial(Optional::empty);
|
||||
|
||||
public JpaTransactionManagerImpl(EntityManagerFactory emf, Clock clock) {
|
||||
|
||||
+8
-5
@@ -132,13 +132,16 @@ public class TransactionManagerFactory {
|
||||
/**
|
||||
* Sets the return of {@link #tm()} to the given instance of {@link TransactionManager}.
|
||||
*
|
||||
* <p>DO NOT CALL THIS DIRECTLY IF POSSIBLE. Strongly prefer the use of <code>TmOverrideExtension
|
||||
* </code> in test code instead.
|
||||
*
|
||||
* <p>Used when overriding the per-test transaction manager for dual-database tests. Should be
|
||||
* matched with a corresponding invocation of {@link #removeTmOverrideForTest()} either at the end
|
||||
* of the test or in an <code>@AfterEach</code> handler.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void setTmForTest(TransactionManager newTm) {
|
||||
tmForTest = Optional.of(newTm);
|
||||
public static void setTmOverrideForTest(TransactionManager newTmOverride) {
|
||||
tmForTest = Optional.of(newTmOverride);
|
||||
}
|
||||
|
||||
/** Resets the overridden transaction manager post-test. */
|
||||
@@ -152,10 +155,10 @@ public class TransactionManagerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/** Thrown when a write is attempted when the DB is in read-only mode. */
|
||||
/** Registry is currently undergoing maintenance and is in read-only mode. */
|
||||
public static class ReadOnlyModeException extends IllegalStateException {
|
||||
public ReadOnlyModeException() {
|
||||
super("Registry is currently in read-only mode");
|
||||
ReadOnlyModeException() {
|
||||
super("Registry is currently undergoing maintenance and is in read-only mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import google.registry.request.auth.Auth;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
@@ -60,7 +61,7 @@ import org.joda.time.DateTime;
|
||||
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
|
||||
public final class BrdaCopyAction implements Runnable {
|
||||
|
||||
static final String PATH = "/_dr/task/brdaCopy";
|
||||
public static final String PATH = "/_dr/task/brdaCopy";
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@@ -69,6 +70,7 @@ public final class BrdaCopyAction implements Runnable {
|
||||
@Inject @Config("rdeBucket") String stagingBucket;
|
||||
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
||||
@Inject @Parameter(RdeModule.PARAM_WATERMARK) DateTime watermark;
|
||||
@Inject @Parameter(RdeModule.PARAM_PREFIX) Optional<String> prefix;
|
||||
@Inject @Key("brdaReceiverKey") PGPPublicKey receiverKey;
|
||||
@Inject @Key("brdaSigningKey") PGPKeyPair signingKey;
|
||||
@Inject @Key("rdeStagingDecryptionKey") PGPPrivateKey stagingDecryptionKey;
|
||||
@@ -84,11 +86,12 @@ public final class BrdaCopyAction implements Runnable {
|
||||
}
|
||||
|
||||
private void copyAsRyde() throws IOException {
|
||||
String prefix = RdeNamingUtils.makeRydeFilename(tld, watermark, THIN, 1, 0);
|
||||
BlobId xmlFilename = BlobId.of(stagingBucket, prefix + ".xml.ghostryde");
|
||||
BlobId xmlLengthFilename = BlobId.of(stagingBucket, prefix + ".xml.length");
|
||||
BlobId rydeFile = BlobId.of(brdaBucket, prefix + ".ryde");
|
||||
BlobId sigFile = BlobId.of(brdaBucket, prefix + ".sig");
|
||||
String nameWithoutPrefix = RdeNamingUtils.makeRydeFilename(tld, watermark, THIN, 1, 0);
|
||||
String name = prefix.orElse("") + nameWithoutPrefix;
|
||||
BlobId xmlFilename = BlobId.of(stagingBucket, name + ".xml.ghostryde");
|
||||
BlobId xmlLengthFilename = BlobId.of(stagingBucket, name + ".xml.length");
|
||||
BlobId rydeFile = BlobId.of(brdaBucket, nameWithoutPrefix + ".ryde");
|
||||
BlobId sigFile = BlobId.of(brdaBucket, nameWithoutPrefix + ".sig");
|
||||
|
||||
long xmlLength = readXmlLength(xmlLengthFilename);
|
||||
|
||||
@@ -97,11 +100,12 @@ public final class BrdaCopyAction implements Runnable {
|
||||
InputStream ghostrydeDecoder = Ghostryde.decoder(gcsInput, stagingDecryptionKey);
|
||||
OutputStream rydeOut = gcsUtils.openOutputStream(rydeFile);
|
||||
OutputStream sigOut = gcsUtils.openOutputStream(sigFile);
|
||||
RydeEncoder rydeEncoder = new RydeEncoder.Builder()
|
||||
.setRydeOutput(rydeOut, receiverKey)
|
||||
.setSignatureOutput(sigOut, signingKey)
|
||||
.setFileMetadata(prefix, xmlLength, watermark)
|
||||
.build()) {
|
||||
RydeEncoder rydeEncoder =
|
||||
new RydeEncoder.Builder()
|
||||
.setRydeOutput(rydeOut, receiverKey)
|
||||
.setSignatureOutput(sigOut, signingKey)
|
||||
.setFileMetadata(nameWithoutPrefix, xmlLength, watermark)
|
||||
.build()) {
|
||||
ByteStreams.copy(ghostrydeDecoder, rydeEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ public abstract class RdeModule {
|
||||
public static final String PARAM_MODE = "mode";
|
||||
public static final String PARAM_REVISION = "revision";
|
||||
public static final String PARAM_LENIENT = "lenient";
|
||||
public static final String PARAM_PREFIX = "prefix";
|
||||
public static final String RDE_UPLOAD_QUEUE = "rde-upload";
|
||||
public static final String RDE_REPORT_QUEUE = "rde-report";
|
||||
public static final String BRDA_QUEUE = "brda";
|
||||
|
||||
@Provides
|
||||
@Parameter(PARAM_WATERMARK)
|
||||
@@ -92,10 +96,11 @@ public abstract class RdeModule {
|
||||
return extractBooleanParameter(req, PARAM_LENIENT);
|
||||
}
|
||||
|
||||
// TODO (jianglai): Make it a required parameter once we migrate to Cloud SQL.
|
||||
@Provides
|
||||
@Named("brda")
|
||||
static Queue provideQueueBrda() {
|
||||
return getQueue("brda");
|
||||
@Parameter(PARAM_PREFIX)
|
||||
static Optional<String> providePrefix(HttpServletRequest req) {
|
||||
return extractOptionalParameter(req, PARAM_PREFIX);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -68,6 +68,7 @@ public final class RdeReportAction implements Runnable, EscrowTask {
|
||||
@Inject Response response;
|
||||
@Inject RdeReporter reporter;
|
||||
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
||||
@Inject @Parameter(RdeModule.PARAM_PREFIX) Optional<String> prefix;
|
||||
@Inject @Config("rdeBucket") String bucket;
|
||||
@Inject @Config("rdeInterval") Duration interval;
|
||||
@Inject @Config("rdeReportLockTimeout") Duration timeout;
|
||||
@@ -96,8 +97,9 @@ public final class RdeReportAction implements Runnable, EscrowTask {
|
||||
RdeRevision.getCurrentRevision(tld, watermark, FULL)
|
||||
.orElseThrow(
|
||||
() -> new IllegalStateException("RdeRevision was not set on generated deposit"));
|
||||
String prefix = RdeNamingUtils.makeRydeFilename(tld, watermark, FULL, 1, revision);
|
||||
BlobId reportFilename = BlobId.of(bucket, prefix + "-report.xml.ghostryde");
|
||||
String name =
|
||||
prefix.orElse("") + RdeNamingUtils.makeRydeFilename(tld, watermark, FULL, 1, revision);
|
||||
BlobId reportFilename = BlobId.of(bucket, name + "-report.xml.ghostryde");
|
||||
verify(gcsUtils.existsAndNotEmpty(reportFilename), "Missing file: %s", reportFilename);
|
||||
reporter.send(readReportFromGcs(reportFilename));
|
||||
response.setContentType(PLAIN_TEXT_UTF_8);
|
||||
|
||||
@@ -14,20 +14,33 @@
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static google.registry.beam.BeamUtils.createJobName;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.xml.ValidationMode.LENIENT;
|
||||
import static google.registry.xml.ValidationMode.STRICT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.api.services.dataflow.Dataflow;
|
||||
import com.google.api.services.dataflow.model.LaunchFlexTemplateParameter;
|
||||
import com.google.api.services.dataflow.model.LaunchFlexTemplateRequest;
|
||||
import com.google.api.services.dataflow.model.LaunchFlexTemplateResponse;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import google.registry.beam.rde.RdePipeline;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.mapreduce.MapreduceRunner;
|
||||
import google.registry.mapreduce.inputs.EppResourceInputs;
|
||||
import google.registry.mapreduce.inputs.NullInput;
|
||||
@@ -47,6 +60,7 @@ import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.xml.ValidationMode;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -201,6 +215,7 @@ public final class RdeStagingAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/rdeStaging";
|
||||
|
||||
private static final String PIPELINE_NAME = "rde_pipeline";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Inject Clock clock;
|
||||
@@ -209,7 +224,11 @@ public final class RdeStagingAction implements Runnable {
|
||||
@Inject Response response;
|
||||
@Inject GcsUtils gcsUtils;
|
||||
@Inject MapreduceRunner mrRunner;
|
||||
@Inject @Config("projectId") String projectId;
|
||||
@Inject @Config("defaultJobRegion") String jobRegion;
|
||||
@Inject @Config("transactionCooldown") Duration transactionCooldown;
|
||||
@Inject @Config("beamStagingBucketUrl") String stagingBucketUrl;
|
||||
@Inject @Config("rdeBucket") String rdeBucket;
|
||||
@Inject @Parameter(RdeModule.PARAM_MANUAL) boolean manual;
|
||||
@Inject @Parameter(RdeModule.PARAM_DIRECTORY) Optional<String> directory;
|
||||
@Inject @Parameter(RdeModule.PARAM_MODE) ImmutableSet<String> modeStrings;
|
||||
@@ -217,7 +236,8 @@ public final class RdeStagingAction implements Runnable {
|
||||
@Inject @Parameter(RdeModule.PARAM_WATERMARKS) ImmutableSet<DateTime> watermarks;
|
||||
@Inject @Parameter(RdeModule.PARAM_REVISION) Optional<Integer> revision;
|
||||
@Inject @Parameter(RdeModule.PARAM_LENIENT) boolean lenient;
|
||||
|
||||
@Inject @Key("rdeStagingEncryptionKey") byte[] stagingKeyBytes;
|
||||
@Inject Dataflow dataflow;
|
||||
@Inject RdeStagingAction() {}
|
||||
|
||||
@Override
|
||||
@@ -228,27 +248,66 @@ public final class RdeStagingAction implements Runnable {
|
||||
String message = "Nothing needs to be deposited.";
|
||||
logger.atInfo().log(message);
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setPayload(message);
|
||||
// No need to set payload as HTTP 204 response status code does not allow a payload.
|
||||
return;
|
||||
}
|
||||
for (PendingDeposit pending : pendings.values()) {
|
||||
logger.atInfo().log("Pending deposit: %s", pending);
|
||||
}
|
||||
ValidationMode validationMode = lenient ? LENIENT : STRICT;
|
||||
RdeStagingMapper mapper = new RdeStagingMapper(validationMode, pendings);
|
||||
RdeStagingReducer reducer = reducerFactory.create(validationMode, gcsUtils);
|
||||
|
||||
mrRunner
|
||||
.setJobName("Stage escrow deposits for all TLDs")
|
||||
.setModuleName("backend")
|
||||
.setDefaultReduceShards(pendings.size())
|
||||
.runMapreduce(
|
||||
mapper,
|
||||
reducer,
|
||||
ImmutableList.of(
|
||||
// Add an extra shard that maps over a null resource. See the mapper code for why.
|
||||
new NullInput<>(), EppResourceInputs.createEntityInput(EppResource.class)))
|
||||
.sendLinkToMapreduceConsole(response);
|
||||
if (tm().isOfy()) {
|
||||
RdeStagingMapper mapper = new RdeStagingMapper(validationMode, pendings);
|
||||
RdeStagingReducer reducer = reducerFactory.create(validationMode, gcsUtils);
|
||||
mrRunner
|
||||
.setJobName("Stage escrow deposits for all TLDs")
|
||||
.setModuleName("backend")
|
||||
.setDefaultReduceShards(pendings.size())
|
||||
.runMapreduce(
|
||||
mapper,
|
||||
reducer,
|
||||
ImmutableList.of(
|
||||
// Add an extra shard that maps over a null resource. See the mapper code for why.
|
||||
new NullInput<>(), EppResourceInputs.createEntityInput(EppResource.class)))
|
||||
.sendLinkToMapreduceConsole(response);
|
||||
} else {
|
||||
try {
|
||||
LaunchFlexTemplateParameter parameter =
|
||||
new LaunchFlexTemplateParameter()
|
||||
.setJobName(createJobName("rde", clock))
|
||||
.setContainerSpecGcsPath(
|
||||
String.format("%s/%s_metadata.json", stagingBucketUrl, PIPELINE_NAME))
|
||||
.setParameters(
|
||||
ImmutableMap.of(
|
||||
"pendings",
|
||||
RdePipeline.encodePendings(pendings),
|
||||
"validationMode",
|
||||
validationMode.name(),
|
||||
"rdeStagingBucket",
|
||||
rdeBucket,
|
||||
"stagingKey",
|
||||
BaseEncoding.base64Url().omitPadding().encode(stagingKeyBytes),
|
||||
"registryEnvironment",
|
||||
RegistryEnvironment.get().name()));
|
||||
LaunchFlexTemplateResponse launchResponse =
|
||||
dataflow
|
||||
.projects()
|
||||
.locations()
|
||||
.flexTemplates()
|
||||
.launch(
|
||||
projectId,
|
||||
jobRegion,
|
||||
new LaunchFlexTemplateRequest().setLaunchParameter(parameter))
|
||||
.execute();
|
||||
logger.atInfo().log("Got response: %s", launchResponse.getJob().toPrettyString());
|
||||
response.setStatus(SC_OK);
|
||||
response.setPayload(
|
||||
String.format("Launched RDE pipeline: %s", launchResponse.getJob().getId()));
|
||||
} catch (IOException e) {
|
||||
logger.atWarning().withCause(e).log("Pipeline Launch failed");
|
||||
response.setStatus(SC_INTERNAL_SERVER_ERROR);
|
||||
response.setPayload(String.format("Pipeline launch failed: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ImmutableSetMultimap<String, PendingDeposit> getStandardPendingDeposits() {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
|
||||
import static com.jcraft.jsch.ChannelSftp.OVERWRITE;
|
||||
@@ -24,14 +23,15 @@ import static google.registry.model.common.Cursor.getCursorTimeOrStartOfTime;
|
||||
import static google.registry.model.rde.RdeMode.FULL;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||
import static google.registry.rde.RdeModule.RDE_REPORT_QUEUE;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.jcraft.jsch.JSch;
|
||||
@@ -49,14 +49,15 @@ import google.registry.model.tld.Registry;
|
||||
import google.registry.rde.EscrowTaskRunner.EscrowTask;
|
||||
import google.registry.rde.JSchSshSession.JSchSshSessionFactory;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.HttpException.NoContentException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.TaskQueueUtils;
|
||||
import google.registry.util.TeeOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -66,7 +67,6 @@ import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
@@ -90,7 +90,7 @@ import org.joda.time.Duration;
|
||||
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
|
||||
public final class RdeUploadAction implements Runnable, EscrowTask {
|
||||
|
||||
static final String PATH = "/_dr/task/rdeUpload";
|
||||
public static final String PATH = "/_dr/task/rdeUpload";
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@@ -109,9 +109,10 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
|
||||
@Inject JSchSshSessionFactory jschSshSessionFactory;
|
||||
@Inject Response response;
|
||||
@Inject SftpProgressMonitor sftpProgressMonitor;
|
||||
@Inject TaskQueueUtils taskQueueUtils;
|
||||
@Inject CloudTasksUtils cloudTasksUtils;
|
||||
@Inject Retrier retrier;
|
||||
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
||||
@Inject @Parameter(RdeModule.PARAM_PREFIX) Optional<String> prefix;
|
||||
@Inject @Config("rdeBucket") String bucket;
|
||||
@Inject @Config("rdeInterval") Duration interval;
|
||||
@Inject @Config("rdeUploadLockTimeout") Duration timeout;
|
||||
@@ -120,15 +121,21 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
|
||||
@Inject @Key("rdeReceiverKey") PGPPublicKey receiverKey;
|
||||
@Inject @Key("rdeSigningKey") PGPKeyPair signingKey;
|
||||
@Inject @Key("rdeStagingDecryptionKey") PGPPrivateKey stagingDecryptionKey;
|
||||
@Inject @Named("rde-report") Queue reportQueue;
|
||||
@Inject RdeUploadAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.atInfo().log("Attempting to acquire RDE upload lock for TLD '%s'.", tld);
|
||||
runner.lockRunAndRollForward(this, Registry.get(tld), timeout, CursorType.RDE_UPLOAD, interval);
|
||||
taskQueueUtils.enqueue(
|
||||
reportQueue, withUrl(RdeReportAction.PATH).param(RequestParameters.PARAM_TLD, tld));
|
||||
HashMultimap<String, String> params = HashMultimap.create();
|
||||
params.put(RequestParameters.PARAM_TLD, tld);
|
||||
if (prefix.isPresent()) {
|
||||
params.put(RdeModule.PARAM_PREFIX, prefix.get());
|
||||
}
|
||||
cloudTasksUtils.enqueue(
|
||||
RDE_REPORT_QUEUE,
|
||||
CloudTasksUtils.createPostTask(
|
||||
RdeReportAction.PATH, Service.BACKEND.getServiceId(), params));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,7 +171,9 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
|
||||
RdeRevision.getCurrentRevision(tld, watermark, FULL)
|
||||
.orElseThrow(
|
||||
() -> new IllegalStateException("RdeRevision was not set on generated deposit"));
|
||||
final String name = RdeNamingUtils.makeRydeFilename(tld, watermark, FULL, 1, revision);
|
||||
final String nameWithoutPrefix =
|
||||
RdeNamingUtils.makeRydeFilename(tld, watermark, FULL, 1, revision);
|
||||
final String name = prefix.orElse("") + nameWithoutPrefix;
|
||||
final BlobId xmlFilename = BlobId.of(bucket, name + ".xml.ghostryde");
|
||||
final BlobId xmlLengthFilename = BlobId.of(bucket, name + ".xml.length");
|
||||
BlobId reportFilename = BlobId.of(bucket, name + "-report.xml.ghostryde");
|
||||
@@ -174,7 +183,8 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
|
||||
logger.atInfo().log("Commencing RDE upload for TLD '%s' to '%s'.", tld, uploadUrl);
|
||||
final long xmlLength = readXmlLength(xmlLengthFilename);
|
||||
retrier.callWithRetry(
|
||||
() -> upload(xmlFilename, xmlLength, watermark, name), JSchException.class);
|
||||
() -> upload(xmlFilename, xmlLength, watermark, name, nameWithoutPrefix),
|
||||
JSchException.class);
|
||||
logger.atInfo().log(
|
||||
"Updating RDE cursor '%s' for TLD '%s' following successful upload.", RDE_UPLOAD_SFTP, tld);
|
||||
tm().transact(
|
||||
@@ -210,7 +220,8 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
|
||||
* }</pre>
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected void upload(BlobId xmlFile, long xmlLength, DateTime watermark, String name)
|
||||
protected void upload(
|
||||
BlobId xmlFile, long xmlLength, DateTime watermark, String name, String nameWithoutPrefix)
|
||||
throws Exception {
|
||||
logger.atInfo().log("Uploading XML file '%s' to remote path '%s'.", xmlFile, uploadUrl);
|
||||
try (InputStream gcsInput = gcsUtils.openInputStream(xmlFile);
|
||||
@@ -218,8 +229,8 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
|
||||
try (JSchSshSession session = jschSshSessionFactory.create(lazyJsch.get(), uploadUrl);
|
||||
JSchSftpChannel ftpChan = session.openSftpChannel()) {
|
||||
ByteArrayOutputStream sigOut = new ByteArrayOutputStream();
|
||||
String rydeFilename = name + ".ryde";
|
||||
BlobId rydeGcsFilename = BlobId.of(bucket, rydeFilename);
|
||||
String rydeFilename = nameWithoutPrefix + ".ryde";
|
||||
BlobId rydeGcsFilename = BlobId.of(bucket, name + ".ryde");
|
||||
try (OutputStream ftpOutput =
|
||||
ftpChan.get().put(rydeFilename, sftpProgressMonitor, OVERWRITE);
|
||||
OutputStream gcsOutput = gcsUtils.openOutputStream(rydeGcsFilename);
|
||||
@@ -228,14 +239,15 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
|
||||
new RydeEncoder.Builder()
|
||||
.setRydeOutput(teeOutput, receiverKey)
|
||||
.setSignatureOutput(sigOut, signingKey)
|
||||
.setFileMetadata(name, xmlLength, watermark)
|
||||
.setFileMetadata(nameWithoutPrefix, xmlLength, watermark)
|
||||
.build()) {
|
||||
long bytesCopied = ByteStreams.copy(ghostrydeDecoder, rydeEncoder);
|
||||
logger.atInfo().log("Uploaded %,d bytes to path '%s'.", bytesCopied, rydeFilename);
|
||||
}
|
||||
String sigFilename = name + ".sig";
|
||||
String sigFilename = nameWithoutPrefix + ".sig";
|
||||
BlobId sigGcsFilename = BlobId.of(bucket, name + ".sig");
|
||||
byte[] signature = sigOut.toByteArray();
|
||||
gcsUtils.createFromBytes(BlobId.of(bucket, sigFilename), signature);
|
||||
gcsUtils.createFromBytes(sigGcsFilename, signature);
|
||||
ftpChan.get().put(new ByteArrayInputStream(signature), sigFilename);
|
||||
logger.atInfo().log("Uploaded %,d bytes to path '%s'.", signature.length, sigFilename);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.PrimaryDatabase;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.request.Action;
|
||||
@@ -127,7 +128,9 @@ public class GenerateInvoicesAction implements Runnable {
|
||||
"database",
|
||||
database.name(),
|
||||
"billingBucketUrl",
|
||||
billingBucketUrl));
|
||||
billingBucketUrl,
|
||||
"registryEnvironment",
|
||||
RegistryEnvironment.get().name()));
|
||||
LaunchFlexTemplateResponse launchResponse =
|
||||
dataflow
|
||||
.projects()
|
||||
|
||||
+19
-14
@@ -16,6 +16,7 @@ package google.registry.reporting.icann;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.reporting.icann.IcannReportingModule.DATASTORE_EXPORT_DATA_SET;
|
||||
import static google.registry.reporting.icann.IcannReportingModule.ICANN_REPORTING_DATA_SET;
|
||||
import static google.registry.reporting.icann.QueryBuilderUtils.getQueryFromFile;
|
||||
import static google.registry.reporting.icann.QueryBuilderUtils.getTableName;
|
||||
|
||||
@@ -23,6 +24,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.util.SqlTemplate;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.YearMonth;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
@@ -38,29 +40,32 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
static final String EPP_METRICS = "epp_metrics";
|
||||
static final String WHOIS_COUNTS = "whois_counts";
|
||||
static final String ACTIVITY_REPORT_AGGREGATION = "activity_report_aggregation";
|
||||
final String BIGQUERY_DATA_SET = tm().isOfy() ? "icann_reporting" : "cloud_sql_icann_reporting";
|
||||
|
||||
private final String projectId;
|
||||
private final DnsCountQueryCoordinator dnsCountQueryCoordinator;
|
||||
private final String icannReportingDataSet;
|
||||
|
||||
@Inject
|
||||
@Config("projectId")
|
||||
String projectId;
|
||||
|
||||
@Inject DnsCountQueryCoordinator dnsCountQueryCoordinator;
|
||||
|
||||
@Inject
|
||||
ActivityReportingQueryBuilder() {}
|
||||
ActivityReportingQueryBuilder(
|
||||
@Config("projectId") String projectId,
|
||||
@Named(ICANN_REPORTING_DATA_SET) String icannReportingDataSet,
|
||||
DnsCountQueryCoordinator dnsCountQueryCoordinator) {
|
||||
this.projectId = projectId;
|
||||
this.dnsCountQueryCoordinator = dnsCountQueryCoordinator;
|
||||
this.icannReportingDataSet = icannReportingDataSet;
|
||||
}
|
||||
|
||||
/** Returns the aggregate query which generates the activity report from the saved view. */
|
||||
@Override
|
||||
public String getReportQuery(YearMonth yearMonth) {
|
||||
return String.format(
|
||||
"#standardSQL\nSELECT * FROM `%s.%s.%s`",
|
||||
projectId, BIGQUERY_DATA_SET, getTableName(ACTIVITY_REPORT_AGGREGATION, yearMonth));
|
||||
projectId, icannReportingDataSet, getTableName(ACTIVITY_REPORT_AGGREGATION, yearMonth));
|
||||
}
|
||||
|
||||
/** Sets the month we're doing activity reporting for, and returns the view query map. */
|
||||
@Override
|
||||
public ImmutableMap<String, String> getViewQueryMap(YearMonth yearMonth) {
|
||||
|
||||
LocalDate firstDayOfMonth = yearMonth.toLocalDate(1);
|
||||
// The pattern-matching is inclusive, so we subtract 1 day to only report that month's data.
|
||||
LocalDate lastDayOfMonth = yearMonth.toLocalDate(1).plusMonths(1).minusDays(1);
|
||||
@@ -102,7 +107,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
String eppQuery =
|
||||
SqlTemplate.create(getQueryFromFile("epp_metrics.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("ICANN_REPORTING_DATA_SET", BIGQUERY_DATA_SET)
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("MONTHLY_LOGS_TABLE", getTableName(MONTHLY_LOGS, yearMonth))
|
||||
// All metadata logs for reporting come from google.registry.flows.FlowReporter.
|
||||
.put(
|
||||
@@ -114,7 +119,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
String whoisQuery =
|
||||
SqlTemplate.create(getQueryFromFile("whois_counts.sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("ICANN_REPORTING_DATA_SET", BIGQUERY_DATA_SET)
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("MONTHLY_LOGS_TABLE", getTableName(MONTHLY_LOGS, yearMonth))
|
||||
.build();
|
||||
queriesBuilder.put(getTableName(WHOIS_COUNTS, yearMonth), whoisQuery);
|
||||
@@ -129,7 +134,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
.put(
|
||||
"REGISTRAR_OPERATING_STATUS_TABLE",
|
||||
getTableName(REGISTRAR_OPERATING_STATUS, yearMonth))
|
||||
.put("ICANN_REPORTING_DATA_SET", BIGQUERY_DATA_SET)
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("DNS_COUNTS_TABLE", getTableName(DNS_COUNTS, yearMonth))
|
||||
.put("EPP_METRICS_TABLE", getTableName(EPP_METRICS, yearMonth))
|
||||
.put("WHOIS_COUNTS_TABLE", getTableName(WHOIS_COUNTS, yearMonth));
|
||||
@@ -147,7 +152,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
return queriesBuilder.build();
|
||||
}
|
||||
|
||||
public void prepareForQuery(YearMonth yearMonth) throws InterruptedException {
|
||||
void prepareForQuery(YearMonth yearMonth) throws InterruptedException {
|
||||
dnsCountQueryCoordinator.prepareForQuery(yearMonth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.reporting.icann;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.RequestParameters.extractOptionalParameter;
|
||||
import static google.registry.request.RequestParameters.extractRequiredParameter;
|
||||
import static google.registry.request.RequestParameters.extractSetOfEnumParameters;
|
||||
@@ -24,9 +23,11 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.bigquery.BigqueryConnection;
|
||||
import google.registry.persistence.transaction.TransactionManager;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.Parameter;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Named;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
@@ -42,8 +43,7 @@ public final class IcannReportingModule {
|
||||
|
||||
static final String PARAM_SUBDIR = "subdir";
|
||||
static final String PARAM_REPORT_TYPES = "reportTypes";
|
||||
static final String ICANN_REPORTING_DATA_SET =
|
||||
tm().isOfy() ? "icann_reporting" : "cloud_sql_icann_reporting";
|
||||
static final String ICANN_REPORTING_DATA_SET = "icannReportingDataSet";
|
||||
static final String DATASTORE_EXPORT_DATA_SET = "latest_datastore_export";
|
||||
static final String MANIFEST_FILE_NAME = "MANIFEST.txt";
|
||||
|
||||
@@ -88,11 +88,12 @@ public final class IcannReportingModule {
|
||||
*/
|
||||
@Provides
|
||||
static BigqueryConnection provideBigqueryConnection(
|
||||
BigqueryConnection.Builder bigQueryConnectionBuilder) {
|
||||
BigqueryConnection.Builder bigQueryConnectionBuilder,
|
||||
@Named(ICANN_REPORTING_DATA_SET) String icannReportingDataSet) {
|
||||
try {
|
||||
return bigQueryConnectionBuilder
|
||||
.setExecutorService(MoreExecutors.newDirectExecutorService())
|
||||
.setDatasetId(ICANN_REPORTING_DATA_SET)
|
||||
.setDatasetId(icannReportingDataSet)
|
||||
.setOverwrite(true)
|
||||
.setPollInterval(Duration.standardSeconds(1))
|
||||
.build();
|
||||
@@ -100,4 +101,10 @@ public final class IcannReportingModule {
|
||||
throw new RuntimeException("Could not initialize BigqueryConnection!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named(ICANN_REPORTING_DATA_SET)
|
||||
static String provideIcannReportingDataSet(TransactionManager tm) {
|
||||
return tm.isOfy() ? "icann_reporting" : "cloud_sql_icann_reporting";
|
||||
}
|
||||
}
|
||||
|
||||
+46
-57
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-6
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.util.SqlTemplate;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalTime;
|
||||
import org.joda.time.YearMonth;
|
||||
@@ -34,9 +35,16 @@ import org.joda.time.format.DateTimeFormatter;
|
||||
*/
|
||||
public final class TransactionsReportingQueryBuilder implements QueryBuilder {
|
||||
|
||||
@Inject @Config("projectId") String projectId;
|
||||
final String projectId;
|
||||
private final String icannReportingDataSet;
|
||||
|
||||
@Inject TransactionsReportingQueryBuilder() {}
|
||||
@Inject
|
||||
TransactionsReportingQueryBuilder(
|
||||
@Config("projectId") String projectId,
|
||||
@Named(ICANN_REPORTING_DATA_SET) String icannReportingDataSet) {
|
||||
this.projectId = projectId;
|
||||
this.icannReportingDataSet = icannReportingDataSet;
|
||||
}
|
||||
|
||||
static final String TRANSACTIONS_REPORT_AGGREGATION = "transactions_report_aggregation";
|
||||
static final String REGISTRAR_IANA_ID = "registrar_iana_id";
|
||||
@@ -51,9 +59,7 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
|
||||
public String getReportQuery(YearMonth yearMonth) {
|
||||
return String.format(
|
||||
"#standardSQL\nSELECT * FROM `%s.%s.%s`",
|
||||
projectId,
|
||||
ICANN_REPORTING_DATA_SET,
|
||||
getTableName(TRANSACTIONS_REPORT_AGGREGATION, yearMonth));
|
||||
projectId, icannReportingDataSet, getTableName(TRANSACTIONS_REPORT_AGGREGATION, yearMonth));
|
||||
}
|
||||
|
||||
/** Sets the month we're doing transactions reporting for, and returns the view query map. */
|
||||
@@ -151,7 +157,7 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", DATASTORE_EXPORT_DATA_SET)
|
||||
.put("REGISTRY_TABLE", "Registry")
|
||||
.put("ICANN_REPORTING_DATA_SET", ICANN_REPORTING_DATA_SET)
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("REGISTRAR_IANA_ID_TABLE", getTableName(REGISTRAR_IANA_ID, yearMonth))
|
||||
.put("TOTAL_DOMAINS_TABLE", getTableName(TOTAL_DOMAINS, yearMonth))
|
||||
.put("TOTAL_NAMESERVERS_TABLE", getTableName(TOTAL_NAMESERVERS, yearMonth))
|
||||
|
||||
@@ -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)
|
||||
@@ -123,7 +125,7 @@ public final class RegistryTool {
|
||||
.put("update_allocation_tokens", UpdateAllocationTokensCommand.class)
|
||||
.put("update_cursors", UpdateCursorsCommand.class)
|
||||
.put("update_domain", UpdateDomainCommand.class)
|
||||
.put("update_kms_keyring", UpdateKmsKeyringCommand.class)
|
||||
.put("update_keyring_secret", UpdateKeyringSecretCommand.class)
|
||||
.put("update_premium_list", UpdatePremiumListCommand.class)
|
||||
.put("update_registrar", UpdateRegistrarCommand.class)
|
||||
.put("update_reserved_list", UpdateReservedListCommand.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);
|
||||
@@ -160,7 +163,7 @@ interface RegistryToolComponent {
|
||||
|
||||
void inject(UpdateDomainCommand command);
|
||||
|
||||
void inject(UpdateKmsKeyringCommand command);
|
||||
void inject(UpdateKeyringSecretCommand command);
|
||||
|
||||
void inject(UpdateRegistrarCommand command);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.util.CollectionUtils.findDuplicates;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
@@ -53,6 +54,17 @@ final class RenewDomainCommand extends MutatingEppToolCommand {
|
||||
@Parameter(description = "Names of the domains to renew.", required = true)
|
||||
private List<String> mainParameters;
|
||||
|
||||
@Parameter(
|
||||
names = {"--reason"},
|
||||
description = "Reason for the change.")
|
||||
String reason;
|
||||
|
||||
@Parameter(
|
||||
names = {"--registrar_request"},
|
||||
description = "Whether the change was requested by a registrar.",
|
||||
arity = 1)
|
||||
Boolean requestedByRegistrar;
|
||||
|
||||
@Inject
|
||||
Clock clock;
|
||||
|
||||
@@ -70,12 +82,23 @@ final class RenewDomainCommand extends MutatingEppToolCommand {
|
||||
checkArgumentPresent(domainOptional, "Domain '%s' does not exist or is deleted", domainName);
|
||||
setSoyTemplate(DomainRenewSoyInfo.getInstance(), DomainRenewSoyInfo.RENEWDOMAIN);
|
||||
DomainBase domain = domainOptional.get();
|
||||
addSoyRecord(
|
||||
isNullOrEmpty(clientId) ? domain.getCurrentSponsorRegistrarId() : clientId,
|
||||
|
||||
SoyMapData soyMapData =
|
||||
new SoyMapData(
|
||||
"domainName", domain.getDomainName(),
|
||||
"expirationDate", domain.getRegistrationExpirationTime().toString(DATE_FORMATTER),
|
||||
"period", String.valueOf(period)));
|
||||
"period", String.valueOf(period));
|
||||
|
||||
if (requestedByRegistrar != null) {
|
||||
soyMapData.put("requestedByRegistrar", requestedByRegistrar.toString());
|
||||
}
|
||||
if (reason != null) {
|
||||
checkArgumentNotNull(
|
||||
requestedByRegistrar, "--registrar_request is required when --reason is specified");
|
||||
soyMapData.put("reason", reason);
|
||||
}
|
||||
addSoyRecord(
|
||||
isNullOrEmpty(clientId) ? domain.getCurrentSponsorRegistrarId() : clientId, soyMapData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.tools.soy.DomainRenewSoyInfo;
|
||||
import google.registry.tools.soy.UniformRapidSuspensionSoyInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -43,6 +44,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
|
||||
/** A command to suspend a domain for the Uniform Rapid Suspension process. */
|
||||
@Parameters(separators = " =",
|
||||
@@ -97,6 +99,13 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
|
||||
description = "Flag indicating that is is an undo command, which removes locks.")
|
||||
private boolean undo;
|
||||
|
||||
@Parameter(
|
||||
names = {"--renew_one_year"},
|
||||
required = true,
|
||||
description = "Flag indicating whether or not the domain will be renewed for a year.",
|
||||
arity = 1)
|
||||
private boolean renewOneYear;
|
||||
|
||||
/** Set of existing locks that need to be preserved during undo, sorted for nicer output. */
|
||||
ImmutableSortedSet<String> existingLocks;
|
||||
|
||||
@@ -114,19 +123,20 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
|
||||
superuser = true;
|
||||
DateTime now = DateTime.now(UTC);
|
||||
ImmutableSet<String> newHostsSet = ImmutableSet.copyOf(newHosts);
|
||||
Optional<DomainBase> domain = loadByForeignKey(DomainBase.class, domainName, now);
|
||||
checkArgumentPresent(domain, "Domain '%s' does not exist or is deleted", domainName);
|
||||
Optional<DomainBase> domainOpt = loadByForeignKey(DomainBase.class, domainName, now);
|
||||
checkArgumentPresent(domainOpt, "Domain '%s' does not exist or is deleted", domainName);
|
||||
DomainBase domain = domainOpt.get();
|
||||
Set<String> missingHosts =
|
||||
difference(newHostsSet, checkResourcesExist(HostResource.class, newHosts, now));
|
||||
checkArgument(missingHosts.isEmpty(), "Hosts do not exist: %s", missingHosts);
|
||||
checkArgument(
|
||||
locksToPreserve.isEmpty() || undo,
|
||||
"Locks can only be preserved when running with --undo");
|
||||
existingNameservers = getExistingNameservers(domain.get());
|
||||
existingLocks = getExistingLocks(domain.get());
|
||||
existingDsData = getExistingDsData(domain.get());
|
||||
existingNameservers = getExistingNameservers(domain);
|
||||
existingLocks = getExistingLocks(domain);
|
||||
existingDsData = getExistingDsData(domain);
|
||||
removeStatuses =
|
||||
(hasClientHold(domain.get()) && !undo)
|
||||
(hasClientHold(domain) && !undo)
|
||||
? ImmutableSet.of(StatusValue.CLIENT_HOLD.getXmlName())
|
||||
: ImmutableSet.of();
|
||||
ImmutableSet<String> statusesToApply;
|
||||
@@ -138,6 +148,30 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
|
||||
} else {
|
||||
statusesToApply = URS_LOCKS;
|
||||
}
|
||||
|
||||
// trigger renew flow
|
||||
if (renewOneYear) {
|
||||
setSoyTemplate(DomainRenewSoyInfo.getInstance(), DomainRenewSoyInfo.RENEWDOMAIN);
|
||||
addSoyRecord(
|
||||
CLIENT_ID,
|
||||
new SoyMapData(
|
||||
"domainName",
|
||||
domain.getDomainName(),
|
||||
"expirationDate",
|
||||
domain
|
||||
.getRegistrationExpirationTime()
|
||||
.toString(DateTimeFormat.forPattern("YYYY-MM-dd")),
|
||||
// period is the number of years to renew the registration for
|
||||
"period",
|
||||
String.valueOf(1),
|
||||
// use the same values for reason and requestedByRegistrar from update flow
|
||||
"reason",
|
||||
(undo ? "Undo " : "") + "Uniform Rapid Suspension",
|
||||
"requestedByRegistrar",
|
||||
Boolean.toString(false)));
|
||||
}
|
||||
|
||||
// trigger update flow
|
||||
setSoyTemplate(
|
||||
UniformRapidSuspensionSoyInfo.getInstance(),
|
||||
UniformRapidSuspensionSoyInfo.UNIFORMRAPIDSUSPENSION);
|
||||
@@ -157,7 +191,12 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
|
||||
"newDsData",
|
||||
newDsData != null ? DsRecord.convertToSoy(newDsData) : new SoyListData(),
|
||||
"reason",
|
||||
(undo ? "Undo " : "") + "Uniform Rapid Suspension"));
|
||||
(undo ? "Undo " : "") + "Uniform Rapid Suspension",
|
||||
// Domain auto-renewal is disabled as part of URS, and it's re-enabled if URS is undone.
|
||||
// Therefore, autorenews is set to false by default and it's set to true only if the
|
||||
// command is run in --undo mode.
|
||||
"autorenews",
|
||||
Boolean.toString(undo)));
|
||||
}
|
||||
|
||||
private ImmutableSortedSet<String> getExistingNameservers(DomainBase domain) {
|
||||
|
||||
+6
-7
@@ -27,17 +27,16 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Command to set and update {@code KmsKeyring} values. */
|
||||
@Parameters(
|
||||
separators = " =",
|
||||
commandDescription = "Update values of secrets in KmsKeyring."
|
||||
)
|
||||
final class UpdateKmsKeyringCommand implements CommandWithRemoteApi {
|
||||
/**
|
||||
* Command to set and update ASCII-armored secret from the active {@code Keyring} implementation.
|
||||
*/
|
||||
@Parameters(separators = " =", commandDescription = "Update values of secret in the keyring.")
|
||||
final class UpdateKeyringSecretCommand implements CommandWithRemoteApi {
|
||||
|
||||
@Inject KmsUpdater kmsUpdater;
|
||||
|
||||
@Inject
|
||||
UpdateKmsKeyringCommand() {}
|
||||
UpdateKeyringSecretCommand() {}
|
||||
|
||||
@Parameter(names = "--keyname", description = "The secret to update", required = true)
|
||||
private KeyringKeyName keyringKeyName;
|
||||
+37
-26
@@ -21,6 +21,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
|
||||
import com.google.appengine.tools.mapreduce.Mapper;
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
@@ -29,16 +30,12 @@ import google.registry.mapreduce.inputs.EppResourceInputs;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.rde.RdeStagingAction;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.tools.server.GenerateZoneFilesAction;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
|
||||
/**
|
||||
* A mapreduce that creates synthetic history objects in SQL for all {@link EppResource} objects.
|
||||
@@ -86,12 +83,12 @@ public class CreateSyntheticHistoryEntriesAction implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of shards to run the map-only mapreduce on.
|
||||
* The default number of shards to run the map-only mapreduce on.
|
||||
*
|
||||
* <p>This is much lower than the default of 100, or even 10, because we can afford it being slow
|
||||
* and we want to avoid overloading SQL.
|
||||
* <p>This is much lower than the default of 100 because we can afford it being slow and we want
|
||||
* to avoid overloading SQL.
|
||||
*/
|
||||
private static final int NUM_SHARDS = 3;
|
||||
private static final int NUM_SHARDS = 10;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -105,27 +102,44 @@ public class CreateSyntheticHistoryEntriesAction implements Runnable {
|
||||
.sendLinkToMapreduceConsole(response);
|
||||
}
|
||||
|
||||
// Lifted from HistoryEntryDao
|
||||
private static Optional<? extends HistoryEntry> mostRecentHistoryFromSql(EppResource resource) {
|
||||
// Returns true iff any of the *History objects in SQL contain a representation of this resource
|
||||
// at the point in time that the *History object was created.
|
||||
private static boolean hasHistoryContainingResource(EppResource resource) {
|
||||
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());
|
||||
// The field representing repo ID unfortunately varies by history class
|
||||
String repoIdFieldName = getRepoIdFieldNameFromHistoryClass(historyClass);
|
||||
CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder();
|
||||
CriteriaQuery<? extends HistoryEntry> criteriaQuery =
|
||||
CriteriaQueryBuilder.create(historyClass)
|
||||
.where(repoIdFieldName, criteriaBuilder::equal, resource.getRepoId())
|
||||
.orderByDesc("modificationTime")
|
||||
.build();
|
||||
return jpaTm()
|
||||
.criteriaQuery(criteriaQuery)
|
||||
.setMaxResults(1)
|
||||
.getResultStream()
|
||||
.findFirst();
|
||||
String repoIdFieldName =
|
||||
CaseFormat.LOWER_CAMEL.to(
|
||||
CaseFormat.LOWER_UNDERSCORE,
|
||||
getRepoIdFieldNameFromHistoryClass(historyClass));
|
||||
// The "history" fields in the *History objects are all prefixed with "history_". If
|
||||
// any of the non-"history_" fields are non-null, that means that that row contains
|
||||
// a representation of that EppResource at that point in time. We use creation_time as
|
||||
// a marker since it's the simplest field and all EppResources will have it.
|
||||
return (boolean)
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery(
|
||||
String.format(
|
||||
"SELECT EXISTS (SELECT 1 FROM \"%s\" WHERE %s = :repoId AND"
|
||||
+ " creation_time IS NOT NULL)",
|
||||
historyClass.getSimpleName(), repoIdFieldName))
|
||||
.setParameter("repoId", resource.getRepoId())
|
||||
.getSingleResult();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -164,10 +178,7 @@ public class CreateSyntheticHistoryEntriesAction implements Runnable {
|
||||
EppResource eppResource = auditedOfy().load().key(resourceKey).now();
|
||||
// Only save new history entries if the most recent history for this object in SQL does not
|
||||
// have the resource at that point in time already
|
||||
Optional<? extends HistoryEntry> maybeHistory = mostRecentHistoryFromSql(eppResource);
|
||||
if (maybeHistory
|
||||
.map(history -> !history.getResourceAtPointInTime().isPresent())
|
||||
.orElse(true)) {
|
||||
if (!hasHistoryContainingResource(eppResource)) {
|
||||
ofyTm()
|
||||
.transact(
|
||||
() ->
|
||||
|
||||
+135
@@ -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.";
|
||||
}
|
||||
}
|
||||
+9
@@ -2,6 +2,15 @@
|
||||
"name": "Bulk Delete Cloud Datastore",
|
||||
"description": "An Apache Beam batch pipeline that deletes Cloud Datastore in bulk. This is easier to use than the GCP-provided template.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "registryEnvironment",
|
||||
"label": "The Registry environment.",
|
||||
"helpText": "The Registry environment, required only because the worker initializer demands it.",
|
||||
"is_optional": false,
|
||||
"regexes": [
|
||||
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "kindsToDelete",
|
||||
"label": "The data KINDs to delete.",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
{
|
||||
"name": "registryEnvironment",
|
||||
"label": "The Registry environment.",
|
||||
"helpText": "The Registry environment, required if environment-specific initialization (such as JPA) is needed on worker VMs.",
|
||||
"is_optional": true,
|
||||
"helpText": "The Registry environment.",
|
||||
"is_optional": false,
|
||||
"regexes": [
|
||||
"^[0-9A-Z_]+$"
|
||||
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
{
|
||||
"name": "registryEnvironment",
|
||||
"label": "The Registry environment.",
|
||||
"helpText": "The Registry environment, required if environment-specific initialization (such as JPA) is needed on worker VMs.",
|
||||
"is_optional": true,
|
||||
"helpText": "The Registry environment.",
|
||||
"is_optional": false,
|
||||
"regexes": [
|
||||
"^[0-9A-Z_]+$"
|
||||
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,12 +2,21 @@
|
||||
"name": "RDE/BRDA Deposit Generation",
|
||||
"description": "An Apache Beam pipeline generates RDE or BRDA deposits and deposits them to GCS with GhostRyde encryption.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "registryEnvironment",
|
||||
"label": "The Registry environment.",
|
||||
"helpText": "The Registry environment.",
|
||||
"is_optional": false,
|
||||
"regexes": [
|
||||
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pendings",
|
||||
"label": "The pendings deposits to generate.",
|
||||
"helpText": "A TLD to PendingDeposit map that is serialized and Base64 URL-safe encoded.",
|
||||
"regexes": [
|
||||
"A-Za-z0-9\\-_"
|
||||
"[A-Za-z0-9\\-_]+"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -19,12 +28,12 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "gcsBucket",
|
||||
"label": "The GCS bucket that where the resulting files will be stored.",
|
||||
"name": "rdeStagingBucket",
|
||||
"label": "The GCS bucket that where the resulting files will be stored.",
|
||||
"helpText": "Only the bucket name itself, without the leading \"gs://\".",
|
||||
"is_optional": false,
|
||||
"regexes": [
|
||||
"^[a-zA-Z0-9_\\-]+$"
|
||||
"[a-zA-Z0-9_\\-]+$"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -32,7 +41,7 @@
|
||||
"label": "The PGP public key used to encrypt the RDE/BRDA deposit files.",
|
||||
"helpText": "The key is Base64 URL-safe encoded.",
|
||||
"regexes": [
|
||||
"A-Za-z0-9\\-_"
|
||||
"[A-Za-z0-9\\-_]+"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
{
|
||||
"name": "registryEnvironment",
|
||||
"label": "The Registry environment.",
|
||||
"helpText": "The Registry environment, required if environment-specific initialization (such as JPA) is needed on worker VMs.",
|
||||
"is_optional": true,
|
||||
"helpText": "The Registry environment.",
|
||||
"is_optional": false,
|
||||
"regexes": [
|
||||
"^[0-9A-Z_]+$"
|
||||
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
{@param domainName: string}
|
||||
{@param expirationDate: string}
|
||||
{@param period: string}
|
||||
{@param? reason: string}
|
||||
{@param? requestedByRegistrar: string}
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
@@ -32,6 +34,18 @@
|
||||
<domain:period unit="y">{$period}</domain:period>
|
||||
</domain:renew>
|
||||
</renew>
|
||||
{if $reason or $requestedByRegistrar}
|
||||
<extension>
|
||||
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
{if $reason}
|
||||
<metadata:reason>{$reason}</metadata:reason>
|
||||
{/if}
|
||||
{if $requestedByRegistrar}
|
||||
<metadata:requestedByRegistrar>{$requestedByRegistrar}</metadata:requestedByRegistrar>
|
||||
{/if}
|
||||
</metadata:metadata>
|
||||
</extension>
|
||||
{/if}
|
||||
<clTRID>RegistryTool</clTRID>
|
||||
</command>
|
||||
</epp>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
{@param statusesToRemove: list<string>}
|
||||
{@param newDsData: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
|
||||
{@param reason: string}
|
||||
{@param autorenews: string}
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
@@ -75,6 +76,9 @@
|
||||
</secDNS:add>
|
||||
{/if}
|
||||
</secDNS:update>
|
||||
<superuser:domainUpdate xmlns:superuser="urn:google:params:xml:ns:superuser-1.0">
|
||||
<superuser:autorenews>{$autorenews}</superuser:autorenews>
|
||||
</superuser:domainUpdate>
|
||||
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
<metadata:reason>{$reason}</metadata:reason>
|
||||
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
|
||||
|
||||
+164
-86
@@ -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
|
||||
@@ -639,12 +667,39 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void run_responseStatusIs200() {
|
||||
void run_sentZeroEmail_responseStatusIs200() {
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Done. Sent 0 expiring certificate notification emails in total.");
|
||||
}
|
||||
|
||||
/** Returns a sample registrar with a customized registrar name, client id and certificate* */
|
||||
@TestOfyAndSql
|
||||
void run_sentEmails_responseStatusIs200() throws Exception {
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
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);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Done. Sent 5 expiring certificate notification emails in total.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sample registrar builder with a customized registrar name, client id and certificate.
|
||||
*/
|
||||
private Registrar.Builder createRegistrar(
|
||||
String registrarId,
|
||||
String registrarName,
|
||||
@@ -683,4 +738,27 @@ class SendExpiringCertificateNotificationEmailActionTest {
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/** Returns persisted sample contacts with a customized contact email type. */
|
||||
private ImmutableList<RegistrarContact> persistSampleContacts(
|
||||
Registrar registrar, RegistrarContact.Type emailType) {
|
||||
return persistSimpleResources(
|
||||
ImmutableList.of(
|
||||
new RegistrarContact.Builder()
|
||||
.setParent(registrar)
|
||||
.setName("Will Doe")
|
||||
.setEmailAddress("will@example-registrar.tld")
|
||||
.setPhoneNumber("+1.0105551213")
|
||||
.setFaxNumber("+1.0105551213")
|
||||
.setTypes(ImmutableSet.of(emailType))
|
||||
.build(),
|
||||
new RegistrarContact.Builder()
|
||||
.setParent(registrar)
|
||||
.setName("Will Smith")
|
||||
.setEmailAddress("will@test-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551213")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setTypes(ImmutableSet.of(emailType))
|
||||
.build()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.batch;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static org.apache.http.HttpStatus.SC_OK;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.ContactAuthInfo;
|
||||
import google.registry.model.contact.ContactBase;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Disclose;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.eppcommon.PresenceMarker;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.InjectExtension;
|
||||
import google.registry.testing.TestSqlOnly;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for {@link WipeOutContactHistoryPiiAction}. */
|
||||
@DualDatabaseTest
|
||||
class WipeOutContactHistoryPiiActionTest {
|
||||
|
||||
private static final int TEST_BATCH_SIZE = 20;
|
||||
private static final int MIN_MONTHS_BEFORE_WIPE_OUT = 18;
|
||||
private static final ContactResource defaultContactResource =
|
||||
new ContactResource.Builder()
|
||||
.setContactId("sh8013")
|
||||
.setRepoId("2FF-ROID")
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.CLIENT_DELETE_PROHIBITED))
|
||||
.setLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.LOCALIZED)
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 Grand Ave"))
|
||||
.build())
|
||||
.build())
|
||||
.setInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.INTERNATIONALIZED)
|
||||
.setName("John Doe")
|
||||
.setOrg("Example Inc.")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 Example Dr.", "Suite 100"))
|
||||
.setCity("Dulles")
|
||||
.setState("VA")
|
||||
.setZip("20166-6503")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.setVoiceNumber(
|
||||
new ContactPhoneNumber.Builder()
|
||||
.setPhoneNumber("+1.7035555555")
|
||||
.setExtension("1234")
|
||||
.build())
|
||||
.setFaxNumber(new ContactPhoneNumber.Builder().setPhoneNumber("+1.7035555556").build())
|
||||
.setEmailAddress("jdoe@example.com")
|
||||
.setPersistedCurrentSponsorRegistrarId("TheRegistrar")
|
||||
.setCreationRegistrarId("NewRegistrar")
|
||||
.setLastEppUpdateRegistrarId("NewRegistrar")
|
||||
.setCreationTimeForTest(DateTime.parse("1999-04-03T22:00:00.0Z"))
|
||||
.setLastEppUpdateTime(DateTime.parse("1999-12-03T09:00:00.0Z"))
|
||||
.setLastTransferTime(DateTime.parse("2000-04-08T09:00:00.0Z"))
|
||||
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("2fooBAR")))
|
||||
.setDisclose(
|
||||
new Disclose.Builder()
|
||||
.setFlag(true)
|
||||
.setVoice(new PresenceMarker())
|
||||
.setEmail(new PresenceMarker())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@RegisterExtension
|
||||
public final AppEngineExtension appEngine =
|
||||
AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build();
|
||||
|
||||
@RegisterExtension public final InjectExtension inject = new InjectExtension();
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2021-08-26T20:21:22Z"));
|
||||
|
||||
private FakeResponse response;
|
||||
private WipeOutContactHistoryPiiAction action;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
response = new FakeResponse();
|
||||
action =
|
||||
new WipeOutContactHistoryPiiAction(
|
||||
clock, MIN_MONTHS_BEFORE_WIPE_OUT, TEST_BATCH_SIZE, response);
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void getAllHistoryEntitiesOlderThan_returnsAllPersistedEntities() {
|
||||
ImmutableList<ContactHistory> expectedToBeWipedOut =
|
||||
persistLotsOfContactHistoryEntities(
|
||||
20, MIN_MONTHS_BEFORE_WIPE_OUT + 1, 0, defaultContactResource);
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
assertThat(
|
||||
action.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT)))
|
||||
.containsExactlyElementsIn(expectedToBeWipedOut));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void getAllHistoryEntitiesOlderThan_returnsOnlyOldEnoughPersistedEntities() {
|
||||
ImmutableList<ContactHistory> expectedToBeWipedOut =
|
||||
persistLotsOfContactHistoryEntities(
|
||||
19, MIN_MONTHS_BEFORE_WIPE_OUT + 2, 0, defaultContactResource);
|
||||
|
||||
// persisted entities that should not be part of the actual result
|
||||
persistLotsOfContactHistoryEntities(
|
||||
15, 17, MIN_MONTHS_BEFORE_WIPE_OUT - 1, defaultContactResource);
|
||||
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
assertThat(
|
||||
action.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT)))
|
||||
.containsExactlyElementsIn(expectedToBeWipedOut));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void run_withNoEntitiesToWipeOut_success() {
|
||||
assertThat(
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
action
|
||||
.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))
|
||||
.count()))
|
||||
.isEqualTo(0);
|
||||
action.run();
|
||||
|
||||
assertThat(
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
action
|
||||
.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))
|
||||
.count()))
|
||||
.isEqualTo(0);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Done. Wiped out PII of 0 ContactHistory entities in total.");
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void run_withOneBatchOfEntities_success() {
|
||||
int numOfMonthsFromNow = MIN_MONTHS_BEFORE_WIPE_OUT + 2;
|
||||
ImmutableList<ContactHistory> expectedToBeWipedOut =
|
||||
persistLotsOfContactHistoryEntities(20, numOfMonthsFromNow, 0, defaultContactResource);
|
||||
|
||||
// The query should return a stream of all persisted entities.
|
||||
assertThat(
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
action
|
||||
.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))
|
||||
.count()))
|
||||
.isEqualTo(expectedToBeWipedOut.size());
|
||||
|
||||
assertAllEntitiesContainPii(DatabaseHelper.loadByEntitiesIfPresent(expectedToBeWipedOut));
|
||||
|
||||
action.run();
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Done. Wiped out PII of 20 ContactHistory entities in total.");
|
||||
|
||||
// The query should return an empty stream after the wipe out action.
|
||||
assertThat(
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
action
|
||||
.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))
|
||||
.count()))
|
||||
.isEqualTo(0);
|
||||
|
||||
assertAllPiiFieldsAreWipedOut(DatabaseHelper.loadByEntitiesIfPresent(expectedToBeWipedOut));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void run_withMultipleBatches_numOfEntitiesAsNonMultipleOfBatchSize_success() {
|
||||
int numOfMonthsFromNow = MIN_MONTHS_BEFORE_WIPE_OUT + 2;
|
||||
ImmutableList<ContactHistory> expectedToBeWipedOut =
|
||||
persistLotsOfContactHistoryEntities(56, numOfMonthsFromNow, 0, defaultContactResource);
|
||||
|
||||
// The query should return a subset of all persisted data.
|
||||
assertThat(
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
action
|
||||
.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))
|
||||
.count()))
|
||||
.isEqualTo(TEST_BATCH_SIZE);
|
||||
|
||||
assertAllEntitiesContainPii(DatabaseHelper.loadByEntitiesIfPresent(expectedToBeWipedOut));
|
||||
action.run();
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Done. Wiped out PII of 56 ContactHistory entities in total.");
|
||||
|
||||
// The query should return an empty stream after the wipe out action.
|
||||
assertThat(
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
action
|
||||
.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))
|
||||
.count()))
|
||||
.isEqualTo(0);
|
||||
|
||||
assertAllPiiFieldsAreWipedOut(DatabaseHelper.loadByEntitiesIfPresent(expectedToBeWipedOut));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void run_withMultipleBatches_numOfEntitiesAsMultiplesOfBatchSize_success() {
|
||||
int numOfMonthsFromNow = MIN_MONTHS_BEFORE_WIPE_OUT + 2;
|
||||
ImmutableList<ContactHistory> expectedToBeWipedOut =
|
||||
persistLotsOfContactHistoryEntities(
|
||||
TEST_BATCH_SIZE * 2, numOfMonthsFromNow, 0, defaultContactResource);
|
||||
|
||||
// The query should return a subset of all persisted data.
|
||||
assertThat(
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
action
|
||||
.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))
|
||||
.count()))
|
||||
.isEqualTo(TEST_BATCH_SIZE);
|
||||
|
||||
assertAllEntitiesContainPii(DatabaseHelper.loadByEntitiesIfPresent(expectedToBeWipedOut));
|
||||
action.run();
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Done. Wiped out PII of 40 ContactHistory entities in total.");
|
||||
|
||||
// The query should return an empty stream after the wipe out action.
|
||||
assertThat(
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
action
|
||||
.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))
|
||||
.count()))
|
||||
.isEqualTo(0);
|
||||
|
||||
assertAllPiiFieldsAreWipedOut(DatabaseHelper.loadByEntitiesIfPresent(expectedToBeWipedOut));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void wipeOutContactHistoryData_wipesOutNoEntity() {
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
assertThat(
|
||||
action.wipeOutContactHistoryData(
|
||||
action.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT))))
|
||||
.isEqualTo(0);
|
||||
});
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void wipeOutContactHistoryData_wipesOutMultipleEntities() {
|
||||
int numOfMonthsFromNow = MIN_MONTHS_BEFORE_WIPE_OUT + 3;
|
||||
ImmutableList<ContactHistory> expectedToBeWipedOut =
|
||||
persistLotsOfContactHistoryEntities(20, numOfMonthsFromNow, 0, defaultContactResource);
|
||||
|
||||
assertAllEntitiesContainPii(DatabaseHelper.loadByEntitiesIfPresent(expectedToBeWipedOut));
|
||||
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
action.wipeOutContactHistoryData(
|
||||
action.getNextContactHistoryEntitiesWithPiiBatch(
|
||||
clock.nowUtc().minusMonths(MIN_MONTHS_BEFORE_WIPE_OUT)));
|
||||
});
|
||||
|
||||
assertAllPiiFieldsAreWipedOut(DatabaseHelper.loadByEntitiesIfPresent(expectedToBeWipedOut));
|
||||
}
|
||||
|
||||
/** persists a number of ContactHistory entities for load and query testing. */
|
||||
ImmutableList<ContactHistory> persistLotsOfContactHistoryEntities(
|
||||
int numOfEntities, int minusMonths, int minusDays, ContactResource contact) {
|
||||
ImmutableList.Builder<ContactHistory> expectedEntitesBuilder = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < numOfEntities; i++) {
|
||||
expectedEntitesBuilder.add(
|
||||
persistResource(
|
||||
new ContactHistory()
|
||||
.asBuilder()
|
||||
.setRegistrarId("NewRegistrar")
|
||||
.setModificationTime(clock.nowUtc().minusMonths(minusMonths).minusDays(minusDays))
|
||||
.setType(ContactHistory.Type.CONTACT_DELETE)
|
||||
.setContact(persistResource(contact))
|
||||
.build()));
|
||||
}
|
||||
return expectedEntitesBuilder.build();
|
||||
}
|
||||
|
||||
boolean areAllPiiFieldsWiped(ContactBase contactBase) {
|
||||
return contactBase.getEmailAddress() == null
|
||||
&& contactBase.getFaxNumber() == null
|
||||
&& contactBase.getInternationalizedPostalInfo() == null
|
||||
&& contactBase.getLocalizedPostalInfo() == null
|
||||
&& contactBase.getVoiceNumber() == null;
|
||||
}
|
||||
|
||||
boolean containsPii(ContactBase contactBase) {
|
||||
return contactBase.getEmailAddress() != null
|
||||
|| contactBase.getFaxNumber() != null
|
||||
|| contactBase.getInternationalizedPostalInfo() != null
|
||||
|| contactBase.getLocalizedPostalInfo() != null
|
||||
|| contactBase.getVoiceNumber() != null;
|
||||
}
|
||||
|
||||
void assertAllPiiFieldsAreWipedOut(ImmutableList<ContactHistory> entities) {
|
||||
ImmutableList.Builder<ContactHistory> notWipedEntities = new ImmutableList.Builder<>();
|
||||
for (ContactHistory entity : entities) {
|
||||
if (!areAllPiiFieldsWiped(entity.getContactBase().get())) {
|
||||
notWipedEntities.add(entity);
|
||||
}
|
||||
}
|
||||
assertWithMessage("Not all PII fields of the contact history entities were wiped.")
|
||||
.that(notWipedEntities.build())
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
void assertAllEntitiesContainPii(ImmutableList<ContactHistory> entities) {
|
||||
ImmutableList.Builder<ContactHistory> entitiesWithNoPii = new ImmutableList.Builder<>();
|
||||
for (ContactHistory entity : entities) {
|
||||
if (!containsPii(entity.getContactBase().get())) {
|
||||
entitiesWithNoPii.add(entity);
|
||||
}
|
||||
}
|
||||
assertWithMessage("Not all contact history entities contain PII.")
|
||||
.that(entitiesWithNoPii.build())
|
||||
.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -40,13 +40,13 @@ public abstract class BeamActionTestBase {
|
||||
protected Dataflow dataflow = mock(Dataflow.class);
|
||||
private Projects projects = mock(Projects.class);
|
||||
private Locations locations = mock(Locations.class);
|
||||
private FlexTemplates templates = mock(FlexTemplates.class);
|
||||
protected FlexTemplates templates = mock(FlexTemplates.class);
|
||||
protected Launch launch = mock(Launch.class);
|
||||
private LaunchFlexTemplateResponse launchResponse =
|
||||
new LaunchFlexTemplateResponse().setJob(new Job().setId("jobid"));
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
protected void beforeEach() throws Exception {
|
||||
when(dataflow.projects()).thenReturn(projects);
|
||||
when(projects.locations()).thenReturn(locations);
|
||||
when(locations.flexTemplates()).thenReturn(templates);
|
||||
|
||||
@@ -62,7 +62,8 @@ public class RegistryJpaReadTest {
|
||||
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT - 1)
|
||||
final transient DatastoreEntityExtension datastore = new DatastoreEntityExtension();
|
||||
final transient DatastoreEntityExtension datastore =
|
||||
new DatastoreEntityExtension().allThreads(true);
|
||||
|
||||
@RegisterExtension
|
||||
final transient JpaIntegrationTestExtension database =
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ package google.registry.beam.initsql;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
|
||||
import com.google.appengine.api.datastore.DatastoreService;
|
||||
import com.google.appengine.api.datastore.DatastoreServiceFactory;
|
||||
@@ -75,7 +75,8 @@ public final class BackupTestStore implements AutoCloseable {
|
||||
/** Returns the timestamp of the transaction. */
|
||||
long transact(Iterable<Object> deletes, Iterable<Object> newOrUpdated) {
|
||||
long timestamp = fakeClock.nowUtc().getMillis();
|
||||
tm().transact(
|
||||
ofyTm()
|
||||
.transact(
|
||||
() -> {
|
||||
auditedOfy().delete().entities(deletes);
|
||||
auditedOfy().save().entities(newOrUpdated);
|
||||
@@ -91,7 +92,7 @@ public final class BackupTestStore implements AutoCloseable {
|
||||
@SafeVarargs
|
||||
public final long insertOrUpdate(Object... entities) {
|
||||
long timestamp = fakeClock.nowUtc().getMillis();
|
||||
tm().transact(() -> auditedOfy().save().entities(entities).now());
|
||||
ofyTm().transact(() -> auditedOfy().save().entities(entities).now());
|
||||
fakeClock.advanceOneMilli();
|
||||
return timestamp;
|
||||
}
|
||||
@@ -100,7 +101,7 @@ public final class BackupTestStore implements AutoCloseable {
|
||||
@SafeVarargs
|
||||
public final long delete(Object... entities) {
|
||||
long timestamp = fakeClock.nowUtc().getMillis();
|
||||
tm().transact(() -> auditedOfy().delete().entities(entities).now());
|
||||
ofyTm().transact(() -> auditedOfy().delete().entities(entities).now());
|
||||
fakeClock.advanceOneMilli();
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class CommitLogTransformsTest implements Serializable {
|
||||
@RegisterExtension
|
||||
@Order(value = 1)
|
||||
final transient DatastoreEntityExtension datastoreEntityExtension =
|
||||
new DatastoreEntityExtension();
|
||||
new DatastoreEntityExtension().allThreads(true);
|
||||
|
||||
@RegisterExtension
|
||||
final transient TestPipelineExtension testPipeline =
|
||||
|
||||
@@ -30,7 +30,6 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.backup.AppEngineEnvironment;
|
||||
import google.registry.beam.TestPipelineExtension;
|
||||
import google.registry.flows.domain.DomainFlowUtils;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
@@ -106,7 +105,8 @@ class InitSqlPipelineTest {
|
||||
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT - 1)
|
||||
final transient DatastoreEntityExtension datastore = new DatastoreEntityExtension();
|
||||
final transient DatastoreEntityExtension datastore =
|
||||
new DatastoreEntityExtension("test").allThreads(true);
|
||||
|
||||
@RegisterExtension final transient InjectExtension injectExtension = new InjectExtension();
|
||||
|
||||
@@ -331,20 +331,18 @@ class InitSqlPipelineTest {
|
||||
.as(InitSqlPipelineOptions.class);
|
||||
InitSqlPipeline initSqlPipeline = new InitSqlPipeline(options);
|
||||
initSqlPipeline.run(testPipeline).waitUntilFinish();
|
||||
try (AppEngineEnvironment env = new AppEngineEnvironment("test")) {
|
||||
assertHostResourceEquals(
|
||||
jpaTm().transact(() -> jpaTm().loadByKey(hostResource.createVKey())), hostResource);
|
||||
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(Registrar.class)))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence("lastUpdateTime"))
|
||||
.containsExactly(registrar1, registrar2);
|
||||
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(ContactResource.class)))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence("revisions", "updateTimestamp"))
|
||||
.containsExactly(contact1, contact2);
|
||||
assertDomainEquals(jpaTm().transact(() -> jpaTm().loadByKey(domain.createVKey())), domain);
|
||||
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(Cursor.class)))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence())
|
||||
.containsExactly(globalCursor, tldCursor);
|
||||
}
|
||||
assertHostResourceEquals(
|
||||
jpaTm().transact(() -> jpaTm().loadByKey(hostResource.createVKey())), hostResource);
|
||||
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(Registrar.class)))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence("lastUpdateTime"))
|
||||
.containsExactly(registrar1, registrar2);
|
||||
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(ContactResource.class)))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence("revisions", "updateTimestamp"))
|
||||
.containsExactly(contact1, contact2);
|
||||
assertDomainEquals(jpaTm().transact(() -> jpaTm().loadByKey(domain.createVKey())), domain);
|
||||
assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(Cursor.class)))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence())
|
||||
.containsExactly(globalCursor, tldCursor);
|
||||
}
|
||||
|
||||
private static void assertHostResourceEquals(HostResource actual, HostResource expected) {
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.truth.Truth;
|
||||
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
|
||||
import google.registry.backup.AppEngineEnvironment;
|
||||
import google.registry.backup.VersionedEntity;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
@@ -124,20 +123,18 @@ public final class InitSqlTestUtils {
|
||||
public void processElement(
|
||||
@Element KV<String, Iterable<VersionedEntity>> input,
|
||||
OutputReceiver<String> out) {
|
||||
try (AppEngineEnvironment env = new AppEngineEnvironment()) {
|
||||
ImmutableList<KV<Long, Object>> actual =
|
||||
Streams.stream(input.getValue())
|
||||
.map(InitSqlTestUtils::rawEntityToOfyWithTimestamp)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
try {
|
||||
Truth.assertThat(actual)
|
||||
.containsExactlyElementsIn(
|
||||
Stream.of(expected)
|
||||
.map(InitSqlTestUtils::expectedToOfyWithTimestamp)
|
||||
.collect(ImmutableList.toImmutableList()));
|
||||
} catch (AssertionError e) {
|
||||
out.output(e.toString());
|
||||
}
|
||||
ImmutableList<KV<Long, Object>> actual =
|
||||
Streams.stream(input.getValue())
|
||||
.map(InitSqlTestUtils::rawEntityToOfyWithTimestamp)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
try {
|
||||
Truth.assertThat(actual)
|
||||
.containsExactlyElementsIn(
|
||||
Stream.of(expected)
|
||||
.map(InitSqlTestUtils::expectedToOfyWithTimestamp)
|
||||
.collect(ImmutableList.toImmutableList()));
|
||||
} catch (AssertionError e) {
|
||||
out.output(e.toString());
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -95,7 +95,7 @@ class LoadDatastoreSnapshotTest {
|
||||
@RegisterExtension
|
||||
@Order(value = 1)
|
||||
final transient DatastoreEntityExtension datastoreEntityExtension =
|
||||
new DatastoreEntityExtension();
|
||||
new DatastoreEntityExtension().allThreads(true);
|
||||
|
||||
@RegisterExtension
|
||||
final transient TestPipelineExtension testPipeline =
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.beam.initsql;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.beam.initsql.Transforms.repairBadData;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.newDomainBase;
|
||||
import static google.registry.testing.DatabaseHelper.newHostResource;
|
||||
|
||||
import com.google.appengine.api.datastore.Entity;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for {@link Transforms}. */
|
||||
public class TransformsTest {
|
||||
|
||||
@RegisterExtension
|
||||
public final AppEngineExtension appEngine =
|
||||
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
createTld("tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRepairBadData_canonicalizesDomainName() {
|
||||
DomainBase domain = newDomainBase("foobar.tld");
|
||||
Entity entity = ofyTm().transact(() -> auditedOfy().toEntity(domain));
|
||||
entity.setIndexedProperty("fullyQualifiedDomainName", "FOOBäR.TLD");
|
||||
assertThat(((DomainBase) auditedOfy().toPojo(repairBadData(entity))).getDomainName())
|
||||
.isEqualTo("xn--foobr-jra.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRepairBadData_canonicalizesHostName() {
|
||||
HostResource host = newHostResource("baz.foobar.tld");
|
||||
Entity entity = ofyTm().transact(() -> auditedOfy().toEntity(host));
|
||||
entity.setIndexedProperty(
|
||||
"fullyQualifiedHostName", "b̴̹͔͓̣̭̫͇͕̻̬̱͇͗͌́̆̋͒a̶̬̖͚̋̈́̽̇͝͠z̵͠.FOOBäR.TLD");
|
||||
assertThat(((HostResource) auditedOfy().toPojo(repairBadData(entity))).getHostName())
|
||||
.isEqualTo(
|
||||
"xn--baz-kdcb2ajgzb4jtg6doej4e6b9am7c7b6c5nd4k7gpa2a9a7dufyewec.xn--foobr-jra.tld");
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,6 @@ package google.registry.beam.invoicing;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.tld.Registry.TldState.GENERAL_AVAILABILITY;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.removeTmOverrideForTest;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.setTmForTest;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.newRegistry;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
@@ -48,6 +45,7 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT
|
||||
import google.registry.testing.DatastoreEntityExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.TestDataHelper;
|
||||
import google.registry.testing.TmOverrideExtension;
|
||||
import google.registry.util.ResourceUtils;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
@@ -77,6 +75,25 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
/** Unit tests for {@link InvoicingPipeline}. */
|
||||
class InvoicingPipelineTest {
|
||||
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT - 1)
|
||||
final transient DatastoreEntityExtension datastore =
|
||||
new DatastoreEntityExtension().allThreads(true);
|
||||
|
||||
@RegisterExtension
|
||||
final TestPipelineExtension pipeline =
|
||||
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension database =
|
||||
new JpaTestExtensions.Builder().withClock(new FakeClock()).buildIntegrationTestExtension();
|
||||
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT + 1)
|
||||
TmOverrideExtension tmOverrideExtension = TmOverrideExtension.withJpa();
|
||||
|
||||
@TempDir Path tmpDir;
|
||||
|
||||
private static final String BILLING_BUCKET_URL = "billing_bucket";
|
||||
private static final String YEAR_MONTH = "2017-10";
|
||||
private static final String INVOICE_FILE_PREFIX = "REG-INV";
|
||||
@@ -225,20 +242,6 @@ class InvoicingPipelineTest {
|
||||
"2017-10-01,2018-09-30,456,20.50,USD,10125,1,PURCHASE,bestdomains - test,1,"
|
||||
+ "RENEW | TLD: test | TERM: 1-year,20.50,USD,116688");
|
||||
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT - 1)
|
||||
final transient DatastoreEntityExtension datastore = new DatastoreEntityExtension();
|
||||
|
||||
@RegisterExtension
|
||||
final TestPipelineExtension pipeline =
|
||||
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension database =
|
||||
new JpaTestExtensions.Builder().withClock(new FakeClock()).buildIntegrationTestExtension();
|
||||
|
||||
@TempDir Path tmpDir;
|
||||
|
||||
private final InvoicingPipelineOptions options =
|
||||
PipelineOptionsFactory.create().as(InvoicingPipelineOptions.class);
|
||||
|
||||
@@ -260,13 +263,12 @@ class InvoicingPipelineTest {
|
||||
String query = InvoicingPipeline.makeQuery("2017-10", "my-project-id");
|
||||
assertThat(query)
|
||||
.isEqualTo(TestDataHelper.loadFile(this.getClass(), "billing_events_test.sql"));
|
||||
// This is necessary because the TestPipelineExtension verifies that the pipelien is run.
|
||||
// This is necessary because the TestPipelineExtension verifies that the pipeline is run.
|
||||
pipeline.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fullSqlPipeline() throws Exception {
|
||||
setTmForTest(jpaTm());
|
||||
setupCloudSql();
|
||||
options.setDatabase("CLOUD_SQL");
|
||||
InvoicingPipeline invoicingPipeline = new InvoicingPipeline(options);
|
||||
@@ -281,18 +283,15 @@ class InvoicingPipelineTest {
|
||||
+ "UnitPriceCurrency,PONumber");
|
||||
assertThat(overallInvoice.subList(1, overallInvoice.size()))
|
||||
.containsExactlyElementsIn(EXPECTED_INVOICE_OUTPUT);
|
||||
removeTmOverrideForTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_readFromCloudSql() throws Exception {
|
||||
setTmForTest(jpaTm());
|
||||
setupCloudSql();
|
||||
PCollection<BillingEvent> billingEvents = InvoicingPipeline.readFromCloudSql(options, pipeline);
|
||||
billingEvents = billingEvents.apply(new ChangeDomainRepo());
|
||||
PAssert.that(billingEvents).containsInAnyOrder(INPUT_EVENTS);
|
||||
pipeline.run().waitUntilFinish();
|
||||
removeTmOverrideForTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -22,7 +22,6 @@ import static google.registry.beam.rde.RdePipeline.encodePendings;
|
||||
import static google.registry.model.common.Cursor.CursorType.RDE_STAGING;
|
||||
import static google.registry.model.rde.RdeMode.FULL;
|
||||
import static google.registry.model.rde.RdeMode.THIN;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.rde.RdeResourceType.CONTACT;
|
||||
import static google.registry.rde.RdeResourceType.DOMAIN;
|
||||
@@ -64,14 +63,16 @@ import google.registry.model.tld.Registry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||
import google.registry.rde.DepositFragment;
|
||||
import google.registry.rde.Ghostryde;
|
||||
import google.registry.rde.PendingDeposit;
|
||||
import google.registry.rde.RdeResourceType;
|
||||
import google.registry.testing.CloudTasksHelper;
|
||||
import google.registry.testing.CloudTasksHelper.TaskMatcher;
|
||||
import google.registry.testing.DatastoreEntityExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeKeyringModule;
|
||||
import google.registry.testing.TmOverrideExtension;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -86,7 +87,6 @@ import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -134,6 +134,8 @@ public class RdePipelineTest {
|
||||
|
||||
private final GcsUtils gcsUtils = new GcsUtils(LocalStorageHelper.getOptions());
|
||||
|
||||
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
|
||||
|
||||
private final PGPPublicKey encryptionKey =
|
||||
new FakeKeyringModule().get().getRdeStagingEncryptionKey();
|
||||
|
||||
@@ -152,6 +154,10 @@ public class RdePipelineTest {
|
||||
final JpaIntegrationTestExtension database =
|
||||
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
|
||||
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT + 1)
|
||||
TmOverrideExtension tmOverrideExtension = TmOverrideExtension.withJpa();
|
||||
|
||||
@RegisterExtension
|
||||
final TestPipelineExtension pipeline =
|
||||
TestPipelineExtension.fromOptions(options).enableAbandonedNodeEnforcement(true);
|
||||
@@ -160,7 +166,6 @@ public class RdePipelineTest {
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
TransactionManagerFactory.setTmForTest(jpaTm());
|
||||
loadInitialData();
|
||||
|
||||
// Two real registrars have been created by loadInitialData(), named "New Registrar" and "The
|
||||
@@ -212,14 +217,9 @@ public class RdePipelineTest {
|
||||
options.setValidationMode("LENIENT");
|
||||
options.setStagingKey(
|
||||
BaseEncoding.base64Url().encode(PgpHelper.convertPublicKeyToBytes(encryptionKey)));
|
||||
options.setGcsBucket("gcs-bucket");
|
||||
options.setRdeStagingBucket("gcs-bucket");
|
||||
options.setJobName("rde-job");
|
||||
rdePipeline = new RdePipeline(options, gcsUtils);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
TransactionManagerFactory.removeTmOverrideForTest();
|
||||
rdePipeline = new RdePipeline(options, gcsUtils, cloudTasksHelper.getTestCloudTasksUtils());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -314,6 +314,21 @@ public class RdePipelineTest {
|
||||
assertThat(loadCursorTime(CursorType.RDE_STAGING))
|
||||
.isEquivalentAccordingToCompareTo(now.plus(Duration.standardDays(1)));
|
||||
assertThat(loadRevision(now, FULL)).isEqualTo(1);
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"brda",
|
||||
new TaskMatcher()
|
||||
.url("/_dr/task/brdaCopy")
|
||||
.service("backend")
|
||||
.param("tld", "soy")
|
||||
.param("watermark", now.toString())
|
||||
.param("prefix", "rde-job/"));
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
"rde-upload",
|
||||
new TaskMatcher()
|
||||
.url("/_dr/task/rdeUpload")
|
||||
.service("backend")
|
||||
.param("tld", "soy")
|
||||
.param("prefix", "rde-job/"));
|
||||
}
|
||||
|
||||
// The GCS folder listing can be a bit flaky, so retry if necessary
|
||||
@@ -337,6 +352,7 @@ public class RdePipelineTest {
|
||||
|
||||
assertThat(loadCursorTime(CursorType.RDE_STAGING)).isEquivalentAccordingToCompareTo(now);
|
||||
assertThat(loadRevision(now, FULL)).isEqualTo(0);
|
||||
cloudTasksHelper.assertNoTasksEnqueued("brda", "rde-upload");
|
||||
}
|
||||
|
||||
private void verifyFiles(
|
||||
|
||||
@@ -18,8 +18,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.immutableObjectCorrespondence;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.removeTmOverrideForTest;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.setTmForTest;
|
||||
import static google.registry.testing.AppEngineExtension.makeRegistrar1;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
@@ -52,6 +50,7 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT
|
||||
import google.registry.testing.DatastoreEntityExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeSleeper;
|
||||
import google.registry.testing.TmOverrideExtension;
|
||||
import google.registry.util.ResourceUtils;
|
||||
import google.registry.util.Retrier;
|
||||
import java.io.File;
|
||||
@@ -116,7 +115,8 @@ class Spec11PipelineTest {
|
||||
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT - 1)
|
||||
final transient DatastoreEntityExtension datastore = new DatastoreEntityExtension();
|
||||
final transient DatastoreEntityExtension datastore =
|
||||
new DatastoreEntityExtension().allThreads(true);
|
||||
|
||||
@TempDir Path tmpDir;
|
||||
|
||||
@@ -128,6 +128,10 @@ class Spec11PipelineTest {
|
||||
final JpaIntegrationTestExtension database =
|
||||
new JpaTestExtensions.Builder().withClock(new FakeClock()).buildIntegrationTestExtension();
|
||||
|
||||
@RegisterExtension
|
||||
@Order(Order.DEFAULT + 1)
|
||||
TmOverrideExtension tmOverrideExtension = TmOverrideExtension.withJpa();
|
||||
|
||||
private final Spec11PipelineOptions options =
|
||||
PipelineOptionsFactory.create().as(Spec11PipelineOptions.class);
|
||||
|
||||
@@ -232,7 +236,6 @@ class Spec11PipelineTest {
|
||||
}
|
||||
|
||||
private void setupCloudSql() {
|
||||
setTmForTest(jpaTm());
|
||||
persistNewRegistrar("TheRegistrar");
|
||||
persistNewRegistrar("NewRegistrar");
|
||||
Registrar registrar1 =
|
||||
@@ -272,7 +275,6 @@ class Spec11PipelineTest {
|
||||
persistResource(createDomain("no-email.com", "2A4BA9BBC-COM", registrar2, contact2));
|
||||
persistResource(
|
||||
createDomain("anti-anti-anti-virus.dev", "555666888-DEV", registrar3, contact3));
|
||||
removeTmOverrideForTest();
|
||||
}
|
||||
|
||||
private void verifySaveToGcs() throws Exception {
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.FlowUtils.UnknownCurrencyEppException;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
@@ -533,6 +534,55 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, DomainBa
|
||||
.build());
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_metaData_withReasonAndRequestedByRegistrar() throws Exception {
|
||||
eppRequestSource = EppRequestSource.TOOL;
|
||||
setEppInput(
|
||||
"domain_renew_metadata_with_reason_and_requestedByRegistrar.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN",
|
||||
"example.tld",
|
||||
"EXPDATE",
|
||||
"2000-04-03",
|
||||
"YEARS",
|
||||
"1",
|
||||
"REASON",
|
||||
"domain-renew-test",
|
||||
"REQUESTED",
|
||||
"false"));
|
||||
persistDomain();
|
||||
runFlow();
|
||||
DomainBase domain = reloadResourceByForeignKey();
|
||||
assertAboutDomains()
|
||||
.that(domain)
|
||||
.hasOneHistoryEntryEachOfTypes(
|
||||
HistoryEntry.Type.DOMAIN_CREATE, HistoryEntry.Type.DOMAIN_RENEW);
|
||||
assertAboutHistoryEntries()
|
||||
.that(getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_RENEW))
|
||||
.hasMetadataReason("domain-renew-test")
|
||||
.and()
|
||||
.hasMetadataRequestedByRegistrar(false);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_metaData_withRequestedByRegistrarOnly() throws Exception {
|
||||
eppRequestSource = EppRequestSource.TOOL;
|
||||
setEppInput("domain_renew_metadata_with_requestedByRegistrar_only.xml");
|
||||
|
||||
persistDomain();
|
||||
runFlow();
|
||||
DomainBase domain1 = reloadResourceByForeignKey();
|
||||
assertAboutDomains()
|
||||
.that(domain1)
|
||||
.hasOneHistoryEntryEachOfTypes(
|
||||
HistoryEntry.Type.DOMAIN_CREATE, HistoryEntry.Type.DOMAIN_RENEW);
|
||||
assertAboutHistoryEntries()
|
||||
.that(getOnlyHistoryEntryOfType(domain1, HistoryEntry.Type.DOMAIN_RENEW))
|
||||
.hasMetadataReason(null)
|
||||
.and()
|
||||
.hasMetadataRequestedByRegistrar(true);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testFailure_neverExisted() throws Exception {
|
||||
ResourceDoesNotExistException thrown =
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -696,7 +696,7 @@ public class DomainBaseTest extends EntityTestCase {
|
||||
IllegalArgumentException.class, () -> domain.asBuilder().setDomainName("AAA.BBB"));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("Domain name must be in puny-coded, lower-case form");
|
||||
.isEqualTo("Domain name AAA.BBB not in puny-coded, lower-case form");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -706,7 +706,7 @@ public class DomainBaseTest extends EntityTestCase {
|
||||
IllegalArgumentException.class, () -> domain.asBuilder().setDomainName("みんな.みんな"));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("Domain name must be in puny-coded, lower-case form");
|
||||
.isEqualTo("Domain name みんな.みんな not in puny-coded, lower-case form");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -24,11 +24,15 @@ import static google.registry.testing.DatabaseHelper.newContactResource;
|
||||
import static google.registry.testing.DatabaseHelper.newContactResourceWithRoid;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.EntityTestCase;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.ContactBase;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -130,6 +134,60 @@ public class ContactHistoryTest extends EntityTestCase {
|
||||
.hasFieldsEqualTo(jpaTm().loadByEntity(contactHistory).getContactBase().get()));
|
||||
}
|
||||
|
||||
@TestSqlOnly
|
||||
void testWipeOutPii_assertsAllPiiFieldsAreNull() {
|
||||
ContactHistory originalEntity =
|
||||
createContactHistory(
|
||||
new ContactResource.Builder()
|
||||
.setRepoId("1-FOOBAR")
|
||||
.setLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.LOCALIZED)
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
|
||||
.setCity("New York")
|
||||
.setState("NY")
|
||||
.setZip("10011")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.setInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.INTERNATIONALIZED)
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
|
||||
.setCity("New York")
|
||||
.setState("NY")
|
||||
.setZip("10011")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.setVoiceNumber(new ContactPhoneNumber.Builder().setPhoneNumber("867-5309").build())
|
||||
.setFaxNumber(
|
||||
new ContactPhoneNumber.Builder()
|
||||
.setPhoneNumber("867-5309")
|
||||
.setExtension("1000")
|
||||
.build())
|
||||
.setEmailAddress("test@example.com")
|
||||
.build());
|
||||
|
||||
assertThat(originalEntity.getContactBase().get().getEmailAddress()).isNotNull();
|
||||
assertThat(originalEntity.getContactBase().get().getLocalizedPostalInfo()).isNotNull();
|
||||
assertThat(originalEntity.getContactBase().get().getInternationalizedPostalInfo()).isNotNull();
|
||||
assertThat(originalEntity.getContactBase().get().getVoiceNumber()).isNotNull();
|
||||
assertThat(originalEntity.getContactBase().get().getFaxNumber()).isNotNull();
|
||||
|
||||
ContactHistory wipedEntity = originalEntity.asBuilder().wipeOutPii().build();
|
||||
|
||||
assertThat(wipedEntity.getContactBase().get().getEmailAddress()).isNull();
|
||||
assertThat(wipedEntity.getContactBase().get().getLocalizedPostalInfo()).isNull();
|
||||
assertThat(wipedEntity.getContactBase().get().getInternationalizedPostalInfo()).isNull();
|
||||
assertThat(wipedEntity.getContactBase().get().getVoiceNumber()).isNull();
|
||||
assertThat(wipedEntity.getContactBase().get().getFaxNumber()).isNull();
|
||||
}
|
||||
|
||||
private ContactHistory createContactHistory(ContactBase contact) {
|
||||
return new ContactHistory.Builder()
|
||||
.setType(HistoryEntry.Type.HOST_CREATE)
|
||||
|
||||
@@ -200,7 +200,7 @@ class HostResourceTest extends EntityTestCase {
|
||||
IllegalArgumentException.class, () -> host.asBuilder().setHostName("AAA.BBB.CCC"));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("Host name must be in puny-coded, lower-case form");
|
||||
.isEqualTo("Host name AAA.BBB.CCC not in puny-coded, lower-case form");
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -210,7 +210,7 @@ class HostResourceTest extends EntityTestCase {
|
||||
IllegalArgumentException.class, () -> host.asBuilder().setHostName("みんな.みんな.みんな"));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("Host name must be in puny-coded, lower-case form");
|
||||
.isEqualTo("Host name みんな.みんな.みんな not in puny-coded, lower-case form");
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.model.ofy;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
|
||||
import com.google.appengine.api.datastore.Entity;
|
||||
@@ -66,7 +65,7 @@ public class CommitLogMutationTest {
|
||||
Entity rawEntity = convertToEntityInTxn(someObject);
|
||||
// Needs to be in a transaction so that registry-saving-to-entity will work.
|
||||
CommitLogMutation mutation =
|
||||
tm().transact(() -> CommitLogMutation.create(manifestKey, someObject));
|
||||
auditedOfy().transact(() -> CommitLogMutation.create(manifestKey, someObject));
|
||||
assertThat(Key.create(mutation))
|
||||
.isEqualTo(CommitLogMutation.createKey(manifestKey, Key.create(someObject)));
|
||||
assertThat(mutation.getEntity()).isEqualTo(rawEntity);
|
||||
@@ -86,6 +85,6 @@ public class CommitLogMutationTest {
|
||||
}
|
||||
|
||||
private static Entity convertToEntityInTxn(final ImmutableObject object) {
|
||||
return tm().transact(() -> auditedOfy().save().toEntity(object));
|
||||
return auditedOfy().transact(() -> auditedOfy().save().toEntity(object));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user