mirror of
https://github.com/google/nomulus
synced 2026-01-31 01:52:28 +00:00
Compare commits
5 Commits
proxy-2023
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1de5b5dcc1 | ||
|
|
32279e42e4 | ||
|
|
ba0f90bdaf | ||
|
|
85308eb975 | ||
|
|
ed62f27a4a |
@@ -162,7 +162,7 @@
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordn-phase=sunrise]]></url>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=sunrise]]></url>
|
||||
<description>
|
||||
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 @@
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordn-phase=claims]]></url>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=claims]]></url>
|
||||
<description>
|
||||
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 @@
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=sunrise&pullQueue]]></url>
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<timezone>UTC</timezone>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=claims&pullQueue]]></url>
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<timezone>UTC</timezone>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<description>
|
||||
|
||||
@@ -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;
|
||||
@@ -277,8 +280,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
registrarId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
boolean defaultTokenUsed = false;
|
||||
if (!allocationToken.isPresent() && !registry.getDefaultPromoTokens().isEmpty()) {
|
||||
allocationToken = checkForDefaultToken(registry, command);
|
||||
if (allocationToken.isPresent()) {
|
||||
defaultTokenUsed = true;
|
||||
}
|
||||
}
|
||||
boolean isAnchorTenant =
|
||||
isAnchorTenant(
|
||||
@@ -337,7 +344,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
FeesAndCredits feesAndCredits =
|
||||
pricingLogic.getCreatePrice(
|
||||
registry, targetId, now, years, isAnchorTenant, allocationToken);
|
||||
validateFeeChallenge(feeCreate, feesAndCredits);
|
||||
validateFeeChallenge(feeCreate, feesAndCredits, defaultTokenUsed);
|
||||
Optional<SecDnsCreateExtension> secDnsCreate =
|
||||
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
|
||||
DateTime registrationExpirationTime = leapSafeAddYears(now, years);
|
||||
@@ -377,7 +384,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 +404,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 +711,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,12 +778,13 @@ public class DomainFlowUtils {
|
||||
*/
|
||||
public static void validateFeeChallenge(
|
||||
final Optional<? extends FeeTransformCommandExtension> feeCommand,
|
||||
FeesAndCredits feesAndCredits)
|
||||
FeesAndCredits feesAndCredits,
|
||||
boolean defaultTokenUsed)
|
||||
throws EppException {
|
||||
if (feesAndCredits.hasAnyPremiumFees() && !feeCommand.isPresent()) {
|
||||
throw new FeesRequiredForPremiumNameException();
|
||||
}
|
||||
validateFeesAckedIfPresent(feeCommand, feesAndCredits);
|
||||
validateFeesAckedIfPresent(feeCommand, feesAndCredits, defaultTokenUsed);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -795,7 +796,8 @@ public class DomainFlowUtils {
|
||||
*/
|
||||
public static void validateFeesAckedIfPresent(
|
||||
final Optional<? extends FeeTransformCommandExtension> feeCommand,
|
||||
FeesAndCredits feesAndCredits)
|
||||
FeesAndCredits feesAndCredits,
|
||||
boolean defaultTokenUsed)
|
||||
throws EppException {
|
||||
// Check for the case where a fee command extension was required but not provided.
|
||||
// This only happens when the total fees are non-zero and include custom fees requiring the
|
||||
@@ -856,7 +858,9 @@ public class DomainFlowUtils {
|
||||
// Checking if total amount is expected. Extra fees that we are not expecting may be passed in.
|
||||
// Or if there is only a single fee type expected.
|
||||
if (!feeTotal.equals(feesAndCredits.getTotalCost())) {
|
||||
throw new FeesMismatchException(feesAndCredits.getTotalCost());
|
||||
if (!defaultTokenUsed || feeTotal.isLessThan(feesAndCredits.getTotalCost())) {
|
||||
throw new FeesMismatchException(feesAndCredits.getTotalCost());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
now,
|
||||
years,
|
||||
existingRecurringBillingEvent);
|
||||
validateFeeChallenge(feeRenew, feesAndCredits);
|
||||
validateFeeChallenge(feeRenew, feesAndCredits, false);
|
||||
flowCustomLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder()
|
||||
.setExistingDomain(existingDomain)
|
||||
|
||||
@@ -226,7 +226,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
if (!existingDomain.getGracePeriodStatuses().contains(GracePeriodStatus.REDEMPTION)) {
|
||||
throw new DomainNotEligibleForRestoreException();
|
||||
}
|
||||
validateFeeChallenge(feeUpdate, feesAndCredits);
|
||||
validateFeeChallenge(feeUpdate, feesAndCredits, false);
|
||||
}
|
||||
|
||||
private static Domain performRestore(
|
||||
|
||||
@@ -210,7 +210,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
if (feesAndCredits.isPresent()) {
|
||||
validateFeeChallenge(feeTransfer, feesAndCredits.get());
|
||||
validateFeeChallenge(feeTransfer, feesAndCredits.get(), false);
|
||||
}
|
||||
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
|
||||
historyBuilder
|
||||
|
||||
@@ -231,7 +231,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
Optional<FeeUpdateCommandExtension> feeUpdate =
|
||||
eppInput.getSingleExtension(FeeUpdateCommandExtension.class);
|
||||
FeesAndCredits feesAndCredits = pricingLogic.getUpdatePrice(registry, targetId, now);
|
||||
validateFeesAckedIfPresent(feeUpdate, feesAndCredits);
|
||||
validateFeesAckedIfPresent(feeUpdate, feesAndCredits, false);
|
||||
verifyNotInPendingDelete(
|
||||
add.getContacts(),
|
||||
command.getInnerChange().getRegistrant(),
|
||||
|
||||
@@ -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<MigrationState> 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<MigrationState> 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. */
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Boolean> 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<Domain> 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<Domain> 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(
|
||||
|
||||
@@ -22,6 +22,7 @@ import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
|
||||
import google.registry.model.CacheUtils;
|
||||
@@ -55,6 +56,7 @@ public final class TmchCertificateAuthority {
|
||||
private static final String ROOT_CRT_PILOT_FILE = "icann-tmch-pilot.crt";
|
||||
private static final String CRL_FILE = "icann-tmch.crl";
|
||||
private static final String CRL_PILOT_FILE = "icann-tmch-pilot.crl";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final TmchCaMode tmchCaMode;
|
||||
private final Clock clock;
|
||||
@@ -142,8 +144,14 @@ public final class TmchCertificateAuthority {
|
||||
* @see X509Utils#verifyCrl
|
||||
*/
|
||||
public void updateCrl(String asciiCrl, String url) throws GeneralSecurityException {
|
||||
X509CRL crl = X509Utils.loadCrl(asciiCrl);
|
||||
X509Utils.verifyCrl(getAndValidateRoot(), getCrl(), crl, clock.nowUtc().toDate());
|
||||
X509CRL newCrl = X509Utils.loadCrl(asciiCrl);
|
||||
X509CRL oldCrl = null;
|
||||
try {
|
||||
oldCrl = getCrl();
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log("Old CRL is invalid, ignored during CRL update.");
|
||||
}
|
||||
X509Utils.verifyCrl(getAndValidateRoot(), oldCrl, newCrl, clock.nowUtc().toDate());
|
||||
TmchCrl.set(asciiCrl, url);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Boolean> provideUsePullQueue(HttpServletRequest req) {
|
||||
return extractOptionalBooleanParameter(req, NordnUploadAction.PULL_QUEUE_PARAM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<DomainCreateFlow, Domain
|
||||
clock.setTo(DateTime.parse("1999-04-03T22:00:00.0Z").minus(Duration.millis(1)));
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
DatabaseMigrationStateSchedule.useUncachedForTest();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initCreateTest() throws Exception {
|
||||
createTld("tld");
|
||||
@@ -390,7 +401,10 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.that(reloadResourceByForeignKey())
|
||||
.hasSmdId(null)
|
||||
.and()
|
||||
.hasLaunchNotice(null);
|
||||
.hasLaunchNotice(null)
|
||||
.and()
|
||||
.hasLordnPhase(LordnPhase.NONE);
|
||||
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
|
||||
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
|
||||
}
|
||||
|
||||
@@ -400,14 +414,20 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.hasSmdId(SMD_ID)
|
||||
.and()
|
||||
.hasLaunchNotice(null);
|
||||
String expectedPayload =
|
||||
String.format(
|
||||
"%s,%s,%s,1,%s",
|
||||
reloadResourceByForeignKey().getRepoId(),
|
||||
domainName,
|
||||
SMD_ID,
|
||||
SMD_VALID_TIME.plusMillis(17));
|
||||
assertTasksEnqueued(QUEUE_SUNRISE, new TaskMatcher().payload(expectedPayload));
|
||||
if (DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc())
|
||||
.equals(MigrationState.NORDN_SQL)) {
|
||||
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.SUNRISE);
|
||||
} else {
|
||||
String expectedPayload =
|
||||
String.format(
|
||||
"%s,%s,%s,1,%s",
|
||||
reloadResourceByForeignKey().getRepoId(),
|
||||
domainName,
|
||||
SMD_ID,
|
||||
SMD_VALID_TIME.plusMillis(17));
|
||||
assertTasksEnqueued(QUEUE_SUNRISE, new TaskMatcher().payload(expectedPayload));
|
||||
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertClaimsLordn() throws Exception {
|
||||
@@ -421,13 +441,19 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
"tmch",
|
||||
DateTime.parse("2010-08-16T09:00:00.0Z"),
|
||||
DateTime.parse("2009-08-16T09:00:00.0Z")));
|
||||
TaskMatcher task =
|
||||
new TaskMatcher()
|
||||
.payload(
|
||||
reloadResourceByForeignKey().getRepoId()
|
||||
+ ",example-one.tld,370d0b7c9223372036854775807,1,"
|
||||
+ "2009-08-16T09:00:00.017Z,2009-08-16T09:00:00.000Z");
|
||||
assertTasksEnqueued(QUEUE_CLAIMS, task);
|
||||
if (DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc())
|
||||
.equals(MigrationState.NORDN_SQL)) {
|
||||
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.CLAIMS);
|
||||
} else {
|
||||
TaskMatcher task =
|
||||
new TaskMatcher()
|
||||
.payload(
|
||||
reloadResourceByForeignKey().getRepoId()
|
||||
+ ",example-one.tld,370d0b7c9223372036854775807,1,"
|
||||
+ "2009-08-16T09:00:00.017Z,2009-08-16T09:00:00.000Z");
|
||||
assertTasksEnqueued(QUEUE_CLAIMS, task);
|
||||
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(
|
||||
@@ -677,7 +703,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -685,7 +713,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -693,7 +723,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -701,7 +733,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -709,7 +743,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -717,7 +753,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -939,8 +977,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_claimsNotice() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_claimsNotice(boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
clock.setTo(DateTime.parse("2009-08-16T09:00:00.0Z"));
|
||||
setEppInput("domain_create_claim_notice.xml");
|
||||
persistContactsAndHosts();
|
||||
@@ -950,8 +992,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertClaimsLordn();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_claimsNoticeInQuietPeriod() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_claimsNoticeInQuietPeriod(boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
@@ -1077,6 +1123,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v06() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 8))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "12.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v06() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 100))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v11() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
@@ -1087,6 +1184,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v11() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 8))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "12.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v11() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 100))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v12() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
@@ -1097,6 +1245,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v12() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 8))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "12.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v12() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 100))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v06() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "EUR"));
|
||||
@@ -1220,8 +1419,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAllocationTokenWasRedeemed("abcDEF23456");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_anchorTenant_withClaims() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_anchorTenant_withClaims(boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setDomainName("example-one.tld")
|
||||
@@ -1289,8 +1492,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_anchorTenantInSunrise_withSignedMark() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_anchorTenantInSunrise_withSignedMark(boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
@@ -1890,9 +2097,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertSuccessfulCreate("tld", ImmutableSet.of(RESERVED));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_reservedNameCollisionDomain_inSunrise_setsServerHoldAndPollMessage()
|
||||
throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_reservedNameCollisionDomain_inSunrise_setsServerHoldAndPollMessage(
|
||||
boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
@@ -2435,8 +2646,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
"tld", "domain_create_response.xml", SUPERUSER, ImmutableMap.of("DOMAIN", "example.tld"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark(boolean usePullQueue)
|
||||
throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
createTld("tld", START_DATE_SUNRISE);
|
||||
clock.setTo(SMD_VALID_TIME);
|
||||
setEppInput(
|
||||
@@ -2458,8 +2674,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
/** Test that missing type= argument on launch create works in start-date sunrise. */
|
||||
@Test
|
||||
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark_noType() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark_noType(boolean usePullQueue)
|
||||
throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
createTld("tld", START_DATE_SUNRISE);
|
||||
clock.setTo(SMD_VALID_TIME);
|
||||
setEppInput(
|
||||
@@ -3512,4 +3733,97 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.isEqualTo(
|
||||
"The package token abc123 cannot be used to register names for longer than 1 year.");
|
||||
}
|
||||
|
||||
private static void useNordnSql() {
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(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<DateTime, MigrationState>(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<DateTime, MigrationState>(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<DateTime, MigrationState>(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<DateTime, MigrationState>(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<DateTime, MigrationState>(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<DateTime, MigrationState>(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<DateTime, MigrationState>(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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Domain, Doma
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasDomainName(String domainName) {
|
||||
return hasValue(domainName, actual.getDomainName(), "has domainName");
|
||||
return hasValue(domainName, actual.getDomainName(), "domainName");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasExactlyDsData(DomainDsData... dsData) {
|
||||
@@ -49,15 +50,19 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasExactlyDsData(Set<DomainDsData> dsData) {
|
||||
return hasValue(dsData, actual.getDsData(), "has dsData");
|
||||
return hasValue(dsData, actual.getDsData(), "dsData");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasNumDsData(int num) {
|
||||
return hasValue(num, actual.getDsData().size(), "has num dsData");
|
||||
return hasValue(num, actual.getDsData().size(), "dsData.size()");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasLaunchNotice(LaunchNotice launchNotice) {
|
||||
return hasValue(launchNotice, actual.getLaunchNotice(), "has launchNotice");
|
||||
return hasValue(launchNotice, actual.getLaunchNotice(), "launchNotice");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasLordnPhase(LordnPhase lordnPhase) {
|
||||
return hasValue(lordnPhase, actual.getLordnPhase(), "lordnPhase");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasAuthInfoPwd(String pw) {
|
||||
@@ -67,7 +72,7 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
|
||||
|
||||
public And<DomainSubject> hasCurrentSponsorRegistrarId(String registrarId) {
|
||||
return hasValue(
|
||||
registrarId, actual.getCurrentSponsorRegistrarId(), "has currentSponsorRegistrarId");
|
||||
registrarId, actual.getCurrentSponsorRegistrarId(), "currentSponsorRegistrarId");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasRegistrationExpirationTime(DateTime expiration) {
|
||||
|
||||
@@ -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<Domain> 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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<extension>
|
||||
<fee:creData xmlns:fee="urn:ietf:params:xml:ns:fee-%FEE_VERSION%">
|
||||
<fee:currency>USD</fee:currency>
|
||||
<fee:fee description="create">26.00</fee:fee>
|
||||
<fee:fee description="create">%FEE%</fee:fee>
|
||||
</fee:creData>
|
||||
</extension>
|
||||
<trID>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -134,3 +134,4 @@ V133__add_pull_queue_replace_columns.sql
|
||||
V134__drop_not_null_request_id_lock_table.sql
|
||||
V135__null_gaia_id_user.sql
|
||||
V136__add_dns_refresh_request_table.sql
|
||||
V137__add_process_time_column.sql
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
-- you may not use this file except in compliance with the License.
|
||||
-- You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
ALTER TABLE "DnsRefreshRequest"
|
||||
ADD COLUMN IF NOT EXISTS process_time timestamptz NOT NULL;
|
||||
|
||||
CREATE INDEX IDX3i7i2ktts9d7lcjbs34h0pvwo ON "DnsRefreshRequest" (process_time);
|
||||
@@ -349,7 +349,8 @@ CREATE TABLE public."DnsRefreshRequest" (
|
||||
name text NOT NULL,
|
||||
request_time timestamp with time zone NOT NULL,
|
||||
tld text NOT NULL,
|
||||
type text NOT NULL
|
||||
type text NOT NULL,
|
||||
process_time timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
@@ -1627,6 +1628,13 @@ CREATE INDEX idx1rcgkdd777bpvj0r94sltwd5y ON public."Domain" USING btree (domain
|
||||
CREATE INDEX idx2exdfbx6oiiwnhr8j6gjpqt2j ON public."BillingCancellation" USING btree (event_time);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx3i7i2ktts9d7lcjbs34h0pvwo; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX idx3i7i2ktts9d7lcjbs34h0pvwo ON public."DnsRefreshRequest" USING btree (process_time);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx3y3k7m2bkgahm9sixiohgyrga; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.Tainted;
|
||||
|
||||
/** X.509 Public Key Infrastructure (PKI) helper functions. */
|
||||
@@ -163,12 +164,12 @@ public final class X509Utils {
|
||||
* are correct with respect to {@code now}.
|
||||
*
|
||||
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
*/
|
||||
public static void verifyCrl(
|
||||
X509Certificate rootCert, X509CRL oldCrl, @Tainted X509CRL newCrl, Date now)
|
||||
X509Certificate rootCert, @Nullable X509CRL oldCrl, @Tainted X509CRL newCrl, Date now)
|
||||
throws GeneralSecurityException {
|
||||
if (newCrl.getThisUpdate().before(oldCrl.getThisUpdate())) {
|
||||
if (oldCrl != null && newCrl.getThisUpdate().before(oldCrl.getThisUpdate())) {
|
||||
throw new CRLException(String.format(
|
||||
"New CRL is more out of date than our current CRL. %s < %s\n%s",
|
||||
newCrl.getThisUpdate(), oldCrl.getThisUpdate(), newCrl));
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[
|
||||
{
|
||||
"pattern": "([^/]+/build/)(.*)",
|
||||
"pattern": "([^/]+/build/generated)/(.*)",
|
||||
"vname": {
|
||||
"corpus": "github.com/google/nomulus",
|
||||
"path": "@2@",
|
||||
"root": "external"
|
||||
"root": "@1@"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user