diff --git a/core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml b/core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml
index 28508a7d1..00a91a2a8 100644
--- a/core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml
+++ b/core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml
@@ -162,7 +162,7 @@
-
+
This job uploads LORDN Sunrise CSV files for each TLD to MarksDB. It should be
run at most every three hours, or at absolute minimum every 26 hours.
@@ -174,7 +174,7 @@
-
+
This job uploads LORDN Claims CSV files for each TLD to MarksDB. It should be
run at most every three hours, or at absolute minimum every 26 hours.
@@ -185,6 +185,32 @@
backend
+
+
+
+ This job uploads LORDN Sunrise CSV files for each TLD to MarksDB using
+ pull queue. It should be run at most every three hours, or at absolute
+ minimum every 26 hours.
+
+
+ every 12 hours synchronized
+ UTC
+ backend
+
+
+
+
+
+ This job uploads LORDN Claims CSV files for each TLD to MarksDB using pull
+ queue. It should be run at most every three hours, or at absolute minimum
+ every 26 hours.
+
+
+ every 12 hours synchronized
+ UTC
+ backend
+
+
diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java
index fdb898380..dfaeee896 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java
@@ -86,6 +86,8 @@ import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
+import google.registry.model.common.DatabaseMigrationStateSchedule;
+import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.DomainCommand.Create;
@@ -123,6 +125,7 @@ import google.registry.model.tmch.ClaimsList;
import google.registry.model.tmch.ClaimsListDao;
import google.registry.persistence.VKey;
import google.registry.tmch.LordnTaskUtils;
+import google.registry.tmch.LordnTaskUtils.LordnPhase;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
@@ -377,7 +380,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
reservationTypes.contains(NAME_COLLISION)
? ImmutableSet.of(SERVER_HOLD)
: ImmutableSet.of();
- Domain domain =
+ Domain.Builder domainBuilder =
new Domain.Builder()
.setCreationRegistrarId(registrarId)
.setPersistedCurrentSponsorRegistrarId(registrarId)
@@ -397,7 +400,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
.setContacts(command.getContacts())
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
- .build();
+ .setLordnPhase(
+ !DatabaseMigrationStateSchedule.getValueAtTime(tm().getTransactionTime())
+ .equals(MigrationState.NORDN_SQL)
+ ? LordnPhase.NONE
+ : hasSignedMarks
+ ? LordnPhase.SUNRISE
+ : hasClaimsNotice ? LordnPhase.CLAIMS : LordnPhase.NONE);
+ Domain domain = domainBuilder.build();
if (allocationToken.isPresent()
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
if (years > 1) {
@@ -697,7 +707,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
if (newDomain.shouldPublishToDns()) {
dnsQueue.addDomainRefreshTask(newDomain.getDomainName());
}
- if (hasClaimsNotice || hasSignedMarks) {
+ if (!DatabaseMigrationStateSchedule.getValueAtTime(tm().getTransactionTime())
+ .equals(MigrationState.NORDN_SQL)
+ && (hasClaimsNotice || hasSignedMarks)) {
LordnTaskUtils.enqueueDomainTask(newDomain);
}
}
diff --git a/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java b/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java
index adb169182..93b867bb2 100644
--- a/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java
+++ b/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java
@@ -44,6 +44,8 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static boolean useUncachedForTest = false;
+
public enum PrimaryDatabase {
CLOUD_SQL,
DATASTORE
@@ -85,7 +87,10 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
SQL_ONLY(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY),
/** Toggles SQL Sequence based allocateId */
- SEQUENCE_BASED_ALLOCATE_ID(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
+ SEQUENCE_BASED_ALLOCATE_ID(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY),
+
+ /** Use SQL-based Nordn upload flow instead of the pull queue-based one. */
+ NORDN_SQL(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
private final PrimaryDatabase primaryDatabase;
private final boolean isReadOnly;
@@ -164,7 +169,9 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
MigrationState.SQL_ONLY,
MigrationState.SQL_PRIMARY_READ_ONLY,
MigrationState.SQL_PRIMARY)
- .putAll(MigrationState.SQL_ONLY, MigrationState.SEQUENCE_BASED_ALLOCATE_ID);
+ .putAll(MigrationState.SQL_ONLY, MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
+ .putAll(MigrationState.SEQUENCE_BASED_ALLOCATE_ID, MigrationState.NORDN_SQL)
+ .putAll(MigrationState.NORDN_SQL, MigrationState.SEQUENCE_BASED_ALLOCATE_ID);
// In addition, we can always transition from a state to itself (useful when updating the map).
Arrays.stream(MigrationState.values()).forEach(state -> builder.put(state, state));
@@ -181,8 +188,8 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
public TimedTransitionProperty migrationTransitions =
TimedTransitionProperty.withInitialValue(MigrationState.DATASTORE_ONLY);
- // Required for Objectify initialization
- private DatabaseMigrationStateSchedule() {}
+ // Required for Hibernate initialization
+ protected DatabaseMigrationStateSchedule() {}
@VisibleForTesting
public DatabaseMigrationStateSchedule(
@@ -205,6 +212,11 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
CACHE.invalidateAll();
}
+ @VisibleForTesting
+ public static void useUncachedForTest() {
+ useUncachedForTest = true;
+ }
+
/** Loads the currently-set migration schedule from the cache, or the default if none exists. */
public static TimedTransitionProperty get() {
return CACHE.get(DatabaseMigrationStateSchedule.class);
@@ -212,7 +224,9 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
/** Returns the database migration status at the given time. */
public static MigrationState getValueAtTime(DateTime dateTime) {
- return get().getValueAtTime(dateTime);
+ return useUncachedForTest
+ ? getUncached().getValueAtTime(dateTime)
+ : get().getValueAtTime(dateTime);
}
/** Loads the currently-set migration schedule from SQL, or the default if none exists. */
diff --git a/core/src/main/java/google/registry/tmch/LordnTaskUtils.java b/core/src/main/java/google/registry/tmch/LordnTaskUtils.java
index b51feb8ca..2c43b3d00 100644
--- a/core/src/main/java/google/registry/tmch/LordnTaskUtils.java
+++ b/core/src/main/java/google/registry/tmch/LordnTaskUtils.java
@@ -66,7 +66,12 @@ public final class LordnTaskUtils {
}
/** Returns the corresponding CSV LORDN line for a sunrise domain. */
- public static String getCsvLineForSunriseDomain(Domain domain, DateTime transactionTime) {
+ public static String getCsvLineForSunriseDomain(Domain domain) {
+ return getCsvLineForSunriseDomain(domain, domain.getCreationTime());
+ }
+
+ // TODO: Merge into the function above after pull queue migration.
+ private static String getCsvLineForSunriseDomain(Domain domain, DateTime transactionTime) {
return Joiner.on(',')
.join(
domain.getRepoId(),
@@ -77,7 +82,12 @@ public final class LordnTaskUtils {
}
/** Returns the corresponding CSV LORDN line for a claims domain. */
- public static String getCsvLineForClaimsDomain(Domain domain, DateTime transactionTime) {
+ public static String getCsvLineForClaimsDomain(Domain domain) {
+ return getCsvLineForClaimsDomain(domain, domain.getCreationTime());
+ }
+
+ // TODO: Merge into the function above after pull queue migration.
+ private static String getCsvLineForClaimsDomain(Domain domain, DateTime transactionTime) {
return Joiner.on(',')
.join(
domain.getRepoId(),
@@ -100,16 +110,8 @@ public final class LordnTaskUtils {
private LordnTaskUtils() {}
public enum LordnPhase {
- SUNRISE(QUEUE_SUNRISE),
-
- CLAIMS(QUEUE_CLAIMS),
-
- NONE(null);
-
- final String queue;
-
- LordnPhase(String queue) {
- this.queue = queue;
- }
+ SUNRISE,
+ CLAIMS,
+ NONE
}
}
diff --git a/core/src/main/java/google/registry/tmch/NordnUploadAction.java b/core/src/main/java/google/registry/tmch/NordnUploadAction.java
index 994e94d87..78172f7f0 100644
--- a/core/src/main/java/google/registry/tmch/NordnUploadAction.java
+++ b/core/src/main/java/google/registry/tmch/NordnUploadAction.java
@@ -19,9 +19,13 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.net.HttpHeaders.LOCATION;
import static com.google.common.net.MediaType.CSV_UTF_8;
+import static google.registry.persistence.transaction.QueryComposer.Comparator.EQ;
+import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
import static google.registry.tmch.LordnTaskUtils.COLUMNS_CLAIMS;
import static google.registry.tmch.LordnTaskUtils.COLUMNS_SUNRISE;
+import static google.registry.tmch.LordnTaskUtils.getCsvLineForClaimsDomain;
+import static google.registry.tmch.LordnTaskUtils.getCsvLineForSunriseDomain;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
@@ -33,6 +37,7 @@ import com.google.appengine.api.taskqueue.TaskHandle;
import com.google.appengine.api.taskqueue.TransientFailureException;
import com.google.apphosting.api.DeadlineExceededException;
import com.google.cloud.tasks.v2.Task;
+import com.google.common.base.Ascii;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@@ -42,6 +47,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
+import google.registry.model.domain.Domain;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.Parameter;
@@ -49,6 +55,7 @@ import google.registry.request.RequestParameters;
import google.registry.request.UrlConnectionService;
import google.registry.request.UrlConnectionUtils;
import google.registry.request.auth.Auth;
+import google.registry.tmch.LordnTaskUtils.LordnPhase;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import google.registry.util.Retrier;
@@ -59,6 +66,7 @@ import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.List;
+import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@@ -81,9 +89,11 @@ import org.joda.time.Duration;
public final class NordnUploadAction implements Runnable {
static final String PATH = "/_dr/task/nordnUpload";
- static final String LORDN_PHASE_PARAM = "lordn-phase";
+ static final String LORDN_PHASE_PARAM = "lordnPhase";
+ // TODO: Delete after migrating off of pull queue.
+ static final String PULL_QUEUE_PARAM = "pullQueue";
- private static final int QUEUE_BATCH_SIZE = 1000;
+ private static final int BATCH_SIZE = 1000;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Duration LEASE_PERIOD = Duration.standardHours(1);
@@ -100,31 +110,102 @@ public final class NordnUploadAction implements Runnable {
@Inject LordnRequestInitializer lordnRequestInitializer;
@Inject UrlConnectionService urlConnectionService;
- @Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl;
- @Inject @Parameter(LORDN_PHASE_PARAM) String phase;
- @Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
+ @Inject
+ @Config("tmchMarksdbUrl")
+ String tmchMarksdbUrl;
+
+ @Inject
+ @Parameter(LORDN_PHASE_PARAM)
+ String phase;
+
+ @Inject
+ @Parameter(RequestParameters.PARAM_TLD)
+ String tld;
+
+ @Inject
+ @Parameter(PULL_QUEUE_PARAM)
+ Optional usePullQueue;
@Inject CloudTasksUtils cloudTasksUtils;
- @Inject NordnUploadAction() {}
+ @Inject
+ NordnUploadAction() {}
/**
* These LORDN parameter names correspond to the relative paths in LORDN URLs and cannot be
* changed on our end.
*/
- private static final String PARAM_LORDN_PHASE_SUNRISE = "sunrise";
+ private static final String PARAM_LORDN_PHASE_SUNRISE =
+ Ascii.toLowerCase(LordnPhase.SUNRISE.toString());
- private static final String PARAM_LORDN_PHASE_CLAIMS = "claims";
+ private static final String PARAM_LORDN_PHASE_CLAIMS =
+ Ascii.toLowerCase(LordnPhase.CLAIMS.toString());
/** How long to wait before attempting to verify an upload by fetching the log. */
private static final Duration VERIFY_DELAY = Duration.standardMinutes(30);
@Override
public void run() {
- try {
- processLordnTasks();
- } catch (IOException | GeneralSecurityException e) {
- throw new RuntimeException(e);
+ if (usePullQueue.orElse(false)) {
+ try {
+ processLordnTasks();
+ } catch (IOException | GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ checkArgument(
+ phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS),
+ "Invalid phase specified to NordnUploadAction: %s.",
+ phase);
+ tm().transact(
+ () -> {
+ // Note here that we load all domains pending Nordn in one batch, which should not
+ // be a problem for the rate of domain registration that we see. If we anticipate
+ // a peak in claims during TLD launch (sunrise is NOT first-come-first-serve, so
+ // there should be no expectation of a peak during it), we can consider temporarily
+ // increasing the frequency of Nordn upload to reduce the size of each batch.
+ //
+ // We did not further divide the domains into smaller batches because the
+ // read-upload-write operation per small batch needs to be inside a single
+ // transaction to prevent race conditions, and running several uploads in rapid
+ // sucession will likely overwhelm the MarksDB upload server, which recommands a
+ // maximum upload frequency of every 3 hours.
+ //
+ // See:
+ // https://datatracker.ietf.org/doc/html/draft-ietf-regext-tmch-func-spec-01#section-5.2.3.3
+ List domains =
+ tm().createQueryComposer(Domain.class)
+ .where("lordnPhase", EQ, LordnPhase.valueOf(Ascii.toUpperCase(phase)))
+ .where("tld", EQ, tld)
+ .orderBy("creationTime")
+ .list();
+ if (domains.isEmpty()) {
+ return;
+ }
+ StringBuilder csv = new StringBuilder();
+ ImmutableList.Builder newDomains = new ImmutableList.Builder<>();
+
+ domains.forEach(
+ domain -> {
+ if (phase.equals(PARAM_LORDN_PHASE_SUNRISE)) {
+ csv.append(getCsvLineForSunriseDomain(domain)).append('\n');
+ } else {
+ csv.append(getCsvLineForClaimsDomain(domain)).append('\n');
+ }
+ Domain newDomain = domain.asBuilder().setLordnPhase(LordnPhase.NONE).build();
+ newDomains.add(newDomain);
+ });
+ String columns =
+ phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
+ String header =
+ String.format("1,%s,%d\n%s\n", clock.nowUtc(), domains.size(), columns);
+ try {
+ uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), header + csv);
+ } catch (IOException | GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ tm().updateAll(newDomains.build());
+ });
}
}
@@ -158,7 +239,7 @@ public final class NordnUploadAction implements Runnable {
queue.leaseTasks(
LeaseOptions.Builder.withTag(tld)
.leasePeriod(LEASE_PERIOD.getMillis(), TimeUnit.MILLISECONDS)
- .countLimit(QUEUE_BATCH_SIZE)),
+ .countLimit(BATCH_SIZE)),
TransientFailureException.class,
DeadlineExceededException.class);
if (tasks.isEmpty()) {
@@ -171,7 +252,7 @@ public final class NordnUploadAction implements Runnable {
private void processLordnTasks() throws IOException, GeneralSecurityException {
checkArgument(
phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS),
- "Invalid phase specified to Nordn servlet: %s.",
+ "Invalid phase specified to NordnUploadAction: %s.",
phase);
DateTime now = clock.nowUtc();
Queue queue =
@@ -184,12 +265,12 @@ public final class NordnUploadAction implements Runnable {
// Note: This upload/task deletion isn't done atomically (it's not clear how one would do so
// anyway). As a result, it is possible that the upload might succeed yet the deletion of
// enqueued tasks might fail. If so, this would result in the same lines being uploaded to NORDN
- // across mulitple uploads. This is probably OK; all that we really cannot have is a missing
+ // across multiple uploads. This is probably OK; all that we really cannot have is a missing
// line.
if (!tasks.isEmpty()) {
String csvData = convertTasksToCsv(tasks, now, columns);
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), csvData);
- Lists.partition(tasks, QUEUE_BATCH_SIZE)
+ Lists.partition(tasks, BATCH_SIZE)
.forEach(
batch ->
retrier.callWithRetry(
diff --git a/core/src/main/java/google/registry/tmch/TmchModule.java b/core/src/main/java/google/registry/tmch/TmchModule.java
index ba2c106b9..9769663ff 100644
--- a/core/src/main/java/google/registry/tmch/TmchModule.java
+++ b/core/src/main/java/google/registry/tmch/TmchModule.java
@@ -16,6 +16,7 @@ package google.registry.tmch;
import static com.google.common.io.Resources.asByteSource;
import static com.google.common.io.Resources.getResource;
+import static google.registry.request.RequestParameters.extractOptionalBooleanParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter;
import dagger.Module;
@@ -25,6 +26,7 @@ import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.bouncycastle.openpgp.PGPPublicKey;
@@ -64,4 +66,10 @@ public final class TmchModule {
static String provideNordnLogId(HttpServletRequest req) {
return extractRequiredParameter(req, NordnVerifyAction.NORDN_LOG_ID_PARAM);
}
+
+ @Provides
+ @Parameter(NordnUploadAction.PULL_QUEUE_PARAM)
+ static Optional provideUsePullQueue(HttpServletRequest req) {
+ return extractOptionalBooleanParameter(req, NordnUploadAction.PULL_QUEUE_PARAM);
+ }
}
diff --git a/core/src/main/java/google/registry/tools/GenerateLordnCommand.java b/core/src/main/java/google/registry/tools/GenerateLordnCommand.java
index a6bf88e53..f1f992d4a 100644
--- a/core/src/main/java/google/registry/tools/GenerateLordnCommand.java
+++ b/core/src/main/java/google/registry/tools/GenerateLordnCommand.java
@@ -94,10 +94,10 @@ final class GenerateLordnCommand implements Command {
Domain domain) {
String status = " ";
if (domain.getLaunchNotice() == null && domain.getSmdId() != null) {
- sunriseCsv.add(LordnTaskUtils.getCsvLineForSunriseDomain(domain, domain.getCreationTime()));
+ sunriseCsv.add(LordnTaskUtils.getCsvLineForSunriseDomain(domain));
status = "S";
} else if (domain.getLaunchNotice() != null || domain.getSmdId() != null) {
- claimsCsv.add(LordnTaskUtils.getCsvLineForClaimsDomain(domain, domain.getCreationTime()));
+ claimsCsv.add(LordnTaskUtils.getCsvLineForClaimsDomain(domain));
status = "C";
}
System.out.printf("%s[%s] ", domain.getDomainName(), status);
diff --git a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java
index 41a68dfac..35de5af75 100644
--- a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java
+++ b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java
@@ -155,6 +155,8 @@ import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
+import google.registry.model.common.DatabaseMigrationStateSchedule;
+import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
@@ -182,6 +184,7 @@ import google.registry.persistence.VKey;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.TaskQueueExtension;
import google.registry.testing.TaskQueueHelper.TaskMatcher;
+import google.registry.tmch.LordnTaskUtils.LordnPhase;
import google.registry.tmch.SmdrlCsvParser;
import google.registry.tmch.TmchData;
import google.registry.tmch.TmchTestData;
@@ -192,9 +195,12 @@ import javax.annotation.Nullable;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.junitpioneer.jupiter.cartesian.CartesianTest;
import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
@@ -224,6 +230,11 @@ class DomainCreateFlowTest extends ResourceFlowTestCase
+ DatabaseMigrationStateSchedule.set(
+ new ImmutableSortedMap.Builder(Ordering.natural())
+ .put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
+ .put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
+ .build()));
+ tm().transact(
+ () ->
+ DatabaseMigrationStateSchedule.set(
+ new ImmutableSortedMap.Builder(Ordering.natural())
+ .put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
+ .put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
+ .put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
+ .build()));
+ tm().transact(
+ () ->
+ DatabaseMigrationStateSchedule.set(
+ new ImmutableSortedMap.Builder(Ordering.natural())
+ .put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
+ .put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
+ .put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
+ .put(
+ START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
+ .build()));
+ tm().transact(
+ () ->
+ DatabaseMigrationStateSchedule.set(
+ new ImmutableSortedMap.Builder(Ordering.natural())
+ .put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
+ .put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
+ .put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
+ .put(
+ START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
+ .build()));
+ tm().transact(
+ () ->
+ DatabaseMigrationStateSchedule.set(
+ new ImmutableSortedMap.Builder(Ordering.natural())
+ .put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
+ .put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
+ .put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
+ .put(
+ START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
+ .build()));
+ tm().transact(
+ () ->
+ DatabaseMigrationStateSchedule.set(
+ new ImmutableSortedMap.Builder(Ordering.natural())
+ .put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
+ .put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
+ .put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
+ .put(
+ START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
+ .put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
+ .build()));
+ tm().transact(
+ () ->
+ DatabaseMigrationStateSchedule.set(
+ new ImmutableSortedMap.Builder(Ordering.natural())
+ .put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
+ .put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
+ .put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
+ .put(
+ START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
+ .put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
+ .put(START_OF_TIME.plusMillis(7), MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
+ .build()));
+ tm().transact(
+ () ->
+ DatabaseMigrationStateSchedule.set(
+ new ImmutableSortedMap.Builder(Ordering.natural())
+ .put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
+ .put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
+ .put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
+ .put(
+ START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
+ .put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
+ .put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
+ .put(START_OF_TIME.plusMillis(7), MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
+ .put(START_OF_TIME.plusMillis(8), MigrationState.NORDN_SQL)
+ .build()));
+ }
}
diff --git a/core/src/test/java/google/registry/testing/DatabaseHelper.java b/core/src/test/java/google/registry/testing/DatabaseHelper.java
index 031611163..df498f758 100644
--- a/core/src/test/java/google/registry/testing/DatabaseHelper.java
+++ b/core/src/test/java/google/registry/testing/DatabaseHelper.java
@@ -312,6 +312,7 @@ public final class DatabaseHelper {
return persistResource(domain.asBuilder().setDeletionTime(deletionTime).build());
}
+ // TODO: delete after pull queue migration.
/** Persists a domain and enqueues a LORDN task of the appropriate type for it. */
public static Domain persistDomainAndEnqueueLordn(final Domain domain) {
final Domain persistedDomain = persistResource(domain);
diff --git a/core/src/test/java/google/registry/testing/DomainSubject.java b/core/src/test/java/google/registry/testing/DomainSubject.java
index bc8451789..0548991b1 100644
--- a/core/src/test/java/google/registry/testing/DomainSubject.java
+++ b/core/src/test/java/google/registry/testing/DomainSubject.java
@@ -27,6 +27,7 @@ import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.testing.TruthChainer.And;
+import google.registry.tmch.LordnTaskUtils.LordnPhase;
import java.util.Set;
import org.joda.time.DateTime;
@@ -41,7 +42,7 @@ public final class DomainSubject extends AbstractEppResourceSubject hasDomainName(String domainName) {
- return hasValue(domainName, actual.getDomainName(), "has domainName");
+ return hasValue(domainName, actual.getDomainName(), "domainName");
}
public And hasExactlyDsData(DomainDsData... dsData) {
@@ -49,15 +50,19 @@ public final class DomainSubject extends AbstractEppResourceSubject hasExactlyDsData(Set dsData) {
- return hasValue(dsData, actual.getDsData(), "has dsData");
+ return hasValue(dsData, actual.getDsData(), "dsData");
}
public And hasNumDsData(int num) {
- return hasValue(num, actual.getDsData().size(), "has num dsData");
+ return hasValue(num, actual.getDsData().size(), "dsData.size()");
}
public And hasLaunchNotice(LaunchNotice launchNotice) {
- return hasValue(launchNotice, actual.getLaunchNotice(), "has launchNotice");
+ return hasValue(launchNotice, actual.getLaunchNotice(), "launchNotice");
+ }
+
+ public And hasLordnPhase(LordnPhase lordnPhase) {
+ return hasValue(lordnPhase, actual.getLordnPhase(), "lordnPhase");
}
public And hasAuthInfoPwd(String pw) {
@@ -67,7 +72,7 @@ public final class DomainSubject extends AbstractEppResourceSubject hasCurrentSponsorRegistrarId(String registrarId) {
return hasValue(
- registrarId, actual.getCurrentSponsorRegistrarId(), "has currentSponsorRegistrarId");
+ registrarId, actual.getCurrentSponsorRegistrarId(), "currentSponsorRegistrarId");
}
public And hasRegistrationExpirationTime(DateTime expiration) {
diff --git a/core/src/test/java/google/registry/tmch/NordnUploadActionTest.java b/core/src/test/java/google/registry/tmch/NordnUploadActionTest.java
index a3e150354..613d7d5e5 100644
--- a/core/src/test/java/google/registry/tmch/NordnUploadActionTest.java
+++ b/core/src/test/java/google/registry/tmch/NordnUploadActionTest.java
@@ -14,13 +14,17 @@
package google.registry.tmch;
+import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static com.google.common.net.HttpHeaders.LOCATION;
import static com.google.common.net.MediaType.FORM_DATA;
import static com.google.common.truth.Truth.assertThat;
+import static google.registry.model.ForeignKeyUtils.load;
import static google.registry.testing.DatabaseHelper.createTld;
+import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
+import static google.registry.testing.DatabaseHelper.newDomain;
import static google.registry.testing.DatabaseHelper.persistDomainAndEnqueueLordn;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -34,6 +38,7 @@ import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.google.appengine.api.taskqueue.LeaseOptions;
@@ -48,15 +53,17 @@ import com.google.common.collect.ImmutableList;
import google.registry.model.domain.Domain;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.tld.Registry;
+import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
+import google.registry.request.RequestParameters;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
-import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeSleeper;
import google.registry.testing.FakeUrlConnectionService;
import google.registry.testing.TaskQueueExtension;
+import google.registry.tmch.LordnTaskUtils.LordnPhase;
import google.registry.util.CloudTasksUtils;
import google.registry.util.Retrier;
import google.registry.util.UrlConnectionException;
@@ -67,25 +74,32 @@ import java.net.URL;
import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
import org.joda.time.DateTime;
+import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
/** Unit tests for {@link NordnUploadAction}. */
class NordnUploadActionTest {
private static final String CLAIMS_CSV =
- "1,2010-05-01T10:11:12.000Z,1\n"
+ "1,2010-05-04T10:11:12.000Z,2\n"
+ "roid,domain-name,notice-id,registrar-id,registration-datetime,ack-datetime,"
+ "application-datetime\n"
- + "2-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-01T10:11:12.000Z,"
- + "1969-12-31T23:00:00.000Z\n";
+ + "6-TLD,claims-landrush2.tld,landrush2tcn,88888,2010-05-03T10:11:12.000Z,"
+ + "2010-05-03T08:11:12.000Z\n"
+ + "8-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-04T10:11:12.000Z,"
+ + "2010-05-04T09:11:12.000Z\n";
private static final String SUNRISE_CSV =
- "1,2010-05-01T10:11:12.000Z,1\n"
+ "1,2010-05-04T10:11:12.000Z,2\n"
+ "roid,domain-name,SMD-id,registrar-id,registration-datetime,application-datetime\n"
- + "2-TLD,sunrise1.tld,my-smdid,99999,2010-05-01T10:11:12.000Z\n";
+ + "2-TLD,sunrise2.tld,new-smdid,88888,2010-05-01T10:11:12.000Z\n"
+ + "4-TLD,sunrise1.tld,my-smdid,99999,2010-05-02T10:11:12.000Z\n";
private static final String LOCATION_URL = "http://trololol";
@@ -116,8 +130,12 @@ class NordnUploadActionTest {
when(httpUrlConnection.getHeaderField(LOCATION)).thenReturn("http://trololol");
when(httpUrlConnection.getOutputStream()).thenReturn(connectionOutputStream);
persistResource(loadRegistrar("TheRegistrar").asBuilder().setIanaIdentifier(99999L).build());
+ persistResource(loadRegistrar("NewRegistrar").asBuilder().setIanaIdentifier(88888L).build());
createTld("tld");
persistResource(Registry.get("tld").asBuilder().setLordnUsername("lolcat").build());
+ persistSunriseModeDomain();
+ clock.advanceBy(Duration.standardDays(1));
+ persistClaimsModeDomain();
action.clock = clock;
action.cloudTasksUtils = cloudTasksUtils;
action.urlConnectionService = urlConnectionService;
@@ -127,6 +145,7 @@ class NordnUploadActionTest {
action.tmchMarksdbUrl = "http://127.0.0.1";
action.random = new SecureRandom();
action.retrier = new Retrier(new FakeSleeper(clock), 3);
+ action.usePullQueue = Optional.empty();
}
@Test
@@ -137,7 +156,7 @@ class NordnUploadActionTest {
makeTaskHandle("task1", "example", "csvLine1", "lordn-sunrise"),
makeTaskHandle("task3", "example", "ending", "lordn-sunrise"));
assertThat(NordnUploadAction.convertTasksToCsv(tasks, clock.nowUtc(), "col1,col2"))
- .isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
+ .isEqualTo("1,2010-05-04T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
}
@Test
@@ -149,13 +168,13 @@ class NordnUploadActionTest {
makeTaskHandle("task3", "example", "ending", "lordn-sunrise"),
makeTaskHandle("task1", "example", "csvLine1", "lordn-sunrise"));
assertThat(NordnUploadAction.convertTasksToCsv(tasks, clock.nowUtc(), "col1,col2"))
- .isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
+ .isEqualTo("1,2010-05-04T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
}
@Test
void test_convertTasksToCsv_doesntFailOnEmptyTasks() {
assertThat(NordnUploadAction.convertTasksToCsv(ImmutableList.of(), clock.nowUtc(), "col1,col2"))
- .isEqualTo("1,2010-05-01T10:11:12.000Z,0\ncol1,col2\n");
+ .isEqualTo("1,2010-05-04T10:11:12.000Z,0\ncol1,col2\n");
}
@Test
@@ -187,122 +206,105 @@ class NordnUploadActionTest {
}
@Test
- void testRun_claimsMode_appendsTldAndClaimsToRequestUrl() throws Exception {
- persistClaimsModeDomain();
- action.run();
- assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/LORDN/tld/claims"));
- }
-
- @Test
- void testRun_sunriseMode_appendsTldAndClaimsToRequestUrl() throws Exception {
- persistSunriseModeDomain();
- action.run();
- assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/LORDN/tld/sunrise"));
- }
-
- @Test
- void testRun_usesMultipartContentType() throws Exception {
- persistClaimsModeDomain();
- action.run();
- verify(httpUrlConnection)
- .setRequestProperty(eq(CONTENT_TYPE), startsWith("multipart/form-data; boundary="));
- verify(httpUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- void testRun_hasPassword_setsAuthorizationHeader() {
- persistClaimsModeDomain();
- action.run();
- verify(httpUrlConnection)
- .setRequestProperty(
- AUTHORIZATION, "Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
- }
-
- @Test
- void testRun_noPassword_doesntSendAuthorizationHeader() {
+ void testSuccess_noPassword_doesntSendAuthorizationHeader() {
action.lordnRequestInitializer = new LordnRequestInitializer(Optional.empty());
- persistClaimsModeDomain();
action.run();
verify(httpUrlConnection, times(0)).setRequestProperty(eq(AUTHORIZATION), anyString());
}
@Test
- void testRun_claimsMode_payloadMatchesClaimsCsv() {
- persistClaimsModeDomain();
+ void testSuccess_nothingScheduled() {
+ persistResource(
+ loadByKey(load(Domain.class, "claims-landrush1.tld", clock.nowUtc()))
+ .asBuilder()
+ .setLordnPhase(LordnPhase.NONE)
+ .build());
+ persistResource(
+ loadByKey(load(Domain.class, "claims-landrush2.tld", clock.nowUtc()))
+ .asBuilder()
+ .setLordnPhase(LordnPhase.NONE)
+ .build());
action.run();
- assertThat(connectionOutputStream.toString(UTF_8)).contains(CLAIMS_CSV);
+ verifyNoInteractions(httpUrlConnection);
+ cloudTasksHelper.assertNoTasksEnqueued(NordnVerifyAction.QUEUE);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void testSuccess_claimsMode(boolean usePullQueue) throws Exception {
+ testRun("claims", "claims-landrush1.tld", "claims-landrush2.tld", CLAIMS_CSV, usePullQueue);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void testSuccess_sunriseMode(boolean usePullQueue) throws Exception {
+ testRun("sunrise", "sunrise1.tld", "sunrise2.tld", SUNRISE_CSV, usePullQueue);
}
@Test
- void testRun_claimsMode_verifyTaskGetsEnqueuedWithClaimsCsv() {
- persistClaimsModeDomain();
- action.run();
- cloudTasksHelper.assertTasksEnqueued(
- NordnVerifyAction.QUEUE,
- new TaskMatcher()
- .url(NordnVerifyAction.PATH)
- .param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
- .header(CONTENT_TYPE, FORM_DATA.toString()));
- }
-
- @Test
- void testRun_sunriseMode_payloadMatchesSunriseCsv() {
- persistSunriseModeDomain();
- action.run();
- assertThat(connectionOutputStream.toString(UTF_8)).contains(SUNRISE_CSV);
- }
-
- @Test
- void test_noResponseContent_stillWorksNormally() throws Exception {
+ void testSuccess_noResponseContent_stillWorksNormally() throws Exception {
// Returning null only affects logging.
when(httpUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[] {}));
- persistSunriseModeDomain();
- action.run();
- assertThat(connectionOutputStream.toString(UTF_8)).contains(SUNRISE_CSV);
- }
-
- @Test
- void testRun_sunriseMode_verifyTaskGetsEnqueuedWithSunriseCsv() {
- persistSunriseModeDomain();
- action.run();
- cloudTasksHelper.assertTasksEnqueued(
- NordnVerifyAction.QUEUE,
- new TaskMatcher()
- .url(NordnVerifyAction.PATH)
- .param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
- .header(CONTENT_TYPE, FORM_DATA.toString()));
+ testRun("claims", "claims-landrush1.tld", "claims-landrush2.tld", CLAIMS_CSV, false);
}
@Test
void testFailure_nullRegistryUser() {
- persistClaimsModeDomain();
persistResource(Registry.get("tld").asBuilder().setLordnUsername(null).build());
VerifyException thrown = assertThrows(VerifyException.class, action::run);
assertThat(thrown).hasMessageThat().contains("lordnUsername is not set for tld.");
}
@Test
- void testFetchFailure() throws Exception {
- persistClaimsModeDomain();
+ void testFailure_errorResponseCode() throws Exception {
when(httpUrlConnection.getResponseCode()).thenReturn(SC_INTERNAL_SERVER_ERROR);
assertThrows(UrlConnectionException.class, action::run);
}
- private static void persistClaimsModeDomain() {
- Domain domain = DatabaseHelper.newDomain("claims-landrush1.tld");
+ @Test
+ void testFailure_noLocationHeaderInResponse() throws Exception {
+ when(httpUrlConnection.getHeaderField(LOCATION)).thenReturn(null);
+ assertThrows(UrlConnectionException.class, action::run);
+ }
+
+ private void persistClaimsModeDomain() {
persistDomainAndEnqueueLordn(
- domain
+ newDomain("claims-landrush2.tld")
.asBuilder()
+ .setCreationTimeForTest(clock.nowUtc())
+ .setCreationRegistrarId("NewRegistrar")
.setLaunchNotice(
- LaunchNotice.create(
- "landrush1tcn", null, null, domain.getCreationTime().minusHours(1)))
+ LaunchNotice.create("landrush2tcn", null, null, clock.nowUtc().minusHours(2)))
+ .setLordnPhase(LordnPhase.CLAIMS)
+ .build());
+ clock.advanceBy(Duration.standardDays(1));
+ persistDomainAndEnqueueLordn(
+ newDomain("claims-landrush1.tld")
+ .asBuilder()
+ .setCreationTimeForTest(clock.nowUtc())
+ .setLaunchNotice(
+ LaunchNotice.create("landrush1tcn", null, null, clock.nowUtc().minusHours(1)))
+ .setLordnPhase(LordnPhase.CLAIMS)
.build());
}
private void persistSunriseModeDomain() {
- action.phase = "sunrise";
- Domain domain = DatabaseHelper.newDomain("sunrise1.tld");
- persistDomainAndEnqueueLordn(domain.asBuilder().setSmdId("my-smdid").build());
+ persistDomainAndEnqueueLordn(
+ newDomain("sunrise2.tld")
+ .asBuilder()
+ .setCreationTimeForTest(clock.nowUtc())
+ .setCreationRegistrarId("NewRegistrar")
+ .setSmdId("new-smdid")
+ .setLordnPhase(LordnPhase.SUNRISE)
+ .build());
+ clock.advanceBy(Duration.standardDays(1));
+ persistDomainAndEnqueueLordn(
+ newDomain("sunrise1.tld")
+ .asBuilder()
+ .setCreationTimeForTest(clock.nowUtc())
+ .setSmdId("my-smdid")
+ .setLordnPhase(LordnPhase.SUNRISE)
+ .build());
}
private static TaskHandle makeTaskHandle(
@@ -311,4 +313,46 @@ class NordnUploadActionTest {
TaskOptions.Builder.withPayload(payload).method(Method.PULL).tag(tag).taskName(taskName),
queue);
}
+
+ private void verifyColumnCleared(String domainName) {
+ VKey domainKey = load(Domain.class, domainName, clock.nowUtc());
+ Domain domain = loadByKey(domainKey);
+ assertThat(domain.getLordnPhase()).isEqualTo(LordnPhase.NONE);
+ }
+
+ private void testRun(
+ String phase, String domain1, String domain2, String csv, boolean usePullQueue)
+ throws Exception {
+ action.usePullQueue = Optional.of(usePullQueue);
+ action.phase = phase;
+ action.run();
+ verify(httpUrlConnection)
+ .setRequestProperty(
+ AUTHORIZATION, "Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
+ verify(httpUrlConnection)
+ .setRequestProperty(eq(CONTENT_TYPE), startsWith("multipart/form-data; boundary="));
+ verify(httpUrlConnection).setRequestMethod("POST");
+ assertThat(httpUrlConnection.getURL())
+ .isEqualTo(new URL("http://127.0.0.1/LORDN/tld/" + phase));
+ assertThat(connectionOutputStream.toString(UTF_8)).contains(csv);
+ if (!usePullQueue) {
+ verifyColumnCleared(domain1);
+ verifyColumnCleared(domain2);
+ } else {
+ assertThat(
+ getQueue("lordn-" + phase)
+ .leaseTasks(
+ LeaseOptions.Builder.withTag("tld")
+ .leasePeriod(1, TimeUnit.HOURS)
+ .countLimit(100)))
+ .isEmpty();
+ }
+ cloudTasksHelper.assertTasksEnqueued(
+ NordnVerifyAction.QUEUE,
+ new TaskMatcher()
+ .url(NordnVerifyAction.PATH)
+ .param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
+ .param(RequestParameters.PARAM_TLD, "tld")
+ .header(CONTENT_TYPE, FORM_DATA.toString()));
+ }
}