Compare commits

...
Author SHA1 Message Date
Pavlo TkachandGitHub b1f2eb5921 Rename EPP Server container (#3201) 2026-08-05 20:11:32 +00:00
gbrodmanandGitHub 4f332d397e Add tld,domain_name idx on Domain for RDAP searches (#3200)
this allows us to quickly serve requests like "*.tld" which we need to
order to allow for cursored results

b/535250462
2026-08-05 19:55:01 +00:00
gbrodmanandGitHub c9a82f1322 Streamline large synch blocks in list/TMCH CA loading (#3183)
For reserved/premium lists:
Use double-check locking so that subsequent calls to get the entire map
of entries don't need to even check the locking object. This makes
things quicker and removes lock-tracking overhead.

For TMCH CA:
we can just remove the synchronization block entirely. Everything inside
of it is either constants (e.g. ROOT_CERTS) or a Guava loading cache
(CRL_CACHE) which takes care of synchronization for us anyway.
2026-08-04 20:06:53 +00:00
gbrodmanandGitHub fabf0c07b2 Tighten control on Marksdb URL hostname (#3198)
this doesn't really matter but eh, a URL shouldn't be able to be like,
ry.marksdb.org.attacker.com

b/535251045
2026-08-04 19:42:38 +00:00
Pavlo TkachandGitHub aa54f9ddc9 Add epp server to cloud build deploy (#3197) 2026-08-04 19:13:32 +00:00
gbrodmanandGitHub 92edbfbde2 Skip unnecessary domain reads in Spec11Pipeline (#3196)
We can just grab the fields we need from the original query. There's no
point in making a ton of extra lookups.
2026-08-04 18:45:06 +00:00
Weimin YuandGitHub b1e127798f Refactor LoadTestAction for usability (#3192)
Calculates the delay seconds automatically. This value helps ensure that
all EPP requests are enqueued before the scheduled test start time.
Since queue insertion is much slower than dispatch, this is essential to
maintain a stable QPS rate.

Also parallelizes queue insertion using a thread pool. This reduces the
delay for enqueuing the requests.

BUG=http://b/533414332
2026-08-04 18:41:54 +00:00
gbrodmanandGitHub 3474cd6e9b Batch DNS refresh requests on host renames (#3181)
Some hosts can have more than 100k domains linked to them so we probably
don't want to insert all those entries at once.
2026-08-04 02:36:50 +00:00
gbrodmanandGitHub 1fc4a281c0 Add an IncrementalMetric for sync-cache-action runs (#3195)
This is configured to run every 5 minutes. We need to make sure that the
cache doesn't get too out of date, otherwise we'll be serving stale
data. We'll add an alert that fires if SUCCESS or NOT_CONFIGURED hasn't
happened recently.
2026-08-03 20:17:12 +00:00
Juan CelhayandGitHub 9a420a69b0 Add pre and post deploy steps to Cloud Deploy delivery pipeline (#3187)
* Fix image replacement in cd (#3186)

* read sql jobs from ar

* revert release change

* flatten file path for sql jobs

* no source to sql command

* add automation to pipeline

* fix automation

* fix replica seize for backend and console in partial phases
2026-08-03 18:34:47 +00:00
Pavlo TkachandGitHub a3421f2999 Update IPs name to match reserved for epp-server (#3194) 2026-08-03 13:42:58 +00:00
25 changed files with 349 additions and 199 deletions
@@ -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>&nbsp;</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
+1
View File
@@ -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: -
--
+4 -3
View File
@@ -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
+1
View File
@@ -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
+1 -1
View File
@@ -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:
+19 -2
View File
@@ -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'
+1 -1
View File
@@ -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.
+35 -18
View File
@@ -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
+6
View File
@@ -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: { }