Compare commits

...
Author SHA1 Message Date
Rachel GuanandGitHub a369e57e5c Apply registrar code change after registrar schema change is taken effect (#1254)
* Apply code change after registrar schema change is taken effect
2021-07-28 16:40:49 -04:00
gbrodmanandGitHub f32d80fb9d Use the DB migration schedule for SQL->DS replay (#1242)
This is instead of the current configuration parameter.

In addition, this adds some helpers to DatabaseHelper to make the
transitions easier, since we more frequently need to alter + reset the
schedule.
2021-07-27 16:05:59 -04:00
gbrodmanandGitHub afa5a353f1 Use raw EntityManager to load during beforeSqlSave (#1253)
If we use the transaction manager methods, JpaTransactionManagerImpl
will attempt to detach the EppResource in question that we're loading --
this fails because that entity has been saved in the same transaction
already. We don't need detaching during these methods (it's just for
resource population) so we can use the raw loads to get around it.
2021-07-26 19:14:49 -04:00
Rachel GuanandGitHub c4c5ac85da Remove isNearingExpiration() after shouldReceiveExpiringNotification() being added to code base (#1255)
* Resolve merge conflict
2021-07-26 18:23:14 -04:00
Ben McIlwainandGitHub 4d0078607f Add SECURITY.md security policy (#1257)
* Add SECURITY.md security policy
2021-07-26 17:35:59 -04:00
Rachel GuanandGitHub 2b78433682 Add method that checks if client should be notified for expiring certificate (#1245)
* fix merge conflict
2021-07-26 17:20:12 -04:00
Weimin YuandGitHub a0fcd02ed2 Remove KmsSecret model entities (#1252)
* Remove KmsSecret model entities

Now that we have been using the SecretManager for almost a month now,
remove the KmsSecret and KmsSecretRevision entities from Java code base.
A follow-up PR will drop the relevant tables in the schema.

Also removed a few unused classes in the beam package.
2021-07-26 17:09:09 -04:00
Rachel GuanandGitHub 58e413af89 Expand registrar schema to support sending expiring certificate notification emails (#1247)
* Expand registrar schema to support sending expiring certificate notification emails

* Remove java change (restrictly schema change only)
2021-07-22 17:11:32 -04:00
gbrodmanandGitHub 38c8e81690 Fix runtime issues with commit-log-to-SQL replay (#1240)
* Fix runtime issues with commit-log-to-SQL replay

- We now use a more intelligent prefix to narrow the listObjects search
space in GCS. Otherwise, we're returning >30k objects which can take
roughly 50 seconds. This results in a listObjects time of 1-3 seconds.

- We now search hour by hour to efficiently make use of the prefixing.
Basically, we keep searching for new files until we hit the current time
or until we hit the overall replay timeout.

- Dry-run only prints out the first hour's worth of files
2021-07-22 13:59:28 -04:00
Rachel GuanandGitHub 3beb207fcc Add email set up for sending expiring certificate notification emails (#1248)
* Add email set up for sending expiring certificate notification emails
2021-07-21 15:47:27 -04:00
gbrodmanandGitHub 8cf88b7e18 Avoid unnecessary tm() calls without ofy init in Spec11PipelineTest (#1250)
* Avoid unnecessary tm() calls without ofy init in Spec11PipelineTest
2021-07-20 15:10:50 -04:00
gbrodmanandGitHub 6ec2e9501d Fix flaky test issues caused by lack of ofy init (#1246) 2021-07-20 13:14:41 -04:00
69 changed files with 4828 additions and 4880 deletions
+4
View File
@@ -0,0 +1,4 @@
To report a security issue, please use http://g.co/vulnz. We use
http://g.co/vulnz for our intake, and do coordination and disclosure here on
GitHub (including using GitHub Security Advisory). The Google Security Team will
respond within 5 working days of your report on g.co/vulnz.
-2
View File
@@ -74,8 +74,6 @@ def fragileTestPatterns = [
"google/registry/cron/TldFanoutActionTest.*",
// Test Datastore inexplicably aborts transaction.
"google/registry/model/tmch/ClaimsListShardTest.*",
// Creates large object (64MBytes), occasionally throws OOM error.
"google/registry/model/server/KmsSecretRevisionTest.*",
// Changes cache timeouts and for some reason appears to have contention
// with other tests.
"google/registry/whois/WhoisCommandFactoryTest.*",
@@ -15,6 +15,7 @@
package google.registry.backup;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.backup.BackupUtils.GcsMetadataKeys.LOWER_BOUND_CHECKPOINT;
import static google.registry.backup.ExportCommitLogDiffAction.DIFF_FILE_PREFIX;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -23,13 +24,13 @@ import static google.registry.util.DateTimeUtils.latestOf;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.UncheckedExecutionException;
import dagger.Lazy;
import google.registry.backup.BackupModule.Backups;
import google.registry.gcs.GcsUtils;
import java.io.IOException;
@@ -39,6 +40,7 @@ import java.util.TreeMap;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import org.joda.time.DateTime;
/** Utility class to list commit logs diff files stored on GCS. */
@@ -51,7 +53,7 @@ class GcsDiffFileLister {
@Inject GcsUtils gcsUtils;
@Inject @Backups Lazy<ListeningExecutorService> lazyExecutor;
@Inject @Backups Provider<ListeningExecutorService> executorProvider;
@Inject ScheduledExecutorService scheduledExecutorService;
@Inject
@@ -113,22 +115,27 @@ class GcsDiffFileLister {
// (extracted from the filename) to its asynchronously-loaded metadata, keeping only files with
// an upper checkpoint time > fromTime.
TreeMap<DateTime, ListenableFuture<BlobInfo>> upperBoundTimesToBlobInfo = new TreeMap<>();
ImmutableList<String> strippedFilenames;
String commitLogDiffPrefix = getCommitLogDiffPrefix(fromTime, toTime);
ImmutableList<String> filenames;
try {
strippedFilenames = gcsUtils.listFolderObjects(gcsBucket, DIFF_FILE_PREFIX);
filenames =
gcsUtils.listFolderObjects(gcsBucket, commitLogDiffPrefix).stream()
.map(s -> commitLogDiffPrefix + s)
.collect(toImmutableList());
} catch (IOException e) {
throw new RuntimeException(e);
}
DateTime lastUpperBoundTime = START_OF_TIME;
TreeMap<DateTime, BlobInfo> sequence = new TreeMap<>();
ListeningExecutorService executor = executorProvider.get();
try {
for (String strippedFilename : strippedFilenames) {
final String filename = DIFF_FILE_PREFIX + strippedFilename;
for (String filename : filenames) {
String strippedFilename = filename.replaceFirst(DIFF_FILE_PREFIX, "");
DateTime upperBoundTime = DateTime.parse(strippedFilename);
if (isInRange(upperBoundTime, fromTime, toTime)) {
upperBoundTimesToBlobInfo.put(
upperBoundTime, lazyExecutor.get().submit(() -> getBlobInfo(gcsBucket, filename)));
upperBoundTime, executor.submit(() -> getBlobInfo(gcsBucket, filename)));
lastUpperBoundTime = latestOf(upperBoundTime, lastUpperBoundTime);
}
}
@@ -173,7 +180,7 @@ class GcsDiffFileLister {
"Unable to compute commit diff history, there are either gaps or forks in the history "
+ "file set. Check log for details.");
} finally {
lazyExecutor.get().shutdown();
executor.shutdown();
}
logger.atInfo().log(
@@ -198,4 +205,36 @@ class GcsDiffFileLister {
private BlobInfo getBlobInfo(String gcsBucket, String filename) {
return gcsUtils.getBlobInfo(BlobId.of(gcsBucket, filename));
}
/**
* Returns a prefix guaranteed to cover all commit log diff files in the given range.
*
* <p>The listObjects call can be fairly slow if we search over many thousands or tens of
* thousands of files, so we restrict the search space. The commit logs have a file format of
* "commit_diff_until_2021-05-11T06:48:00.070Z" so we can often filter down as far as the hour.
*
* <p>Here, we get the longest prefix possible based on which fields (year, month, day, hour) the
* times in question have in common.
*/
@VisibleForTesting
static String getCommitLogDiffPrefix(DateTime from, @Nullable DateTime to) {
StringBuilder result = new StringBuilder(DIFF_FILE_PREFIX);
if (to == null || from.getYear() != to.getYear()) {
return result.toString();
}
result.append(from.getYear()).append('-');
if (from.getMonthOfYear() != to.getMonthOfYear()) {
return result.toString();
}
result.append(String.format("%02d-", from.getMonthOfYear()));
if (from.getDayOfMonth() != to.getDayOfMonth()) {
return result.toString();
}
result.append(String.format("%02dT", from.getDayOfMonth()));
if (from.getHourOfDay() != to.getHourOfDay()) {
return result.toString();
}
result.append(String.format("%02d:", from.getHourOfDay()));
return result.toString();
}
}
@@ -20,13 +20,15 @@ import static google.registry.backup.RestoreCommitLogsAction.DRY_RUN_PARAM;
import static google.registry.model.ofy.EntityWritePriorities.getEntityPriority;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.joda.time.Duration.standardHours;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.cloud.storage.BlobInfo;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
@@ -120,26 +122,15 @@ public class ReplayCommitLogsToSqlAction implements Runnable {
}
try {
logger.atInfo().log("Beginning replay of commit logs.");
ImmutableList<BlobInfo> commitLogFiles = getFilesToReplay();
String resultMessage;
if (dryRun) {
response.setStatus(HttpServletResponse.SC_OK);
ImmutableList<String> filenames =
commitLogFiles.stream()
.limit(10)
.map(file -> file.getName())
.collect(toImmutableList());
String dryRunMessage =
"Running in dry-run mode; would have processed %d files. They are (limit 10):\n"
+ Joiner.on('\n').join(filenames);
response.setPayload(dryRunMessage);
logger.atInfo().log(dryRunMessage);
resultMessage = executeDryRun();
} else {
replayFiles(commitLogFiles);
response.setStatus(HttpServletResponse.SC_OK);
String message = "ReplayCommitLogsToSqlAction completed successfully.";
response.setPayload(message);
logger.atInfo().log(message);
resultMessage = replayFiles();
}
response.setStatus(SC_OK);
response.setPayload(resultMessage);
logger.atInfo().log(resultMessage);
} catch (Throwable t) {
String message = "Errored out replaying files.";
logger.atSevere().withCause(t).log(message);
@@ -150,52 +141,77 @@ public class ReplayCommitLogsToSqlAction implements Runnable {
}
}
private ImmutableList<BlobInfo> getFilesToReplay() {
private String executeDryRun() {
// Start at the first millisecond we haven't seen yet
DateTime fromTime = jpaTm().transact(() -> SqlReplayCheckpoint.get().plusMillis(1));
logger.atInfo().log("Starting replay from: %s.", fromTime);
// If there's an inconsistent file set, this will throw IllegalStateException and the job
// will try later -- this is likely because an export hasn't finished yet.
ImmutableList<BlobInfo> commitLogFiles =
diffLister.listDiffFiles(gcsBucket, fromTime, /* current time */ null);
logger.atInfo().log("Found %d new commit log files to process.", commitLogFiles.size());
return commitLogFiles;
DateTime searchStartTime = jpaTm().transact(() -> SqlReplayCheckpoint.get().plusMillis(1));
// Search through the end of the hour
DateTime searchEndTime =
searchStartTime.withMinuteOfHour(59).withSecondOfMinute(59).withMillisOfSecond(999);
ImmutableList<String> fileBatch =
diffLister.listDiffFiles(gcsBucket, searchStartTime, searchEndTime).stream()
.map(BlobInfo::getName)
.collect(toImmutableList());
return String.format(
"Running in dry-run mode, the first set of commit log files processed would be from "
+ "searching from %s to %s and would contain %d file(s). They are (limit 10): \n%s",
searchStartTime,
searchEndTime,
fileBatch.size(),
fileBatch.stream().limit(10).collect(toImmutableList()));
}
private void replayFiles(ImmutableList<BlobInfo> commitLogFiles) {
private String replayFiles() {
DateTime replayTimeoutTime = clock.nowUtc().plus(REPLAY_TIMEOUT_DURATION);
int processedFiles = 0;
for (BlobInfo metadata : commitLogFiles) {
// One transaction per GCS file
jpaTm().transact(() -> processFile(metadata));
processedFiles++;
if (clock.nowUtc().isAfter(replayTimeoutTime)) {
logger.atInfo().log(
"Reached max execution time after replaying %d files, leaving %d files for next run.",
processedFiles, commitLogFiles.size() - processedFiles);
return;
DateTime searchStartTime = jpaTm().transact(() -> SqlReplayCheckpoint.get().plusMillis(1));
int filesProcessed = 0;
// Starting from one millisecond after the last file we processed, search for and import files
// one hour at a time until we catch up to the current time or we hit the replay timeout (in
// which case the next run will pick up from where we leave off).
//
// We use hour-long batches because GCS supports filename prefix-based searches.
while (true) {
if (isAtOrAfter(clock.nowUtc(), replayTimeoutTime)) {
return String.format(
"Reached max execution time after replaying %d file(s).", filesProcessed);
}
if (isBeforeOrAt(clock.nowUtc(), searchStartTime)) {
return String.format(
"Caught up to current time after replaying %d file(s).", filesProcessed);
}
// Search through the end of the hour
DateTime searchEndTime =
searchStartTime.withMinuteOfHour(59).withSecondOfMinute(59).withMillisOfSecond(999);
ImmutableList<BlobInfo> fileBatch =
diffLister.listDiffFiles(gcsBucket, searchStartTime, searchEndTime);
if (fileBatch.isEmpty()) {
logger.atInfo().log(
"No remaining files found in hour %s, continuing search in the next hour.",
searchStartTime.toString("yyyy-MM-dd HH"));
}
for (BlobInfo file : fileBatch) {
jpaTm().transact(() -> processFile(file));
filesProcessed++;
if (clock.nowUtc().isAfter(replayTimeoutTime)) {
return String.format(
"Reached max execution time after replaying %d file(s).", filesProcessed);
}
}
searchStartTime = searchEndTime.plusMillis(1);
}
logger.atInfo().log("Replayed %d commit log files to SQL successfully.", processedFiles);
}
private void processFile(BlobInfo metadata) {
logger.atInfo().log(
"Processing commit log file %s of size %d B.", metadata.getName(), metadata.getSize());
try (InputStream input = gcsUtils.openInputStream(metadata.getBlobId())) {
// Load and process the Datastore transactions one at a time
ImmutableList<ImmutableList<VersionedEntity>> allTransactions =
CommitLogImports.loadEntitiesByTransaction(input);
logger.atInfo().log(
"Replaying %d transactions from commit log file %s.",
allTransactions.size(), metadata.getName());
allTransactions.forEach(this::replayTransaction);
// if we succeeded, set the last-seen time
DateTime checkpoint = DateTime.parse(metadata.getName().substring(DIFF_FILE_PREFIX.length()));
SqlReplayCheckpoint.set(checkpoint);
logger.atInfo().log(
"Replayed %d transactions from commit log file %s.",
allTransactions.size(), metadata.getName());
"Replayed %d transactions from commit log file %s with size %d B.",
allTransactions.size(), metadata.getName(), metadata.getSize());
} catch (IOException e) {
throw new RuntimeException(
"Errored out while replaying commit log file " + metadata.getName(), e);
@@ -223,7 +239,7 @@ public class ReplayCommitLogsToSqlAction implements Runnable {
.toSqlEntity()
.ifPresent(
sqlEntity -> {
ReplaySpecializer.beforeSqlSave(sqlEntity);
sqlEntity.beforeSqlSaveOnReplay();
jpaTm().put(sqlEntity);
});
} else {
@@ -1,50 +0,0 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.beam.initsql;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.api.services.cloudkms.v1.model.DecryptRequest;
import com.google.common.base.Strings;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.kms.KmsConnection;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.inject.Inject;
/**
* Decrypts data using Cloud KMS, with the same crypto key with which Cloud SQL credential files on
* GCS was encrypted. See {@link BackupPaths#getCloudSQLCredentialFilePatterns} for more
* information.
*/
public class CloudSqlCredentialDecryptor {
private static final String CRYPTO_KEY_NAME = "nomulus-tool-key";
private final KmsConnection kmsConnection;
@Inject
CloudSqlCredentialDecryptor(@Config("beamKmsConnection") KmsConnection kmsConnection) {
this.kmsConnection = kmsConnection;
}
public String decrypt(String data) {
checkArgument(!Strings.isNullOrEmpty(data), "Null or empty data.");
byte[] ciphertext = Base64.getDecoder().decode(data);
// Re-encode for Cloud KMS JSON REST API, invoked through kmsConnection.
String urlSafeCipherText = new DecryptRequest().encodeCiphertext(ciphertext).getCiphertext();
return new String(
kmsConnection.decrypt(CRYPTO_KEY_NAME, urlSafeCipherText), StandardCharsets.UTF_8);
}
}
@@ -1,45 +0,0 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.beam.initsql;
import com.google.auto.value.AutoValue;
import java.util.Optional;
/**
* Information needed to connect to a database, including JDBC URL, user name, password, and in the
* case of Cloud SQL, the database instance's name.
*/
@AutoValue
abstract class SqlAccessInfo {
abstract String jdbcUrl();
abstract String user();
abstract String password();
abstract Optional<String> cloudSqlInstanceName();
public static SqlAccessInfo createCloudSqlAccessInfo(
String sqlInstanceName, String username, String password) {
return new AutoValue_SqlAccessInfo(
"jdbc:postgresql://google/postgres", username, password, Optional.of(sqlInstanceName));
}
public static SqlAccessInfo createLocalSqlAccessInfo(
String jdbcUrl, String username, String password) {
return new AutoValue_SqlAccessInfo(jdbcUrl, username, password, Optional.empty());
}
}
@@ -1267,16 +1267,34 @@ public final class RegistryConfig {
@Provides
@Config("expirationWarningDays")
public static int provideDaysToExpiration(RegistryConfigSettings config) {
public static int provideExpirationWarningDays(RegistryConfigSettings config) {
return config.sslCertificateValidation.expirationWarningDays;
}
@Provides
@Config("expirationWarningIntervalDays")
public static int provideExpirationWarningIntervalDays(RegistryConfigSettings config) {
return config.sslCertificateValidation.expirationWarningIntervalDays;
}
@Provides
@Config("minimumRsaKeyLength")
public static int provideMinimumRsaKeyLength(RegistryConfigSettings config) {
return config.sslCertificateValidation.minimumRsaKeyLength;
}
@Provides
@Config("expirationWarningEmailBodyText")
public static String provideExpirationWarningEmailBodyText(RegistryConfigSettings config) {
return config.sslCertificateValidation.expirationWarningEmailBodyText;
}
@Provides
@Config("expirationWarningEmailSubjectText")
public static String provideExpirationWarningEmailSubjectText(RegistryConfigSettings config) {
return config.sslCertificateValidation.expirationWarningEmailSubjectText;
}
@Provides
@Config("allowedEcdsaCurves")
public static ImmutableSet<String> provideAllowedEcdsaCurves(RegistryConfigSettings config) {
@@ -1476,21 +1494,6 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get().hibernate.hikariIdleTimeout;
}
/**
* Returns whether to replicate cloud SQL transactions to datastore.
*
* <p>If true, all cloud SQL transactions will be persisted as TransactionEntity objects in the
* Transaction table and replayed against datastore in a cron job.
*/
public static boolean getCloudSqlReplicateTransactions() {
return CONFIG_SETTINGS.get().cloudSql.replicateTransactions;
}
@VisibleForTesting
public static void overrideCloudSqlReplicateTransactions(boolean replicateTransactions) {
CONFIG_SETTINGS.get().cloudSql.replicateTransactions = replicateTransactions;
}
/** Returns the roid suffix to be used for the roids of all contacts and hosts. */
public static String getContactAndHostRoidSuffix() {
return CONFIG_SETTINGS.get().registryPolicy.contactAndHostRoidSuffix;
@@ -126,7 +126,6 @@ public class RegistryConfigSettings {
// TODO(05012021): remove username field after it is removed from all yaml files.
public String username;
public String instanceConnectionName;
public boolean replicateTransactions;
}
/** Configuration for Apache Beam (Cloud Dataflow). */
@@ -228,7 +227,10 @@ public class RegistryConfigSettings {
public static class SslCertificateValidation {
public Map<String, Integer> maxValidityDaysSchedule;
public int expirationWarningDays;
public int expirationWarningIntervalDays;
public int minimumRsaKeyLength;
public Set<String> allowedEcdsaCurves;
public String expirationWarningEmailBodyText;
public String expirationWarningEmailSubjectText;
}
}
@@ -227,9 +227,6 @@ cloudSql:
jdbcUrl: jdbc:postgresql://localhost
# This name is used by Cloud SQL when connecting to the database.
instanceConnectionName: project-id:region:instance-id
# Set this to true to replicate cloud SQL transactions to datastore in the
# background.
replicateTransactions: false
cloudDns:
# Set both properties to null in Production.
@@ -452,6 +449,15 @@ sslCertificateValidation:
# The number of days before a certificate expires that indicates the
# certificate is nearing expiration and warnings should be sent.
expirationWarningDays: 30
# The minimum number of days between two successive expiring notification emails.
expirationWarningIntervalDays: 15
# Text for expiring certificate notification email subject.
expirationWarningEmailSubjectText: Certificate Expring Within 30 Days.
# Text for expiring certificate notification email body that accepts 3 parameters:
# registrar name, certificate type, and expiration date, respectively.
expirationWarningEmailBodyText: |
Hello Registrar %s,
The %s certificate is expiring on %s.
# The minimum number of bits an RSA key must contain.
minimumRsaKeyLength: 2048
# The ECDSA curves that are allowed for public keys.
@@ -24,6 +24,7 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.util.Clock;
import google.registry.util.DateTimeUtils;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -32,10 +33,15 @@ import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
import org.bouncycastle.util.io.pem.PemObjectGenerator;
import org.bouncycastle.util.io.pem.PemWriter;
import org.joda.time.DateTime;
import org.joda.time.Days;
@@ -43,10 +49,11 @@ import org.joda.time.Days;
public class CertificateChecker {
private final ImmutableSortedMap<DateTime, Integer> maxValidityLengthSchedule;
private final int daysToExpiration;
private final int expirationWarningDays;
private final int minimumRsaKeyLength;
private final Clock clock;
private final ImmutableSet<String> allowedEcdsaCurves;
private final int expirationWarningIntervalDays;
/**
* Constructs a CertificateChecker instance with the specified configuration parameters.
@@ -72,6 +79,7 @@ public class CertificateChecker {
@Config("maxValidityDaysSchedule")
ImmutableSortedMap<DateTime, Integer> maxValidityDaysSchedule,
@Config("expirationWarningDays") int expirationWarningDays,
@Config("expirationWarningIntervalDays") int expirationWarningIntervalDays,
@Config("minimumRsaKeyLength") int minimumRsaKeyLength,
@Config("allowedEcdsaCurves") ImmutableSet<String> allowedEcdsaCurves,
Clock clock) {
@@ -79,12 +87,46 @@ public class CertificateChecker {
maxValidityDaysSchedule.containsKey(START_OF_TIME),
"Max validity length schedule must contain an entry for START_OF_TIME");
this.maxValidityLengthSchedule = maxValidityDaysSchedule;
this.daysToExpiration = expirationWarningDays;
this.expirationWarningDays = expirationWarningDays;
this.minimumRsaKeyLength = minimumRsaKeyLength;
this.allowedEcdsaCurves = allowedEcdsaCurves;
this.expirationWarningIntervalDays = expirationWarningIntervalDays;
this.clock = clock;
}
private static int getValidityLengthInDays(X509Certificate certificate) {
DateTime start = DateTime.parse(certificate.getNotBefore().toInstant().toString());
DateTime end = DateTime.parse(certificate.getNotAfter().toInstant().toString());
return Days.daysBetween(start.withTimeAtStartOfDay(), end.withTimeAtStartOfDay()).getDays();
}
/** Checks if the curve used for a public key is in the list of acceptable curves. */
private static boolean checkCurveName(PublicKey key, ImmutableSet<String> allowedEcdsaCurves) {
ECParameterSpec params;
// These 2 different instances of PublicKey need to be handled separately since their OIDs are
// encoded differently. More details on this can be found at
// https://stackoverflow.com/questions/49895713/how-to-find-the-matching-curve-name-from-an-ecpublickey.
if (key instanceof ECPublicKey) {
ECPublicKey ecKey = (ECPublicKey) key;
params = EC5Util.convertSpec(ecKey.getParams(), false);
} else if (key instanceof org.bouncycastle.jce.interfaces.ECPublicKey) {
org.bouncycastle.jce.interfaces.ECPublicKey ecKey =
(org.bouncycastle.jce.interfaces.ECPublicKey) key;
params = ecKey.getParameters();
} else {
throw new IllegalArgumentException("Unrecognized instance of PublicKey.");
}
return allowedEcdsaCurves.stream()
.anyMatch(
curve -> {
ECNamedCurveParameterSpec cParams = ECNamedCurveTable.getParameterSpec(curve);
return cParams.getN().equals(params.getN())
&& cParams.getH().equals(params.getH())
&& cParams.getCurve().equals(params.getCurve())
&& cParams.getG().equals(params.getG());
});
}
/**
* Checks the given certificate string for violations and throws an exception if any violations
* exist.
@@ -156,67 +198,57 @@ public class CertificateChecker {
* the violations the certificate has.
*/
public ImmutableSet<CertificateViolation> checkCertificate(String certificateString) {
X509Certificate certificate;
return checkCertificate(getCertificate(certificateString));
}
/** Converts the given string to a certificate object. */
public X509Certificate getCertificate(String certificateStr) {
X509Certificate certificate;
try {
certificate =
(X509Certificate)
CertificateFactory.getInstance("X509")
.generateCertificate(new ByteArrayInputStream(certificateString.getBytes(UTF_8)));
.generateCertificate(new ByteArrayInputStream(certificateStr.getBytes(UTF_8)));
} catch (CertificateException e) {
throw new IllegalArgumentException("Unable to read given certificate.");
throw new IllegalArgumentException(
String.format("Unable to read given certificate %s", certificateStr), e);
}
return checkCertificate(certificate);
return certificate;
}
/**
* Returns whether the certificate is nearing expiration.
*
* <p>Note that this is <i>all</i> that it checks. The certificate itself may well be expired or
* not yet valid and this message will still return false. So you definitely want to pair a call
* to this method with a call to {@link #checkCertificate} to determine other issues with the
* certificate that may be occurring.
*/
public boolean isNearingExpiration(X509Certificate certificate) {
Date nearingExpirationDate =
DateTime.parse(certificate.getNotAfter().toInstant().toString())
.minusDays(daysToExpiration)
.toDate();
return clock.nowUtc().toDate().after(nearingExpirationDate);
}
private static int getValidityLengthInDays(X509Certificate certificate) {
DateTime start = DateTime.parse(certificate.getNotBefore().toInstant().toString());
DateTime end = DateTime.parse(certificate.getNotAfter().toInstant().toString());
return Days.daysBetween(start.withTimeAtStartOfDay(), end.withTimeAtStartOfDay()).getDays();
}
/** Checks if the curve used for a public key is in the list of acceptable curves. */
private static boolean checkCurveName(PublicKey key, ImmutableSet<String> allowedEcdsaCurves) {
org.bouncycastle.jce.spec.ECParameterSpec params;
// These 2 different instances of PublicKey need to be handled separately since their OIDs are
// encoded differently. More details on this can be found at
// https://stackoverflow.com/questions/49895713/how-to-find-the-matching-curve-name-from-an-ecpublickey.
if (key instanceof ECPublicKey) {
ECPublicKey ecKey = (ECPublicKey) key;
params = EC5Util.convertSpec(ecKey.getParams(), false);
} else if (key instanceof org.bouncycastle.jce.interfaces.ECPublicKey) {
org.bouncycastle.jce.interfaces.ECPublicKey ecKey =
(org.bouncycastle.jce.interfaces.ECPublicKey) key;
params = ecKey.getParameters();
} else {
throw new IllegalArgumentException("Unrecognized instance of PublicKey.");
/** Serializes the certificate object to a certificate string. */
public String serializeCertificate(X509Certificate certificate) throws Exception {
StringWriter sw = new StringWriter();
try (PemWriter pw = new PemWriter(sw)) {
PemObjectGenerator generator = new JcaMiscPEMGenerator(certificate);
pw.writeObject(generator);
}
return allowedEcdsaCurves.stream()
.anyMatch(
curve -> {
ECNamedCurveParameterSpec cParams = ECNamedCurveTable.getParameterSpec(curve);
return cParams.getN().equals(params.getN())
&& cParams.getH().equals(params.getH())
&& cParams.getCurve().equals(params.getCurve())
&& cParams.getG().equals(params.getG());
});
return sw.toString();
}
/** Returns whether the client should receive a notification email. */
public boolean shouldReceiveExpiringNotification(
@Nullable DateTime lastExpiringNotificationSentDate, String certificateStr) {
X509Certificate certificate = getCertificate(certificateStr);
DateTime now = clock.nowUtc();
// expiration date is one day after lastValidDate
Date lastValidDate = certificate.getNotAfter();
if (lastValidDate.before(now.toDate())) {
return false;
}
/*
* Client should receive a notification if :
* 1) client has never received notification and the certificate has entered
* the expiring period, OR
* 2) client has received notification but the interval between now and
* lastExpiringNotificationSentDate is greater than expirationWarningIntervalDays.
*/
return !lastValidDate.after(now.plusDays(expirationWarningDays).toDate())
&& (lastExpiringNotificationSentDate == null
|| !lastExpiringNotificationSentDate
.plusDays(expirationWarningIntervalDays)
.toDate()
.after(now.toDate()));
}
private String getViolationDisplayMessage(CertificateViolation certificateViolation) {
@@ -28,14 +28,8 @@ import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
/**
* A {@link Keyring} implementation which stores encrypted secrets in Datastore and decrypts them
* using encryption keys stored in Cloud KMS.
*
* @see <a href="https://cloud.google.com/kms/docs/">Google Cloud Key Management Service
* Documentation</a>
*/
// TODO(2021-07-01): rename this class to SecretManagerKeyring and delete KmsSecretRevision
/** A {@link Keyring} implementation which stores sensitive data in the Secret Manager. */
// TODO(2021-08-01): rename this class to SecretManagerKeyring and update config files.
public class KmsKeyring implements Keyring {
/** Key labels for private key secrets. */
@@ -43,8 +43,6 @@ import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.server.KmsSecret;
import google.registry.model.server.KmsSecretRevision;
import google.registry.model.server.Lock;
import google.registry.model.server.ServerSecret;
import google.registry.model.tmch.ClaimsList;
@@ -88,8 +86,6 @@ public final class EntityClasses {
HistoryEntry.class,
HostHistory.class,
HostResource.class,
KmsSecret.class,
KmsSecretRevision.class,
LastSqlTransaction.class,
Lock.class,
PollMessage.class,
@@ -225,7 +225,7 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton
@VisibleForTesting
static TimedTransitionProperty<MigrationState, MigrationStateTransition> getUncached() {
return ofyTm()
.transact(
.transactNew(
() ->
ofyTm()
.loadSingleton(DatabaseMigrationStateSchedule.class)
@@ -136,9 +136,11 @@ public class ContactHistory extends HistoryEntry implements SqlEntity {
}
// Used to fill out the contactBase field during asynchronous replay
public static void beforeSqlSave(ContactHistory contactHistory) {
contactHistory.contactBase =
jpaTm().loadByKey(VKey.createSql(ContactResource.class, contactHistory.getContactRepoId()));
@Override
public void beforeSqlSaveOnReplay() {
if (contactBase == null) {
contactBase = jpaTm().getEntityManager().find(ContactResource.class, getContactRepoId());
}
}
/** Class to represent the composite primary key of {@link ContactHistory} entity. */
@@ -294,9 +294,26 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
}
// Used to fill out the domainContent field during asynchronous replay
public static void beforeSqlSave(DomainHistory domainHistory) {
domainHistory.domainContent =
jpaTm().loadByKey(VKey.createSql(DomainBase.class, domainHistory.getDomainRepoId()));
@Override
public void beforeSqlSaveOnReplay() {
if (domainContent == null) {
domainContent = jpaTm().getEntityManager().find(DomainBase.class, getDomainRepoId());
fillAuxiliaryFieldsFromDomain(this);
}
}
private static void fillAuxiliaryFieldsFromDomain(DomainHistory domainHistory) {
if (domainHistory.domainContent != null) {
domainHistory.nsHosts = nullToEmptyImmutableCopy(domainHistory.domainContent.nsHosts);
domainHistory.dsDataHistories =
nullToEmptyImmutableCopy(domainHistory.domainContent.getDsData()).stream()
.map(dsData -> DomainDsDataHistory.createFrom(domainHistory.id, dsData))
.collect(toImmutableSet());
domainHistory.gracePeriodHistories =
nullToEmptyImmutableCopy(domainHistory.domainContent.getGracePeriods()).stream()
.map(gracePeriod -> GracePeriodHistory.createFrom(domainHistory.id, gracePeriod))
.collect(toImmutableSet());
}
}
/** Class to represent the composite primary key of {@link DomainHistory} entity. */
@@ -391,17 +408,7 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
// Note that we cannot assert that instance.domainContent is not null here because this
// builder is also used to convert legacy HistoryEntry objects to DomainHistory, when
// domainContent is not available.
if (instance.domainContent != null) {
instance.nsHosts = nullToEmptyImmutableCopy(instance.domainContent.nsHosts);
instance.dsDataHistories =
nullToEmptyImmutableCopy(instance.domainContent.getDsData()).stream()
.map(dsData -> DomainDsDataHistory.createFrom(instance.id, dsData))
.collect(toImmutableSet());
instance.gracePeriodHistories =
nullToEmptyImmutableCopy(instance.domainContent.getGracePeriods()).stream()
.map(gracePeriod -> GracePeriodHistory.createFrom(instance.id, gracePeriod))
.collect(toImmutableSet());
}
fillAuxiliaryFieldsFromDomain(instance);
return instance;
}
}
@@ -136,9 +136,11 @@ public class HostHistory extends HistoryEntry implements SqlEntity {
}
// Used to fill out the hostBase field during asynchronous replay
public static void beforeSqlSave(HostHistory hostHistory) {
hostHistory.hostBase =
jpaTm().loadByKey(VKey.createSql(HostResource.class, hostHistory.getHostRepoId()));
@Override
public void beforeSqlSaveOnReplay() {
if (hostBase == null) {
hostBase = jpaTm().getEntityManager().find(HostResource.class, getHostRepoId());
}
}
/** Class to represent the composite primary key of {@link HostHistory} entity. */
@@ -37,6 +37,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
import static google.registry.util.PasswordUtils.hashPassword;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@@ -458,6 +459,14 @@ public class Registrar extends ImmutableObject
/** The time that the certificate was last updated. */
DateTime lastCertificateUpdateTime;
/** The time that an expiring certificate notification email was sent to the registrar. */
DateTime lastExpiringCertNotificationSentDate = START_OF_TIME;
/**
* The time that an expiring failover certificate notification email was sent to the registrar.
*/
DateTime lastExpiringFailoverCertNotificationSentDate = START_OF_TIME;
/** Telephone support passcode (5-digit numeric) */
String phonePasscode;
@@ -508,6 +517,14 @@ public class Registrar extends ImmutableObject
return lastCertificateUpdateTime;
}
public DateTime getLastExpiringCertNotificationSentDate() {
return lastExpiringCertNotificationSentDate;
}
public DateTime getLastExpiringFailoverCertNotificationSentDate() {
return lastExpiringFailoverCertNotificationSentDate;
}
public String getRegistrarName() {
return registrarName;
}
@@ -671,6 +688,10 @@ public class Registrar extends ImmutableObject
.putString("creationTime", creationTime.getTimestamp())
.putString("lastUpdateTime", lastUpdateTime.getTimestamp())
.putString("lastCertificateUpdateTime", lastCertificateUpdateTime)
.putString("lastExpiringCertNotificationSentDate", lastExpiringCertNotificationSentDate)
.putString(
"lastExpiringFailoverCertNotificationSentDate",
lastExpiringFailoverCertNotificationSentDate)
.put("registrarName", registrarName)
.put("type", type)
.put("state", state)
@@ -839,6 +860,19 @@ public class Registrar extends ImmutableObject
return this;
}
public Builder setLastExpiringCertNotificationSentDate(DateTime now) {
checkArgumentNotNull(now, "Registrar lastExpiringCertNotificationSentDate cannot be null");
getInstance().lastExpiringCertNotificationSentDate = now;
return this;
}
public Builder setLastExpiringFailoverCertNotificationSentDate(DateTime now) {
checkArgumentNotNull(
now, "Registrar lastExpiringFailoverCertNotificationSentDate cannot be null");
getInstance().lastExpiringFailoverCertNotificationSentDate = now;
return this;
}
public Builder setFailoverClientCertificate(String clientCertificate, DateTime now) {
clientCertificate = emptyToNull(clientCertificate);
String clientCertificateHash = calculateHash(clientCertificate);
@@ -1,57 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.server;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
import google.registry.schema.replay.DatastoreOnlyEntity;
/** Pointer to the latest {@link KmsSecretRevision}. */
@Entity
@ReportedOn
@InCrossTld
public class KmsSecret extends ImmutableObject implements DatastoreOnlyEntity {
/** The unique name of this {@link KmsSecret}. */
@Id String name;
@Parent Key<EntityGroupRoot> parent = getCrossTldKey();
/** The pointer to the latest {@link KmsSecretRevision}. */
Key<KmsSecretRevision> latestRevision;
public String getName() {
return name;
}
public Key<KmsSecretRevision> getLatestRevision() {
return latestRevision;
}
public static KmsSecret create(String name, KmsSecretRevision latestRevision) {
KmsSecret instance = new KmsSecret();
instance.name = name;
instance.latestRevision = Key.create(latestRevision);
return instance;
}
}
@@ -1,162 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.server;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.schema.replay.NonReplicatedEntity;
import javax.persistence.Column;
import javax.persistence.Index;
import javax.persistence.PostLoad;
import javax.persistence.Table;
import javax.persistence.Transient;
/**
* An encrypted value.
*
* <p>Used to store passwords and other sensitive information in Datastore. Multiple versions of a
* {@link KmsSecretRevision} may be persisted but only the latest version is primary. A key to the
* primary version is stored by {@link KmsSecret#latestRevision}.
*
* <p>The value can be encrypted and decrypted using Cloud KMS.
*
* <p>Note that the primary key of this entity is {@link #revisionKey}, which is auto-generated by
* the database. So, if a retry of insertion happens after the previous attempt unexpectedly
* succeeds, we will end up with having two exact same revisions that differ only by revisionKey.
* This is fine though, because we only use the revision with the highest revisionKey.
*
* <p>TODO(b/177567432): remove Datastore-specific fields post-Registry-3.0-migration and rename to
* KmsSecret.
*
* @see <a href="https://cloud.google.com/kms/docs/">Google Cloud Key Management Service
* Documentation</a>
* @see google.registry.keyring.kms.KmsKeyring
*/
@Entity
@ReportedOn
@javax.persistence.Entity(name = "KmsSecret")
@Table(indexes = {@Index(columnList = "secretName")})
@InCrossTld
public class KmsSecretRevision extends ImmutableObject implements NonReplicatedEntity {
/**
* The maximum allowable secret size. Although Datastore allows entities up to 1 MB in size,
* BigQuery imports of Datastore backups limit individual columns (entity attributes) to 64 KB.
*/
private static final int MAX_SECRET_SIZE_BYTES = 64 * 1024 * 1024;
/**
* The revision of this secret.
*
* <p>TODO(b/177567432): change name of the variable to revisionId once we're off Datastore
*/
@Id
@javax.persistence.Id
@Column(name = "revisionId")
long revisionKey;
/** The parent {@link KmsSecret} which contains metadata about this {@link KmsSecretRevision}. */
@Parent @Transient Key<KmsSecret> parent;
@Column(nullable = false)
@Ignore
String secretName;
/**
* The name of the {@code cryptoKeyVersion} associated with this {@link KmsSecretRevision}.
*
* <p>TODO: change name of the variable to cryptoKeyVersionName once we're off Datastore
*
* @see <a
* href="https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys.cryptoKeyVersions">projects.locations.keyRings.cryptoKeys.cryptoKeyVersions</a>
*/
@Column(nullable = false, name = "cryptoKeyVersionName")
String kmsCryptoKeyVersionName;
/**
* The base64-encoded encrypted value of this {@link KmsSecretRevision} as returned by the Cloud
* KMS API.
*
* @see <a
* href="https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys/encrypt">projects.locations.keyRings.cryptoKeys.encrypt</a>
*/
@Column(nullable = false)
String encryptedValue;
/** An automatically managed creation timestamp. */
@Column(nullable = false)
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
public String getKmsCryptoKeyVersionName() {
return kmsCryptoKeyVersionName;
}
public String getEncryptedValue() {
return encryptedValue;
}
// When loading from SQL, fill out the Datastore-specific field
@PostLoad
void postLoad() {
parent = Key.create(getCrossTldKey(), KmsSecret.class, secretName);
}
// When loading from Datastore, fill out the SQL-specific field
@OnLoad
void onLoad() {
secretName = parent.getName();
}
/** A builder for constructing {@link KmsSecretRevision} entities, since they are immutable. */
public static class Builder extends Buildable.Builder<KmsSecretRevision> {
public Builder setKmsCryptoKeyVersionName(String kmsCryptoKeyVersionName) {
getInstance().kmsCryptoKeyVersionName = kmsCryptoKeyVersionName;
return this;
}
public Builder setEncryptedValue(String encryptedValue) {
checkArgument(
encryptedValue.length() <= MAX_SECRET_SIZE_BYTES,
"Secret is greater than %s bytes",
MAX_SECRET_SIZE_BYTES);
getInstance().encryptedValue = encryptedValue;
return this;
}
/**
* Set the parent {@link KmsSecret}.
*
* <p>The secret may not exist yet, so it is referred to by name rather than by reference.
*/
public Builder setParent(String secretName) {
getInstance().parent = Key.create(getCrossTldKey(), KmsSecret.class, secretName);
getInstance().secretName = secretName;
return this;
}
}
}
@@ -1,53 +0,0 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.server;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import java.util.Optional;
/**
* A {@link KmsSecretRevision} DAO for Cloud SQL.
*
* <p>TODO(b/177567432): Rename this class to KmsSecretDao after migrating to Cloud SQL.
*/
public class KmsSecretRevisionSqlDao {
private KmsSecretRevisionSqlDao() {}
/** Saves the given KMS secret revision. */
public static void save(KmsSecretRevision kmsSecretRevision) {
checkArgumentNotNull(kmsSecretRevision, "kmsSecretRevision cannot be null");
jpaTm().assertInTransaction();
jpaTm().put(kmsSecretRevision);
}
/** Returns the latest revision for the secret name given, or absent if nonexistent. */
public static Optional<KmsSecretRevision> getLatestRevision(String secretName) {
checkArgument(!isNullOrEmpty(secretName), "secretName cannot be null or empty");
jpaTm().assertInTransaction();
return jpaTm()
.query(
"FROM KmsSecret ks WHERE ks.revisionKey IN (SELECT MAX(revisionKey) FROM "
+ "KmsSecret subKs WHERE subKs.secretName = :secretName)",
KmsSecretRevision.class)
.setParameter("secretName", secretName)
.getResultStream()
.findFirst();
}
}
@@ -30,14 +30,14 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.ReplayDirection;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyContactIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyDomainIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.model.server.KmsSecret;
import google.registry.model.tmch.ClaimsList.ClaimsListSingleton;
import google.registry.persistence.JpaRetries;
import google.registry.persistence.VKey;
@@ -94,8 +94,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
EppResourceIndex.class,
ForeignKeyContactIndex.class,
ForeignKeyDomainIndex.class,
ForeignKeyHostIndex.class,
KmsSecret.class);
ForeignKeyHostIndex.class);
// EntityManagerFactory is thread safe.
private final EntityManagerFactory emf;
@@ -105,6 +104,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
// synchronously.
private 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 =
ThreadLocal.withInitial(Optional::empty);
public JpaTransactionManagerImpl(EntityManagerFactory emf, Clock clock) {
this.emf = emf;
@@ -738,6 +741,16 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
return entity;
}
/** Sets the override to always/never replay SQL transactions to Datastore. */
public static void setReplaySqlToDatastoreOverrideForTest(boolean replaySqlToDs) {
replaySqlToDatastoreOverrideForTest.set(Optional.of(replaySqlToDs));
}
/** Removes the replay-SQL-to-Datastore override; the migration schedule will then be used. */
public static void removeReplaySqlToDsOverrideForTest() {
replaySqlToDatastoreOverrideForTest.set(Optional.empty());
}
private static class TransactionInfo {
EntityManager entityManager;
boolean inTransaction = false;
@@ -757,7 +770,14 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
checkArgumentNotNull(clock);
inTransaction = true;
transactionTime = clock.nowUtc();
if (withBackup && RegistryConfig.getCloudSqlReplicateTransactions()) {
if (withBackup
&& replaySqlToDatastoreOverrideForTest
.get()
.orElseGet(
() ->
DatabaseMigrationStateSchedule.getValueAtTime(transactionTime)
.getReplayDirection()
.equals(ReplayDirection.SQL_TO_DATASTORE))) {
contentsBuilder = new Transaction.Builder();
}
}
@@ -28,17 +28,11 @@ import java.lang.reflect.Method;
public class ReplaySpecializer {
public static void beforeSqlDelete(VKey<?> key) {
invokeMethod(key.getKind(), "beforeSqlDelete", key);
}
public static void beforeSqlSave(SqlEntity sqlEntity) {
invokeMethod(sqlEntity.getClass(), "beforeSqlSave", sqlEntity);
}
private static <T> void invokeMethod(Class<T> clazz, String methodName, Object argument) {
String methodName = "beforeSqlDelete";
Class<?> clazz = key.getKind();
try {
Method method = clazz.getMethod(methodName, argument.getClass());
method.invoke(null, argument);
Method method = clazz.getMethod(methodName, VKey.class);
method.invoke(null, key);
} catch (NoSuchMethodException e) {
// Ignore, this just means that the class doesn't need this hook.
} catch (IllegalAccessException e) {
@@ -26,4 +26,7 @@ import java.util.Optional;
public interface SqlEntity {
Optional<DatastoreEntity> toDatastoreEntity();
/** A method that will ber called before the object is saved to SQL in asynchronous replay. */
default void beforeSqlSaveOnReplay() {}
}
@@ -53,7 +53,7 @@ import javax.inject.Inject;
* However, since this is meant to be run during the Datastore-primary, SQL-secondary stage of the
* migration, we want to make sure that we are using the most up-to-date version of the data. The
* resource field of the history objects will be populated during asynchronous migration, e.g. in
* {@link DomainHistory#beforeSqlSave(DomainHistory)}.
* {@link DomainHistory#beforeSqlSaveOnReplay}.
*/
@Action(
service = Action.Service.BACKEND,
@@ -65,7 +65,6 @@
<class>google.registry.model.registry.Registry</class>
<class>google.registry.model.reporting.DomainTransactionRecord</class>
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
<class>google.registry.model.server.KmsSecretRevision</class>
<class>google.registry.model.server.Lock</class>
<class>google.registry.model.server.ServerSecret</class>
<class>google.registry.model.smd.SignedMarkRevocationList</class>
@@ -18,6 +18,7 @@ import static com.google.common.collect.Iterables.transform;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.backup.BackupUtils.GcsMetadataKeys.LOWER_BOUND_CHECKPOINT;
import static google.registry.backup.ExportCommitLogDiffAction.DIFF_FILE_PREFIX;
import static google.registry.backup.GcsDiffFileLister.getCommitLogDiffPrefix;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
@@ -61,7 +62,7 @@ public class GcsDiffFileListerTest {
@BeforeEach
void beforeEach() throws Exception {
diffLister.gcsUtils = gcsUtils;
diffLister.lazyExecutor = MoreExecutors::newDirectExecutorService;
diffLister.executorProvider = MoreExecutors::newDirectExecutorService;
diffLister.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
for (int i = 0; i < 5; i++) {
addGcsFile(i, i + 1);
@@ -189,12 +190,23 @@ public class GcsDiffFileListerTest {
@Test
void testList_toTimeSpecified() {
assertThat(listDiffFiles(
now.minusMinutes(4).minusSeconds(1), now.minusMinutes(2).plusSeconds(1)))
.containsExactly(
now.minusMinutes(4),
now.minusMinutes(3),
now.minusMinutes(2))
assertThat(
listDiffFiles(now.minusMinutes(4).minusSeconds(1), now.minusMinutes(2).plusSeconds(1)))
.containsExactly(now.minusMinutes(4), now.minusMinutes(3), now.minusMinutes(2))
.inOrder();
}
@Test
void testPrefix_lengthened() {
DateTime from = DateTime.parse("2021-05-11T06:48:00.070Z");
assertThat(getCommitLogDiffPrefix(from, null)).isEqualTo("commit_diff_until_");
assertThat(getCommitLogDiffPrefix(from, DateTime.parse("2021-07-01")))
.isEqualTo("commit_diff_until_2021-");
assertThat(getCommitLogDiffPrefix(from, DateTime.parse("2021-05-21")))
.isEqualTo("commit_diff_until_2021-05-");
assertThat(getCommitLogDiffPrefix(from, DateTime.parse("2021-05-11T09:48:00.070Z")))
.isEqualTo("commit_diff_until_2021-05-11T");
assertThat(getCommitLogDiffPrefix(from, DateTime.parse("2021-05-11T06:59:00.070Z")))
.isEqualTo("commit_diff_until_2021-05-11T06:");
}
}
@@ -67,6 +67,7 @@ import google.registry.persistence.transaction.TransactionManagerFactory;
import google.registry.schema.replay.SqlReplayCheckpoint;
import google.registry.schema.tld.PremiumEntry;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.TestObject;
@@ -132,7 +133,7 @@ public class ReplayCommitLogsToSqlActionTest {
action.gcsBucket = "gcs bucket";
action.diffLister = new GcsDiffFileLister();
action.diffLister.gcsUtils = gcsUtils;
action.diffLister.lazyExecutor = MoreExecutors::newDirectExecutorService;
action.diffLister.executorProvider = MoreExecutors::newDirectExecutorService;
action.diffLister.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
ofyTm()
.transact(
@@ -149,7 +150,7 @@ public class ReplayCommitLogsToSqlActionTest {
@AfterEach
void afterEach() {
ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(DEFAULT_TRANSITION_MAP.toValueMap()));
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@Test
@@ -200,7 +201,7 @@ public class ReplayCommitLogsToSqlActionTest {
CommitLogMutation.create(manifest2Key, TestObject.create("f")));
jpaTm().transact(() -> SqlReplayCheckpoint.set(now.minusMinutes(1).minusMillis(1)));
fakeClock.advanceOneMilli();
runAndAssertSuccess(now);
runAndAssertSuccess(now, 2);
assertExpectedIds("previous to keep", "b", "d", "e", "f");
}
@@ -211,7 +212,7 @@ public class ReplayCommitLogsToSqlActionTest {
saveDiffFileNotToRestore(gcsUtils, now.minusMinutes(1));
saveDiffFile(gcsUtils, createCheckpoint(now.minusMillis(2)));
jpaTm().transact(() -> SqlReplayCheckpoint.set(now.minusMillis(1)));
runAndAssertSuccess(now.minusMillis(1));
runAndAssertSuccess(now.minusMillis(1), 0);
assertExpectedIds("previous to keep");
}
@@ -235,8 +236,10 @@ public class ReplayCommitLogsToSqlActionTest {
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getPayload())
.isEqualTo(
"Running in dry-run mode; would have processed %d files. They are (limit 10):\n"
+ "commit_diff_until_1999-12-31T23:59:00.000Z");
"Running in dry-run mode, the first set of commit log files processed would be from "
+ "searching from 1999-12-31T23:59:00.000Z to 1999-12-31T23:59:59.999Z and would "
+ "contain 1 file(s). They are (limit 10): \n"
+ "[commit_diff_until_1999-12-31T23:59:00.000Z]");
}
@Test
@@ -253,7 +256,7 @@ public class ReplayCommitLogsToSqlActionTest {
CommitLogManifest.create(bucketKey, now, null),
CommitLogMutation.create(manifestKey, TestObject.create("a")),
CommitLogMutation.create(manifestKey, TestObject.create("b")));
runAndAssertSuccess(now.minusMinutes(1));
runAndAssertSuccess(now.minusMinutes(1), 1);
assertExpectedIds("previous to keep", "a", "b");
}
@@ -275,7 +278,7 @@ public class ReplayCommitLogsToSqlActionTest {
getBucketKey(1),
now,
ImmutableSet.of(Key.create(TestObject.create("previous to delete")))));
runAndAssertSuccess(now.minusMinutes(1));
runAndAssertSuccess(now.minusMinutes(1), 1);
assertExpectedIds("previous to keep");
}
@@ -347,7 +350,7 @@ public class ReplayCommitLogsToSqlActionTest {
domainMutation,
contactMutation);
runAndAssertSuccess(now.minusMinutes(1));
runAndAssertSuccess(now.minusMinutes(1), 1);
// Verify two things:
// 1. that the contact insert occurred before the domain insert (necessary for FK ordering)
// even though the domain came first in the file
@@ -390,7 +393,7 @@ public class ReplayCommitLogsToSqlActionTest {
CommitLogManifest.create(
getBucketKey(1), now.minusMinutes(1).plusMillis(1), ImmutableSet.of()),
contactMutation);
runAndAssertSuccess(now.minusMinutes(1).plusMillis(1));
runAndAssertSuccess(now.minusMinutes(1).plusMillis(1), 1);
// Verify that the delete occurred first (because it was in the first transaction) even though
// deletes have higher weight
ArgumentCaptor<Object> putCaptor = ArgumentCaptor.forClass(Object.class);
@@ -435,7 +438,7 @@ public class ReplayCommitLogsToSqlActionTest {
throw new RuntimeException(e);
}
});
runAndAssertSuccess(now.minusMinutes(1));
runAndAssertSuccess(now.minusMinutes(1), 1);
// jpaTm()::put should only have been called with the checkpoint
verify(spy, times(2)).put(any(SqlReplayCheckpoint.class));
verify(spy, times(2)).put(any());
@@ -460,7 +463,7 @@ public class ReplayCommitLogsToSqlActionTest {
// one object only exists in Datastore, one is dually-written (so isn't replicated)
ImmutableSet.of(getCrossTldKey(), claimsListKey)));
runAndAssertSuccess(now.minusMinutes(1));
runAndAssertSuccess(now.minusMinutes(1), 1);
verify(spy, times(0)).delete(any(VKey.class));
}
@@ -503,7 +506,7 @@ public class ReplayCommitLogsToSqlActionTest {
createCheckpoint(now.minusMinutes(1)),
CommitLogManifest.create(bucketKey, now, null),
CommitLogMutation.create(manifestKey, TestObject.create("a")));
runAndAssertSuccess(now.minusMinutes(1));
runAndAssertSuccess(now.minusMinutes(1), 1);
assertThat(TestObject.beforeSqlSaveCallCount).isEqualTo(1);
}
@@ -522,11 +525,12 @@ public class ReplayCommitLogsToSqlActionTest {
assertThat(TestObject.beforeSqlDeleteCallCount).isEqualTo(1);
}
private void runAndAssertSuccess(DateTime expectedCheckpointTime) {
private void runAndAssertSuccess(DateTime expectedCheckpointTime, int numFiles) {
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getPayload())
.isEqualTo("ReplayCommitLogsToSqlAction completed successfully.");
.isEqualTo(
String.format("Caught up to current time after replaying %d file(s).", numFiles));
assertThat(jpaTm().transact(SqlReplayCheckpoint::get)).isEqualTo(expectedCheckpointTime);
}
@@ -88,7 +88,7 @@ public class RestoreCommitLogsActionTest {
action.gcsBucketOverride = Optional.empty();
action.diffLister = new GcsDiffFileLister();
action.diffLister.gcsUtils = gcsUtils;
action.diffLister.lazyExecutor = MoreExecutors::newDirectExecutorService;
action.diffLister.executorProvider = MoreExecutors::newDirectExecutorService;
action.diffLister.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
}
@@ -23,7 +23,6 @@ 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.setTmForTest;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.rde.RdeResourceType.CONTACT;
import static google.registry.rde.RdeResourceType.DOMAIN;
@@ -50,7 +49,7 @@ import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestExtension;
import google.registry.persistence.transaction.TransactionManager;
import google.registry.persistence.transaction.TransactionManagerFactory;
import google.registry.rde.DepositFragment;
import google.registry.rde.PendingDeposit;
import google.registry.rde.RdeResourceType;
@@ -117,12 +116,9 @@ public class RdePipelineTest {
private RdePipeline rdePipeline;
private TransactionManager originalTm;
@BeforeEach
void beforeEach() throws Exception {
originalTm = tm();
setTmForTest(jpaTm());
TransactionManagerFactory.setTmForTest(jpaTm());
loadInitialData();
// Two real registrars have been created by loadInitialData(), named "New Registrar" and "The
@@ -169,7 +165,7 @@ public class RdePipelineTest {
@AfterEach
void afterEach() {
setTmForTest(originalTm);
TransactionManagerFactory.removeTmOverrideForTest();
}
@Test
@@ -18,8 +18,8 @@ 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.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.AppEngineExtension.makeRegistrar1;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
@@ -49,7 +49,6 @@ import google.registry.model.reporting.Spec11ThreatMatch.ThreatType;
import google.registry.model.reporting.Spec11ThreatMatchDao;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestExtension;
import google.registry.persistence.transaction.TransactionManager;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeSleeper;
@@ -230,7 +229,6 @@ class Spec11PipelineTest {
}
private void setupCloudSql() {
TransactionManager originalTm = tm();
setTmForTest(jpaTm());
persistNewRegistrar("TheRegistrar");
persistNewRegistrar("NewRegistrar");
@@ -271,7 +269,7 @@ class Spec11PipelineTest {
persistResource(createDomain("no-email.com", "2A4BA9BBC-COM", registrar2, contact2));
persistResource(
createDomain("anti-anti-anti-virus.dev", "555666888-DEV", registrar3, contact3));
setTmForTest(originalTm);
removeTmOverrideForTest();
}
private void verifySaveToGcs() throws Exception {
@@ -55,6 +55,7 @@ class EppLoginTlsTest extends EppTestCase {
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
@@ -64,6 +64,7 @@ class FlowRunnerTest {
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
@@ -53,6 +53,7 @@ final class TlsCredentialsTest {
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
@@ -50,6 +50,7 @@ class CertificateCheckerTest {
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
fakeClock);
@@ -214,23 +215,45 @@ class CertificateCheckerTest {
assertThrows(
IllegalArgumentException.class,
() -> certificateChecker.checkCertificate("bad certificate string"));
assertThat(thrown).hasMessageThat().isEqualTo("Unable to read given certificate.");
assertThat(thrown).hasMessageThat().contains("Unable to read given certificate");
}
@Test
void test_isNearingExpiration_yesItIs() throws Exception {
fakeClock.setTo(DateTime.parse("2021-09-20T00:00:00Z"));
void test_getCertificate_throwsException_invalidCertificateString() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> certificateChecker.getCertificate("invalidCertificateString"));
assertThat(thrown).hasMessageThat().contains("Unable to read given certificate");
}
@Test
void test_getCertificate_returnsCertificateObject() throws Exception {
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.isNearingExpiration(certificate)).isTrue();
String certificateStr = certificateChecker.serializeCertificate(certificate);
assertThat(certificateChecker.getCertificate(certificateStr).equals(certificate)).isTrue();
}
@Test
void test_isNearingExpiration_noItsNot() throws Exception {
void test_serializeCertificate_returnsCertificateString() throws Exception {
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(
certificateChecker.getCertificate(certificateChecker.serializeCertificate(certificate)))
.isEqualTo(certificate);
}
@Test
void test_shouldReceiveExpiringNotification_returnsFalse_greaterThan30() throws Exception {
fakeClock.setTo(DateTime.parse("2021-07-20T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
@@ -238,7 +261,124 @@ class CertificateCheckerTest {
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.isNearingExpiration(certificate)).isFalse();
String certificateStr = certificateChecker.serializeCertificate(certificate);
assertThat(certificateChecker.shouldReceiveExpiringNotification(null, certificateStr))
.isFalse();
}
@Test
void test_shouldReceiveExpiringNotification_returnsFalse_hasExpired() throws Exception {
fakeClock.setTo(DateTime.parse("2021-10-20T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
String certificateStr = certificateChecker.serializeCertificate(certificate);
assertThat(certificateChecker.shouldReceiveExpiringNotification(null, certificateStr))
.isFalse();
}
@Test
void test_shouldReceiveExpiringNotification_returnsTrue_on15days() throws Exception {
fakeClock.setTo(DateTime.parse("2021-08-16T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-08-30T00:00:00Z"))
.cert();
String certificateStr = certificateChecker.serializeCertificate(certificate);
DateTime lastExpiringNotificationSentDate = DateTime.parse("2021-08-01T00:00:00Z");
assertThat(
certificateChecker.shouldReceiveExpiringNotification(
lastExpiringNotificationSentDate, certificateStr))
.isTrue();
}
@Test
void test_shouldReceiveExpiringNotification_returnsTrue_on30days() throws Exception {
fakeClock.setTo(DateTime.parse("2021-01-01T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-01-02T00:00:00Z"),
DateTime.parse("2021-01-30T00:00:00Z"))
.cert();
String certificateStr = certificateChecker.serializeCertificate(certificate);
assertThat(certificateChecker.shouldReceiveExpiringNotification(null, certificateStr)).isTrue();
}
@Test
void test_shouldReceiveExpiringNotification_returnsFalse_between30and15_lastSentDateIsNotNull()
throws Exception {
fakeClock.setTo(DateTime.parse("2021-07-05T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-06-02T00:00:00Z"),
DateTime.parse("2021-07-25T00:00:00Z"))
.cert();
String certificateStr = certificateChecker.serializeCertificate(certificate);
DateTime lastExpiringNotificationSentDate = DateTime.parse("2021-07-04T00:00:00Z");
assertThat(
certificateChecker.shouldReceiveExpiringNotification(
lastExpiringNotificationSentDate, certificateStr))
.isFalse();
}
@Test
void test_shouldReceiveExpiringNotification_returnsTrue_between30and15_lastSentDateIsNull()
throws Exception {
fakeClock.setTo(DateTime.parse("2021-07-05T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-06-02T00:00:00Z"),
DateTime.parse("2021-07-25T00:00:00Z"))
.cert();
String certificateStr = certificateChecker.serializeCertificate(certificate);
assertThat(certificateChecker.shouldReceiveExpiringNotification(null, certificateStr)).isTrue();
}
@Test
void
test_shouldReceiveExpiringNotification_returnsFalse_between15and0_lastSentDateBetween30and15_butLate()
throws Exception {
fakeClock.setTo(DateTime.parse("2021-07-05T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-06-02T00:00:00Z"),
DateTime.parse("2021-07-18T00:00:00Z"))
.cert();
String certificateStr = certificateChecker.serializeCertificate(certificate);
DateTime lastExpiringNotificationSentDate = DateTime.parse("2021-07-01T00:00:00Z");
// The first notification date is late and difference between now and
// lastExpiringNotificationSentDate is within expirationWarningIntervalDays.
assertThat(
certificateChecker.shouldReceiveExpiringNotification(
lastExpiringNotificationSentDate, certificateStr))
.isFalse();
}
@Test
void test_shouldReceiveExpiringNotification_returnsTrue_between15and0_lastSentDateBetween30and15()
throws Exception {
fakeClock.setTo(DateTime.parse("2021-07-05T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-06-02T00:00:00Z"),
DateTime.parse("2021-07-18T00:00:00Z"))
.cert();
String certificateStr = certificateChecker.serializeCertificate(certificate);
DateTime lastExpiringNotificationSentDate = DateTime.parse("2021-06-20T00:00:00Z");
assertThat(
certificateChecker.shouldReceiveExpiringNotification(
lastExpiringNotificationSentDate, certificateStr))
.isTrue();
}
@Test
@@ -50,6 +50,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
@@ -15,7 +15,6 @@
package google.registry.model.common;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.DatabaseMigrationStateSchedule.DEFAULT_TRANSITION_MAP;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_ONLY;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY;
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY_READ_ONLY;
@@ -30,6 +29,7 @@ import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.EntityTestCase;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.testing.DatabaseHelper;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.AfterEach;
@@ -45,7 +45,7 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
@AfterEach
void afterEach() {
ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(DEFAULT_TRANSITION_MAP.toValueMap()));
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@Test
@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.newContactResource;
import static google.registry.testing.DatabaseHelper.newContactResourceWithRoid;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -47,7 +48,7 @@ public class ContactHistoryTest extends EntityTestCase {
jpaTm().transact(() -> jpaTm().insert(contact));
VKey<ContactResource> contactVKey = contact.createVKey();
ContactResource contactFromDb = jpaTm().transact(() -> jpaTm().loadByKey(contactVKey));
ContactHistory contactHistory = createContactHistory(contactFromDb, contact.getRepoId());
ContactHistory contactHistory = createContactHistory(contactFromDb);
jpaTm().transact(() -> jpaTm().insert(contactHistory));
jpaTm()
.transact(
@@ -67,10 +68,7 @@ public class ContactHistoryTest extends EntityTestCase {
VKey<ContactResource> contactVKey = contact.createVKey();
ContactResource contactFromDb = jpaTm().transact(() -> jpaTm().loadByKey(contactVKey));
ContactHistory contactHistory =
createContactHistory(contactFromDb, contact.getRepoId())
.asBuilder()
.setContact(null)
.build();
createContactHistory(contactFromDb).asBuilder().setContact(null).build();
jpaTm().transact(() -> jpaTm().insert(contactHistory));
jpaTm()
@@ -91,7 +89,7 @@ public class ContactHistoryTest extends EntityTestCase {
VKey<ContactResource> contactVKey = contact.createVKey();
ContactResource contactFromDb = tm().transact(() -> tm().loadByKey(contactVKey));
fakeClock.advanceOneMilli();
ContactHistory contactHistory = createContactHistory(contactFromDb, contact.getRepoId());
ContactHistory contactHistory = createContactHistory(contactFromDb);
tm().transact(() -> tm().insert(contactHistory));
// retrieving a HistoryEntry or a ContactHistory with the same key should return the same object
@@ -106,7 +104,38 @@ public class ContactHistoryTest extends EntityTestCase {
assertThat(hostHistoryFromDb).isEqualTo(historyEntryFromDb);
}
private ContactHistory createContactHistory(ContactBase contact, String contactRepoId) {
@Test
void testBeforeSqlSave_afterContactPersisted() {
saveRegistrar("TheRegistrar");
ContactResource contactResource = newContactResource("contactId");
ContactHistory contactHistory =
new ContactHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setClientId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(true)
.setContactRepoId(contactResource.getRepoId())
.build();
jpaTm()
.transact(
() -> {
jpaTm().put(contactResource);
contactHistory.beforeSqlSaveOnReplay();
jpaTm().put(contactHistory);
});
jpaTm()
.transact(
() ->
assertAboutImmutableObjects()
.that(jpaTm().loadByEntity(contactResource))
.hasFieldsEqualTo(jpaTm().loadByEntity(contactHistory).getContactBase().get()));
}
private ContactHistory createContactHistory(ContactBase contact) {
return new ContactHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
@@ -117,7 +146,7 @@ public class ContactHistoryTest extends EntityTestCase {
.setReason("reason")
.setRequestedByRegistrar(true)
.setContact(contact)
.setContactRepoId(contactRepoId)
.setContactRepoId(contact.getRepoId())
.build();
}
@@ -190,6 +190,44 @@ public class DomainHistoryTest extends EntityTestCase {
jpaTm().transact(() -> jpaTm().put(domainHistoryFromDb2));
}
@TestSqlOnly
void testBeforeSqlSave_afterDomainPersisted() {
DomainBase domain = createDomainWithContactsAndHosts();
DomainHistory historyWithoutResource =
new DomainHistory.Builder()
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setClientId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(true)
.setDomainRepoId(domain.getRepoId())
.setOtherClientId("otherClient")
.setPeriod(Period.create(1, Period.Unit.YEARS))
.build();
jpaTm()
.transact(
() -> {
jpaTm()
.put(
domain
.asBuilder()
.setPersistedCurrentSponsorClientId("NewRegistrar")
.build());
historyWithoutResource.beforeSqlSaveOnReplay();
jpaTm().put(historyWithoutResource);
});
jpaTm()
.transact(
() ->
assertAboutImmutableObjects()
.that(jpaTm().loadByEntity(domain))
.hasFieldsEqualTo(
jpaTm().loadByEntity(historyWithoutResource).getDomainContent().get()));
}
static DomainBase createDomainWithContactsAndHosts() {
createTld("tld");
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.newHostResource;
import static google.registry.testing.DatabaseHelper.newHostResourceWithRoid;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -48,7 +49,7 @@ public class HostHistoryTest extends EntityTestCase {
VKey<HostResource> hostVKey =
VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1"));
HostResource hostFromDb = jpaTm().transact(() -> jpaTm().loadByKey(hostVKey));
HostHistory hostHistory = createHostHistory(hostFromDb, host.getRepoId());
HostHistory hostHistory = createHostHistory(hostFromDb);
jpaTm().transact(() -> jpaTm().insert(hostHistory));
jpaTm()
.transact(
@@ -66,8 +67,7 @@ public class HostHistoryTest extends EntityTestCase {
jpaTm().transact(() -> jpaTm().insert(host));
HostResource hostFromDb = jpaTm().transact(() -> jpaTm().loadByKey(host.createVKey()));
HostHistory hostHistory =
createHostHistory(hostFromDb, host.getRepoId()).asBuilder().setHost(null).build();
HostHistory hostHistory = createHostHistory(hostFromDb).asBuilder().setHost(null).build();
jpaTm().transact(() -> jpaTm().insert(hostHistory));
jpaTm()
@@ -88,7 +88,7 @@ public class HostHistoryTest extends EntityTestCase {
VKey<HostResource> hostVKey =
VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1"));
HostResource hostFromDb = tm().transact(() -> tm().loadByKey(hostVKey));
HostHistory hostHistory = createHostHistory(hostFromDb, host.getRepoId());
HostHistory hostHistory = createHostHistory(hostFromDb);
fakeClock.advanceOneMilli();
tm().transact(() -> tm().insert(hostHistory));
@@ -104,6 +104,37 @@ public class HostHistoryTest extends EntityTestCase {
assertThat(hostHistoryFromDb).isEqualTo(historyEntryFromDb);
}
@Test
void testBeforeSqlSave_afterHostPersisted() {
saveRegistrar("TheRegistrar");
HostResource hostResource = newHostResource("ns1.example.tld");
HostHistory hostHistory =
new HostHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setClientId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(true)
.setHostRepoId(hostResource.getRepoId())
.build();
jpaTm()
.transact(
() -> {
jpaTm().put(hostResource);
hostHistory.beforeSqlSaveOnReplay();
jpaTm().put(hostHistory);
});
jpaTm()
.transact(
() ->
assertAboutImmutableObjects()
.that(jpaTm().loadByEntity(hostResource))
.hasFieldsEqualTo(jpaTm().loadByEntity(hostHistory).getHostBase().get()));
}
private void assertHostHistoriesEqual(HostHistory one, HostHistory two) {
assertAboutImmutableObjects().that(one).isEqualExceptFields(two, "hostBase");
assertAboutImmutableObjects()
@@ -111,7 +142,7 @@ public class HostHistoryTest extends EntityTestCase {
.isEqualExceptFields(two.getHostBase().orElse(null), "repoId");
}
private HostHistory createHostHistory(HostBase hostBase, String hostRepoId) {
private HostHistory createHostHistory(HostBase hostBase) {
return new HostHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
@@ -122,7 +153,7 @@ public class HostHistoryTest extends EntityTestCase {
.setReason("reason")
.setRequestedByRegistrar(true)
.setHost(hostBase)
.setHostRepoId(hostRepoId)
.setHostRepoId(hostBase.getRepoId())
.build();
}
}
@@ -74,7 +74,7 @@ public class LegacyHistoryObjectTest extends EntityTestCase {
assertAboutImmutableObjects()
.that(legacyHistoryEntry)
.isEqualExceptFields(fromObjectify, "contactBase", "contactRepoId");
assertThat(fromObjectify instanceof ContactHistory).isTrue();
assertThat(fromObjectify).isInstanceOf(ContactHistory.class);
ContactHistory legacyContactHistory = (ContactHistory) fromObjectify;
// Next, save that from-Datastore object in SQL and verify we can load it back in
@@ -124,7 +124,7 @@ public class LegacyHistoryObjectTest extends EntityTestCase {
"nsHosts",
"dsDataHistories",
"gracePeriodHistories");
assertThat(fromObjectify instanceof DomainHistory).isTrue();
assertThat(fromObjectify).isInstanceOf(DomainHistory.class);
DomainHistory legacyDomainHistory = (DomainHistory) fromObjectify;
// Next, save that from-Datastore object in SQL and verify we can load it back in
@@ -137,15 +137,7 @@ public class LegacyHistoryObjectTest extends EntityTestCase {
// Don't compare nsHosts directly because one is null and the other is empty
assertAboutImmutableObjects()
.that(legacyDomainHistory)
.isEqualExceptFields(
// NB: period, transaction records, and other client ID are added in #794
legacyHistoryFromSql,
"period",
"domainTransactionRecords",
"otherClientId",
"nsHosts",
"dsDataHistories",
"gracePeriodHistories");
.isEqualExceptFields(legacyHistoryFromSql, "nsHosts");
assertThat(nullToEmpty(legacyDomainHistory.getNsHosts()))
.isEqualTo(nullToEmpty(legacyHistoryFromSql.getNsHosts()));
});
@@ -174,7 +166,7 @@ public class LegacyHistoryObjectTest extends EntityTestCase {
assertAboutImmutableObjects()
.that(legacyHistoryEntry)
.isEqualExceptFields(fromObjectify, "hostBase", "hostRepoId");
assertThat(fromObjectify instanceof HostHistory).isTrue();
assertThat(fromObjectify).isInstanceOf(HostHistory.class);
HostHistory legacyHostHistory = (HostHistory) fromObjectify;
// Next, save that from-Datastore object in SQL and verify we can load it back in
@@ -29,6 +29,7 @@ import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResources;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
@@ -414,6 +415,64 @@ class RegistrarTest extends EntityTestCase {
IllegalArgumentException.class, () -> new Registrar.Builder().setPhonePasscode("code1"));
}
@TestOfyAndSql
void testSuccess_getLastExpiringCertNotificationSentDate_returnsInitialValue() {
assertThat(registrar.getLastExpiringCertNotificationSentDate()).isEqualTo(START_OF_TIME);
}
@TestOfyAndSql
void testSuccess_getLastExpiringFailoverCertNotificationSentDate_returnsInitialValue() {
assertThat(registrar.getLastExpiringFailoverCertNotificationSentDate())
.isEqualTo(START_OF_TIME);
}
@TestOfyAndSql
void testSuccess_setLastExpiringCertNotificationSentDate() {
assertThat(
registrar
.asBuilder()
.setLastExpiringCertNotificationSentDate(fakeClock.nowUtc())
.build()
.getLastExpiringCertNotificationSentDate())
.isEqualTo(fakeClock.nowUtc());
}
@TestOfyAndSql
void testFailure_setLastExpiringCertNotificationSentDate_nullDate() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> new Registrar.Builder().setLastExpiringCertNotificationSentDate(null).build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Registrar lastExpiringCertNotificationSentDate cannot be null");
}
@TestOfyAndSql
void testSuccess_setLastExpiringFailoverCertNotificationSentDate() {
assertThat(
registrar
.asBuilder()
.setLastExpiringFailoverCertNotificationSentDate(fakeClock.nowUtc())
.build()
.getLastExpiringFailoverCertNotificationSentDate())
.isEqualTo(fakeClock.nowUtc());
}
@TestOfyAndSql
void testFailure_setLastExpiringFailoverCertNotificationSentDate_nullDate() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
new Registrar.Builder()
.setLastExpiringFailoverCertNotificationSentDate(null)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Registrar lastExpiringFailoverCertNotificationSentDate cannot be null");
}
@TestOfyAndSql
void testSuccess_setAllowedTlds() {
assertThat(
@@ -1,86 +0,0 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.server;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import java.util.Optional;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Tests for {@link google.registry.model.server.KmsSecretRevisionSqlDao}. */
public class KmsSecretRevisionSqlDaoTest {
private final FakeClock fakeClock = new FakeClock();
@RegisterExtension
@Order(value = 1)
DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension();
@RegisterExtension
JpaIntegrationWithCoverageExtension jpa =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@Test
void testSaveAndRetrieve() {
KmsSecretRevision revision = createRevision();
jpaTm().transact(() -> KmsSecretRevisionSqlDao.save(revision));
Optional<KmsSecretRevision> fromSql =
jpaTm().transact(() -> KmsSecretRevisionSqlDao.getLatestRevision("secretName"));
assertThat(fromSql.isPresent()).isTrue();
assertAboutImmutableObjects().that(revision).isEqualExceptFields(fromSql.get(), "creationTime");
}
@Test
void testMultipleRevisions() {
KmsSecretRevision revision = createRevision();
jpaTm().transact(() -> KmsSecretRevisionSqlDao.save(revision));
KmsSecretRevision secondRevision = createRevision();
secondRevision.encryptedValue = "someOtherValue";
jpaTm().transact(() -> KmsSecretRevisionSqlDao.save(secondRevision));
Optional<KmsSecretRevision> fromSql =
jpaTm().transact(() -> KmsSecretRevisionSqlDao.getLatestRevision("secretName"));
assertThat(fromSql.isPresent()).isTrue();
assertThat(fromSql.get().getEncryptedValue()).isEqualTo("someOtherValue");
}
@Test
void testNonexistent() {
KmsSecretRevision revision = createRevision();
jpaTm().transact(() -> KmsSecretRevisionSqlDao.save(revision));
assertThat(
jpaTm()
.transact(() -> KmsSecretRevisionSqlDao.getLatestRevision("someOtherSecretName"))
.isPresent())
.isFalse();
}
private KmsSecretRevision createRevision() {
return new KmsSecretRevision.Builder()
.setEncryptedValue("encrypted")
.setKmsCryptoKeyVersionName("version")
.setParent("secretName")
.build();
}
}
@@ -1,68 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.server;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.base.Strings;
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 google.registry.model.server.KmsSecretRevision}. */
public class KmsSecretRevisionTest {
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
private KmsSecretRevision secretRevision;
@BeforeEach
void beforeEach() {
secretRevision =
persistResource(
new KmsSecretRevision.Builder()
.setKmsCryptoKeyVersionName("foo")
.setParent("bar")
.setEncryptedValue("blah")
.build());
}
@Test
void test_setEncryptedValue_tooLong_throwsException() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
secretRevision =
persistResource(
new KmsSecretRevision.Builder()
.setKmsCryptoKeyVersionName("foo")
.setParent("bar")
.setEncryptedValue(Strings.repeat("a", 64 * 1024 * 1024 + 1))
.build()));
assertThat(thrown).hasMessageThat().contains("Secret is greater than 67108864 bytes");
}
@Test
void testPersistence() {
assertThat(ofyTm().loadByEntity(secretRevision)).isEqualTo(secretRevision);
}
}
@@ -1,52 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.server;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.testing.DatabaseHelper.persistResource;
import google.registry.testing.AppEngineExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
public class KmsSecretTest {
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
private KmsSecret secret;
private KmsSecretRevision secretRevision;
@BeforeEach
void setUp() {
secretRevision =
persistResource(
new KmsSecretRevision.Builder()
.setKmsCryptoKeyVersionName("foo")
.setParent("bar")
.setEncryptedValue("blah")
.build());
secret = persistResource(KmsSecret.create("someSecret", secretRevision));
}
@Test
void testPersistence() {
assertThat(ofyTm().loadByEntity(secret)).isEqualTo(secret);
}
}
@@ -202,11 +202,13 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft
JpaTransactionManagerImpl txnManager = new JpaTransactionManagerImpl(emf, clock);
cachedTm = TransactionManagerFactory.jpaTm();
TransactionManagerFactory.setJpaTm(Suppliers.ofInstance(txnManager));
JpaTransactionManagerImpl.setReplaySqlToDatastoreOverrideForTest(false);
}
@Override
public void afterEach(ExtensionContext context) {
TransactionManagerFactory.setJpaTm(Suppliers.ofInstance(cachedTm));
JpaTransactionManagerImpl.removeReplaySqlToDsOverrideForTest();
cachedTm = null;
}
@@ -17,7 +17,7 @@ package google.registry.persistence.transaction;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.DatabaseHelper.assertDetached;
import static google.registry.testing.DatabaseHelper.assertDetachedFromEntityManager;
import static google.registry.testing.TestDataHelper.fileClassPath;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
@@ -388,7 +388,8 @@ class JpaTransactionManagerImplTest {
void load_succeeds() {
assertThat(jpaTm().transact(() -> jpaTm().exists(theEntity))).isFalse();
jpaTm().transact(() -> jpaTm().insert(theEntity));
TestEntity persisted = jpaTm().transact(() -> assertDetached(jpaTm().loadByKey(theEntityKey)));
TestEntity persisted =
jpaTm().transact(() -> assertDetachedFromEntityManager(jpaTm().loadByKey(theEntityKey)));
assertThat(persisted.name).isEqualTo("theEntity");
assertThat(persisted.data).isEqualTo("foo");
}
@@ -404,7 +405,8 @@ class JpaTransactionManagerImplTest {
@Test
void loadByEntity_succeeds() {
jpaTm().transact(() -> jpaTm().insert(theEntity));
TestEntity persisted = jpaTm().transact(() -> assertDetached(jpaTm().loadByEntity(theEntity)));
TestEntity persisted =
jpaTm().transact(() -> assertDetachedFromEntityManager(jpaTm().loadByEntity(theEntity)));
assertThat(persisted.name).isEqualTo("theEntity");
assertThat(persisted.data).isEqualTo("foo");
}
@@ -414,7 +416,11 @@ class JpaTransactionManagerImplTest {
assertThat(jpaTm().transact(() -> jpaTm().exists(theEntity))).isFalse();
jpaTm().transact(() -> jpaTm().insert(theEntity));
TestEntity persisted =
jpaTm().transact(() -> assertDetached(jpaTm().loadByKeyIfPresent(theEntityKey).get()));
jpaTm()
.transact(
() ->
assertDetachedFromEntityManager(
jpaTm().loadByKeyIfPresent(theEntityKey).get()));
assertThat(persisted.name).isEqualTo("theEntity");
assertThat(persisted.data).isEqualTo("foo");
}
@@ -431,7 +437,9 @@ class JpaTransactionManagerImplTest {
assertThat(jpaTm().transact(() -> jpaTm().exists(compoundIdEntity))).isFalse();
jpaTm().transact(() -> jpaTm().insert(compoundIdEntity));
TestCompoundIdEntity persisted =
jpaTm().transact(() -> assertDetached(jpaTm().loadByKey(compoundIdEntityKey)));
jpaTm()
.transact(
() -> assertDetachedFromEntityManager(jpaTm().loadByKey(compoundIdEntityKey)));
assertThat(persisted.name).isEqualTo("compoundIdEntity");
assertThat(persisted.age).isEqualTo(10);
assertThat(persisted.data).isEqualTo("foo");
@@ -450,7 +458,7 @@ class JpaTransactionManagerImplTest {
theEntityKey, VKey.createSql(TestEntity.class, "does-not-exist")));
assertThat(results).containsExactly(theEntityKey, theEntity);
assertDetached(results.get(theEntityKey));
assertDetachedFromEntityManager(results.get(theEntityKey));
});
}
@@ -463,7 +471,7 @@ class JpaTransactionManagerImplTest {
ImmutableMap<VKey<? extends TestEntity>, TestEntity> results =
jpaTm().loadByKeysIfPresent(ImmutableList.of(theEntityKey));
assertThat(results).containsExactly(theEntityKey, theEntity);
assertDetached(results.get(theEntityKey));
assertDetachedFromEntityManager(results.get(theEntityKey));
});
}
@@ -478,7 +486,7 @@ class JpaTransactionManagerImplTest {
.loadByEntitiesIfPresent(
ImmutableList.of(theEntity, new TestEntity("does-not-exist", "bar")));
assertThat(results).containsExactly(theEntity);
assertDetached(results.get(0));
assertDetachedFromEntityManager(results.get(0));
});
}
@@ -491,7 +499,7 @@ class JpaTransactionManagerImplTest {
ImmutableList<TestEntity> results =
jpaTm().loadByEntities(ImmutableList.of(theEntity));
assertThat(results).containsExactly(theEntity);
assertDetached(results.get(0));
assertDetachedFromEntityManager(results.get(0));
});
}
@@ -503,7 +511,7 @@ class JpaTransactionManagerImplTest {
.transact(
() ->
jpaTm().loadAllOf(TestEntity.class).stream()
.map(DatabaseHelper::assertDetached)
.map(DatabaseHelper::assertDetachedFromEntityManager)
.collect(toImmutableList()));
assertThat(persisted).containsExactlyElementsIn(moreEntities);
}
@@ -77,7 +77,7 @@ public class QueryComposerTest {
tm().createQueryComposer(TestEntity.class)
.where("name", Comparator.GT, "bravo")
.first()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.get()))
.isEqualTo(charlie);
assertThat(
@@ -86,7 +86,7 @@ public class QueryComposerTest {
tm().createQueryComposer(TestEntity.class)
.where("name", Comparator.GTE, "charlie")
.first()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.get()))
.isEqualTo(charlie);
assertThat(
@@ -95,7 +95,7 @@ public class QueryComposerTest {
tm().createQueryComposer(TestEntity.class)
.where("name", Comparator.LT, "bravo")
.first()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.get()))
.isEqualTo(alpha);
assertThat(
@@ -104,7 +104,7 @@ public class QueryComposerTest {
tm().createQueryComposer(TestEntity.class)
.where("name", Comparator.LTE, "alpha")
.first()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.get()))
.isEqualTo(alpha);
}
@@ -125,7 +125,7 @@ public class QueryComposerTest {
assertThat(
transactIfJpaTm(
() ->
DatabaseHelper.assertDetached(
QueryComposerTest.assertDetachedIfJpa(
tm().createQueryComposer(TestEntity.class)
.where("name", Comparator.EQ, "alpha")
.getSingleResult())))
@@ -174,7 +174,7 @@ public class QueryComposerTest {
.createQueryComposer(TestEntity.class)
.where("name", Comparator.GT, "alpha")
.stream()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.collect(toImmutableList())))
.containsExactly(bravo, charlie);
assertThat(
@@ -184,7 +184,7 @@ public class QueryComposerTest {
.createQueryComposer(TestEntity.class)
.where("name", Comparator.GTE, "bravo")
.stream()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.collect(toImmutableList())))
.containsExactly(bravo, charlie);
assertThat(
@@ -194,7 +194,7 @@ public class QueryComposerTest {
.createQueryComposer(TestEntity.class)
.where("name", Comparator.LT, "charlie")
.stream()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.collect(toImmutableList())))
.containsExactly(alpha, bravo);
assertThat(
@@ -204,7 +204,7 @@ public class QueryComposerTest {
.createQueryComposer(TestEntity.class)
.where("name", Comparator.LTE, "bravo")
.stream()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.collect(toImmutableList())))
.containsExactly(alpha, bravo);
}
@@ -228,7 +228,7 @@ public class QueryComposerTest {
tm().createQueryComposer(TestEntity.class)
.where("val", Comparator.EQ, 2)
.first()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.get()))
.isEqualTo(bravo);
}
@@ -243,7 +243,7 @@ public class QueryComposerTest {
.where("val", Comparator.GT, 1)
.orderBy("val")
.stream()
.map(DatabaseHelper::assertDetached)
.map(QueryComposerTest::assertDetachedIfJpa)
.collect(toImmutableList())))
.containsExactly(bravo, alpha);
}
@@ -354,6 +354,13 @@ public class QueryComposerTest {
.contains("The LIKE operation is not supported on Datastore.");
}
private static <T> T assertDetachedIfJpa(T entity) {
if (!tm().isOfy()) {
return DatabaseHelper.assertDetachedFromEntityManager(entity);
}
return entity;
}
@javax.persistence.Entity
@Entity(name = "QueryComposerTestEntity")
private static class TestEntity extends ImmutableObject {
@@ -22,36 +22,53 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.model.ofy.Ofy;
import google.registry.persistence.VKey;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
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;
class TransactionTest {
private final FakeClock fakeClock = new FakeClock(DateTime.parse("2000-01-01TZ"));
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder()
.withDatastoreAndCloudSql()
.withClock(fakeClock)
.withOfyTestEntities(TestEntity.class)
.withJpaUnitTestEntities(TestEntity.class)
.build();
@RegisterExtension public final InjectExtension inject = new InjectExtension();
private TestEntity fooEntity, barEntity;
@BeforeEach
void beforeEach() {
JpaTransactionManagerImpl.removeReplaySqlToDsOverrideForTest();
inject.setStaticField(Ofy.class, "clock", fakeClock);
fooEntity = new TestEntity("foo");
barEntity = new TestEntity("bar");
}
@AfterEach
void afterEach() {
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@Test
void testTransactionReplay() {
Transaction txn = new Transaction.Builder().addUpdate(fooEntity).addUpdate(barEntity).build();
@@ -102,14 +119,13 @@ class TransactionTest {
@Test
void testTransactionSerialization() throws IOException {
RegistryConfig.overrideCloudSqlReplicateTransactions(true);
try {
jpaTm()
.transact(
() -> {
jpaTm().insert(fooEntity);
jpaTm().insert(barEntity);
});
DatabaseHelper.setMigrationScheduleToSqlPrimary(fakeClock);
jpaTm()
.transact(
() -> {
jpaTm().insert(fooEntity);
jpaTm().insert(barEntity);
});
TransactionEntity txnEnt =
jpaTm().transact(() -> jpaTm().loadByKey(VKey.createSql(TransactionEntity.class, 1L)));
Transaction txn = Transaction.deserialize(txnEnt.getContents());
@@ -125,9 +141,6 @@ class TransactionTest {
assertThat(
jpaTm().transact(() -> jpaTm().exists(VKey.createSql(TransactionEntity.class, 2L))))
.isFalse();
} finally {
RegistryConfig.overrideCloudSqlReplicateTransactions(false);
}
}
@Test
@@ -30,7 +30,6 @@ import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.model.registry.RegistryTest;
import google.registry.model.registry.label.ReservedListDaoTest;
import google.registry.model.reporting.Spec11ThreatMatchTest;
import google.registry.model.server.KmsSecretRevisionSqlDaoTest;
import google.registry.model.server.LockTest;
import google.registry.model.server.ServerSecretTest;
import google.registry.model.smd.SignedMarkRevocationListDaoTest;
@@ -89,7 +88,6 @@ import org.junit.runner.RunWith;
DomainBaseSqlTest.class,
DomainHistoryTest.class,
HostHistoryTest.class,
KmsSecretRevisionSqlDaoTest.class,
LockTest.class,
PollMessageTest.class,
PremiumListDaoTest.class,
@@ -26,15 +26,16 @@ import com.google.common.testing.TestLogHandler;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.ofy.CommitLogBucket;
import google.registry.model.ofy.Ofy;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
import google.registry.persistence.transaction.TransactionEntity;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import java.util.List;
@@ -68,41 +69,21 @@ public class ReplicateToDatastoreActionTest {
@BeforeEach
public void setUp() {
JpaTransactionManagerImpl.removeReplaySqlToDsOverrideForTest();
injectExtension.setStaticField(Ofy.class, "clock", fakeClock);
// Use a single bucket to expose timestamp inversion problems.
injectExtension.setStaticField(
CommitLogBucket.class, "bucketIdSupplier", Suppliers.ofInstance(1));
fakeClock.setAutoIncrementByOneMilli();
RegistryConfig.overrideCloudSqlReplicateTransactions(true);
DatabaseHelper.setMigrationScheduleToSqlPrimary(fakeClock);
Logger.getLogger(ReplicateToDatastoreAction.class.getCanonicalName()).addHandler(logHandler);
DateTime now = fakeClock.nowUtc();
ofyTm()
.transact(
() ->
DatabaseMigrationStateSchedule.set(
ImmutableSortedMap.of(
START_OF_TIME,
MigrationState.DATASTORE_ONLY,
now,
MigrationState.DATASTORE_PRIMARY,
now.plusHours(1),
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
now.plusHours(2),
MigrationState.SQL_PRIMARY)));
fakeClock.advanceBy(Duration.standardDays(1));
}
@AfterEach
public void tearDown() {
DatabaseHelper.removeDatabaseMigrationSchedule();
fakeClock.disableAutoIncrement();
RegistryConfig.overrideCloudSqlReplicateTransactions(false);
ofyTm()
.transact(
() ->
ofyTm()
.loadSingleton(DatabaseMigrationStateSchedule.class)
.ifPresent(ofyTm()::delete));
DatabaseMigrationStateSchedule.CACHE.invalidateAll();
}
@RetryingTest(4)
@@ -35,6 +35,7 @@ import static google.registry.model.ResourceTransferUtils.createTransferResponse
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.model.registry.Registry.TldState.GENERAL_AVAILABILITY;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.ofyTmOrDoNothing;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
@@ -71,6 +72,8 @@ import google.registry.model.EppResourceUtils;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
@@ -123,6 +126,7 @@ import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
/** Static utils for setting up test resources. */
public class DatabaseHelper {
@@ -1351,16 +1355,54 @@ public class DatabaseHelper {
}
/**
* In JPA mode, assert that the given entity is detached from the current entity manager.
* Asserts that the given entity is detached from the current JPA entity manager.
*
* <p>Returns the original entity object.
*/
public static <T> T assertDetached(T entity) {
if (!tm().isOfy()) {
assertThat(jpaTm().getEntityManager().contains(entity)).isFalse();
}
public static <T> T assertDetachedFromEntityManager(T entity) {
assertThat(jpaTm().getEntityManager().contains(entity)).isFalse();
return entity;
}
/**
* Sets a SQL_PRIMARY state on the {@link DatabaseMigrationStateSchedule}.
*
* <p>In order to allow for tests to manipulate the clock how they need, we start the transitions
* one millisecond after the clock's current time (in case the clock's current value is
* START_OF_TIME). We then advance the clock one second so that we're in the SQL_PRIMARY phase.
*
* <p>We must use the current time, otherwise the setting of the migration state will fail due to
* an invalid transition.
*/
public static void setMigrationScheduleToSqlPrimary(FakeClock fakeClock) {
DateTime now = fakeClock.nowUtc();
ofyTm()
.transact(
() ->
DatabaseMigrationStateSchedule.set(
ImmutableSortedMap.of(
START_OF_TIME,
MigrationState.DATASTORE_ONLY,
now.plusMillis(1),
MigrationState.DATASTORE_PRIMARY,
now.plusMillis(2),
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
now.plusMillis(3),
MigrationState.SQL_PRIMARY)));
fakeClock.advanceBy(Duration.standardSeconds(1));
}
/** Removes the database migration schedule, in essence transitioning to DATASTORE_ONLY. */
public static void removeDatabaseMigrationSchedule() {
// use the raw calls because going SQL_PRIMARY -> DATASTORE_ONLY is not valid
ofyTm()
.transact(
() ->
ofyTm()
.loadSingleton(DatabaseMigrationStateSchedule.class)
.ifPresent(ofyTm()::delete));
DatabaseMigrationStateSchedule.CACHE.invalidateAll();
}
private DatabaseHelper() {}
}
@@ -22,12 +22,12 @@ import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.model.ofy.CommitLogBucket;
import google.registry.model.ofy.ReplayQueue;
import google.registry.model.ofy.TransactionInfo;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
import google.registry.persistence.transaction.TransactionEntity;
import google.registry.schema.replay.DatastoreEntity;
import google.registry.schema.replay.ReplicateToDatastoreAction;
@@ -92,7 +92,7 @@ public class ReplayExtension implements BeforeEachCallback, AfterEachCallback {
// transaction manager gets injected.
inOfyContext = DualDatabaseTestInvocationContextProvider.inOfyContext(context);
if (sqlToDsReplicator != null && !inOfyContext) {
RegistryConfig.overrideCloudSqlReplicateTransactions(true);
JpaTransactionManagerImpl.setReplaySqlToDatastoreOverrideForTest(true);
}
context.getStore(ExtensionContext.Namespace.GLOBAL).put(ReplayExtension.class, this);
@@ -105,7 +105,7 @@ public class ReplayExtension implements BeforeEachCallback, AfterEachCallback {
replay();
injectExtension.afterEach(context);
if (sqlToDsReplicator != null) {
RegistryConfig.overrideCloudSqlReplicateTransactions(false);
JpaTransactionManagerImpl.setReplaySqlToDatastoreOverrideForTest(false);
}
}
@@ -116,7 +116,6 @@ public class ReplayExtension implements BeforeEachCallback, AfterEachCallback {
"PremiumListEntry",
"ReservedList",
"RdeRevision",
"KmsSecretRevision",
"ServerSecret",
"SignedMarkRevocationList",
"ClaimsListShard",
@@ -75,7 +75,8 @@ public class TestObject extends ImmutableObject implements DatastoreAndSqlEntity
beforeSqlDeleteCallCount++;
}
public static void beforeSqlSave(TestObject testObject) {
@Override
public void beforeSqlSaveOnReplay() {
beforeSqlSaveCallCount++;
}
@@ -67,6 +67,7 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
fakeClock);
@@ -21,6 +21,7 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
@@ -33,7 +34,7 @@ public class GetDatabaseMigrationStateCommandTest
@AfterEach
void afterEach() {
ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(DEFAULT_TRANSITION_MAP.toValueMap()));
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@TestOfyAndSql
@@ -25,32 +25,20 @@ import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
/** Tests for {@link SetDatabaseMigrationStateCommand}. */
@DualDatabaseTest
public class SetDatabaseMigrationStateCommandTest
extends CommandTestCase<SetDatabaseMigrationStateCommand> {
@BeforeEach
void beforeEach() {
// clear out any static state that may have been persisted
ofyTm()
.transact(
() ->
ofyTm()
.loadSingleton(DatabaseMigrationStateSchedule.class)
.ifPresent(ofyTm()::delete));
DatabaseMigrationStateSchedule.CACHE.invalidateAll();
}
@AfterEach
void afterEach() {
ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(DEFAULT_TRANSITION_MAP.toValueMap()));
DatabaseHelper.removeDatabaseMigrationSchedule();
}
@TestOfyAndSql
@@ -53,6 +53,7 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
fakeClock);
@@ -65,6 +65,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
fakeClock);
@@ -128,6 +128,7 @@ public abstract class RegistrarSettingsActionTestCase {
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
@@ -11,8 +11,6 @@ ForeignKeyDomainIndex
ForeignKeyHostIndex
HistoryEntry
HostResource
KmsSecret
KmsSecretRevision
LastSqlTransaction
Modification
OneTime
@@ -2,8 +2,6 @@ ClaimsList
ClaimsListSingleton
Cursor
DatabaseMigrationStateSchedule
KmsSecret
KmsSecretRevision
PremiumList
PremiumListEntry
PremiumListRevision
@@ -8,8 +8,6 @@ ForeignKeyDomainIndex
ForeignKeyHostIndex
HistoryEntry
HostResource
KmsSecret
KmsSecretRevision
Modification
OneTime
PollMessage
@@ -600,6 +600,8 @@ class google.registry.model.registrar.Registrar {
java.util.Set<java.lang.String> allowedTlds;
java.util.Set<java.lang.String> rdapBaseUrls;
org.joda.time.DateTime lastCertificateUpdateTime;
org.joda.time.DateTime lastExpiringCertNotificationSentDate;
org.joda.time.DateTime lastExpiringFailoverCertNotificationSentDate;
}
class google.registry.model.registrar.Registrar$BillingAccountEntry {
java.lang.String accountId;
@@ -825,18 +827,6 @@ enum google.registry.model.reporting.HistoryEntry$Type {
RDE_IMPORT;
SYNTHETIC;
}
class google.registry.model.server.KmsSecret {
@Id java.lang.String name;
@Parent com.googlecode.objectify.Key<google.registry.model.common.EntityGroupRoot> parent;
com.googlecode.objectify.Key<google.registry.model.server.KmsSecretRevision> latestRevision;
}
class google.registry.model.server.KmsSecretRevision {
@Id long revisionKey;
@Parent com.googlecode.objectify.Key<google.registry.model.server.KmsSecret> parent;
google.registry.model.CreateAutoTimestamp creationTime;
java.lang.String encryptedValue;
java.lang.String kmsCryptoKeyVersionName;
}
class google.registry.model.server.Lock {
@Id java.lang.String lockId;
java.lang.String requestLogId;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -95,3 +95,4 @@ V94__rename_lock_scope.sql
V95__add_contacts_indexes_on_domain.sql
V96__rename_sql_checkpoint_fields.sql
V97__add_recurrence_history_id_column_to_onetime.sql
V98__add_last_expiring_cert_notification_sent_date.sql
@@ -0,0 +1,18 @@
-- 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.
alter table "Registrar"
add column if not exists "last_expiring_cert_notification_sent_date" timestamptz;
alter table "Registrar"
add column if not exists "last_expiring_failover_cert_notification_sent_date" timestamptz;
@@ -491,15 +491,6 @@
primary key (host_repo_id, history_revision_id)
);
create table "KmsSecret" (
revision_id int8 not null,
creation_time timestamptz not null,
encrypted_value text not null,
crypto_key_version_name text not null,
secret_name text not null,
primary key (revision_id)
);
create table "Lock" (
resource_name text not null,
scope text not null,
@@ -590,6 +581,8 @@
i18n_address_zip text,
ip_address_allow_list text[],
last_certificate_update_time timestamptz,
last_expiring_cert_notification_sent_date timestamptz,
last_expiring_failover_cert_notification_sent_date timestamptz,
last_update_time timestamptz not null,
localized_address_city text,
localized_address_country_code text,
@@ -806,7 +799,6 @@ create index IDX1iy7njgb7wjmj9piml4l2g0qi on "HostHistory" (history_registrar_id
create index IDXkkwbwcwvrdkkqothkiye4jiff on "HostHistory" (host_name);
create index IDXknk8gmj7s47q56cwpa6rmpt5l on "HostHistory" (history_type);
create index IDX67qwkjtlq5q8dv6egtrtnhqi7 on "HostHistory" (history_modification_time);
create index IDXli9nil3s4t4p21i3xluvvilb7 on "KmsSecret" (secret_name);
create index IDXe7wu46c7wpvfmfnj4565abibp on "PollMessage" (registrar_id);
create index IDXaydgox62uno9qx8cjlj5lauye on "PollMessage" (event_time);
create index premiumlist_name_idx on "PremiumList" (name);
@@ -800,7 +800,9 @@ CREATE TABLE public."Registrar" (
state text,
type text NOT NULL,
url text,
whois_server text
whois_server text,
last_expiring_cert_notification_sent_date timestamp with time zone,
last_expiring_failover_cert_notification_sent_date timestamp with time zone
);