1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

..

7 Commits

Author SHA1 Message Date
gbrodman b78d12e73f Add a DelegatingReplicaJpaTransactionManager to handle multiple replicas (#3005)
This will allow us to spread the load across multiple Postgres replica
instances which should help with latency and stability.
2026-04-10 20:46:06 +00:00
Weimin Yu 4a1d0609f3 Support Fee-1.0 XML parser in all environments (#3007)
Add the Fee-1.0 schema in production, allowing the requests with this
extension to be parsed. This allows us to test this extension before hand.

The announcement of this extension in greeting is controlled by a
feature flag in ProtocolDefinition.java. As long as it is not announced,
we do not expect real customers to use this extension.
2026-04-09 20:45:36 +00:00
gbrodman 61b121f464 Fix Gradle issues when running devTool (#3006)
I think this may have been introduced as part of Gradle 9? Not sure why
it's not showing up in the remote builds but without this, I can't run
any "devTool" commands.

Note: the fixes were suggested by gemini-cli
2026-04-09 20:11:44 +00:00
Weimin Yu 074f78cfb3 Fix docker client api version on CloudBuild (#3004)
The docker engine provided by CloudBuild only supports up to 1.41.

Explicitly set API version for downloaded client for now.
2026-04-08 18:13:26 +00:00
Weimin Yu 7be5fe4c01 Fix Flyway script (#3003)
Removing the obsolete command line argument `community`.
2026-04-07 20:40:04 +00:00
Weimin Yu 1876e2c3e8 Fix Kyeth for Java 25 (#3002) 2026-04-06 21:42:11 +00:00
Ben McIlwain 49f14b5e1b Set clock precision to milliseconds for Datetime->Instant migration (#2999)
Our existing precision is milliseconds so we want to stick with that for
Instants. If we want to increase the precision globally after that we can do so
all in one go post-migration, but for now, it would be a bad thing to have mixed
precision going on just depending on whether a class happens to be migrated yet
or not.

This PR also migrates all existing DateTime.nowUtc() calls to use the Clock
interface, so that when they are migrated they will get the benefit of this
precision-setting as well.

BUG= http://b/496985355
2026-04-03 20:38:26 +00:00
89 changed files with 1104 additions and 371 deletions
+52
View File
@@ -0,0 +1,52 @@
# Engineering Standards for Gemini CLI
This document outlines foundational mandates, architectural patterns, and project-specific conventions to ensure high-quality, idiomatic, and consistent code from the first iteration.
## Core Mandates
### 1. Rigorous Import Management
- **Addition:** When adding new symbols, ensure the corresponding import is added.
- **Removal:** When removing the last usage of a class or symbol from a file (e.g., removing a `@Inject Clock clock;` field), **immediately remove the associated import**. Do not wait for a build failure to identify unused imports.
- **Checkstyle:** Proactively fix common checkstyle errors (line length > 100, formatting, unused imports) during the initial code write. Do not wait for CI/build failures to address these, as iterative fixes are inefficient.
- **Verification:** Before finalizing any change, scan the imports section for redundancy.
### 2. Time and Precision Handling (java.time Migration)
- **Millisecond Precision:** Always truncate `Instant.now()` to milliseconds (using `.truncatedTo(ChronoUnit.MILLIS)`) to maintain consistency with Joda `DateTime` and the PostgreSQL schema (which enforces millisecond precision via JPA converters).
- **Clock Injection:**
- Avoid direct calls to `Instant.now()`, `DateTime.now()`, `ZonedDateTime.now()`, or `System.currentTimeMillis()`.
- Inject `google.registry.util.Clock` (production) or `google.registry.testing.FakeClock` (tests).
- Use `clock.nowDate()` to get a `ZonedDateTime` in UTC.
- **Beam Pipelines:**
- Ensure `Clock` is serializable (it is by default in this project) when used in Beam `DoFn`s.
- Pass the `Clock` through the constructor or via Dagger provider methods in the pipeline module.
- **Command-Line Tools:**
- Use `@Inject Clock clock;` in `Command` implementations.
- The `clock` field should be **package-private** (no access modifier) to allow manual initialization in corresponding test classes.
- In test classes (e.g., `UpdateDomainCommandTest`), manually set `command.clock = fakeClock;` in the `@BeforeEach` method.
- Base test classes like `EppToolCommandTestCase` should handle this assignment for their generic command types where applicable.
### 3. Dependency Injection (Dagger)
- **Concrete Types:** Dagger `inject` methods must use explicit concrete types. Generic `inject(Command)` methods will not work.
- **Test Components:** Use `TestRegistryToolComponent` for command-line tool tests to bridge the gap between `main` and `nonprod/test` source sets.
### 4. Database Consistency
- **JPA Converters:** Be aware that JPA converters (like `DateTimeConverter`) may perform truncation or transformation. Ensure application-level logic matches these transformations to avoid "dirty" state or unexpected diffs.
- **Transaction Management:**
- **Top-Level:** Define database transactions (`tm().transact(...)`) at the highest possible level in the call chain (e.g., in an Action, a Command, or a Flow). This ensures all operations are atomic and handled by the retry logic.
- **DAO Methods:** Avoid declaring transactions inside low-level DAO methods. Use `tm().assertInTransaction()` to ensure that these methods are only called within a valid transactional context.
- **Utility/Cache Methods:** Use `tm().reTransact(...)` for utility methods or Caffeine cache loaders that might be invoked from both transactional and non-transactional paths.
- `reTransact` will join an existing transaction if one is present (acting as a no-op) or start a new one if not.
- This is particularly useful for in-memory caches where the loader must be able to fetch data regardless of whether the caller is currently in a transaction.
- **Transactional Time:** Ensure code that relies on `tm().getTransactionTime()` is executed within a transaction context.
### 5. Testing Best Practices
- **FakeClock and Sleeper:** Use `FakeClock` and `Sleeper` for any logic involving timeouts, delays, or expiration.
- **Empirical Reproduction:** Before fixing a bug, always create a test case that reproduces the failure.
- **Base Classes:** Leverage `CommandTestCase`, `EppToolCommandTestCase`, etc., to reduce boilerplate and ensure consistent setup (e.g., clock initialization).
### 6. Project Dependencies
- **Common Module:** When using `Clock` or other core utilities in a new or separate module (like `load-testing`), ensure `implementation project(':common')` is added to the module's `build.gradle`.
## Performance and Efficiency
- **Turn Minimization:** Aim for "perfect" code in the first iteration. Iterative fixes for checkstyle or compilation errors consume significant context and time.
- **Context Management:** Use sub-agents for batch refactoring or high-volume output tasks to keep the main session history lean and efficient.
+1
View File
@@ -200,6 +200,7 @@ allprojects {
"--add-exports",
"jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"]
options.forkOptions.jvmArgs = ["-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
@@ -16,6 +16,8 @@ package google.registry.util;
import java.io.Serializable;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import javax.annotation.concurrent.ThreadSafe;
import org.joda.time.DateTime;
@@ -36,4 +38,9 @@ public interface Clock extends Serializable {
/** Returns current Instant (which is always in UTC). */
Instant now();
/** Returns the current time as a {@link ZonedDateTime} in UTC. */
default ZonedDateTime nowDate() {
return ZonedDateTime.ofInstant(now(), ZoneOffset.UTC);
}
}
@@ -170,6 +170,12 @@ public abstract class DateTimeUtils {
return (instant == null) ? null : new DateTime(instant.toEpochMilli(), DateTimeZone.UTC);
}
/** Convert a java.time {@link java.time.Instant} to a joda {@link org.joda.time.Instant}. */
@Nullable
public static org.joda.time.Instant toJodaInstant(@Nullable java.time.Instant instant) {
return (instant == null) ? null : org.joda.time.Instant.ofEpochMilli(instant.toEpochMilli());
}
public static Instant plusYears(Instant instant, int years) {
return instant.atZone(ZoneOffset.UTC).plusYears(years).toInstant();
}
@@ -38,6 +38,10 @@ public class SystemClock implements Clock {
@Override
public Instant now() {
return Instant.now();
// Truncate to milliseconds to match the precision of Joda DateTime and our database schema
// (which uses millisecond precision via DateTimeConverter). This prevents subtle comparison
// bugs where a high-precision Instant would be considered "after" a truncated database
// timestamp.
return Instant.now().truncatedTo(java.time.temporal.ChronoUnit.MILLIS);
}
}
+3 -5
View File
@@ -74,11 +74,7 @@ sourceSets {
nonprod {
java {
compileClasspath += main.output
// Add the DB runtime classpath to nonprod so we can load the flyway
// scripts.
runtimeClasspath += main.output +
rootProject.project(":db").sourceSets.main.runtimeClasspath
runtimeClasspath += main.output
}
}
test {
@@ -102,6 +98,7 @@ configurations {
devtool
nonprodImplementation.extendsFrom implementation
nonprodRuntimeOnly.extendsFrom runtimeOnly
testImplementation.extendsFrom nonprodImplementation
@@ -259,6 +256,7 @@ dependencies {
implementation project(':util')
// Import NomulusPostreSql from ':db' for implementation but exclude dependencies.
implementation project(path: ':db', configuration: 'implementationApi')
nonprodRuntimeOnly project(':db')
testRuntimeOnly project(':db')
annotationProcessor deps['com.google.auto.service:auto-service']
+15 -8
View File
@@ -100,8 +100,10 @@ com.google.apis:google-api-services-admin-directory:directory_v1-rev20260227-2.0
com.google.apis:google-api-services-bigquery:v2-rev20240815-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-cloudresourcemanager:v1-rev20240310-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dataflow:v1b3-rev20260213-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v1-rev20260219-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v3-rev20260322-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v1-rev20260219-2.0.0=deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v1-rev20260402-2.0.0=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath
com.google.apis:google-api-services-drive:v3-rev20260322-2.0.0=deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v3-rev20260405-2.0.0=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath
com.google.apis:google-api-services-gmail:v1-rev20260112-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-groupssettings:v1-rev20220614-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-healthcare:v1-rev20240130-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -132,7 +134,7 @@ com.google.cloud.opentelemetry:detector-resources-support:0.33.0=deploy_jar,nonp
com.google.cloud.opentelemetry:exporter-metrics:0.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.opentelemetry:shared-resourcemapping:0.33.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud.sql:jdbc-socket-factory-core:1.28.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:postgres-socket-factory:1.28.2=deploy_jar,runtimeClasspath,testRuntimeClasspath
com.google.cloud.sql:postgres-socket-factory:1.28.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigquerystorage:3.9.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.cloud:google-cloud-bigquerystorage:3.9.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigtable:2.43.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
@@ -601,12 +603,17 @@ tools.jackson.core:jackson-core:3.1.0=compileClasspath,deploy_jar,nonprodCompile
tools.jackson.core:jackson-databind:3.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
tools.jackson:jackson-bom:3.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-api:17.1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-diagram:17.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-operations:17.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-postgresql:17.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-text:17.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-diagram:17.10.0=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath
us.fatehi:schemacrawler-diagram:17.9.0=deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-operations:17.10.0=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath
us.fatehi:schemacrawler-operations:17.9.0=deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-postgresql:17.10.0=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath
us.fatehi:schemacrawler-postgresql:17.9.0=deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-text:17.10.0=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath
us.fatehi:schemacrawler-text:17.9.0=deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-tools:17.1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-utility:17.1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler:17.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler:17.10.0=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath
us.fatehi:schemacrawler:17.9.0=deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
xerces:xmlParserAPIs:2.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
empty=devtool,shadow
@@ -28,7 +28,6 @@ import static google.registry.request.RequestParameters.PARAM_BATCH_SIZE;
import static google.registry.request.RequestParameters.PARAM_DRY_RUN;
import static google.registry.request.RequestParameters.PARAM_TLDS;
import static google.registry.util.RegistryEnvironment.PRODUCTION;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@@ -44,6 +43,7 @@ import google.registry.model.tld.Tld.TldType;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.RegistryEnvironment;
import jakarta.inject.Inject;
import jakarta.persistence.TypedQuery;
@@ -111,16 +111,20 @@ public class DeleteProberDataAction implements Runnable {
String registryAdminRegistrarId;
private final Clock clock;
@Inject
DeleteProberDataAction(
@Parameter(PARAM_DRY_RUN) boolean isDryRun,
@Parameter(PARAM_TLDS) ImmutableSet<String> tlds,
@Parameter(PARAM_BATCH_SIZE) Optional<Integer> batchSize,
@Config("registryAdminClientId") String registryAdminRegistrarId) {
@Config("registryAdminClientId") String registryAdminRegistrarId,
Clock clock) {
this.isDryRun = isDryRun;
this.tlds = tlds;
this.batchSize = batchSize.orElse(DEFAULT_BATCH_SIZE);
this.registryAdminRegistrarId = registryAdminRegistrarId;
this.clock = clock;
}
@Override
@@ -145,7 +149,7 @@ public class DeleteProberDataAction implements Runnable {
AtomicInteger softDeletedDomains = new AtomicInteger();
AtomicInteger hardDeletedDomains = new AtomicInteger();
AtomicReference<ImmutableList<Domain>> domainsBatch = new AtomicReference<>();
DateTime startTime = DateTime.now(UTC);
DateTime startTime = clock.nowUtc();
do {
tm().transact(
TRANSACTION_REPEATABLE_READ,
@@ -164,7 +168,7 @@ public class DeleteProberDataAction implements Runnable {
hardDeletedDomains.get(), batchSize);
// Automatically kill the job if it is running for over 20 hours
} while (DateTime.now(UTC).isBefore(startTime.plusHours(20))
} while (clock.nowUtc().isBefore(startTime.plusHours(20))
&& domainsBatch.get().size() == batchSize);
logger.atInfo().log(
"%s %d domains.",
@@ -19,7 +19,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.apache.http.HttpStatus.SC_OK;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -37,6 +36,7 @@ import google.registry.model.registrar.RegistrarPoc.Type;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.EmailMessage;
import jakarta.inject.Inject;
import jakarta.mail.internet.AddressException;
@@ -73,6 +73,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
private final GmailClient gmailClient;
private final String expirationWarningEmailSubjectText;
private final Response response;
private final Clock clock;
@Inject
public SendExpiringCertificateNotificationEmailAction(
@@ -80,12 +81,14 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
@Config("expirationWarningEmailSubjectText") String expirationWarningEmailSubjectText,
GmailClient gmailClient,
CertificateChecker certificateChecker,
Response response) {
Response response,
Clock clock) {
this.certificateChecker = certificateChecker;
this.expirationWarningEmailSubjectText = expirationWarningEmailSubjectText;
this.gmailClient = gmailClient;
this.expirationWarningEmailBodyText = expirationWarningEmailBodyText;
this.response = response;
this.clock = clock;
}
@Override
@@ -186,7 +189,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
*/
updateLastNotificationSentDate(
registrar,
DateTime.now(UTC).minusMinutes((int) UPDATE_TIME_OFFSET.getStandardMinutes()),
clock.nowUtc().minusMinutes((int) UPDATE_TIME_OFFSET.getStandardMinutes()),
certificateType);
return true;
} catch (Exception e) {
@@ -21,6 +21,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.CharStreams;
import google.registry.util.Clock;
import google.registry.util.DateTimeUtils;
import google.registry.util.Retrier;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -40,7 +42,6 @@ import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.joda.time.Instant;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -74,6 +75,8 @@ public class SafeBrowsingTransforms {
/** Provides the SafeBrowsing API key at runtime. */
private final String apiKey;
private final Clock clock;
/**
* Maps a domain name's {@code domainName} to its corresponding {@link DomainNameInfo} to
* facilitate batching SafeBrowsing API requests.
@@ -101,9 +104,10 @@ public class SafeBrowsingTransforms {
* HttpClients#createDefault()}.
*/
@SuppressWarnings("unchecked")
EvaluateSafeBrowsingFn(String apiKey, Retrier retrier) {
EvaluateSafeBrowsingFn(String apiKey, Retrier retrier, Clock clock) {
this.apiKey = apiKey;
this.retrier = retrier;
this.clock = clock;
closeableHttpClientSupplier = (Supplier & Serializable) HttpClients::createDefault;
}
@@ -115,9 +119,10 @@ public class SafeBrowsingTransforms {
*/
@VisibleForTesting
EvaluateSafeBrowsingFn(
String apiKey, Retrier retrier, Supplier<CloseableHttpClient> clientSupplier) {
String apiKey, Retrier retrier, Clock clock, Supplier<CloseableHttpClient> clientSupplier) {
this.apiKey = apiKey;
this.retrier = retrier;
this.clock = clock;
closeableHttpClientSupplier = clientSupplier;
}
@@ -126,7 +131,10 @@ public class SafeBrowsingTransforms {
public void finishBundle(FinishBundleContext context) {
if (!domainNameInfoBuffer.isEmpty()) {
ImmutableSet<KV<DomainNameInfo, ThreatMatch>> results = evaluateAndFlush();
results.forEach((kv) -> context.output(kv, Instant.now(), GlobalWindow.INSTANCE));
results.forEach(
(kv) ->
context.output(
kv, DateTimeUtils.toJodaInstant(clock.now()), GlobalWindow.INSTANCE));
}
}
@@ -30,6 +30,7 @@ import google.registry.model.reporting.Spec11ThreatMatch;
import google.registry.model.reporting.Spec11ThreatMatch.ThreatType;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import google.registry.util.Clock;
import google.registry.util.Retrier;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
@@ -263,8 +264,9 @@ public class Spec11Pipeline implements Serializable {
}
@Provides
EvaluateSafeBrowsingFn provideSafeBrowsingFn(Spec11PipelineOptions options, Retrier retrier) {
return new EvaluateSafeBrowsingFn(options.getSafeBrowsingApiKey(), retrier);
EvaluateSafeBrowsingFn provideSafeBrowsingFn(
Spec11PipelineOptions options, Retrier retrier, Clock clock) {
return new EvaluateSafeBrowsingFn(options.getSafeBrowsingApiKey(), retrier, clock);
}
@Provides
@@ -24,7 +24,6 @@ import static com.google.common.util.concurrent.Futures.transformAsync;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static google.registry.bigquery.BigqueryUtils.toJobReferenceString;
import static google.registry.config.RegistryConfig.getProjectId;
import static org.joda.time.DateTimeZone.UTC;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.AbstractInputStreamContent;
@@ -58,6 +57,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import google.registry.bigquery.BigqueryUtils.DestinationFormat;
import google.registry.bigquery.BigqueryUtils.TableType;
import google.registry.bigquery.BigqueryUtils.WriteDisposition;
import google.registry.util.Clock;
import google.registry.util.NonFinalForTesting;
import google.registry.util.Sleeper;
import google.registry.util.SqlTemplate;
@@ -69,7 +69,6 @@ import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/** Class encapsulating parameters and state for accessing the Bigquery API. */
@@ -94,6 +93,9 @@ public class BigqueryConnection implements AutoCloseable {
/** Bigquery client instance wrapped by this class. */
private final Bigquery bigquery;
/** Clock instance for this connection. */
private final Clock clock;
/** Executor service for bigquery jobs. */
private ListeningExecutorService service;
@@ -109,8 +111,9 @@ public class BigqueryConnection implements AutoCloseable {
/** Duration to wait between polls for job status. */
private Duration pollInterval = Duration.millis(1000);
BigqueryConnection(Bigquery bigquery) {
BigqueryConnection(Bigquery bigquery, Clock clock) {
this.bigquery = bigquery;
this.clock = clock;
}
/** Builder for a {@link BigqueryConnection}, since the latter is immutable once created. */
@@ -118,8 +121,8 @@ public class BigqueryConnection implements AutoCloseable {
private BigqueryConnection instance;
@Inject
Builder(Bigquery bigquery) {
instance = new BigqueryConnection(bigquery);
Builder(Bigquery bigquery, Clock clock) {
instance = new BigqueryConnection(bigquery, clock);
}
/**
@@ -195,6 +198,11 @@ public class BigqueryConnection implements AutoCloseable {
private final TableReference tableRef = new TableReference();
private TableType type = TableType.TABLE;
private WriteDisposition writeDisposition = WriteDisposition.WRITE_EMPTY;
private final Clock clock;
public Builder(Clock clock) {
this.clock = clock;
}
public Builder datasetId(String datasetId) {
tableRef.setDatasetId(datasetId);
@@ -217,7 +225,7 @@ public class BigqueryConnection implements AutoCloseable {
}
public Builder timeToLive(Duration duration) {
this.table.setExpirationTime(DateTime.now(UTC).plus(duration).getMillis());
this.table.setExpirationTime(clock.nowUtc().plus(duration).getMillis());
return this;
}
@@ -302,7 +310,7 @@ public class BigqueryConnection implements AutoCloseable {
/** Returns a partially built DestinationTable with the default dataset and overwrite behavior. */
public DestinationTable.Builder buildDestinationTable(String tableName) {
return new DestinationTable.Builder()
return new DestinationTable.Builder(clock)
.datasetId(datasetId)
.type(TableType.TABLE)
.name(tableName)
@@ -314,7 +322,7 @@ public class BigqueryConnection implements AutoCloseable {
* temporary table dataset, with the default TTL and overwrite behavior.
*/
public DestinationTable.Builder buildTemporaryTable() {
return new DestinationTable.Builder()
return new DestinationTable.Builder(clock)
.datasetId(TEMP_DATASET_NAME)
.type(TableType.TABLE)
.name(getRandomTableName())
@@ -14,6 +14,7 @@
package google.registry.keyring;
import com.google.common.collect.ImmutableList;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -21,7 +22,6 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.api.Keyring;
import google.registry.keyring.secretmanager.SecretManagerKeyring;
import jakarta.inject.Singleton;
import java.util.Optional;
/** Dagger module for {@link Keyring} */
@Module
@@ -38,9 +38,10 @@ public abstract class KeyringModule {
}
@Provides
@Config("cloudSqlReplicaInstanceConnectionName")
public static Optional<String> provideCloudSqlReplicaInstanceConnectionName(Keyring keyring) {
return Optional.ofNullable(keyring.getSqlReplicaConnectionName());
@Config("cloudSqlReplicaInstanceConnectionNames")
public static ImmutableList<String> provideCloudSqlReplicaInstanceConnectionNames(
Keyring keyring) {
return ImmutableList.copyOf(keyring.getSqlReplicaConnectionNames());
}
@Provides
@@ -14,6 +14,7 @@
package google.registry.keyring.api;
import com.google.common.collect.ImmutableList;
import javax.annotation.concurrent.ThreadSafe;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
@@ -151,9 +152,17 @@ public interface Keyring extends AutoCloseable {
/** Returns the Cloud SQL connection name of the primary database instance. */
String getSqlPrimaryConnectionName();
/** Returns the Cloud SQL connection name of the replica database instance. */
/**
* Returns the Cloud SQL connection name of the replica database instance.
*
* <p>Note: It is likely a better idea to use multiple replicas and {@link
* #getSqlReplicaConnectionNames()} instead.
*/
String getSqlReplicaConnectionName();
/** Returns the Cloud SQL connection names of the replica database instances. */
ImmutableList<String> getSqlReplicaConnectionNames();
// Don't throw so try-with-resources works better.
@Override
void close();
@@ -17,6 +17,8 @@ package google.registry.keyring.secretmanager;
import static com.google.common.base.CaseFormat.LOWER_HYPHEN;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import google.registry.keyring.api.KeySerializer;
import google.registry.keyring.api.Keyring;
import google.registry.keyring.api.KeyringException;
@@ -66,7 +68,8 @@ public class SecretManagerKeyring implements Keyring {
RDE_SSH_CLIENT_PUBLIC_STRING,
SAFE_BROWSING_API_KEY,
SQL_PRIMARY_CONN_NAME,
SQL_REPLICA_CONN_NAME;
SQL_REPLICA_CONN_NAME,
SQL_REPLICA_CONN_NAMES;
String getLabel() {
return UPPER_UNDERSCORE.to(LOWER_HYPHEN, name());
@@ -157,7 +160,25 @@ public class SecretManagerKeyring implements Keyring {
@Override
public String getSqlReplicaConnectionName() {
return getString(StringKeyLabel.SQL_REPLICA_CONN_NAME);
try {
return getString(StringKeyLabel.SQL_REPLICA_CONN_NAME);
} catch (KeyringException e) {
return null;
}
}
@Override
public ImmutableList<String> getSqlReplicaConnectionNames() {
try {
String names = getString(StringKeyLabel.SQL_REPLICA_CONN_NAMES);
return ImmutableList.copyOf(
Splitter.on('\n').trimResults().omitEmptyStrings().splitToList(names));
} catch (KeyringException e) {
String replicaConnectionName = getSqlReplicaConnectionName();
return replicaConnectionName == null
? ImmutableList.of()
: ImmutableList.of(replicaConnectionName);
}
}
/** No persistent resources are maintained for this Keyring implementation. */
@@ -34,6 +34,7 @@ import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringK
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.SAFE_BROWSING_API_KEY;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.SQL_PRIMARY_CONN_NAME;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.SQL_REPLICA_CONN_NAME;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.SQL_REPLICA_CONN_NAMES;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.flogger.FluentLogger;
@@ -134,6 +135,10 @@ public final class SecretManagerKeyringUpdater {
return setString(name, SQL_REPLICA_CONN_NAME);
}
public SecretManagerKeyringUpdater setSqlReplicaConnectionNames(String names) {
return setString(names, SQL_REPLICA_CONN_NAMES);
}
/**
* Persists the secrets in the Secret Manager.
*
@@ -14,7 +14,6 @@
package google.registry.model.eppcommon;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.annotations.VisibleForTesting;
@@ -28,7 +27,6 @@ import google.registry.model.domain.fee06.FeeInfoResponseExtensionV06;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.eppoutput.EppResponse;
import google.registry.util.RegistryEnvironment;
import google.registry.xml.ValidationMode;
import google.registry.xml.XmlException;
import google.registry.xml.XmlTransformer;
@@ -71,13 +69,9 @@ public class EppXmlTransformer {
private static final XmlTransformer OUTPUT_TRANSFORMER =
new XmlTransformer(getSchemas(), EppOutput.class);
// TODO(b/159033801): remove method and inline ALL_SCHEMA.
@VisibleForTesting
public static ImmutableList<String> getSchemas() {
if (RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION)) {
return ALL_SCHEMAS.stream()
.filter(s -> !NON_PROD_SCHEMAS.contains(s))
.collect(toImmutableList());
}
return ALL_SCHEMAS;
}
@@ -129,34 +129,31 @@ public final class PremiumListDao {
public static PremiumList save(String name, CurrencyUnit currencyUnit, List<String> inputData) {
checkArgument(!inputData.isEmpty(), "New premium list data cannot be empty");
return save(PremiumListUtils.parseToPremiumList(name, currencyUnit, inputData));
tm().assertInTransaction();
return save(
PremiumListUtils.parseToPremiumList(
name, currencyUnit, inputData, tm().getTransactionTime()));
}
/** Saves the given premium list (and its premium list entries) to Cloud SQL. */
public static PremiumList save(PremiumList premiumListToPersist) {
PremiumList persisted =
tm().transact(
() -> {
// Make a new copy in each attempt to insert. See javadoc of the insert method for
// more information.
PremiumList premiumList = premiumListToPersist.asBuilder().build();
tm().insert(premiumList);
tm().getEntityManager().flush(); // This populates the revisionId.
long revisionId = premiumList.getRevisionId();
tm().assertInTransaction();
// Make a new copy in each attempt to insert. See javadoc of the insert method for
// more information.
PremiumList premiumList = premiumListToPersist.asBuilder().build();
tm().insert(premiumList);
tm().getEntityManager().flush(); // This populates the revisionId.
long revisionId = premiumList.getRevisionId();
if (!isNullOrEmpty(premiumList.getLabelsToPrices())) {
ImmutableSet.Builder<PremiumEntry> entries = new ImmutableSet.Builder<>();
premiumList
.getLabelsToPrices()
.forEach(
(key, value) ->
entries.add(PremiumEntry.create(revisionId, value, key)));
tm().insertAll(entries.build());
}
return premiumList;
});
premiumListCache.invalidate(persisted.getName());
return persisted;
if (!isNullOrEmpty(premiumList.getLabelsToPrices())) {
ImmutableSet.Builder<PremiumEntry> entries = new ImmutableSet.Builder<>();
premiumList
.getLabelsToPrices()
.forEach((key, value) -> entries.add(PremiumEntry.create(revisionId, value, key)));
tm().insertAll(entries.build());
}
premiumListCache.invalidate(premiumList.getName());
return premiumList;
}
public static void delete(PremiumList premiumList) {
@@ -14,8 +14,6 @@
package google.registry.model.tld.label;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import google.registry.model.tld.label.PremiumList.PremiumEntry;
@@ -29,12 +27,12 @@ import org.joda.time.DateTime;
public class PremiumListUtils {
public static PremiumList parseToPremiumList(
String name, CurrencyUnit currencyUnit, List<String> inputData) {
String name, CurrencyUnit currencyUnit, List<String> inputData, DateTime creationTime) {
PremiumList partialPremiumList =
new PremiumList.Builder()
.setName(name)
.setCurrency(currencyUnit)
.setCreationTimestamp(DateTime.now(UTC))
.setCreationTimestamp(creationTime)
.build();
ImmutableMap<String, PremiumEntry> prices = partialPremiumList.parse(inputData);
Map<String, BigDecimal> priceAmounts = Maps.transformValues(prices, PremiumEntry::getValue);
@@ -24,10 +24,10 @@ import static google.registry.model.tld.label.ReservationType.FULLY_BLOCKED;
import static google.registry.persistence.transaction.QueryComposer.Comparator.EQ;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static org.joda.time.DateTimeZone.UTC;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.base.Splitter;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.UncheckedExecutionException;
@@ -47,7 +47,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/**
* A list of reserved domain labels that are blocked from being registered for various reasons.
@@ -234,7 +233,7 @@ public final class ReservedList
*/
private static ImmutableSet<ReservedListEntry> getReservedListEntries(
String label, String tldStr) {
DateTime startTime = DateTime.now(UTC);
Stopwatch stopwatch = Stopwatch.createStarted();
Tld tld = Tld.get(checkNotNull(tldStr, "tld must not be null"));
ImmutableSet.Builder<ReservedListEntry> entriesBuilder = new ImmutableSet.Builder<>();
ImmutableSet.Builder<MetricsReservedListMatch> metricMatchesBuilder =
@@ -251,9 +250,7 @@ public final class ReservedList
}
ImmutableSet<ReservedListEntry> entries = entriesBuilder.build();
DomainLabelMetrics.recordReservedListCheckOutcome(
tldStr,
metricMatchesBuilder.build(),
DateTime.now(UTC).getMillis() - startTime.getMillis());
tldStr, metricMatchesBuilder.build(), stopwatch.elapsed().toMillis());
return entries;
}
@@ -15,6 +15,7 @@
package google.registry.persistence;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.config.RegistryConfig.getHibernateConnectionIsolation;
import static google.registry.config.RegistryConfig.getHibernateHikariConnectionTimeout;
import static google.registry.config.RegistryConfig.getHibernateHikariIdleTimeout;
@@ -28,6 +29,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import dagger.BindsOptionalOf;
@@ -36,6 +38,7 @@ import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.persistence.transaction.CloudSqlCredentialSupplier;
import google.registry.persistence.transaction.DelegatingReplicaJpaTransactionManager;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
import google.registry.persistence.transaction.TransactionManager;
@@ -59,6 +62,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Random;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.hibernate.cfg.Environment;
@@ -264,16 +268,13 @@ public abstract class PersistenceModule {
static JpaTransactionManager provideReadOnlyReplicaJpaTm(
SqlCredentialStore credentialStore,
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs,
@Config("cloudSqlReplicaInstanceConnectionName")
Optional<String> replicaInstanceConnectionName,
Clock clock) {
@Config("cloudSqlReplicaInstanceConnectionNames")
ImmutableList<String> replicaInstanceConnectionNames,
Clock clock,
Random random) {
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
setSqlCredential(credentialStore, new RobotUser(RobotId.NOMULUS), overrides);
replicaInstanceConnectionName.ifPresent(
name -> overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name));
overrides.put(
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ.name());
return new JpaTransactionManagerImpl(create(overrides), clock, true);
return createReplicaJpaTm(overrides, replicaInstanceConnectionNames, clock, random);
}
@Provides
@@ -281,15 +282,34 @@ public abstract class PersistenceModule {
@BeamReadOnlyReplicaJpaTm
static JpaTransactionManager provideBeamReadOnlyReplicaJpaTm(
@BeamPipelineCloudSqlConfigs ImmutableMap<String, String> beamCloudSqlConfigs,
@Config("cloudSqlReplicaInstanceConnectionName")
Optional<String> replicaInstanceConnectionName,
Clock clock) {
@Config("cloudSqlReplicaInstanceConnectionNames")
ImmutableList<String> replicaInstanceConnectionNames,
Clock clock,
Random random) {
HashMap<String, String> overrides = Maps.newHashMap(beamCloudSqlConfigs);
replicaInstanceConnectionName.ifPresent(
name -> overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name));
overrides.put(
return createReplicaJpaTm(overrides, replicaInstanceConnectionNames, clock, random);
}
private static JpaTransactionManager createReplicaJpaTm(
Map<String, String> baseOverrides,
ImmutableList<String> replicaInstanceConnectionNames,
Clock clock,
Random random) {
baseOverrides.put(
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ.name());
return new JpaTransactionManagerImpl(create(overrides), clock, true);
if (replicaInstanceConnectionNames.isEmpty()) {
return new JpaTransactionManagerImpl(create(baseOverrides), clock, true);
}
ImmutableList<JpaTransactionManager> replicas =
replicaInstanceConnectionNames.stream()
.map(
name -> {
HashMap<String, String> overrides = Maps.newHashMap(baseOverrides);
overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name);
return new JpaTransactionManagerImpl(create(overrides), clock, true);
})
.collect(toImmutableList());
return new DelegatingReplicaJpaTransactionManager(replicas, random);
}
/** Constructs the {@link EntityManagerFactory} instance. */
@@ -0,0 +1,361 @@
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence.transaction;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.ImmutableObject;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.metamodel.Metamodel;
import java.time.Instant;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Stream;
import org.joda.time.DateTime;
/**
* A {@link JpaTransactionManager} that load-balances across multiple read-only replicas.
*
* <p>For each top-level transaction, one replica is chosen and used for the duration of the
* transaction. For non-transactional methods, a replica is chosen for each call.
*/
public class DelegatingReplicaJpaTransactionManager implements JpaTransactionManager {
private final ImmutableList<JpaTransactionManager> replicas;
private final Random random;
private static final AtomicLong nextId = new AtomicLong(1);
private static final ThreadLocal<JpaTransactionManager> activeReplica = new ThreadLocal<>();
public DelegatingReplicaJpaTransactionManager(
ImmutableList<JpaTransactionManager> replicas, Random random) {
checkArgument(!replicas.isEmpty(), "At least one replica must be provided");
this.replicas = replicas;
this.random = random;
}
private JpaTransactionManager getReplica() {
JpaTransactionManager replica = activeReplica.get();
if (replica != null) {
return replica;
}
return getRandomReplica();
}
private <T> T runMaybeAssigningReplica(Function<JpaTransactionManager, T> work) {
JpaTransactionManager existing = activeReplica.get();
if (existing != null) {
return work.apply(existing);
}
JpaTransactionManager replica = getRandomReplica();
activeReplica.set(replica);
try {
return work.apply(replica);
} finally {
activeReplica.remove();
}
}
private JpaTransactionManager getRandomReplica() {
return replicas.get(random.nextInt(replicas.size()));
}
@Override
public boolean inTransaction() {
var replica = activeReplica.get();
return replica != null && replica.inTransaction();
}
@Override
public void assertInTransaction() {
JpaTransactionManager replica = activeReplica.get();
if (replica == null) {
throw new IllegalStateException("Not in a transaction");
}
replica.assertInTransaction();
}
@Override
public long allocateId() {
return nextId.getAndIncrement();
}
@Override
public <T> T transact(Callable<T> work) {
return transact(null, work, false);
}
@Override
public <T> T transact(TransactionIsolationLevel isolationLevel, Callable<T> work) {
return transact(isolationLevel, work, false);
}
@Override
public <T> T transactNoRetry(Callable<T> work) {
return transactNoRetry(null, work, false);
}
@Override
public <T> T transactNoRetry(TransactionIsolationLevel isolationLevel, Callable<T> work) {
return transactNoRetry(isolationLevel, work, false);
}
@Override
public <T> T reTransact(Callable<T> work) {
return runMaybeAssigningReplica(replica -> replica.reTransact(work));
}
@Override
public void transact(ThrowingRunnable work) {
transact(
() -> {
work.run();
return null;
});
}
@Override
public void transact(TransactionIsolationLevel isolationLevel, ThrowingRunnable work) {
transact(
isolationLevel,
() -> {
work.run();
return null;
});
}
@Override
public void reTransact(ThrowingRunnable work) {
reTransact(
() -> {
work.run();
return null;
});
}
@Override
public DateTime getTransactionTime() {
return getReplica().getTransactionTime();
}
@Override
public Instant getTxTime() {
return getReplica().getTxTime();
}
@Override
public void insert(Object entity) {
getReplica().insert(entity);
}
@Override
public void insertAll(ImmutableCollection<?> entities) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void insertAll(ImmutableObject... entities) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void put(Object entity) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void putAll(ImmutableObject... entities) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void putAll(ImmutableCollection<?> entities) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void update(Object entity) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void updateAll(ImmutableCollection<?> entities) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void updateAll(ImmutableObject... entities) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public boolean exists(Object entity) {
return getReplica().exists(entity);
}
@Override
public <T> boolean exists(VKey<T> key) {
return getReplica().exists(key);
}
@Override
public <T> Optional<T> loadByKeyIfPresent(VKey<T> key) {
return getReplica().loadByKeyIfPresent(key);
}
@Override
public <T> ImmutableMap<VKey<? extends T>, T> loadByKeysIfPresent(
Iterable<? extends VKey<? extends T>> keys) {
return getReplica().loadByKeysIfPresent(keys);
}
@Override
public <T> ImmutableList<T> loadByEntitiesIfPresent(Iterable<T> entities) {
return getReplica().loadByEntitiesIfPresent(entities);
}
@Override
public <T> T loadByKey(VKey<T> key) {
return getReplica().loadByKey(key);
}
@Override
public <T> ImmutableMap<VKey<? extends T>, T> loadByKeys(
Iterable<? extends VKey<? extends T>> keys) {
return getReplica().loadByKeys(keys);
}
@Override
public <T> T loadByEntity(T entity) {
return getReplica().loadByEntity(entity);
}
@Override
public <T> ImmutableList<T> loadByEntities(Iterable<T> entities) {
return getReplica().loadByEntities(entities);
}
@Override
public <T> ImmutableList<T> loadAllOf(Class<T> clazz) {
return getReplica().loadAllOf(clazz);
}
@Override
public <T> Stream<T> loadAllOfStream(Class<T> clazz) {
return getReplica().loadAllOfStream(clazz);
}
@Override
public <T> Optional<T> loadSingleton(Class<T> clazz) {
return getReplica().loadSingleton(clazz);
}
@Override
public void delete(VKey<?> key) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void delete(Iterable<? extends VKey<?>> keys) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public <T> T delete(T entity) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public <T> QueryComposer<T> createQueryComposer(Class<T> entity) {
return getReplica().createQueryComposer(entity);
}
@Override
public EntityManager getStandaloneEntityManager() {
return getReplica().getStandaloneEntityManager();
}
@Override
public Metamodel getMetaModel() {
return getReplica().getMetaModel();
}
@Override
public EntityManager getEntityManager() {
return getReplica().getEntityManager();
}
@Override
public <T> TypedQuery<T> query(String sqlString, Class<T> resultClass) {
return getReplica().query(sqlString, resultClass);
}
@Override
public <T> TypedQuery<T> criteriaQuery(CriteriaQuery<T> criteriaQuery) {
return getReplica().criteriaQuery(criteriaQuery);
}
@Override
public Query query(String sqlString) {
return getReplica().query(sqlString);
}
@Override
public <T> void assertDelete(VKey<T> key) {
throw new UnsupportedOperationException("This is a replica database");
}
@Override
public void teardown() {
for (JpaTransactionManager replica : replicas) {
replica.teardown();
}
}
@Override
public TransactionIsolationLevel getDefaultTransactionIsolationLevel() {
return replicas.get(0).getDefaultTransactionIsolationLevel();
}
@Override
public TransactionIsolationLevel getCurrentTransactionIsolationLevel() {
return getReplica().getCurrentTransactionIsolationLevel();
}
@Override
public <T> T transact(
TransactionIsolationLevel isolationLevel, Callable<T> work, boolean logSqlStatements) {
return runMaybeAssigningReplica(
replica -> replica.transact(isolationLevel, work, logSqlStatements));
}
@Override
public <T> T transactNoRetry(
TransactionIsolationLevel isolationLevel, Callable<T> work, boolean logSqlStatements) {
return runMaybeAssigningReplica(
replica -> replica.transactNoRetry(isolationLevel, work, logSqlStatements));
}
}
@@ -19,7 +19,6 @@ import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.model.tld.Tlds.findTldForNameOrThrow;
import static google.registry.pricing.PricingEngineProxy.getDomainCreateCost;
import static google.registry.util.StringGenerator.DEFAULT_PASSWORD_LENGTH;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -30,7 +29,6 @@ import google.registry.util.StringGenerator;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.joda.money.Money;
import org.joda.time.DateTime;
/** A command to create a new anchor tenant domain. */
@Parameters(separators = " =", commandDescription = "Provision a domain for an anchor tenant.")
@@ -86,7 +84,7 @@ final class CreateAnchorTenantCommand extends MutatingEppToolCommand {
Money cost = null;
if (fee) {
cost = getDomainCreateCost(domainName, DateTime.now(UTC), DEFAULT_ANCHOR_TENANT_PERIOD_YEARS);
cost = getDomainCreateCost(domainName, clock.nowUtc(), DEFAULT_ANCHOR_TENANT_PERIOD_YEARS);
}
setSoyTemplate(CreateAnchorTenantSoyInfo.getInstance(),
@@ -17,7 +17,6 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -28,7 +27,6 @@ import google.registry.util.StringGenerator;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.joda.money.Money;
import org.joda.time.DateTime;
/** A command to create a new domain via EPP. */
@Parameters(separators = " =", commandDescription = "Create a new domain via EPP.")
@@ -64,7 +62,7 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
for (String domain : domains) {
String currency = null;
String cost = null;
DomainPrices prices = getPricesForDomainName(domain, DateTime.now(UTC));
DomainPrices prices = getPricesForDomainName(domain, clock.nowUtc());
// Check if the domain is premium and set the fee on the create command if so.
if (prices.isPremium()) {
@@ -14,10 +14,14 @@
package google.registry.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
import com.google.common.flogger.FluentLogger;
import google.registry.model.tld.label.PremiumListDao;
import google.registry.tools.params.PathParameter;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.nio.file.Path;
import java.util.List;
import javax.annotation.Nullable;
@@ -29,6 +33,8 @@ import org.joda.money.CurrencyUnit;
*/
abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand {
@Inject Clock clock;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
protected List<String> inputData;
protected CurrencyUnit currency;
@@ -54,7 +60,7 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand {
String.format("Saved premium list %s with %d entries.", name, inputData.size());
try {
logger.atInfo().log("Saving premium list for TLD %s.", name);
PremiumListDao.save(name, currency, inputData);
tm().transact(() -> PremiumListDao.save(name, currency, inputData));
logger.atInfo().log(message);
} catch (Throwable e) {
message = "Unexpected error saving premium list from nomulus tool command.";
@@ -21,7 +21,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameter;
import com.google.common.base.Ascii;
@@ -37,6 +36,7 @@ import google.registry.tools.params.OptionalStringParameter;
import google.registry.tools.params.PathParameter.InputFile;
import google.registry.tools.params.StringListParameter;
import google.registry.util.CidrAddressBlock;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -56,6 +56,8 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
@Inject CertificateChecker certificateChecker;
@Inject Clock clock;
@Parameter(description = "Client identifier of the registrar account", required = true)
List<String> mainParameters;
@@ -272,7 +274,7 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
@Override
protected final void init() throws Exception {
initRegistrarCommand();
DateTime now = DateTime.now(UTC);
DateTime now = clock.nowUtc();
for (String clientId : mainParameters) {
Registrar oldRegistrar = getOldRegistrar(clientId);
Registrar.Builder builder =
@@ -19,6 +19,8 @@ import com.google.common.flogger.FluentLogger;
import google.registry.model.tld.label.ReservedList;
import google.registry.model.tld.label.ReservedListDao;
import google.registry.tools.params.PathParameter;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.nio.file.Path;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
@@ -29,6 +31,8 @@ import javax.annotation.Nullable;
*/
public abstract class CreateOrUpdateReservedListCommand extends ConfirmingCommand {
@Inject Clock clock;
static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Nullable
@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.tld.Tlds.assertTldExists;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -51,7 +50,7 @@ final class CreateReservedListCommand extends CreateOrUpdateReservedListCommand
if (!override) {
validateListName(name);
}
DateTime now = DateTime.now(UTC);
DateTime now = clock.nowUtc();
List<String> allLines = Files.readAllLines(input, UTF_8);
reservedList =
new ReservedList.Builder()
@@ -38,6 +38,8 @@ import com.google.template.soy.data.SoyRecord;
import com.google.template.soy.parseinfo.SoyFileInfo;
import com.google.template.soy.parseinfo.SoyTemplateInfo;
import google.registry.model.registrar.Registrar;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
@@ -49,6 +51,8 @@ import java.util.Objects;
/** A command to execute an epp command. */
abstract class EppToolCommand extends ConfirmingCommand implements CommandWithConnection {
@Inject Clock clock;
@Parameter(
names = {"-u", "--superuser"},
description = "Run in superuser mode")
@@ -56,8 +56,7 @@ final class GenerateDnsReportCommand implements Command {
validateWith = PathParameter.OutputFile.class)
private Path output = Paths.get("/dev/stdout");
@Inject
Clock clock;
@Inject Clock clock;
@Override
public void run() throws Exception {
@@ -15,7 +15,6 @@
package google.registry.tools;
import static google.registry.model.tld.Tlds.assertTldsExist;
import static org.joda.time.DateTimeZone.UTC;
import static org.joda.time.Duration.standardMinutes;
import com.beust.jcommander.Parameter;
@@ -23,6 +22,8 @@ import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap;
import google.registry.tools.params.DateParameter;
import google.registry.tools.server.GenerateZoneFilesAction;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@@ -40,10 +41,13 @@ final class GenerateZoneFilesCommand implements CommandWithConnection {
// Default to latest midnight that's at least 2 minutes ago.
@Parameter(
names = "--export_date",
description = "The date to generate the file for (defaults to today, or yesterday if run "
+ "before 00:02).",
description =
"The date to generate the file for (defaults to today, or yesterday if run "
+ "before 00:02).",
validateWith = DateParameter.class)
private DateTime exportDate = DateTime.now(UTC).minus(standardMinutes(2)).withTimeAtStartOfDay();
private DateTime exportDate;
@Inject Clock clock;
private ServiceConnection connection;
@@ -54,6 +58,9 @@ final class GenerateZoneFilesCommand implements CommandWithConnection {
@Override
public void run() throws IOException {
if (exportDate == null) {
exportDate = clock.nowUtc().minus(standardMinutes(2)).withTimeAtStartOfDay();
}
assertTldsExist(mainParameters);
ImmutableMap<String, Object> params = ImmutableMap.of(
"tlds", mainParameters,
@@ -17,7 +17,6 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -26,7 +25,9 @@ import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntryDao;
import google.registry.persistence.VKey;
import google.registry.tools.CommandUtilities.ResourceType;
import google.registry.util.Clock;
import google.registry.xml.XmlTransformer;
import jakarta.inject.Inject;
import org.joda.time.DateTime;
/** Command to show history entries. */
@@ -35,6 +36,8 @@ import org.joda.time.DateTime;
commandDescription = "Show history entries that occurred in a given time range")
final class GetHistoryEntriesCommand implements Command {
@Inject Clock clock;
@Parameter(
names = {"-a", "--after"},
description = "Only show history entries that occurred at or after this time")
@@ -58,7 +61,7 @@ final class GetHistoryEntriesCommand implements Command {
checkArgument(
type != null && uniqueId != null,
"If either of 'type' or 'id' are set then both must be");
VKey<? extends EppResource> parentKey = type.getKey(uniqueId, DateTime.now(UTC));
VKey<? extends EppResource> parentKey = type.getKey(uniqueId, clock.nowUtc());
historyEntries = HistoryEntryDao.loadHistoryObjectsForResource(parentKey, after, before);
} else {
historyEntries = HistoryEntryDao.loadAllHistoryObjects(after, before);
@@ -14,6 +14,7 @@
package google.registry.tools;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.keyring.api.KeySerializer;
@@ -95,6 +96,10 @@ final class GetKeyringSecretCommand implements Command {
out.write(KeySerializer.serializeString(keyring.getSqlPrimaryConnectionName()));
case SQL_REPLICA_CONN_NAME ->
out.write(KeySerializer.serializeString(keyring.getSqlReplicaConnectionName()));
case SQL_REPLICA_CONN_NAMES ->
out.write(
KeySerializer.serializeString(
String.join("\n", keyring.getSqlReplicaConnectionNames())));
}
}
}
@@ -26,8 +26,6 @@ import com.google.template.soy.data.SoyMapData;
import google.registry.flows.ResourceFlowUtils;
import google.registry.model.domain.Domain;
import google.registry.tools.soy.DomainRenewSoyInfo;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.util.List;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
@@ -63,9 +61,6 @@ final class RenewDomainCommand extends MutatingEppToolCommand {
arity = 1)
Boolean requestedByRegistrar;
@Inject
Clock clock;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern("YYYY-MM-dd");
@Override
@@ -37,8 +37,6 @@ import google.registry.model.host.Host;
import google.registry.tools.params.NameserversParameter;
import google.registry.tools.soy.DomainRenewSoyInfo;
import google.registry.tools.soy.UniformRapidSuspensionSoyInfo;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import jakarta.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -122,8 +120,6 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
/** Set of status values to remove. */
ImmutableSet<String> removeStatuses;
@Inject Clock clock;
@Override
protected void initMutatingEppToolCommand()
throws ResourceFlowUtils.ResourceDoesNotExistException {
@@ -33,8 +33,6 @@ import google.registry.model.domain.GracePeriodBase;
import google.registry.model.eppcommon.StatusValue;
import google.registry.tools.params.NameserversParameter;
import google.registry.tools.soy.DomainUpdateSoyInfo;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -49,8 +47,6 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject Clock clock;
@Parameter(names = "--statuses", description = "Comma-separated list of statuses to set.")
private List<String> statuses = new ArrayList<>();
@@ -100,6 +100,8 @@ final class UpdateKeyringSecretCommand implements Command {
secretManagerKeyringUpdater.setSqlPrimaryConnectionName(deserializeString(input));
case SQL_REPLICA_CONN_NAME ->
secretManagerKeyringUpdater.setSqlReplicaConnectionName(deserializeString(input));
case SQL_REPLICA_CONN_NAMES ->
secretManagerKeyringUpdater.setSqlReplicaConnectionNames(deserializeString(input));
}
secretManagerKeyringUpdater.update();
@@ -62,7 +62,8 @@ class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
inputData = Files.readAllLines(inputFile, UTF_8);
checkArgument(!inputData.isEmpty(), "New premium list data cannot be empty");
currency = existingList.getCurrency();
PremiumList updatedPremiumList = PremiumListUtils.parseToPremiumList(name, currency, inputData);
PremiumList updatedPremiumList =
PremiumListUtils.parseToPremiumList(name, currency, inputData, clock.nowUtc());
if (!existingList
.getLabelsToPrices()
.entrySet()
@@ -38,5 +38,6 @@ public enum KeyringKeyName {
RDE_STAGING_PUBLIC_KEY,
SAFE_BROWSING_API_KEY,
SQL_PRIMARY_CONN_NAME,
SQL_REPLICA_CONN_NAME
SQL_REPLICA_CONN_NAME,
SQL_REPLICA_CONN_NAMES
}
@@ -16,7 +16,6 @@ package google.registry.ui.server.console;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
@@ -35,7 +34,6 @@ import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.joda.time.DateTime;
@Action(
service = Service.CONSOLE,
@@ -86,7 +84,7 @@ public class ConsoleDumDownloadAction extends ConsoleApiAction {
.setHeader("Cache-Control", "max-age=86400"); // 86400 seconds = 1 day
consoleApiParams
.response()
.setDateHeader("Expires", DateTime.now(UTC).withTimeAtStartOfDay().plusDays(1));
.setDateHeader("Expires", clock.nowUtc().withTimeAtStartOfDay().plusDays(1));
try (var writer = consoleApiParams.response().getWriter()) {
CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT);
@@ -45,6 +45,7 @@ import google.registry.model.tld.Tld.TldType;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.SystemPropertyExtension;
import google.registry.util.RegistryEnvironment;
import java.time.Instant;
@@ -62,9 +63,11 @@ class DeleteProberDataActionTest {
private static final DateTime DELETION_TIME = DateTime.parse("2010-01-01T00:00:00.000Z");
private final FakeClock clock = new FakeClock(DateTime.now(UTC));
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@RegisterExtension
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
@@ -94,7 +97,9 @@ class DeleteProberDataActionTest {
}
private void resetAction() {
action = new DeleteProberDataAction(false, ImmutableSet.of(), Optional.empty(), "TheRegistrar");
action =
new DeleteProberDataAction(
false, ImmutableSet.of(), Optional.empty(), "TheRegistrar", clock);
}
@AfterEach
@@ -124,7 +129,7 @@ class DeleteProberDataActionTest {
Set<ImmutableObject> oaEntities = persistLotsOfDomains("oa-canary.test");
// Create action with batch size of 3
DeleteProberDataAction batchedAction =
new DeleteProberDataAction(false, ImmutableSet.of(), Optional.of(3), "TheRegistrar");
new DeleteProberDataAction(false, ImmutableSet.of(), Optional.of(3), "TheRegistrar", clock);
batchedAction.run();
assertAllAbsent(ibEntities);
assertAllAbsent(oaEntities);
@@ -97,7 +97,8 @@ class SendExpiringCertificateNotificationEmailActionTest {
EXPIRATION_WARNING_EMAIL_SUBJECT_TEXT,
sendEmailService,
certificateChecker,
response);
response,
clock);
sampleRegistrar =
persistResource(createRegistrar("clientId", "sampleRegistrar", null, null).build());
@@ -85,10 +85,13 @@ class SafeBrowsingTransformsTest {
private final CloseableHttpClient mockHttpClient =
mock(CloseableHttpClient.class, withSettings().serializable());
private final FakeClock clock = new FakeClock();
private final EvaluateSafeBrowsingFn safeBrowsingFn =
new EvaluateSafeBrowsingFn(
"API_KEY",
new Retrier(new FakeSleeper(new FakeClock()), 1),
new Retrier(new FakeSleeper(clock), 1),
clock,
Suppliers.ofInstance(mockHttpClient));
@RegisterExtension
@@ -187,7 +187,8 @@ class Spec11PipelineTest {
EvaluateSafeBrowsingFn safeBrowsingFn =
new EvaluateSafeBrowsingFn(
SAFE_BROWSING_API_KEY,
new Retrier(new FakeSleeper(new FakeClock()), 1),
new Retrier(new FakeSleeper(fakeClock), 1),
fakeClock,
Suppliers.ofInstance(mockHttpClient));
when(mockHttpClient.execute(any(HttpPost.class))).thenAnswer(new HttpResponder());
Spec11Pipeline spec11Pipeline = new Spec11Pipeline(options, safeBrowsingFn);
@@ -17,6 +17,7 @@ package google.registry.export;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.export.ExportPremiumTermsAction.EXPORT_MIME_TYPE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.deleteTld;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -75,7 +76,7 @@ public class ExportPremiumTermsActionTest {
@BeforeEach
void beforeEach() throws Exception {
createTld("tld");
PremiumList pl = PremiumListDao.save("pl-name", USD, PREMIUM_NAMES);
PremiumList pl = tm().transact(() -> PremiumListDao.save("pl-name", USD, PREMIUM_NAMES));
persistResource(
Tld.get("tld").asBuilder().setPremiumList(pl).setDriveFolderId("folder_id").build());
when(driveConnection.createOrUpdateFile(
@@ -24,6 +24,7 @@ import static google.registry.model.reporting.DomainTransactionRecord.Transactio
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.assertBillingEventsForResource;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.deleteTestDomain;
@@ -512,12 +513,14 @@ class DomainTransferApproveFlowTest
@Test
void testSuccess_nonpremiumPriceRenewalBehavior_carriesOver() throws Exception {
PremiumList pl =
PremiumListDao.save(
new PremiumList.Builder()
.setCurrency(USD)
.setName("tld")
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setCurrency(USD)
.setName("tld")
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build()));
persistResource(Tld.get("tld").asBuilder().setPremiumList(pl).build());
domain = loadByEntity(domain);
persistResource(
@@ -558,12 +561,14 @@ class DomainTransferApproveFlowTest
@Test
void testSuccess_specifiedPriceRenewalBehavior_carriesOver() throws Exception {
PremiumList pl =
PremiumListDao.save(
new PremiumList.Builder()
.setCurrency(USD)
.setName("tld")
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setCurrency(USD)
.setName("tld")
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build()));
persistResource(Tld.get("tld").asBuilder().setPremiumList(pl).build());
domain = loadByEntity(domain);
persistResource(
@@ -1160,12 +1160,14 @@ class DomainTransferRequestFlowTest
throws Exception {
setupDomain("example", "tld");
PremiumList pl =
PremiumListDao.save(
new PremiumList.Builder()
.setCurrency(USD)
.setName("tld")
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setCurrency(USD)
.setName("tld")
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build()));
persistResource(Tld.get("tld").asBuilder().setPremiumList(pl).build());
domain = loadByEntity(domain);
persistResource(
@@ -1214,12 +1216,14 @@ class DomainTransferRequestFlowTest
throws Exception {
setupDomain("example", "tld");
PremiumList pl =
PremiumListDao.save(
new PremiumList.Builder()
.setCurrency(USD)
.setName("tld")
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setCurrency(USD)
.setName("tld")
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build()));
persistResource(Tld.get("tld").asBuilder().setPremiumList(pl).build());
domain = loadByEntity(domain);
persistResource(
@@ -120,6 +120,23 @@ public class SecretManagerKeyringUpdaterTest {
verifyPersistedSecret("sql-replica-conn-name", name);
}
@Test
void sqlReplicaConnectionNames() {
String names = "name1\nname2";
updater.setSqlReplicaConnectionNames(names).update();
assertThat(keyring.getSqlReplicaConnectionNames()).containsExactly("name1", "name2").inOrder();
verifyPersistedSecret("sql-replica-conn-names", names);
}
@Test
void sqlReplicaConnectionNames_fallback() {
String name = "name";
updater.setSqlReplicaConnectionName(name).update();
assertThat(keyring.getSqlReplicaConnectionNames()).containsExactly(name);
}
@Test
void marksdbDnlLoginAndPassword() {
String secret = "marksdbDnlLoginAndPassword";
@@ -82,11 +82,11 @@ class EppXmlTransformerTest {
}
@Test
void testSchemas_inProduction_skipsFee1Point0() {
void testSchemas_inProduction_includesFee1Point0() {
var currentEnv = RegistryEnvironment.get();
try {
RegistryEnvironment.PRODUCTION.setup();
assertThat(EppXmlTransformer.getSchemas()).doesNotContain("fee-std-v1.xsd");
assertThat(EppXmlTransformer.getSchemas()).contains("fee-std-v1.xsd");
} finally {
currentEnv.setup();
}
@@ -84,7 +84,7 @@ public class PremiumListDaoTest {
@Test
void saveNew_worksSuccessfully() {
PremiumListDao.save(testList);
tm().transact(() -> PremiumListDao.save(testList));
tm().transact(
() -> {
Optional<PremiumList> persistedListOpt = PremiumListDao.getLatestRevision("testname");
@@ -118,24 +118,26 @@ public class PremiumListDaoTest {
@Test
void update_worksSuccessfully() {
PremiumListDao.save(testList);
tm().transact(() -> PremiumListDao.save(testList));
Optional<PremiumList> persistedList = PremiumListDao.getLatestRevision("testname");
assertThat(persistedList).isPresent();
long firstRevisionId = persistedList.get().getRevisionId();
PremiumListDao.save(
new PremiumList.Builder()
.setName("testname")
.setCurrency(USD)
.setLabelsToPrices(
ImmutableMap.of(
"save",
BigDecimal.valueOf(55343.12),
"new",
BigDecimal.valueOf(0.01),
"silver",
BigDecimal.valueOf(30.03)))
.setCreationTimestamp(fakeClock.nowUtc())
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setName("testname")
.setCurrency(USD)
.setLabelsToPrices(
ImmutableMap.of(
"save",
BigDecimal.valueOf(55343.12),
"new",
BigDecimal.valueOf(0.01),
"silver",
BigDecimal.valueOf(30.03)))
.setCreationTimestamp(fakeClock.nowUtc())
.build()));
tm().transact(
() -> {
Optional<PremiumList> savedListOpt = PremiumListDao.getLatestRevision("testname");
@@ -158,7 +160,7 @@ public class PremiumListDaoTest {
@Test
void checkExists_worksSuccessfully() {
assertThat(PremiumListDao.getLatestRevision("testname")).isEmpty();
PremiumListDao.save(testList);
tm().transact(() -> PremiumListDao.save(testList));
assertThat(PremiumListDao.getLatestRevision("testname")).isPresent();
}
@@ -169,20 +171,24 @@ public class PremiumListDaoTest {
@Test
void getLatestRevision_worksSuccessfully() {
PremiumListDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(USD)
.setLabelsToPrices(ImmutableMap.of("wrong", BigDecimal.valueOf(1000.50)))
.setCreationTimestamp(fakeClock.nowUtc())
.build());
PremiumListDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(USD)
.setLabelsToPrices(TEST_PRICES)
.setCreationTimestamp(fakeClock.nowUtc())
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(USD)
.setLabelsToPrices(ImmutableMap.of("wrong", BigDecimal.valueOf(1000.50)))
.setCreationTimestamp(fakeClock.nowUtc())
.build()));
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(USD)
.setLabelsToPrices(TEST_PRICES)
.setCreationTimestamp(fakeClock.nowUtc())
.build()));
tm().transact(
() -> {
Optional<PremiumList> persistedList = PremiumListDao.getLatestRevision("list1");
@@ -196,13 +202,15 @@ public class PremiumListDaoTest {
@Test
void getLabelsToPrices_worksForJpy() {
PremiumListDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(JPY)
.setLabelsToPrices(TEST_PRICES)
.setCreationTimestamp(fakeClock.nowUtc())
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(JPY)
.setLabelsToPrices(TEST_PRICES)
.setCreationTimestamp(fakeClock.nowUtc())
.build()));
tm().transact(
() -> {
PremiumList premiumList = PremiumListDao.getLatestRevision("list1").get();
@@ -220,13 +228,15 @@ public class PremiumListDaoTest {
@Test
void getPremiumPrice_worksSuccessfully() {
PremiumList premiumList =
PremiumListDao.save(
new PremiumList.Builder()
.setName("premlist")
.setCurrency(USD)
.setLabelsToPrices(TEST_PRICES)
.setCreationTimestamp(fakeClock.nowUtc())
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setName("premlist")
.setCurrency(USD)
.setLabelsToPrices(TEST_PRICES)
.setCreationTimestamp(fakeClock.nowUtc())
.build()));
persistResource(newTld("foobar", "FOOBAR").asBuilder().setPremiumList(premiumList).build());
assertThat(PremiumListDao.getPremiumPrice("premlist", "silver")).hasValue(Money.of(USD, 10.23));
assertThat(PremiumListDao.getPremiumPrice("premlist", "gold")).hasValue(Money.of(USD, 1305.47));
@@ -236,20 +246,22 @@ public class PremiumListDaoTest {
@Test
void testGetPremiumPrice_worksForJPY() {
PremiumList premiumList =
PremiumListDao.save(
new PremiumList.Builder()
.setName("premlist")
.setCurrency(JPY)
.setLabelsToPrices(
ImmutableMap.of(
"silver",
BigDecimal.valueOf(10.00),
"gold",
BigDecimal.valueOf(1000.0),
"palladium",
BigDecimal.valueOf(15000)))
.setCreationTimestamp(fakeClock.nowUtc())
.build());
tm().transact(
() ->
PremiumListDao.save(
new PremiumList.Builder()
.setName("premlist")
.setCurrency(JPY)
.setLabelsToPrices(
ImmutableMap.of(
"silver",
BigDecimal.valueOf(10.00),
"gold",
BigDecimal.valueOf(1000.0),
"palladium",
BigDecimal.valueOf(15000)))
.setCreationTimestamp(fakeClock.nowUtc())
.build()));
persistResource(newTld("foobar", "FOOBAR").asBuilder().setPremiumList(premiumList).build());
assertThat(PremiumListDao.getPremiumPrice("premlist", "silver")).hasValue(moneyOf(JPY, 10));
assertThat(PremiumListDao.getPremiumPrice("premlist", "gold")).hasValue(moneyOf(JPY, 1000));
@@ -262,14 +274,18 @@ public class PremiumListDaoTest {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> PremiumListDao.save("test-list", CurrencyUnit.GBP, ImmutableList.of()));
() ->
tm().transact(
() ->
PremiumListDao.save(
"test-list", CurrencyUnit.GBP, ImmutableList.of())));
assertThat(thrown).hasMessageThat().isEqualTo("New premium list data cannot be empty");
}
@Test
void test_savePremiumList_clearsCache() {
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname")).isNull();
PremiumListDao.save(testList);
tm().transact(() -> PremiumListDao.save(testList));
PremiumList pl = PremiumListDao.getLatestRevision("testname").get();
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname")).hasValue(pl);
tm().transact(() -> PremiumListDao.save("testname", USD, ImmutableList.of("test,USD 1")));
@@ -288,7 +304,7 @@ public class PremiumListDaoTest {
.setLabelsToPrices(prices)
.setCreationTimestamp(fakeClock.nowUtc())
.build();
PremiumListDao.save(list);
tm().transact(() -> PremiumListDao.save(list));
long duration = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
if (duration >= 6000) {
// Don't fail directly since we can't rely on what sort of machines the test is running on
@@ -16,6 +16,7 @@ package google.registry.model.tld.label;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistReservedList;
@@ -101,7 +102,8 @@ public class PremiumListTest {
@Test
void testParse_canIncludeOrNotIncludeCurrencyUnit() {
PremiumListDao.save("tld", USD, ImmutableList.of("rofl,USD 90", "paper, 80"));
tm().transact(
() -> PremiumListDao.save("tld", USD, ImmutableList.of("rofl,USD 90", "paper, 80")));
assertThat(PremiumListDao.getPremiumPrice("tld", "rofl")).hasValue(Money.of(USD, 90));
assertThat(PremiumListDao.getPremiumPrice("tld", "paper")).hasValue(Money.of(USD, 80));
}
@@ -21,16 +21,22 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import java.math.BigDecimal;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link PremiumListUtils}. */
class PremiumListUtilsTest {
private static final DateTime SAMPLE_TIME = DateTime.parse("2026-01-26T21:06:12.284Z");
@Test
void parseInputToPremiumList_works() {
PremiumList premiumList =
parseToPremiumList(
"testlist", USD, ImmutableList.of("foo,USD 99.50", "bar,USD 30", "baz,USD 10"));
"testlist",
USD,
ImmutableList.of("foo,USD 99.50", "bar,USD 30", "baz,USD 10"),
SAMPLE_TIME);
assertThat(premiumList.getName()).isEqualTo("testlist");
assertThat(premiumList.getLabelsToPrices())
.containsExactly("foo", twoDigits(99.50), "bar", twoDigits(30), "baz", twoDigits(10));
@@ -45,7 +51,8 @@ class PremiumListUtilsTest {
parseToPremiumList(
"testlist",
USD,
ImmutableList.of("foo,USD 99.50", "bar,USD 30", "baz,JPY 990")));
ImmutableList.of("foo,USD 99.50", "bar,USD 30", "baz,JPY 990"),
SAMPLE_TIME));
assertThat(thrown).hasMessageThat().isEqualTo("The currency unit must be USD");
}
@@ -0,0 +1,135 @@
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence.transaction;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import java.util.Random;
import java.util.concurrent.Callable;
import org.junit.jupiter.api.Test;
/** Tests for {@link DelegatingReplicaJpaTransactionManager}. */
public class DelegatingReplicaJpaTransactionManagerTest {
private JpaTransactionManager replica1 = mock(JpaTransactionManager.class);
private JpaTransactionManager replica2 = mock(JpaTransactionManager.class);
private Random random = mock(Random.class);
private DelegatingReplicaJpaTransactionManager transactionManager =
new DelegatingReplicaJpaTransactionManager(ImmutableList.of(replica1, replica2), random);
@Test
void testGetReplica_rotates() {
when(random.nextInt(2)).thenReturn(0).thenReturn(1);
transactionManager.loadByKey(null);
verify(replica1).loadByKey(null);
transactionManager.loadByKey(null);
verify(replica2).loadByKey(null);
}
@Test
void testTransact_usesSameReplica() throws Exception {
when(random.nextInt(2)).thenReturn(1);
when(replica2.transact(any(), any(), anyBoolean()))
.thenAnswer(
invocation -> {
Callable<Object> work = invocation.getArgument(1);
return work.call();
});
transactionManager.transact(
() -> {
transactionManager.loadByKey(null);
return null;
});
verify(replica2).transact(any(), any(), anyBoolean());
// The loadByKey inside the transact should also use replica2.
verify(replica2).loadByKey(null);
// And it should NOT have called random again for the nested call.
verify(random).nextInt(2);
}
@Test
void testTransactNoRetry_usesSameReplica() throws Exception {
when(random.nextInt(2)).thenReturn(0);
when(replica1.transactNoRetry(any(), any(), anyBoolean()))
.thenAnswer(
invocation -> {
Callable<Object> work = invocation.getArgument(1);
return work.call();
});
transactionManager.transactNoRetry(
() -> {
transactionManager.loadByKey(null);
return null;
});
verify(replica1).transactNoRetry(any(), any(), anyBoolean());
verify(replica1).loadByKey(null);
verify(random).nextInt(2);
}
@Test
void testReTransactNoRetry_usesSameReplica() throws Exception {
when(random.nextInt(2)).thenReturn(0);
when(replica1.reTransact(any(Callable.class)))
.thenAnswer(
invocation -> {
Callable<Object> work = invocation.getArgument(0);
return work.call();
});
transactionManager.reTransact(
() -> {
transactionManager.loadByKey(null);
return null;
});
verify(replica1).reTransact(any(Callable.class));
verify(replica1).loadByKey(null);
verify(random).nextInt(2);
}
@Test
void testInTransaction() {
when(random.nextInt(2)).thenReturn(0);
when(replica1.inTransaction()).thenReturn(true);
// Not in transaction yet
assertThat(transactionManager.inTransaction()).isFalse();
transactionManager.transact(
() -> {
assertThat(transactionManager.inTransaction()).isTrue();
return null;
});
}
@Test
void testTeardown_tearsDownAllReplicas() {
transactionManager.teardown();
verify(replica1).teardown();
verify(replica2).teardown();
}
}
@@ -31,15 +31,18 @@ import google.registry.bigquery.BigqueryConnection.DestinationTable;
import google.registry.bigquery.BigqueryUtils.TableType;
import google.registry.gcs.GcsUtils;
import google.registry.reporting.icann.IcannReportingModule.ReportType;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import org.joda.time.DateTime;
import org.joda.time.YearMonth;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link google.registry.reporting.icann.IcannReportingStager}. */
class IcannReportingStagerTest {
private final FakeClock clock = new FakeClock(DateTime.parse("2026-01-26T21:06:12.284Z"));
private BigqueryConnection bigquery = mock(BigqueryConnection.class);
FakeResponse response = new FakeResponse();
private YearMonth yearMonth = new YearMonth(2017, 6);
@@ -63,7 +66,7 @@ class IcannReportingStagerTest {
when(bigquery.startQuery(any(String.class), any(DestinationTable.class)))
.thenReturn(fakeFuture());
DestinationTable.Builder tableBuilder =
new DestinationTable.Builder()
new DestinationTable.Builder(clock)
.datasetId("testdataset")
.type(TableType.TABLE)
.name("tablename")
@@ -346,7 +346,7 @@ public final class DatabaseHelper {
// increasing sequence, if we don't pad out the ID here, we would have to renumber hundreds of
// unit tests.
tm().reTransact(tm()::allocateId);
PremiumListDao.save(premiumList);
tm().transact(() -> PremiumListDao.save(premiumList));
maybeAdvanceClock();
return premiumList;
}
@@ -19,6 +19,7 @@ import static google.registry.keyring.api.PgpHelper.KeyRequirement.SIGN;
import static google.registry.testing.TestDataHelper.loadBytes;
import static google.registry.testing.TestDataHelper.loadFile;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteSource;
import dagger.Module;
import dagger.Provides;
@@ -57,7 +58,8 @@ public final class FakeKeyringModule {
private static final String MARKSDB_SMDRL_LOGIN_AND_PASSWORD = "smdrl:yolo";
private static final String BSA_API_KEY = "bsaapikey";
private static final String SQL_PRIMARY_CONNECTION = "project:primary-region:primary-name";
private static final String SQL_REPLICA_CONNECTION = "project:replica-region:replica-name";
private static final String SQL_REPLICA_CONNECTION_1 = "project:replica-region:replica-name";
private static final String SQL_REPLICA_CONNECTION_2 = "project:replica-region:replica-name-2";
@Provides
public Keyring get() {
@@ -160,7 +162,12 @@ public final class FakeKeyringModule {
@Override
public String getSqlReplicaConnectionName() {
return SQL_REPLICA_CONNECTION;
return SQL_REPLICA_CONNECTION_1;
}
@Override
public ImmutableList<String> getSqlReplicaConnectionNames() {
return ImmutableList.of(SQL_REPLICA_CONNECTION_1, SQL_REPLICA_CONNECTION_2);
}
@Override
@@ -30,7 +30,6 @@ import google.registry.model.poll.PollMessage.OneTime;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -38,13 +37,12 @@ import org.junit.jupiter.api.Test;
/** Unit tests for {@link AckPollMessagesCommand}. */
public class AckPollMessagesCommandTest extends CommandTestCase<AckPollMessagesCommand> {
private final FakeClock clock = new FakeClock(DateTime.parse("2015-02-04T08:16:32.064Z"));
private DomainHistory domainHistory;
@BeforeEach
final void beforeEach() {
command.clock = clock;
command.clock = fakeClock;
fakeClock.setTo(DateTime.parse("2015-02-04T08:16:32.064Z"));
createTld("tld");
Domain domain =
DatabaseHelper.newDomain("example.tld").asBuilder().setRepoId("FSDGS-TLD").build();
@@ -52,12 +50,12 @@ public class AckPollMessagesCommandTest extends CommandTestCase<AckPollMessagesC
domainHistory =
persistResource(
new DomainHistory.Builder()
.setModificationTime(clock.nowUtc())
.setModificationTime(fakeClock.nowUtc())
.setDomain(domain)
.setRegistrarId(domain.getCreationRegistrarId())
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.build());
clock.advanceOneMilli();
fakeClock.advanceOneMilli();
}
@Test
@@ -68,10 +68,10 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
@BeforeEach
void beforeEach() {
command.clock = fakeClock;
command.mapper = objectMapper;
premiumList = persistPremiumList("test", USD, "silver,USD 50", "gold,USD 80");
command.validDnsWriterNames = ImmutableSet.of("VoidDnsWriter", "FooDnsWriter");
command.clock = fakeClock;
logger.addHandler(logHandler);
}
@@ -31,6 +31,7 @@ class CreateAnchorTenantCommandTest extends EppToolCommandTestCase<CreateAnchorT
@BeforeEach
void beforeEach() {
command.clock = fakeClock;
command.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
}
@@ -37,6 +37,7 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
@BeforeEach
void beforeEach() {
command.clock = fakeClock;
command.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
command.printStream = System.out;
}
@@ -45,6 +45,7 @@ abstract class CreateOrUpdatePremiumListCommandTestCase<T extends CreateOrUpdate
@BeforeEach
void beforeEachCreateOrUpdatePremiumListCommandTestCase() throws IOException {
command.clock = fakeClock;
// initial set up for both CreatePremiumListCommand and UpdatePremiumListCommand test cases;
initialPremiumListData = "doge,USD 9090";
File premiumTermsFile = tmpDir.resolve(TLD_TEST + ".txt").toFile();
@@ -44,6 +44,7 @@ abstract class CreateOrUpdateReservedListCommandTestCase<
@BeforeEach
void beforeEachCreateOrUpdateReservedListCommandTestCase() throws IOException {
command.clock = fakeClock;
File reservedTermsFile = tmpDir.resolve("xn--q9jyb4c_common-reserved.txt").toFile();
File invalidReservedTermsFile = tmpDir.resolve("reserved-terms-wontparse.csv").toFile();
String reservedTermsCsv =
@@ -54,7 +54,6 @@ class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
// since the old entity is always null and file cannot be empty, the prompt will NOT be "No entity
// changes to apply."
void commandPrompt_successStageNewEntity() throws Exception {
CreatePremiumListCommand command = new CreatePremiumListCommand();
command.inputFile = Paths.get(premiumTermsPath);
command.currencyUnit = "USD";
command.prompt();
@@ -63,7 +62,6 @@ class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
@Test
void commandPrompt_successStageNewEntityWithOverride() throws Exception {
CreatePremiumListCommand command = new CreatePremiumListCommand();
String alterTld = "override";
command.inputFile = Paths.get(premiumTermsPath);
command.override = true;
@@ -75,7 +73,6 @@ class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
@Test
void commandPrompt_failureNoInputFile() {
CreatePremiumListCommand command = new CreatePremiumListCommand();
assertThrows(NullPointerException.class, command::prompt);
}
@@ -83,7 +80,6 @@ class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
void commandPrompt_failurePremiumListAlreadyExists() {
String randomStr = "random";
DatabaseHelper.createTld(randomStr);
CreatePremiumListCommand command = new CreatePremiumListCommand();
command.name = randomStr;
command.currencyUnit = "USD";
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::prompt);
@@ -92,7 +88,6 @@ class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
@Test
void commandPrompt_failureMismatchedTldFileName_noOverride() throws Exception {
CreatePremiumListCommand command = new CreatePremiumListCommand();
String fileName = "random";
Path tmpPath = tmpDir.resolve(String.format("%s.txt", fileName));
Files.write(new byte[0], tmpPath.toFile());
@@ -111,7 +106,6 @@ class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
@Test
void commandPrompt_failureMismatchedTldName_noOverride() {
CreatePremiumListCommand command = new CreatePremiumListCommand();
String fileName = "random";
command.name = fileName;
command.currencyUnit = "USD";
@@ -58,6 +58,7 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@BeforeEach
void beforeEach() {
command.clock = fakeClock;
command.setConnection(connection);
command.certificateChecker =
new CertificateChecker(
@@ -36,6 +36,7 @@ class CreateReservedListCommandTest
@BeforeEach
void beforeEach() {
command.clock = fakeClock;
createTlds("xn--q9jyb4c", "soy");
}
@@ -162,7 +163,6 @@ class CreateReservedListCommandTest
@Test
void testStageEntityChange_succeeds() throws Exception {
CreateReservedListCommand command = new CreateReservedListCommand();
// file content is populated in @BeforeEach of CreateOrUpdateReservedListCommandTestCase.java
command.input = Paths.get(reservedTermsPath);
command.init();
@@ -176,7 +176,6 @@ class CreateReservedListCommandTest
void testStageEntityChange_succeedsWithEmptyFile() throws Exception {
Path tmpPath = tmpDir.resolve("xn--q9jyb4c_common-tmp.txt");
Files.write(new byte[0], tmpPath.toFile());
CreateReservedListCommand command = new CreateReservedListCommand();
command.input = tmpPath;
command.init();
assertThat(command.prompt()).contains("reservedListMap=[]");
@@ -33,6 +33,7 @@ public abstract class EppToolCommandTestCase<C extends EppToolCommand> extends C
@BeforeEach
public void beforeEachEppToolCommandTestCase() {
command.clock = fakeClock;
// Create two TLDs for commands that allow multiple TLDs at once.
createTlds("tld", "tld2");
eppVerifier = EppToolVerifier.create(command).expectRegistrarId("NewRegistrar");
@@ -22,7 +22,6 @@ import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException;
@@ -36,13 +35,11 @@ import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.joda.time.DateTime;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.junit.jupiter.api.BeforeEach;
@@ -51,8 +48,6 @@ import org.junit.jupiter.api.Test;
/** Unit tests for {@link GenerateDnsReportCommand}. */
class GenerateDnsReportCommandTest extends CommandTestCase<GenerateDnsReportCommand> {
private final DateTime now = DateTime.now(UTC);
private final FakeClock clock = new FakeClock();
private Path output;
private Object getOutputAsJson() throws IOException, ParseException {
@@ -117,8 +112,7 @@ class GenerateDnsReportCommandTest extends CommandTestCase<GenerateDnsReportComm
@BeforeEach
void beforeEach() {
output = tmpDir.resolve("out.dat");
command.clock = clock;
clock.setTo(now);
command.clock = fakeClock;
createTlds("xn--q9jyb4c", "example");
nameserver1 =
@@ -170,7 +164,7 @@ class GenerateDnsReportCommandTest extends CommandTestCase<GenerateDnsReportComm
@Test
void testSuccess_skipDeletedDomain() throws Exception {
persistResource(domain1.asBuilder().setDeletionTime(now).build());
persistResource(domain1.asBuilder().setDeletionTime(fakeClock.nowUtc()).build());
runCommand("--output=" + output, "--tld=xn--q9jyb4c");
assertThat((Iterable<?>) getOutputAsJson())
.containsExactly(DOMAIN2_OUTPUT, NAMESERVER1_OUTPUT, NAMESERVER2_OUTPUT);
@@ -178,7 +172,7 @@ class GenerateDnsReportCommandTest extends CommandTestCase<GenerateDnsReportComm
@Test
void testSuccess_skipDeletedNameserver() throws Exception {
persistResource(nameserver1.asBuilder().setDeletionTime(now).build());
persistResource(nameserver1.asBuilder().setDeletionTime(fakeClock.nowUtc()).build());
runCommand("--output=" + output, "--tld=xn--q9jyb4c");
Iterable<?> output = (Iterable<?>) getOutputAsJson();
assertThat(output).containsAnyOf(DOMAIN1_OUTPUT, DOMAIN1_OUTPUT_ALT);
@@ -207,7 +201,7 @@ class GenerateDnsReportCommandTest extends CommandTestCase<GenerateDnsReportComm
domain1
.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.setDeletionTime(now.plusDays(30))
.setDeletionTime(fakeClock.nowUtc().plusDays(30))
.build());
runCommand("--output=" + output, "--tld=xn--q9jyb4c");
assertThat((Iterable<?>) getOutputAsJson())
@@ -30,8 +30,8 @@ class GetDomainCommandTest extends CommandTestCase<GetDomainCommand> {
@BeforeEach
void beforeEach() {
createTld("tld");
command.clock = fakeClock;
createTld("tld");
}
@Test
@@ -22,7 +22,6 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntr
import google.registry.model.domain.Domain;
import google.registry.model.domain.Period;
import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.FakeClock;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -30,12 +29,12 @@ import org.junit.jupiter.api.Test;
/** Unit tests for {@link GetClaimsListCommand}. */
class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesCommand> {
private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01T00:00:00Z"));
private Domain domain;
@BeforeEach
void beforeEach() {
fakeClock.setTo(DateTime.parse("2000-01-01T00:00:00Z"));
command.clock = fakeClock;
createTld("tld");
domain = persistActiveDomain("example.tld");
}
@@ -48,18 +47,18 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
HistoryEntry.Type.DOMAIN_CREATE,
Period.create(1, Period.Unit.YEARS),
"created",
clock.nowUtc()));
fakeClock.nowUtc()));
runCommand("--id=example.tld", "--type=DOMAIN");
assertStdoutIs(
"""
Client: TheRegistrar
Time: 2000-01-01T00:00:00.000Z
Client TRID: ABC-123
Server TRID: server-trid
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xml/>
Client: TheRegistrar
Time: 2000-01-01T00:00:00.000Z
Client TRID: ABC-123
Server TRID: server-trid
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xml/>
""");
""");
}
@Test
@@ -70,8 +69,8 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
HistoryEntry.Type.DOMAIN_CREATE,
Period.create(1, Period.Unit.YEARS),
"created",
clock.nowUtc()));
runCommand("--before", clock.nowUtc().minusMinutes(1).toString());
fakeClock.nowUtc()));
runCommand("--before", fakeClock.nowUtc().minusMinutes(1).toString());
assertStdoutIs("");
}
@@ -83,8 +82,8 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
HistoryEntry.Type.DOMAIN_CREATE,
Period.create(1, Period.Unit.YEARS),
"created",
clock.nowUtc()));
runCommand("--after", clock.nowUtc().plusMinutes(1).toString());
fakeClock.nowUtc()));
runCommand("--after", fakeClock.nowUtc().plusMinutes(1).toString());
assertStdoutIs("");
}
@@ -96,22 +95,22 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
HistoryEntry.Type.DOMAIN_CREATE,
Period.create(1, Period.Unit.YEARS),
"created",
clock.nowUtc()));
fakeClock.nowUtc()));
runCommand(
"--after",
clock.nowUtc().minusMinutes(1).toString(),
fakeClock.nowUtc().minusMinutes(1).toString(),
"--before",
clock.nowUtc().plusMinutes(1).toString());
fakeClock.nowUtc().plusMinutes(1).toString());
assertStdoutIs(
"""
Client: TheRegistrar
Time: 2000-01-01T00:00:00.000Z
Client TRID: ABC-123
Server TRID: server-trid
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xml/>
Client: TheRegistrar
Time: 2000-01-01T00:00:00.000Z
Client TRID: ABC-123
Server TRID: server-trid
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xml/>
""");
""");
}
@Test
@@ -122,20 +121,20 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
HistoryEntry.Type.DOMAIN_CREATE,
Period.create(1, Period.Unit.YEARS),
"created",
clock.nowUtc())
fakeClock.nowUtc())
.asBuilder()
.setTrid(null)
.build());
runCommand("--id=example.tld", "--type=DOMAIN");
assertStdoutIs(
"""
Client: TheRegistrar
Time: 2000-01-01T00:00:00.000Z
Client TRID: null
Server TRID: null
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xml/>
Client: TheRegistrar
Time: 2000-01-01T00:00:00.000Z
Client TRID: null
Server TRID: null
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xml/>
""");
""");
}
}
@@ -30,8 +30,8 @@ class GetHostCommandTest extends CommandTestCase<GetHostCommand> {
@BeforeEach
void beforeEach() {
createTld("tld");
command.clock = fakeClock;
createTld("tld");
}
@Test
@@ -47,7 +47,6 @@ import google.registry.model.tld.Tld.TldState;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.util.CidrAddressBlock;
import java.security.cert.CertificateParsingException;
import java.util.Optional;
@@ -70,8 +69,9 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
@BeforeEach
void beforeEach() {
fakeClock.setTo(DateTime.parse("2018-07-07TZ"));
command.clock = fakeClock;
command.passwordGenerator = passwordGenerator;
command.clock = new FakeClock(DateTime.parse("2018-07-07TZ"));
command.maybeGroupEmailAddress = Optional.of("group@example.com");
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
command.iamClient = iamClient;
@@ -41,6 +41,7 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
@BeforeEach
void beforeEach() {
command.clock = fakeClock;
registry = createTld(TLD_TEST, USD, initialPremiumListData);
}
@@ -60,7 +61,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
String newPremiumListData = "omg,USD 1234";
Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
command.name = TLD_TEST;
assertThat(command.prompt()).contains("Update premium list for prime?");
@@ -69,7 +69,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
@Test
void commandPrompt_successStageNoChange() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
command.name = TLD_TEST;
assertThat(command.prompt())
@@ -82,7 +81,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
String newPremiumListData = "eth,USD 9999";
Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
// data come from @beforeEach of CreateOrUpdatePremiumListCommandTestCase.java
command.inputFile = Paths.get(tmpFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile);
@@ -96,7 +94,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
void commandRun_successNoChange() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile);
@@ -113,7 +110,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
String newPremiumListData = "eth,USD 9999";
Files.asCharSink(newPremiumFile, UTF_8).write(newPremiumListData);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
// data come from @beforeEach of CreateOrUpdatePremiumListCommandTestCase.java
command.inputFile = Paths.get(newPremiumFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile);
@@ -129,7 +125,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
String premiumTerms = "foo,USD 9000\ndoge,USD 100\nelon,USD 2021";
Files.asCharSink(tmpFile, UTF_8).write(premiumTerms);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile);
@@ -146,7 +141,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
Path tmpPath = tmpDir.resolve(String.format("%s.txt", TLD_TEST));
Files.write(new byte[0], tmpPath.toFile());
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = tmpPath;
command.name = TLD_TEST;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::prompt);
@@ -156,7 +150,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
@Test
void commandPrompt_failureNoPreviousVersion() {
registry = createTld("random", null, null);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.name = "random";
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::prompt);
assertThat(thrown)
@@ -166,13 +159,11 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
@Test
void commandPrompt_failureNoInputFile() {
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
assertThrows(NullPointerException.class, command::prompt);
}
@Test
void commandPrompt_failureTldFromNameDoesNotExist() {
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.name = "random2";
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::prompt);
assertThat(thrown)
@@ -182,7 +173,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
@Test
void commandPrompt_failureTldFromInputFileDoesNotExist() {
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
// using tld extracted from file name but this tld is not part of the registry
command.inputFile = Paths.get(tmpDir.resolve("random3.txt").toFile().getPath());
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::prompt);
@@ -197,7 +187,6 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
String newPremiumListData = "eth,USD 9999";
Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile, "--dry_run");
@@ -53,6 +53,7 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
@BeforeEach
void beforeEach() {
command.clock = fakeClock;
command.certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
@@ -98,7 +98,6 @@ class UpdateReservedListCommandTest
Files.asCharSink(reservedTermsFile, UTF_8).write(reservedTermsCsv);
reservedTermsPath = reservedTermsFile.getPath();
// create a command instance and assign its input
UpdateReservedListCommand command = new UpdateReservedListCommand();
command.input = Paths.get(reservedTermsPath);
// run again with terms from example_reserved_terms.csv
command.init();
@@ -110,7 +109,6 @@ class UpdateReservedListCommandTest
void testSuccess_withChanges() throws Exception {
// changes come from example_reserved_terms.csv, which are populated in @BeforeEach of
// CreateOrUpdateReservedListCommandTestCases.java
UpdateReservedListCommand command = new UpdateReservedListCommand();
command.input = Paths.get(reservedTermsPath);
command.init();
@@ -50,6 +50,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
@BeforeEach
void beforeEach() {
command.clock = fakeClock;
createTld("tld");
persistResource(
loadRegistrar("NewRegistrar")
@@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableList;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import google.registry.util.Clock;
import google.registry.util.SelfSignedCaCertificate;
import jakarta.inject.Named;
import jakarta.inject.Provider;
@@ -156,9 +157,9 @@ public final class CertificateSupplierModule {
@Singleton
@Provides
static SelfSignedCaCertificate provideSelfSignedCertificate() {
static SelfSignedCaCertificate provideSelfSignedCertificate(Clock clock) {
try {
return SelfSignedCaCertificate.create();
return SelfSignedCaCertificate.create(clock);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -22,6 +22,7 @@ import static google.registry.networking.handler.SslInitializerTestUtils.verifyS
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableList;
import google.registry.testing.FakeClock;
import google.registry.util.SelfSignedCaCertificate;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
@@ -173,7 +174,7 @@ class SslClientInitializerTest {
@MethodSource("provideTestCombinations")
void testFailure_defaultTrustManager_rejectSelfSignedCert(SslProvider sslProvider)
throws Exception {
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress =
new LocalAddress("DEFAULT_TRUST_MANAGER_REJECT_SELF_SIGNED_CERT_" + sslProvider);
nettyExtension.setUpServer(localAddress, getServerHandler(false, ssc.key(), ssc.cert()));
@@ -204,7 +205,7 @@ class SslClientInitializerTest {
KeyPair keyPair = getKeyPair();
// Generate a self-signed certificate, and use it to sign the key pair.
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create(new FakeClock());
X509Certificate cert = signKeyPair(ssc, keyPair, SSL_HOST);
// Set up the server to use the signed cert and private key to perform handshake;
@@ -239,7 +240,7 @@ class SslClientInitializerTest {
KeyPair keyPair = getKeyPair();
// Generate a self-signed certificate, and use it to sign the key pair.
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create(new FakeClock());
X509Certificate cert =
signKeyPair(
ssc, keyPair, SSL_HOST, DateTime.now(UTC).minusDays(2), DateTime.now(UTC).minusDays(1));
@@ -276,7 +277,7 @@ class SslClientInitializerTest {
KeyPair keyPair = getKeyPair();
// Generate a self-signed certificate, and use it to sign the key pair.
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create(new FakeClock());
X509Certificate cert =
signKeyPair(
ssc, keyPair, SSL_HOST, DateTime.now(UTC).plusDays(1), DateTime.now(UTC).plusDays(2));
@@ -310,8 +311,8 @@ class SslClientInitializerTest {
new LocalAddress(
"CUSTOM_TRUST_MANAGER_ACCEPT_SELF_SIGNED_CERT_CLIENT_CERT_REQUIRED_" + sslProvider);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate clientSsc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
SelfSignedCaCertificate clientSsc = SelfSignedCaCertificate.create(new FakeClock());
// Set up the server to require client certificate.
nettyExtension.setUpServer(
@@ -352,7 +353,7 @@ class SslClientInitializerTest {
KeyPair keyPair = getKeyPair();
// Generate a self-signed certificate, and use it to sign the key pair.
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create(new FakeClock());
X509Certificate cert = signKeyPair(ssc, keyPair, "wrong.com");
// Set up the server to use the signed cert and private key to perform handshake;
@@ -24,6 +24,7 @@ import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import google.registry.testing.FakeClock;
import google.registry.util.SelfSignedCaCertificate;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
@@ -148,7 +149,7 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testSuccess_swappedInitializerWithSslHandler(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate ssc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
SslServerInitializer<EmbeddedChannel> sslServerInitializer =
new SslServerInitializer<>(
true,
@@ -169,13 +170,13 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testSuccess_trustAnyClientCert(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress = new LocalAddress("TRUST_ANY_CLIENT_CERT_" + sslProvider);
nettyExtension.setUpServer(
localAddress,
getServerHandler(true, false, sslProvider, serverSsc.key(), serverSsc.cert()));
SelfSignedCaCertificate clientSsc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate clientSsc = SelfSignedCaCertificate.create(new FakeClock());
nettyExtension.setUpClient(
localAddress,
getClientHandler(sslProvider, serverSsc.cert(), clientSsc.key(), clientSsc.cert()));
@@ -193,7 +194,7 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_cipherNotAccepted(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress = new LocalAddress("CIPHER_NOT_ACCEPTED_" + sslProvider);
nettyExtension.setUpServer(
@@ -220,7 +221,7 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testSuccess_someCiphersNotAccepted(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress = new LocalAddress("SOME_CIPHERS_NOT_ACCEPTED_" + sslProvider);
nettyExtension.setUpServer(
@@ -258,7 +259,7 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_protocolNotAccepted(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress = new LocalAddress("PROTOCOL_NOT_ACCEPTED_" + sslProvider);
nettyExtension.setUpServer(
@@ -288,7 +289,7 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_clientCertExpired(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress = new LocalAddress("CLIENT_CERT_EXPIRED_" + sslProvider);
nettyExtension.setUpServer(
@@ -309,7 +310,7 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_clientCertNotYetValid(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress = new LocalAddress("CLIENT_CERT_EXPIRED_" + sslProvider);
nettyExtension.setUpServer(
@@ -330,7 +331,7 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testSuccess_doesNotRequireClientCert(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress = new LocalAddress("DOES_NOT_REQUIRE_CLIENT_CERT_" + sslProvider);
nettyExtension.setUpServer(
@@ -353,7 +354,7 @@ class SslServerInitializerTest {
@MethodSource("provideTestCombinations")
void testSuccess_CertSignedByOtherCa(SslProvider sslProvider) throws Exception {
// The self-signed cert of the CA.
SelfSignedCaCertificate caSsc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate caSsc = SelfSignedCaCertificate.create(new FakeClock());
KeyPair keyPair = getKeyPair();
X509Certificate serverCert = signKeyPair(caSsc, keyPair, SSL_HOST);
LocalAddress localAddress = new LocalAddress("CERT_SIGNED_BY_OTHER_CA_" + sslProvider);
@@ -368,7 +369,7 @@ class SslServerInitializerTest {
// Serving both the server cert, and the CA cert
serverCert,
caSsc.cert()));
SelfSignedCaCertificate clientSsc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate clientSsc = SelfSignedCaCertificate.create(new FakeClock());
nettyExtension.setUpClient(
localAddress,
getClientHandler(
@@ -392,7 +393,7 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_requireClientCertificate(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST, new FakeClock());
LocalAddress localAddress = new LocalAddress("REQUIRE_CLIENT_CERT_" + sslProvider);
nettyExtension.setUpServer(
@@ -417,13 +418,14 @@ class SslServerInitializerTest {
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_wrongHostnameInCertificate(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create("wrong.com");
SelfSignedCaCertificate serverSsc =
SelfSignedCaCertificate.create("wrong.com", new FakeClock());
LocalAddress localAddress = new LocalAddress("WRONG_HOSTNAME_" + sslProvider);
nettyExtension.setUpServer(
localAddress,
getServerHandler(true, false, sslProvider, serverSsc.key(), serverSsc.cert()));
SelfSignedCaCertificate clientSsc = SelfSignedCaCertificate.create();
SelfSignedCaCertificate clientSsc = SelfSignedCaCertificate.create(new FakeClock());
nettyExtension.setUpClient(
localAddress,
getClientHandler(sslProvider, serverSsc.cert(), clientSsc.key(), clientSsc.cert()));
@@ -26,6 +26,8 @@ import dagger.Component;
import dagger.Module;
import dagger.Provides;
import google.registry.networking.module.CertificateSupplierModule.Mode;
import google.registry.testing.FakeClock;
import google.registry.util.Clock;
import google.registry.util.SelfSignedCaCertificate;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
@@ -59,7 +61,7 @@ class CertificateSupplierModuleTest {
@BeforeEach
void beforeEach() throws Exception {
ssc = SelfSignedCaCertificate.create();
ssc = SelfSignedCaCertificate.create(new FakeClock());
KeyPair keyPair = getKeyPair();
key = keyPair.getPrivate();
cert = signKeyPair(ssc, keyPair, "example.tld");
@@ -147,6 +149,11 @@ class CertificateSupplierModuleTest {
// Make the supplier always return the save value for test to save time.
return Duration.ofDays(1);
}
@Provides
Clock provideClock() {
return new FakeClock();
}
}
/**
@@ -18,6 +18,7 @@ import com.google.common.annotations.VisibleForTesting;
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
import google.registry.monitoring.blackbox.message.EppRequestMessage;
import google.registry.monitoring.blackbox.message.OutboundMessageType;
import google.registry.util.Clock;
import io.netty.channel.Channel;
import jakarta.inject.Inject;
import jakarta.inject.Named;
@@ -33,6 +34,7 @@ public abstract class EppToken extends Token {
private static AtomicInteger clientIdSuffix = new AtomicInteger();
protected final String tld;
protected final Clock clock;
private String host;
private String currentDomainName;
@@ -40,15 +42,16 @@ public abstract class EppToken extends Token {
* Always the constructor used to provide any {@link EppToken}, with {@code tld} and {@code host}
* specified by Dagger.
*/
protected EppToken(String tld, String host) {
protected EppToken(String tld, String host, Clock clock) {
this.tld = tld;
this.host = host;
this.clock = clock;
currentDomainName = newDomainName(getNewTRID());
}
/** Constructor used when passing on same {@link Channel} to next {@link Token}. */
protected EppToken(String tld, String host, Channel channel) {
this(tld, host);
protected EppToken(String tld, String host, Clock clock, Channel channel) {
this(tld, host, clock);
setChannel(channel);
}
@@ -79,7 +82,7 @@ public abstract class EppToken extends Token {
private String getNewTRID() {
return String.format(
"prober-%s-%d-%d",
"localhost", System.currentTimeMillis(), clientIdSuffix.incrementAndGet());
"localhost", clock.nowUtc().getMillis(), clientIdSuffix.incrementAndGet());
}
/** Return a fully qualified domain label to use, derived from the client transaction ID. */
@@ -103,13 +106,13 @@ public abstract class EppToken extends Token {
public static class Transient extends EppToken {
@Inject
public Transient(@Named("eppTld") String tld, @Named("eppHost") String host) {
super(tld, host);
public Transient(@Named("eppTld") String tld, @Named("eppHost") String host, Clock clock) {
super(tld, host, clock);
}
@Override
public Token next() {
return new Transient(tld, host());
return new Transient(tld, host(), clock);
}
}
@@ -121,18 +124,18 @@ public abstract class EppToken extends Token {
public static class Persistent extends EppToken {
@Inject
public Persistent(@Named("eppTld") String tld, @Named("eppHost") String host) {
super(tld, host);
public Persistent(@Named("eppTld") String tld, @Named("eppHost") String host, Clock clock) {
super(tld, host, clock);
}
/** Constructor used on call to {@code next} to preserve channel. */
private Persistent(String tld, String host, Channel channel) {
super(tld, host, channel);
private Persistent(String tld, String host, Clock clock, Channel channel) {
super(tld, host, clock, channel);
}
@Override
public Token next() {
return new Persistent(tld, host(), channel());
return new Persistent(tld, host(), clock, channel());
}
}
}
@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
import google.registry.monitoring.blackbox.message.EppRequestMessage;
import google.registry.monitoring.blackbox.util.EppUtils;
import google.registry.testing.FakeClock;
import io.netty.channel.Channel;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -28,9 +29,10 @@ class EppTokenTest {
private static String TEST_HOST = "host";
private static String TEST_TLD = "tld";
private final FakeClock fakeClock = new FakeClock();
private EppToken persistentEppToken = new EppToken.Persistent(TEST_TLD, TEST_HOST);
private EppToken transientEppToken = new EppToken.Transient(TEST_TLD, TEST_HOST);
private EppToken persistentEppToken = new EppToken.Persistent(TEST_TLD, TEST_HOST, fakeClock);
private EppToken transientEppToken = new EppToken.Transient(TEST_TLD, TEST_HOST, fakeClock);
@Test
void testMessageModificationSuccess_PersistentToken() throws UndeterminedStateException {
@@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.base.Throwables;
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
import google.registry.testing.FakeClock;
import google.registry.util.SelfSignedCaCertificate;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
@@ -49,11 +50,11 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
private static final byte[] HELLO_BYTES =
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<hello/>
</epp>
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<hello/>
</epp>
"""
.getBytes(UTF_8);
private X509Certificate certificate;
@@ -122,7 +123,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
@Override
void beforeEach() throws Exception {
testComponent = makeTestComponent();
certificate = SelfSignedCaCertificate.create().cert();
certificate = SelfSignedCaCertificate.create(new FakeClock()).cert();
initializeChannel(
ch -> {
ch.attr(REMOTE_ADDRESS_KEY).set(CLIENT_ADDRESS);
@@ -32,6 +32,7 @@ import com.google.common.io.BaseEncoding;
import google.registry.proxy.TestUtils;
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
import google.registry.proxy.metric.FrontendMetrics;
import google.registry.testing.FakeClock;
import google.registry.util.ProxyHttpHeaders;
import google.registry.util.SelfSignedCaCertificate;
import io.netty.buffer.ByteBuf;
@@ -114,7 +115,7 @@ class EppServiceHandlerTest {
@BeforeEach
void beforeEach() throws Exception {
clientCertificate = SelfSignedCaCertificate.create().cert();
clientCertificate = SelfSignedCaCertificate.create(new FakeClock()).cert();
channel = setUpNewChannel(eppServiceHandler);
}
@@ -171,7 +172,7 @@ class EppServiceHandlerTest {
new EppServiceHandler(
RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
X509Certificate clientCertificate2 = SelfSignedCaCertificate.create().cert();
X509Certificate clientCertificate2 = SelfSignedCaCertificate.create(new FakeClock()).cert();
setHandshakeSuccess(channel2, clientCertificate2);
String certHash2 = getCertificateHash(clientCertificate2);
+1
View File
@@ -31,6 +31,7 @@ steps:
export JAVAC_EXTRACTOR_JAR="$${PWD}/kythe/extractors/javac_extractor.jar"
jvmopts="--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED"
jvmopts="$${jvmopts} --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"
jvmopts="$${jvmopts} --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED"
jvmopts="$${jvmopts} --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED"
jvmopts="$${jvmopts} --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"
jvmopts="$${jvmopts} --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED"
+4 -2
View File
@@ -27,7 +27,7 @@ steps:
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
# Set home for Gradle caches. Must be consistent with last step below
# and ./build_nomulus_for_env.sh
env: [ 'GRADLE_USER_HOME=/workspace/cloudbuild-caches' ]
env: [ 'GRADLE_USER_HOME=/workspace/cloudbuild-caches', 'DOCKER_API_VERSION=1.41' ]
entrypoint: /bin/bash
args:
- -c
@@ -45,7 +45,7 @@ steps:
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
# Set home for Gradle caches. Must be consistent with last step below
# and ./build_nomulus_for_env.sh
env: [ 'GRADLE_USER_HOME=/workspace/cloudbuild-caches' ]
env: [ 'GRADLE_USER_HOME=/workspace/cloudbuild-caches', 'DOCKER_API_VERSION=1.41' ]
entrypoint: /bin/bash
args:
- -c
@@ -102,6 +102,7 @@ steps:
# nomulus.jar built earlier.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
entrypoint: /bin/bash
env: [ 'DOCKER_API_VERSION=1.41' ]
args:
- -c
- |
@@ -119,6 +120,7 @@ steps:
# nomulus.jar built earlier.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
entrypoint: /bin/bash
env: [ 'DOCKER_API_VERSION=1.41' ]
args:
- -c
- |
+2 -2
View File
@@ -78,7 +78,7 @@ if ! pgrep cloud_sql_proxy; then
exit 1
fi
/flyway/flyway -postgresql.transactional.lock=false -community \
/flyway/flyway -postgresql.transactional.lock=false \
-user=${db_user} -password=${db_password} \
-url=jdbc:postgresql://localhost:5432/postgres \
-locations=classpath:sql/flyway \
@@ -87,7 +87,7 @@ migration_result=$?
if [ ${flyway_action} == "migrate" ]; then
# After deployment, log the current schema.
/flyway/flyway -community -user=${db_user} -password=${db_password} \
/flyway/flyway -user=${db_user} -password=${db_password} \
-url=jdbc:postgresql://localhost:5432/postgres \
-locations=classpath:sql/flyway \
info
@@ -324,12 +324,12 @@ public final class PosixTarHeader {
private final byte[] header = new byte[HEADER_LENGTH];
private boolean hasName = false;
private boolean hasSize = false;
private boolean hasMtime = false;
public Builder() {
setMode(DEFAULT_MODE);
setUid(DEFAULT_UID);
setGid(DEFAULT_GID);
setMtime(DateTime.now(UTC));
setType(DEFAULT_TYPE);
setMagic();
setVersion();
@@ -418,6 +418,7 @@ public final class PosixTarHeader {
public Builder setMtime(DateTime mtime) {
checkNotNull(mtime, "mtime");
setField("mtime", 136, 12, String.format("%011o", mtime.getMillis() / MILLIS_PER_SECOND));
hasMtime = true;
return this;
}
@@ -483,8 +484,10 @@ public final class PosixTarHeader {
public PosixTarHeader build() {
checkState(hasName, "name not set");
checkState(hasSize, "size not set");
checkState(hasMtime, "mtime not set");
hasName = false;
hasSize = false;
hasMtime = false;
setChksum(); // Calculate the checksum last.
return new PosixTarHeader(header.clone());
}
@@ -15,7 +15,6 @@
package google.registry.util;
import static com.google.common.base.Preconditions.checkArgument;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableMap;
import java.math.BigInteger;
@@ -42,8 +41,6 @@ import org.joda.time.DateTime;
public class SelfSignedCaCertificate {
private static final String DEFAULT_ISSUER_FQDN = "registry-test";
private static final DateTime DEFAULT_NOT_BEFORE = DateTime.now(UTC).minusHours(1);
private static final DateTime DEFAULT_NOT_AFTER = DateTime.now(UTC).plusDays(1);
private static final Random RANDOM = new Random();
private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();
@@ -68,13 +65,16 @@ public class SelfSignedCaCertificate {
return cert;
}
public static SelfSignedCaCertificate create() throws Exception {
public static SelfSignedCaCertificate create(Clock clock) throws Exception {
return create(
keyGen.generateKeyPair(), DEFAULT_ISSUER_FQDN, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER);
keyGen.generateKeyPair(),
DEFAULT_ISSUER_FQDN,
clock.nowUtc().minusHours(1),
clock.nowUtc().plusDays(1));
}
public static SelfSignedCaCertificate create(String fqdn) throws Exception {
return create(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER);
public static SelfSignedCaCertificate create(String fqdn, Clock clock) throws Exception {
return create(fqdn, clock.nowUtc().minusHours(1), clock.nowUtc().plusDays(1));
}
public static SelfSignedCaCertificate create(String fqdn, DateTime from, DateTime to)
@@ -27,6 +27,7 @@ import java.io.InputStream;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.ISODateTimeFormat;
import org.junit.jupiter.api.Test;
@@ -199,7 +200,11 @@ class PosixTarHeaderTest {
@Test
void testBadChecksum() {
PosixTarHeader header =
new PosixTarHeader.Builder().setName("(◕‿◕).txt").setSize(31337).build();
new PosixTarHeader.Builder()
.setName("(◕‿◕).txt")
.setSize(31337)
.setMtime(DateTime.now(DateTimeZone.UTC))
.build();
byte[] bytes = header.getBytes();
bytes[150] = '0';
bytes[151] = '0';
@@ -234,6 +239,7 @@ class PosixTarHeaderTest {
new PosixTarHeader.Builder()
.setName("(•︵•).txt") // Awwwww! It looks so sad...
.setSize(123)
.setMtime(DateTime.now(DateTimeZone.UTC))
.build())
.testEquals();
}