mirror of
https://github.com/google/nomulus
synced 2026-08-07 07:46:12 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1f2eb5921 | ||
|
|
4f332d397e | ||
|
|
c9a82f1322 | ||
|
|
fabf0c07b2 | ||
|
|
aa54f9ddc9 | ||
|
|
92edbfbde2 | ||
|
|
b1e127798f | ||
|
|
3474cd6e9b | ||
|
|
1fc4a281c0 | ||
|
|
9a420a69b0 | ||
|
|
a3421f2999 |
@@ -28,6 +28,9 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.monitoring.metrics.IncrementableMetric;
|
||||
import com.google.monitoring.metrics.LabelDescriptor;
|
||||
import com.google.monitoring.metrics.MetricRegistryImpl;
|
||||
import google.registry.cache.SimplifiedJedisClient;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.common.Cursor;
|
||||
@@ -39,6 +42,7 @@ import google.registry.request.Action;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.request.lock.LockHandler;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -61,6 +65,25 @@ public class SyncRemoteCacheAction implements Runnable {
|
||||
private static final String LOCK_NAME = "syncRemoteCacheAction";
|
||||
private static final int BATCH_SIZE = 10000;
|
||||
|
||||
public enum SyncStatus {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
NOT_CONFIGURED
|
||||
}
|
||||
|
||||
private static final ImmutableSet<LabelDescriptor> LABEL_DESCRIPTORS =
|
||||
ImmutableSet.of(
|
||||
LabelDescriptor.create("status", "Whether SyncRemoteCacheAction succeeded or failed."));
|
||||
|
||||
@NonFinalForTesting
|
||||
static final IncrementableMetric SYNC_CACHE_RUNS_METRIC =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newIncrementableMetric(
|
||||
"/batch/sync_remote_cache/runs",
|
||||
"Count of SyncRemoteCacheAction executions",
|
||||
"count",
|
||||
LABEL_DESCRIPTORS);
|
||||
|
||||
private final LockHandler lockHandler;
|
||||
private final Response response;
|
||||
private final Optional<SimplifiedJedisClient> jedisClient;
|
||||
@@ -79,14 +102,17 @@ public class SyncRemoteCacheAction implements Runnable {
|
||||
if (jedisClient.isEmpty()) {
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setPayload("No Jedis/Valkey configuration found");
|
||||
SYNC_CACHE_RUNS_METRIC.increment(SyncStatus.NOT_CONFIGURED.name());
|
||||
return;
|
||||
}
|
||||
Callable<Void> runner =
|
||||
() -> {
|
||||
try {
|
||||
runLocked();
|
||||
SYNC_CACHE_RUNS_METRIC.increment(SyncStatus.SUCCESS.name());
|
||||
response.setStatus(SC_OK);
|
||||
} catch (Exception e) {
|
||||
SYNC_CACHE_RUNS_METRIC.increment(SyncStatus.FAILURE.name());
|
||||
logger.atSevere().withCause(e).log("Errored out during execution.");
|
||||
response.setStatus(SC_INTERNAL_SERVER_ERROR);
|
||||
response.setPayload(String.format("Errored out with cause: %s", e));
|
||||
@@ -95,6 +121,7 @@ public class SyncRemoteCacheAction implements Runnable {
|
||||
};
|
||||
|
||||
if (!lockHandler.executeWithLocks(runner, null, Duration.ofHours(1), LOCK_NAME)) {
|
||||
SYNC_CACHE_RUNS_METRIC.increment(SyncStatus.FAILURE.name());
|
||||
// Send a 200-series status code to prevent this conflicting action from retrying.
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setPayload("Could not acquire lock; already running?");
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.beam.spec11;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import dagger.Component;
|
||||
@@ -25,11 +24,9 @@ import google.registry.beam.common.RegistryJpaIO;
|
||||
import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.beam.spec11.SafeBrowsingTransforms.EvaluateSafeBrowsingFn;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.model.domain.Domain;
|
||||
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;
|
||||
@@ -39,8 +36,7 @@ import java.time.LocalDate;
|
||||
import java.time.YearMonth;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
import org.apache.beam.sdk.PipelineResult;
|
||||
import org.apache.beam.sdk.coders.KvCoder;
|
||||
import org.apache.beam.sdk.coders.StringUtf8Coder;
|
||||
import org.apache.beam.sdk.coders.SerializableCoder;
|
||||
import org.apache.beam.sdk.io.TextIO;
|
||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
import org.apache.beam.sdk.transforms.DoFn;
|
||||
@@ -112,44 +108,22 @@ public class Spec11Pipeline implements Serializable {
|
||||
}
|
||||
|
||||
static PCollection<DomainNameInfo> readFromCloudSql(Pipeline pipeline) {
|
||||
Read<Object[], KV<String, String>> read =
|
||||
Read<Object[], DomainNameInfo> read =
|
||||
RegistryJpaIO.read(
|
||||
"select d.repoId, r.emailAddress from Domain d join Registrar r on"
|
||||
+ " d.currentSponsorRegistrarId = r.registrarId where r.type = 'REAL' and"
|
||||
+ " d.deletionTime > CAST(now() AS timestamp)",
|
||||
"""
|
||||
SELECT d.domainName, d.repoId, d.currentSponsorRegistrarId, r.emailAddress FROM
|
||||
Domain d JOIN Registrar r ON d.currentSponsorRegistrarId = r.registrarId WHERE
|
||||
r.type = 'REAL' AND d.deletionTime > CAST(now() AS timestamp)
|
||||
""",
|
||||
false,
|
||||
Spec11Pipeline::parseRow)
|
||||
.withCoder(KvCoder.of(StringUtf8Coder.of(), StringUtf8Coder.of()));
|
||||
|
||||
return pipeline
|
||||
.apply("Read active domains from Cloud SQL", read)
|
||||
.apply(
|
||||
"Build DomainNameInfo",
|
||||
ParDo.of(
|
||||
new DoFn<KV<String, String>, DomainNameInfo>() {
|
||||
@ProcessElement
|
||||
public void processElement(
|
||||
@Element KV<String, String> input, OutputReceiver<DomainNameInfo> output) {
|
||||
Domain domain =
|
||||
tm().transact(
|
||||
() -> tm().loadByKey(VKey.create(Domain.class, input.getKey())));
|
||||
String emailAddress = input.getValue();
|
||||
if (emailAddress == null) {
|
||||
emailAddress = "";
|
||||
}
|
||||
DomainNameInfo domainNameInfo =
|
||||
DomainNameInfo.create(
|
||||
domain.getDomainName(),
|
||||
domain.getRepoId(),
|
||||
domain.getCurrentSponsorRegistrarId(),
|
||||
emailAddress);
|
||||
output.output(domainNameInfo);
|
||||
}
|
||||
}));
|
||||
.withCoder(SerializableCoder.of(DomainNameInfo.class));
|
||||
return pipeline.apply("Read active domains from Cloud SQL", read);
|
||||
}
|
||||
|
||||
private static KV<String, String> parseRow(Object[] row) {
|
||||
return KV.of((String) row[0], (String) row[1]);
|
||||
private static DomainNameInfo parseRow(Object[] row) {
|
||||
String emailAddress = row[3] != null ? (String) row[3] : "";
|
||||
return new DomainNameInfo((String) row[0], (String) row[1], (String) row[2], emailAddress);
|
||||
}
|
||||
|
||||
static void saveToSql(
|
||||
|
||||
@@ -14,14 +14,18 @@
|
||||
|
||||
package google.registry.dns;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||
import static google.registry.dns.RefreshDnsOnHostRenameAction.PATH;
|
||||
import static google.registry.model.EppResourceUtils.getLinkedDomainKeys;
|
||||
import static google.registry.model.EppResourceUtils.isDeleted;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -29,8 +33,11 @@ import google.registry.request.Action;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
@@ -43,45 +50,52 @@ public class RefreshDnsOnHostRenameAction implements Runnable {
|
||||
public static final String PARAM_HOST_KEY = "hostKey";
|
||||
public static final String PATH = "/_dr/task/refreshDnsOnHostRename";
|
||||
|
||||
private static final int DNS_REFRESH_BATCH_SIZE = 1000;
|
||||
|
||||
private final VKey<Host> hostKey;
|
||||
private final Response response;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
RefreshDnsOnHostRenameAction(@Parameter(PARAM_HOST_KEY) String hostKey, Response response) {
|
||||
RefreshDnsOnHostRenameAction(
|
||||
@Parameter(PARAM_HOST_KEY) String hostKey, Response response, Clock clock) {
|
||||
this.hostKey = VKey.createEppVKeyFromString(hostKey);
|
||||
this.response = response;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
tm().transact(
|
||||
() -> {
|
||||
Instant now = tm().getTxTime();
|
||||
Host host = tm().loadByKeyIfPresent(hostKey).orElse(null);
|
||||
boolean hostValid = true;
|
||||
String failureMessage = null;
|
||||
if (host == null) {
|
||||
hostValid = false;
|
||||
failureMessage = String.format("Host to refresh does not exist: %s", hostKey);
|
||||
} else if (EppResourceUtils.isDeleted(host, now)) {
|
||||
hostValid = false;
|
||||
failureMessage =
|
||||
String.format("Host to refresh is already deleted: %s", host.getHostName());
|
||||
} else {
|
||||
getLinkedDomainKeys(
|
||||
host.createVKey(), host.getUpdateTimestamp().getTimestamp(), null)
|
||||
.stream()
|
||||
.map(domainKey -> tm().loadByKey(domainKey))
|
||||
.filter(Domain::shouldPublishToDns)
|
||||
.forEach(domain -> requestDomainDnsRefresh(domain.getDomainName()));
|
||||
}
|
||||
Optional<Host> optionalHost = tm().transact(() -> tm().loadByKeyIfPresent(hostKey));
|
||||
if (optionalHost.isEmpty()) {
|
||||
setFailedStatus(String.format("Host to refresh does not exist: %s", hostKey));
|
||||
return;
|
||||
}
|
||||
Instant now = clock.now();
|
||||
Host host = optionalHost.get();
|
||||
if (isDeleted(host, now)) {
|
||||
setFailedStatus(String.format("Host to refresh is already deleted: %s", host.getHostName()));
|
||||
return;
|
||||
}
|
||||
ImmutableSet<VKey<Domain>> linkedDomainKeys =
|
||||
getLinkedDomainKeys(hostKey, host.getUpdateTimestamp().getTimestamp(), null);
|
||||
for (List<VKey<Domain>> batch : Iterables.partition(linkedDomainKeys, DNS_REFRESH_BATCH_SIZE)) {
|
||||
tm().transact(
|
||||
() -> {
|
||||
ImmutableSet<String> domainNames =
|
||||
tm().loadByKeysIfPresent(batch).values().stream()
|
||||
.filter(Domain::shouldPublishToDns)
|
||||
.map(Domain::getDomainName)
|
||||
.collect(toImmutableSet());
|
||||
requestDomainDnsRefresh(domainNames);
|
||||
});
|
||||
}
|
||||
response.setStatus(SC_OK);
|
||||
}
|
||||
|
||||
if (!hostValid) {
|
||||
// Set the response status code to be 204 so to not retry.
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setPayload(failureMessage);
|
||||
}
|
||||
});
|
||||
private void setFailedStatus(String message) {
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setPayload(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.Lists.partition;
|
||||
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||
import static java.util.concurrent.Executors.newFixedThreadPool;
|
||||
|
||||
import com.google.cloud.tasks.v2.Task;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -39,11 +40,15 @@ import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Simple load test action that can generate configurable QPSes of various EPP actions.
|
||||
*
|
||||
* <p>This is not an end-to-end test. It exercises the Nomulus EPP service and the database, but
|
||||
* does not cover the proxy.
|
||||
*
|
||||
* <p>All aspects of the load test are configured via URL parameters that are specified when the
|
||||
* loadtest URL is being POSTed to. The {@code clientId} and {@code tld} parameters are required.
|
||||
* All of the other parameters are optional, but if none are specified then no actual load testing
|
||||
@@ -60,7 +65,7 @@ public class LoadTestAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final int NUM_QUEUES = 10;
|
||||
private static final int NUM_QUEUES = 20;
|
||||
private static final int MAX_TASKS_PER_LOAD = 100;
|
||||
private static final int ARBITRARY_VALID_HOST_LENGTH = 40;
|
||||
private static final int MAX_DOMAIN_LABEL_LENGTH = 63;
|
||||
@@ -72,20 +77,17 @@ public class LoadTestAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/loadtest";
|
||||
|
||||
// Average task insertion rate with a dedicated thread enqueuing to one queue. This is used to
|
||||
// calculate the EPP request dispatch time. This value is based on observation and needs not to
|
||||
// be accurate. However, it should be low enough so that all EPP tasks are enqueued before the
|
||||
// first task is dispatched.
|
||||
private static final int TASK_INSERTIONS_PER_QUEUE_PER_MINUTE = 1000;
|
||||
|
||||
/** The ID of the registrar to use for load testing. */
|
||||
@Inject
|
||||
@Parameter("loadtestClientId")
|
||||
String registrarId;
|
||||
|
||||
/**
|
||||
* The number of seconds to delay the execution of the first load testing tasks by. Preparatory
|
||||
* work of creating independent hosts that will be used for later domain creation testing occurs
|
||||
* during this period, so make sure that it is long enough.
|
||||
*/
|
||||
@Inject
|
||||
@Parameter("delaySeconds")
|
||||
int delaySeconds;
|
||||
|
||||
/**
|
||||
* The number of seconds that tasks will be enqueued for. Note that if system QPS cannot handle
|
||||
* the given load then it will take longer than this number of seconds for the test to complete.
|
||||
@@ -157,9 +159,25 @@ public class LoadTestAction implements Runnable {
|
||||
xmlHostInfo = loadXml("host_info").replace("%host%", EXISTING_HOST);
|
||||
}
|
||||
|
||||
private int eppTaskCount() {
|
||||
// See `run()` below for details: summing two task-generating loops.
|
||||
return successfulDomainCreatesPerSecond
|
||||
+ runSeconds
|
||||
* (successfulHostCreatesPerSecond
|
||||
+ failedHostCreatesPerSecond
|
||||
+ domainInfosPerSecond
|
||||
+ domainChecksPerSecond
|
||||
+ hostInfosPerSecond
|
||||
+ successfulDomainCreatesPerSecond
|
||||
+ failedDomainCreatesPerSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
validateAndLogRequest();
|
||||
// Delay the EPP request dispatch time to account for queue-insertion time.
|
||||
int delaySeconds =
|
||||
Math.ceilDiv(eppTaskCount(), TASK_INSERTIONS_PER_QUEUE_PER_MINUTE * NUM_QUEUES) * 60;
|
||||
validateAndLogRequest(delaySeconds);
|
||||
Instant initialStartSecond = clock.now().plus(Duration.ofSeconds(delaySeconds));
|
||||
ImmutableList.Builder<String> preTaskXmls = new ImmutableList.Builder<>();
|
||||
ImmutableList.Builder<String> hostPrefixesBuilder = new ImmutableList.Builder<>();
|
||||
@@ -209,7 +227,7 @@ public class LoadTestAction implements Runnable {
|
||||
logger.atInfo().log("Added %d total load test tasks.", taskOptions.size());
|
||||
}
|
||||
|
||||
private void validateAndLogRequest() {
|
||||
private void validateAndLogRequest(int delaySeconds) {
|
||||
checkArgument(
|
||||
RegistryEnvironment.get() != RegistryEnvironment.PRODUCTION,
|
||||
"DO NOT RUN LOADTESTS IN PROD!");
|
||||
@@ -297,9 +315,21 @@ public class LoadTestAction implements Runnable {
|
||||
|
||||
private void enqueue(ImmutableList<Task> tasks) {
|
||||
List<List<Task>> chunks = partition(tasks, MAX_TASKS_PER_LOAD);
|
||||
// Farm out tasks to multiple queues to work around queue qps quotas.
|
||||
for (int i = 0; i < chunks.size(); i++) {
|
||||
cloudTasksUtils.enqueue("load" + (i % NUM_QUEUES), chunks.get(i));
|
||||
// Farm out tasks to multiple queues to work around queue qps quotas. Use multiple threads to
|
||||
// speed up the enqueuing.
|
||||
try (ExecutorService executorService = newFixedThreadPool(NUM_QUEUES)) {
|
||||
for (int i = 0; i < chunks.size(); i++) {
|
||||
final int index = i;
|
||||
// Ignore `Future` returned by the pool b/c individual failures do not affect analysis.
|
||||
// lgtm[java/local-variable-is-never-read] Suppress Github CodeQL's outdated warning
|
||||
var _ =
|
||||
executorService.submit(
|
||||
() -> cloudTasksUtils.enqueue(getQueueName(index % NUM_QUEUES), chunks.get(index)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getQueueName(int queueId) {
|
||||
return String.format("load%d", queueId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,6 @@ public final class LoadTestModule {
|
||||
return extractRequiredParameter(req, "clientId");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("delaySeconds")
|
||||
static int provideDelaySeconds(HttpServletRequest req) {
|
||||
return extractOptionalIntParameter(req, "delaySeconds").orElse(60);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("runSeconds")
|
||||
static int provideRunSeconds(HttpServletRequest req) {
|
||||
@@ -74,24 +68,6 @@ public final class LoadTestModule {
|
||||
return extractOptionalIntParameter(req, "domainChecks").orElse(0);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("successfulContactCreates")
|
||||
static int provideSuccessfulContactCreates(HttpServletRequest req) {
|
||||
return extractOptionalIntParameter(req, "successfulContactCreates").orElse(0);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("failedContactCreates")
|
||||
static int provideFailedContactCreates(HttpServletRequest req) {
|
||||
return extractOptionalIntParameter(req, "failedContactCreates").orElse(0);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("contactInfos")
|
||||
static int provideContactInfos(HttpServletRequest req) {
|
||||
return extractOptionalIntParameter(req, "contactInfos").orElse(0);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("successfulHostCreates")
|
||||
static int provideSuccessfulHostCreates(HttpServletRequest req) {
|
||||
|
||||
@@ -59,10 +59,10 @@ public final class PremiumList extends BaseDomainLabelList<BigDecimal, PremiumEn
|
||||
* Mapping from unqualified domain names to their prices.
|
||||
*
|
||||
* <p>This field requires special treatment since we want to lazy load it. We have to remove it
|
||||
* from the immutability contract so we can modify it after construction and we have to handle the
|
||||
* database processing on our own so we can detach it after load.
|
||||
* from the immutability contract so we can modify it after construction, and we have to handle
|
||||
* the database processing on our own so we can detach it after load.
|
||||
*/
|
||||
@Insignificant @Transient ImmutableMap<String, BigDecimal> labelsToPrices;
|
||||
@Insignificant @Transient volatile ImmutableMap<String, BigDecimal> labelsToPrices;
|
||||
|
||||
@Column(nullable = false)
|
||||
BloomFilter<String> bloomFilter;
|
||||
@@ -76,18 +76,27 @@ public final class PremiumList extends BaseDomainLabelList<BigDecimal, PremiumEn
|
||||
* Returns a {@link Map} of domain labels to prices.
|
||||
*
|
||||
* <p>Note that this is lazily loaded and thus must be called inside a transaction. You generally
|
||||
* should not be using this anyway as it's inefficient to load all of the PremiumEntry rows if you
|
||||
* should not be using this anyway as it's inefficient to load all the PremiumEntry rows if you
|
||||
* don't need them. To check prices, use {@link PremiumListDao#getPremiumPrice} instead.
|
||||
*
|
||||
* <p>We use locking to memoize the resulting object. We cannot use a simple memoizing Supplier
|
||||
* because we need to be able to set this value when creating the lists.
|
||||
*/
|
||||
public synchronized ImmutableMap<String, BigDecimal> getLabelsToPrices() {
|
||||
public ImmutableMap<String, BigDecimal> getLabelsToPrices() {
|
||||
if (labelsToPrices == null) {
|
||||
labelsToPrices =
|
||||
PremiumListDao.loadAllPremiumEntries(name).stream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
PremiumEntry::getDomainLabel,
|
||||
// Set the correct amount of precision for the premium list's currency.
|
||||
premiumEntry -> convertAmountToMoney(premiumEntry.getValue()).getAmount()));
|
||||
synchronized (this) {
|
||||
// Extra null check to avoid race conditions
|
||||
if (labelsToPrices == null) {
|
||||
labelsToPrices =
|
||||
PremiumListDao.loadAllPremiumEntries(name).stream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
PremiumEntry::getDomainLabel,
|
||||
// Set the correct amount of precision for the list's currency.
|
||||
premiumEntry ->
|
||||
convertAmountToMoney(premiumEntry.getValue()).getAmount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return labelsToPrices;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import static google.registry.config.RegistryConfig.getDomainLabelListCacheDurat
|
||||
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 com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -69,7 +68,7 @@ public final class ReservedList
|
||||
* from the immutability contract so we can modify it after construction and we have to handle the
|
||||
* database processing on our own so we can detach it after load.
|
||||
*/
|
||||
@Insignificant @Transient Map<String, ReservedListEntry> reservedListMap;
|
||||
@Insignificant @Transient volatile ImmutableMap<String, ReservedListEntry> reservedListMap;
|
||||
|
||||
@RecursivePreRemove
|
||||
void preRemove() {
|
||||
@@ -149,7 +148,7 @@ public final class ReservedList
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link ReservedListEntry} objects, since they are immutable. */
|
||||
private static class Builder
|
||||
public static class Builder
|
||||
extends DomainLabelEntry.Builder<ReservedListEntry, ReservedListEntry.Builder> {
|
||||
|
||||
Builder() {}
|
||||
@@ -185,19 +184,27 @@ public final class ReservedList
|
||||
*
|
||||
* <p>Note that this involves a database fetch of a potentially large number of elements and
|
||||
* should be avoided unless necessary.
|
||||
*
|
||||
* <p>We use locking to memoize the resulting object. We cannot use a simple memoizing Supplier
|
||||
* because we need to be able to set this value when creating the lists.
|
||||
*/
|
||||
public synchronized ImmutableMap<String, ReservedListEntry> getReservedListEntries() {
|
||||
public ImmutableMap<String, ReservedListEntry> getReservedListEntries() {
|
||||
if (reservedListMap == null) {
|
||||
reservedListMap =
|
||||
tm().reTransact(
|
||||
() ->
|
||||
tm()
|
||||
.createQueryComposer(ReservedListEntry.class)
|
||||
.where("revisionId", EQ, revisionId)
|
||||
.stream()
|
||||
.collect(toImmutableMap(ReservedListEntry::getDomainLabel, e -> e)));
|
||||
synchronized (this) {
|
||||
// Extra null check to avoid race conditions
|
||||
if (reservedListMap == null) {
|
||||
reservedListMap =
|
||||
tm().reTransact(
|
||||
() ->
|
||||
tm()
|
||||
.createQueryComposer(ReservedListEntry.class)
|
||||
.where("revisionId", EQ, revisionId)
|
||||
.stream()
|
||||
.collect(toImmutableMap(ReservedListEntry::getDomainLabel, e -> e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImmutableMap.copyOf(nullToEmpty(reservedListMap));
|
||||
return reservedListMap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +227,7 @@ public final class ReservedList
|
||||
*/
|
||||
public static ImmutableSet<ReservationType> getReservationTypes(String label, String tld) {
|
||||
checkNotNull(label, "label");
|
||||
if (label.length() == 0) {
|
||||
if (label.isEmpty()) {
|
||||
return ImmutableSet.of(FULLY_BLOCKED);
|
||||
}
|
||||
return getReservedListEntries(label, tld).stream()
|
||||
|
||||
@@ -271,7 +271,7 @@ public interface TransactionManager {
|
||||
* A runnable that allows for checked exceptions to be thrown.
|
||||
*
|
||||
* <p>This makes it easier to write lambdas without having to worry about wrapping and re-throwing
|
||||
* checked excpetions as unchecked ones.
|
||||
* checked exceptions as unchecked ones.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface ThrowingRunnable {
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class NordnVerifyAction implements Runnable {
|
||||
static final String NORDN_URL_PARAM = "nordnUrl";
|
||||
static final String NORDN_LOG_ID_PARAM = "nordnLogId";
|
||||
|
||||
private static final String MARKSDB_URL_BEGINNING = "ry.marksdb.org";
|
||||
private static final String MARKSDB_HOST_NAME = "ry.marksdb.org";
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@@ -109,11 +109,7 @@ public final class NordnVerifyAction implements Runnable {
|
||||
@VisibleForTesting
|
||||
LordnLog verify() throws IOException, GeneralSecurityException {
|
||||
String host = Ascii.toLowerCase(url.getHost());
|
||||
checkArgument(
|
||||
host.startsWith(MARKSDB_URL_BEGINNING),
|
||||
"URL %s must start with %s",
|
||||
url,
|
||||
MARKSDB_URL_BEGINNING);
|
||||
checkArgument(host.equals(MARKSDB_HOST_NAME), "Host %s must equal %s", host, MARKSDB_HOST_NAME);
|
||||
logger.atInfo().log("LORDN verify task %s: Sending request to URL %s", actionLogId, url);
|
||||
HttpURLConnection connection = urlConnectionService.createConnection(url);
|
||||
lordnRequestInitializer.initialize(connection, tld);
|
||||
|
||||
@@ -127,9 +127,7 @@ public final class TmchCertificateAuthority {
|
||||
* @see X509Utils#verifyCertificate
|
||||
*/
|
||||
public void verify(X509Certificate cert) throws GeneralSecurityException {
|
||||
synchronized (TmchCertificateAuthority.class) {
|
||||
X509Utils.verifyCertificate(getAndValidateRoot(), getCrl(), cert, clock.now());
|
||||
}
|
||||
X509Utils.verifyCertificate(getAndValidateRoot(), getCrl(), cert, clock.now());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,33 +155,23 @@ public final class TmchCertificateAuthority {
|
||||
}
|
||||
|
||||
public X509Certificate getAndValidateRoot() throws GeneralSecurityException {
|
||||
try {
|
||||
X509Certificate root = ROOT_CERTS.get(tmchCaMode);
|
||||
// The current production certificate expires on 2023-07-23. Future code monkey be reminded,
|
||||
// if you are looking at this code because the next line throws an exception, ask ICANN for a
|
||||
// new root certificate! (preferably before the current one expires...)
|
||||
root.checkValidity(Date.from(clock.now()));
|
||||
return root;
|
||||
} catch (Exception e) {
|
||||
if (e instanceof GeneralSecurityException generalSecurityException) {
|
||||
throw generalSecurityException;
|
||||
} else if (e instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
X509Certificate root = ROOT_CERTS.get(tmchCaMode);
|
||||
// The current production certificate expires on 2042-11-15. Future code monkey be reminded,
|
||||
// if you are looking at this code because the next line throws an exception, ask ICANN for a
|
||||
// new root certificate! (preferably before the current one expires...)
|
||||
root.checkValidity(Date.from(clock.now()));
|
||||
return root;
|
||||
}
|
||||
|
||||
public X509CRL getCrl() throws GeneralSecurityException {
|
||||
try {
|
||||
return CRL_CACHE.get(tmchCaMode);
|
||||
} catch (Exception e) {
|
||||
} catch (RuntimeException e) {
|
||||
if (e.getCause() instanceof GeneralSecurityException generalSecurityException) {
|
||||
throw generalSecurityException;
|
||||
} else if (e instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
package google.registry.batch;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat;
|
||||
import static google.registry.batch.SyncRemoteCacheAction.SyncStatus.FAILURE;
|
||||
import static google.registry.batch.SyncRemoteCacheAction.SyncStatus.NOT_CONFIGURED;
|
||||
import static google.registry.batch.SyncRemoteCacheAction.SyncStatus.SUCCESS;
|
||||
import static google.registry.model.common.Cursor.CursorType.REMOTE_CACHE_DOMAIN_SYNC;
|
||||
import static google.registry.model.common.Cursor.CursorType.REMOTE_CACHE_HOST_SYNC;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
@@ -73,15 +77,24 @@ class SyncRemoteCacheActionTest {
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
createTld("tld");
|
||||
SyncRemoteCacheAction.SYNC_CACHE_RUNS_METRIC.reset();
|
||||
action = new SyncRemoteCacheAction(lockHandler, response, Optional.of(jedisClient));
|
||||
}
|
||||
|
||||
private static void verifyMetrics(SyncRemoteCacheAction.SyncStatus status) {
|
||||
assertThat(SyncRemoteCacheAction.SYNC_CACHE_RUNS_METRIC)
|
||||
.hasValueForLabels(1, status.name())
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_noJedisConfig() {
|
||||
action = new SyncRemoteCacheAction(lockHandler, response, Optional.empty());
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload()).contains("No Jedis/Valkey configuration found");
|
||||
verifyMetrics(NOT_CONFIGURED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,6 +104,7 @@ class SyncRemoteCacheActionTest {
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload()).contains("Could not acquire lock");
|
||||
verifyMetrics(FAILURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -100,6 +114,7 @@ class SyncRemoteCacheActionTest {
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
|
||||
assertThat(response.getPayload()).contains("Errored out with cause");
|
||||
verifyMetrics(FAILURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,6 +124,7 @@ class SyncRemoteCacheActionTest {
|
||||
verifyNoInteractions(jedisClient);
|
||||
assertThat(DatabaseHelper.loadByKeyIfPresent(Cursor.createGlobalVKey(REMOTE_CACHE_DOMAIN_SYNC)))
|
||||
.isEmpty();
|
||||
verifyMetrics(SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,6 +147,7 @@ class SyncRemoteCacheActionTest {
|
||||
.getCursorTime()
|
||||
.toString())
|
||||
.isEqualTo("2025-01-01T00:00:00.001Z");
|
||||
verifyMetrics(SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,6 +163,7 @@ class SyncRemoteCacheActionTest {
|
||||
ImmutableList.of(
|
||||
new SimplifiedJedisClient.JedisResource<>("active.tld", activeDomain)));
|
||||
verify(jedisClient).deleteAll(Domain.class, ImmutableList.of("deleted.tld"));
|
||||
verifyMetrics(SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -166,6 +184,7 @@ class SyncRemoteCacheActionTest {
|
||||
verify(jedisClient)
|
||||
.setAll(
|
||||
ImmutableList.of(new SimplifiedJedisClient.JedisResource<>("example2.tld", domain2)));
|
||||
verifyMetrics(SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -175,6 +194,7 @@ class SyncRemoteCacheActionTest {
|
||||
verifyNoInteractions(jedisClient);
|
||||
assertThat(DatabaseHelper.loadByKeyIfPresent(Cursor.createGlobalVKey(REMOTE_CACHE_HOST_SYNC)))
|
||||
.isEmpty();
|
||||
verifyMetrics(SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -197,6 +217,7 @@ class SyncRemoteCacheActionTest {
|
||||
.getCursorTime()
|
||||
.toString())
|
||||
.isEqualTo("2025-01-01T00:00:00.001Z");
|
||||
verifyMetrics(SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -212,5 +233,6 @@ class SyncRemoteCacheActionTest {
|
||||
ImmutableList.of(
|
||||
new SimplifiedJedisClient.JedisResource<>(active.getRepoId(), active)));
|
||||
verify(jedisClient).deleteAll(Host.class, ImmutableList.of(deleted.getRepoId()));
|
||||
verifyMetrics(SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import static jakarta.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
@@ -52,7 +53,7 @@ public class RefreshDnsOnHostRenameActionTest {
|
||||
private RefreshDnsOnHostRenameAction action;
|
||||
|
||||
private void createAction(String hostKey) {
|
||||
action = new RefreshDnsOnHostRenameAction(hostKey, response);
|
||||
action = new RefreshDnsOnHostRenameAction(hostKey, response, clock);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
@@ -99,4 +100,28 @@ public class RefreshDnsOnHostRenameActionTest {
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Host to refresh is already deleted: ns1.example.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_multipleBatches() {
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
ImmutableSet.Builder<String> domainNamesBuilder = new ImmutableSet.Builder<>();
|
||||
for (int i = 1; i <= 1001; i++) {
|
||||
String domainName = "example" + i + ".tld";
|
||||
domainNamesBuilder.add(domainName);
|
||||
persistResource(newDomain(domainName, host));
|
||||
}
|
||||
createAction(host.createVKey().stringify());
|
||||
action.run();
|
||||
assertDomainDnsRequests(Iterables.toArray(domainNamesBuilder.build(), String.class));
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_noLinkedDomains() {
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
createAction(host.createVKey().stringify());
|
||||
action.run();
|
||||
assertNoDnsRequests();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,9 +170,7 @@ class NordnVerifyActionTest {
|
||||
void testFailure_badUrl() throws Exception {
|
||||
action.url = URI.create("http://example.com/blobio").toURL();
|
||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.isEqualTo("URL http://example.com/blobio must start with ry.marksdb.org");
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("Host example.com must equal ry.marksdb.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -257,15 +257,15 @@ td.section {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="property_name">generated by</td>
|
||||
<td class="property_value">SchemaCrawler 17.11.1</td>
|
||||
<td class="property_value">SchemaCrawler 17.12.2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="property_name">generated on</td>
|
||||
<td class="property_value">2026-07-14 19:18:20</td>
|
||||
<td class="property_value">2026-08-05 12:44:04</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="property_name">last flyway file</td>
|
||||
<td id="lastFlywayFile" class="property_value">V225__user_registry_lock_email_address_index.sql</td>
|
||||
<td id="lastFlywayFile" class="property_value">V226__tld_domain_name_index.sql</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -273,7 +273,7 @@ td.section {
|
||||
<p> </p>
|
||||
<svg viewBox="0.00 0.00 4783.00 3613.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="erDiagram" style="overflow: hidden; width: 100%; height: 800px">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 3608.5)">
|
||||
<title>SchemaCrawler_Diagram</title> <polygon fill="white" stroke="none" points="-4,4 -4,-3608.5 4778.75,-3608.5 4778.75,4 -4,4" /> <text xml:space="preserve" text-anchor="start" x="4535.5" y="-29.2" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text> <text xml:space="preserve" text-anchor="start" x="4618.25" y="-29.2" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 17.11.1</text> <text xml:space="preserve" text-anchor="start" x="4534.75" y="-9.45" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text> <text xml:space="preserve" text-anchor="start" x="4618.25" y="-9.45" font-family="Helvetica,sans-Serif" font-size="14.00">2026-07-14 19:18:20</text> <polygon fill="none" stroke="#888888" points="4531.75,-4 4531.75,-45.5 4766.75,-45.5 4766.75,-4 4531.75,-4" /> <!-- allocationtoken_a08ccbef -->
|
||||
<title>SchemaCrawler_Diagram</title> <polygon fill="white" stroke="none" points="-4,4 -4,-3608.5 4778.75,-3608.5 4778.75,4 -4,4" /> <text xml:space="preserve" text-anchor="start" x="4535.5" y="-29.2" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text> <text xml:space="preserve" text-anchor="start" x="4618.25" y="-29.2" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 17.12.2</text> <text xml:space="preserve" text-anchor="start" x="4534.75" y="-9.45" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text> <text xml:space="preserve" text-anchor="start" x="4618.25" y="-9.45" font-family="Helvetica,sans-Serif" font-size="14.00">2026-08-05 12:44:04</text> <polygon fill="none" stroke="#888888" points="4531.75,-4 4531.75,-45.5 4766.75,-45.5 4766.75,-4 4531.75,-4" /> <!-- allocationtoken_a08ccbef -->
|
||||
<g id="node1" class="node">
|
||||
<title>allocationtoken_a08ccbef</title> <polygon fill="#e9c2f2" stroke="none" points="479.25,-1014.62 479.25,-1034.38 664.25,-1034.38 664.25,-1014.62 479.25,-1014.62" /> <text xml:space="preserve" text-anchor="start" x="481.25" y="-1020.08" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."AllocationToken"</text> <polygon fill="#e9c2f2" stroke="none" points="664.25,-1014.62 664.25,-1034.38 737.25,-1034.38 737.25,-1014.62 664.25,-1014.62" /> <text xml:space="preserve" text-anchor="start" x="698.5" y="-1019.08" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text> <text xml:space="preserve" text-anchor="start" x="481.25" y="-1000.33" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">token</text> <text xml:space="preserve" text-anchor="start" x="658.5" y="-999.33" font-family="Helvetica,sans-Serif" font-size="14.00"> </text> <text xml:space="preserve" text-anchor="start" x="666.25" y="-999.33" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text> <text xml:space="preserve" text-anchor="start" x="481.25" y="-979.58" font-family="Helvetica,sans-Serif" font-size="14.00">domain_name</text> <text xml:space="preserve" text-anchor="start" x="658.5" y="-979.58" font-family="Helvetica,sans-Serif" font-size="14.00"> </text> <text xml:space="preserve" text-anchor="start" x="666.25" y="-979.58" font-family="Helvetica,sans-Serif" font-size="14.00">text</text> <text xml:space="preserve" text-anchor="start" x="481.25" y="-959.83" font-family="Helvetica,sans-Serif" font-size="14.00">redemption_domain_repo_id</text> <text xml:space="preserve" text-anchor="start" x="658.5" y="-959.83" font-family="Helvetica,sans-Serif" font-size="14.00"> </text> <text xml:space="preserve" text-anchor="start" x="666.25" y="-959.83" font-family="Helvetica,sans-Serif" font-size="14.00">text</text> <text xml:space="preserve" text-anchor="start" x="481.25" y="-940.08" font-family="Helvetica,sans-Serif" font-size="14.00">token_type</text> <text xml:space="preserve" text-anchor="start" x="658.5" y="-940.08" font-family="Helvetica,sans-Serif" font-size="14.00"> </text> <text xml:space="preserve" text-anchor="start" x="666.25" y="-940.08" font-family="Helvetica,sans-Serif" font-size="14.00">text</text> <polygon fill="none" stroke="#888888" points="478.25,-934.62 478.25,-1035.38 738.25,-1035.38 738.25,-934.62 478.25,-934.62" />
|
||||
</g>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -223,3 +223,4 @@ V222__remove_contact.sql
|
||||
V223__tld_change_xap_enabled_to_transitions.sql
|
||||
V224__add_registrar_expiry_access_period_enabled.sql
|
||||
V225__user_registry_lock_email_address_index.sql
|
||||
V226__tld_domain_name_index.sql
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 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.
|
||||
|
||||
-- RDAP queries by TLD need to order results. Using a btree that combines
|
||||
-- TLD and name allows us to do this quickly
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS domain_tld_domain_name_idx ON "Domain" (tld, domain_name);
|
||||
@@ -1841,6 +1841,13 @@ CREATE INDEX domain_history_to_transaction_record_idx ON public."DomainTransacti
|
||||
CREATE UNIQUE INDEX domain_no_duplicate_active ON public."Domain" USING btree (domain_name) WHERE (deletion_time = '294247-01-10 04:00:54.775+00'::timestamp with time zone);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_tld_domain_name_idx; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX domain_tld_domain_name_idx ON public."Domain" USING btree (tld, domain_name);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domaindsdatahistory_domain_history_revision_id_hash; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
@@ -12,12 +12,13 @@ spec:
|
||||
metadata:
|
||||
labels:
|
||||
service: epp-server
|
||||
traffic: epp-all
|
||||
spec:
|
||||
serviceAccountName: nomulus
|
||||
nodeSelector:
|
||||
cloud.google.com/machine-family: c4
|
||||
containers:
|
||||
- name: frontend
|
||||
- name: epp-server
|
||||
image: gcr.io/GCP_PROJECT/nomulus
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -61,7 +62,7 @@ spec:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: CONTAINER_NAME
|
||||
value: frontend
|
||||
value: epp-server
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
@@ -98,7 +99,7 @@ spec:
|
||||
ipFamilies: [IPv4, IPv6]
|
||||
ipFamilyPolicy: RequireDualStack
|
||||
selector:
|
||||
service: epp-server
|
||||
traffic: epp-all
|
||||
ports:
|
||||
- port: 700
|
||||
targetPort: epp
|
||||
|
||||
@@ -84,6 +84,7 @@ steps:
|
||||
|
||||
sed -i "s|artifactStorage: artifactStorage|artifactStorage: $artifact_storage|" "$target_file"
|
||||
sed -i "s|serviceAccount: serviceAccount|serviceAccount: $service_account|" "$target_file"
|
||||
sed -i "s|serviceAccount: serviceAccount|serviceAccount: $service_account|" release/clouddeploy/delivery-pipeline.yaml
|
||||
sed -i "s|cluster: cluster|cluster: $cluster_val|" "$target_file"
|
||||
sed -i "s|workerPool: workerPool|workerPool: $worker_pool|" "$target_file"
|
||||
fi
|
||||
|
||||
@@ -203,7 +203,7 @@ steps:
|
||||
--images="gcr.io/${PROJECT_ID}/nomulus=gcr.io/${PROJECT_ID}/nomulus@${nomulus_digest}" \
|
||||
--source=. \
|
||||
--skaffold-file=release/clouddeploy/skaffold.yaml \
|
||||
--deploy-parameters="deployed_image=gcr.io/${PROJECT_ID}/nomulus@${nomulus_digest},base_image=us-docker.pkg.dev/${PROJECT_ID}/gcr.io/nomulus"
|
||||
--deploy-parameters="deployed_image=gcr.io/${PROJECT_ID}/nomulus@${nomulus_digest},base_image=us-docker.pkg.dev/${PROJECT_ID}/gcr.io/nomulus,tag_name=${TAG_NAME},project_id=${PROJECT_ID}"
|
||||
# The tarballs and jars to upload to GCS.
|
||||
artifacts:
|
||||
objects:
|
||||
|
||||
@@ -236,11 +236,17 @@ steps:
|
||||
# partial phase manifests
|
||||
for stage in 1 5
|
||||
do
|
||||
if [ ${service} == backend ] || [ ${service} == console ]
|
||||
then
|
||||
replicas=1
|
||||
else
|
||||
replicas=${stage}
|
||||
fi
|
||||
awk 'NR==1,/^---$/ {if ($0 != "---") print}' ./jetty/kubernetes/nomulus-${env}-${service}.yaml | \
|
||||
sed s/name:\ ${service}/name:\ ${service}-partial-phase/g | \
|
||||
sed s/service:\ ${service}/deployment:\ ${service}-partial-phase/g | \
|
||||
sed s/value:\ ${service}/value:\ ${service}-partial-phase/g | \
|
||||
sed "/^spec:$/a\ replicas: ${stage}" \
|
||||
sed "/^spec:$/a\ replicas: ${replicas}" \
|
||||
> ./jetty/kubernetes/nomulus-${env}-${service}-partial-phase-${stage}.yaml
|
||||
done
|
||||
# gateway
|
||||
@@ -254,9 +260,20 @@ steps:
|
||||
> ./jetty/kubernetes/gateway/nomulus-backend-policy-${env}-${service}-canary.yaml
|
||||
done
|
||||
# Generate manifests for epp-server (which doesn't use HTTP gateway routing)
|
||||
# Lowercase EPP-v2 to epp-v2 to match reserved static GCE IP names.
|
||||
sed s/GCP_PROJECT/${PROJECT_ID}/g ./jetty/kubernetes/nomulus-epp-server.yaml | \
|
||||
sed s/latest/${TAG_NAME}/g | \
|
||||
sed s/ENVIRONMENT/${env}/g > ./jetty/kubernetes/nomulus-${env}-epp-server.yaml
|
||||
sed s/ENVIRONMENT/${env}/g | \
|
||||
sed s/EPP-v2/epp-v2/g > ./jetty/kubernetes/nomulus-${env}-epp-server.yaml
|
||||
# Generate partial-phase manifests for epp-server for Cloud Deploy canary stages
|
||||
for stage in 1 5
|
||||
do
|
||||
awk 'NR==1,/^---$/ {if ($0 != "---") print}' ./jetty/kubernetes/nomulus-${env}-epp-server.yaml | \
|
||||
sed s/name:\ epp-server/name:\ epp-server-partial-phase/g | \
|
||||
sed s/service:\ epp-server/deployment:\ epp-server-partial-phase/g | \
|
||||
sed "/^spec:$/a\ replicas: ${stage}" \
|
||||
> ./jetty/kubernetes/nomulus-${env}-epp-server-partial-phase-${stage}.yaml
|
||||
done
|
||||
done
|
||||
# Upload the Gradle binary to GCS if it does not exist and point URL in Gradle wrapper to it.
|
||||
- name: 'gcr.io/cloud-builders/gcloud'
|
||||
|
||||
@@ -5,7 +5,7 @@ This directory contains the Google Cloud Deploy configuration files for the Nomu
|
||||
## Files
|
||||
|
||||
### `delivery-pipeline.yaml`
|
||||
Defines the `DeliveryPipeline` resource named `deploy-nomulus`. It sets up the serial pipeline for rolling out changes to different targets.
|
||||
Defines the `DeliveryPipeline` resource named `deploy-nomulus` and its associated `Automation` resource (`deploy-nomulus/auto-advance-canary`). It sets up the serial pipeline for rolling out changes to different targets and automatically advancing canary rollouts.
|
||||
|
||||
### Target Configurations (e.g., `crash-target.yaml`)
|
||||
Files matching this format define the `Target` resources for Cloud Deploy. They specify the GKE cluster and other environment-specific settings for deployment.
|
||||
|
||||
@@ -14,17 +14,6 @@ serialPipeline:
|
||||
- phaseId: "canary-1"
|
||||
profiles: ["crash-partial-phase-1"]
|
||||
percentage: 10
|
||||
predeploy:
|
||||
tasks:
|
||||
- type: container
|
||||
image: gcr.io/google.com/cloudsdktool/google-cloud-cli:stable
|
||||
env:
|
||||
TARGET_ID: ${{ target.id }}
|
||||
command: ["/bin/bash"]
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
gcloud builds submit --config=release/cloudbuild-schema-verify-${TARGET_ID}.yaml
|
||||
analysis:
|
||||
# 10 minutes.
|
||||
duration: 600s
|
||||
@@ -55,17 +44,20 @@ serialPipeline:
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
gcloud artifacts docker tags add $DEPLOYED_IMAGE \
|
||||
${BASE_IMAGE}:live-cd-${TARGET_ID}
|
||||
gcloud container images add-tag $DEPLOYED_IMAGE \
|
||||
${BASE_IMAGE}:live-cd-${TARGET_ID} --quiet
|
||||
- type: container
|
||||
image: gcr.io/google.com/cloudsdktool/google-cloud-cli:stable
|
||||
env:
|
||||
TARGET_ID: ${{ target.id }}
|
||||
TAG_NAME: ${{ deploy_params['tag_name'] }}
|
||||
PROJECT_ID: ${{ deploy_params['project_id'] }}
|
||||
command: ["/bin/bash"]
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
gcloud builds submit --config=release/cloudbuild-schema-deploy-${TARGET_ID}.yaml
|
||||
gcloud storage cp gs://${PROJECT_ID}-deploy/${TAG_NAME}/cloudbuild-schema-deploy-${TARGET_ID}.yaml .
|
||||
gcloud builds submit --no-source --config=cloudbuild-schema-deploy-${TARGET_ID}.yaml
|
||||
analysis:
|
||||
# 10 minutes.
|
||||
duration: 600s
|
||||
@@ -86,11 +78,14 @@ serialPipeline:
|
||||
image: gcr.io/google.com/cloudsdktool/google-cloud-cli:stable
|
||||
env:
|
||||
TARGET_ID: ${{ target.id }}
|
||||
TAG_NAME: ${{ deploy_params['tag_name'] }}
|
||||
PROJECT_ID: ${{ deploy_params['project_id'] }}
|
||||
command: ["/bin/bash"]
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
gcloud builds submit --config=release/cloudbuild-schema-verify-${TARGET_ID}.yaml
|
||||
gcloud storage cp gs://${PROJECT_ID}-deploy/${TAG_NAME}/cloudbuild-schema-verify-${TARGET_ID}.yaml .
|
||||
gcloud builds submit --no-source --config=cloudbuild-schema-verify-${TARGET_ID}.yaml
|
||||
analysis:
|
||||
# 10 minutes.
|
||||
duration: 600s
|
||||
@@ -121,20 +116,42 @@ serialPipeline:
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
gcloud artifacts docker tags add $DEPLOYED_IMAGE \
|
||||
${BASE_IMAGE}:live-cd-${TARGET_ID}
|
||||
gcloud container images add-tag $DEPLOYED_IMAGE \
|
||||
${BASE_IMAGE}:live-cd-${TARGET_ID} --quiet
|
||||
- type: container
|
||||
image: gcr.io/google.com/cloudsdktool/google-cloud-cli:stable
|
||||
env:
|
||||
TARGET_ID: ${{ target.id }}
|
||||
TAG_NAME: ${{ deploy_params['tag_name'] }}
|
||||
PROJECT_ID: ${{ deploy_params['project_id'] }}
|
||||
command: ["/bin/bash"]
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
gcloud builds submit --config=release/cloudbuild-schema-deploy-${TARGET_ID}.yaml
|
||||
gcloud storage cp gs://${PROJECT_ID}-deploy/${TAG_NAME}/cloudbuild-schema-deploy-${TARGET_ID}.yaml .
|
||||
gcloud builds submit --no-source --config=cloudbuild-schema-deploy-${TARGET_ID}.yaml
|
||||
analysis:
|
||||
# 10 minutes.
|
||||
duration: 600s
|
||||
googleCloud:
|
||||
alertPolicyChecks:
|
||||
sandboxStableDeploymentAlertPolicyChecks
|
||||
---
|
||||
apiVersion: deploy.cloud.google.com/v1
|
||||
kind: Automation
|
||||
metadata:
|
||||
name: deploy-nomulus/auto-advance-canary
|
||||
description: Automatically advances rollouts through canary-1 phase after successful deployment and analysis.
|
||||
# Placeholder: Replace with project service account.
|
||||
serviceAccount: serviceAccount
|
||||
selector:
|
||||
targets:
|
||||
- id: crash
|
||||
- id: sandbox
|
||||
rules:
|
||||
- advanceRolloutRule:
|
||||
id: advance-canary-phases
|
||||
sourcePhases:
|
||||
- "canary-1"
|
||||
wait: 0m
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ profiles:
|
||||
- ../../jetty/kubernetes/nomulus-crash-console.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-frontend.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-pubapi.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-epp-server.yaml
|
||||
deploy:
|
||||
kubectl: { }
|
||||
- name: crash-partial-phase-1
|
||||
@@ -20,6 +21,7 @@ profiles:
|
||||
- ../../jetty/kubernetes/nomulus-crash-console-partial-phase-1.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-frontend-partial-phase-1.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-pubapi-partial-phase-1.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-epp-server-partial-phase-1.yaml
|
||||
deploy:
|
||||
kubectl: { }
|
||||
- name: crash-partial-phase-5
|
||||
@@ -29,6 +31,7 @@ profiles:
|
||||
- ../../jetty/kubernetes/nomulus-crash-console-partial-phase-5.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-frontend-partial-phase-5.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-pubapi-partial-phase-5.yaml
|
||||
- ../../jetty/kubernetes/nomulus-crash-epp-server-partial-phase-5.yaml
|
||||
deploy:
|
||||
kubectl: { }
|
||||
- name: sandbox
|
||||
@@ -38,6 +41,7 @@ profiles:
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-console.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-frontend.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-pubapi.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-epp-server.yaml
|
||||
deploy:
|
||||
kubectl: { }
|
||||
- name: sandbox-partial-phase-1
|
||||
@@ -47,6 +51,7 @@ profiles:
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-console-partial-phase-1.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-frontend-partial-phase-1.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-pubapi-partial-phase-1.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-epp-server-partial-phase-1.yaml
|
||||
deploy:
|
||||
kubectl: { }
|
||||
- name: sandbox-partial-phase-5
|
||||
@@ -56,5 +61,6 @@ profiles:
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-console-partial-phase-5.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-frontend-partial-phase-5.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-pubapi-partial-phase-5.yaml
|
||||
- ../../jetty/kubernetes/nomulus-sandbox-epp-server-partial-phase-5.yaml
|
||||
deploy:
|
||||
kubectl: { }
|
||||
Reference in New Issue
Block a user