mirror of
https://github.com/google/nomulus
synced 2026-01-30 01:22:23 +00:00
Compare commits
4 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
743dea9ca2 | ||
|
|
41f9f1ef7d | ||
|
|
44ede2b022 | ||
|
|
e4312322dc |
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.beam.invoicing;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.beam.BeamUtils.getQueryFromFile;
|
||||
import static org.apache.beam.sdk.values.TypeDescriptors.strings;
|
||||
@@ -53,6 +54,7 @@ import org.apache.beam.sdk.transforms.PTransform;
|
||||
import org.apache.beam.sdk.values.KV;
|
||||
import org.apache.beam.sdk.values.PCollection;
|
||||
import org.apache.beam.sdk.values.TypeDescriptor;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
/**
|
||||
* Definition of a Dataflow Flex pipeline template, which generates a given month's invoices.
|
||||
@@ -122,12 +124,19 @@ public class InvoicingPipeline implements Serializable {
|
||||
google.registry.model.billing.BillingEvent.OneTime oneTime =
|
||||
(google.registry.model.billing.BillingEvent.OneTime) row[0];
|
||||
Registrar registrar = (Registrar) row[1];
|
||||
CurrencyUnit currency = oneTime.getCost().getCurrencyUnit();
|
||||
checkState(
|
||||
registrar.getBillingAccountMap().containsKey(currency),
|
||||
"Registrar %s does not have a product account key for the currency unit: %s",
|
||||
registrar.getRegistrarId(),
|
||||
currency);
|
||||
|
||||
return BillingEvent.create(
|
||||
oneTime.getId(),
|
||||
DateTimeUtils.toZonedDateTime(oneTime.getBillingTime(), ZoneId.of("UTC")),
|
||||
DateTimeUtils.toZonedDateTime(oneTime.getEventTime(), ZoneId.of("UTC")),
|
||||
registrar.getRegistrarId(),
|
||||
registrar.getBillingIdentifier().toString(),
|
||||
registrar.getBillingAccountMap().get(currency),
|
||||
registrar.getPoNumber().orElse(""),
|
||||
DomainNameUtils.getTldFromDomainName(oneTime.getTargetId()),
|
||||
oneTime.getReason().toString(),
|
||||
|
||||
@@ -25,6 +25,7 @@ import static google.registry.model.ResourceTransferUtils.handlePendingTransferO
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.model.transfer.TransferStatus.SERVER_CANCELLED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -94,6 +95,7 @@ public final class ContactDeleteFlow implements TransactionalFlow {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
assertAsyncActionsAreAllowed();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
checkLinkedDomains(targetId, now, ContactResource.class, DomainBase::getReferencedContacts);
|
||||
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||
|
||||
@@ -22,6 +22,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -96,6 +97,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
assertAsyncActionsAreAllowed();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
validateHostName(targetId);
|
||||
checkLinkedDomains(targetId, now, HostResource.class, DomainBase::getNameservers);
|
||||
|
||||
@@ -28,6 +28,7 @@ import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomain
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
|
||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.HOST_UPDATE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
|
||||
@@ -136,6 +137,9 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
validateHostName(targetId);
|
||||
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
|
||||
boolean isHostRename = suppliedNewHostName != null;
|
||||
if (isHostRename) {
|
||||
assertAsyncActionsAreAllowed();
|
||||
}
|
||||
String oldHostName = targetId;
|
||||
String newHostName = firstNonNull(suppliedNewHostName, oldHostName);
|
||||
DomainBase oldSuperordinateDomain =
|
||||
|
||||
@@ -30,6 +30,7 @@ import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
|
||||
import google.registry.model.replay.SqlOnlyEntity;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.PersistenceException;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -62,11 +63,28 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
|
||||
* not the phase is read-only.
|
||||
*/
|
||||
public enum MigrationState {
|
||||
/** Datastore is the only DB being used. */
|
||||
DATASTORE_ONLY(PrimaryDatabase.DATASTORE, false, ReplayDirection.NO_REPLAY),
|
||||
|
||||
/** Datastore is the primary DB, with changes replicated to Cloud SQL. */
|
||||
DATASTORE_PRIMARY(PrimaryDatabase.DATASTORE, false, ReplayDirection.DATASTORE_TO_SQL),
|
||||
|
||||
/** Datastore is the primary DB, with replication, and async actions are disallowed. */
|
||||
DATASTORE_PRIMARY_NO_ASYNC(PrimaryDatabase.DATASTORE, false, ReplayDirection.DATASTORE_TO_SQL),
|
||||
|
||||
/** Datastore is the primary DB, with replication, and all mutating actions are disallowed. */
|
||||
DATASTORE_PRIMARY_READ_ONLY(PrimaryDatabase.DATASTORE, true, ReplayDirection.DATASTORE_TO_SQL),
|
||||
|
||||
/**
|
||||
* Cloud SQL is the primary DB, with replication back to Datastore, and all mutating actions are
|
||||
* disallowed.
|
||||
*/
|
||||
SQL_PRIMARY_READ_ONLY(PrimaryDatabase.CLOUD_SQL, true, ReplayDirection.SQL_TO_DATASTORE),
|
||||
|
||||
/** Cloud SQL is the primary DB, with changes replicated to Datastore. */
|
||||
SQL_PRIMARY(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.SQL_TO_DATASTORE),
|
||||
|
||||
/** Cloud SQL is the only DB being used. */
|
||||
SQL_ONLY(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
|
||||
|
||||
private final PrimaryDatabase primaryDatabase;
|
||||
@@ -146,11 +164,17 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
|
||||
.putAll(
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
MigrationState.DATASTORE_ONLY,
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.putAll(
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
|
||||
MigrationState.DATASTORE_ONLY,
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.putAll(
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
MigrationState.DATASTORE_ONLY,
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
|
||||
MigrationState.SQL_PRIMARY_READ_ONLY,
|
||||
MigrationState.SQL_PRIMARY)
|
||||
.putAll(
|
||||
@@ -165,10 +189,9 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
|
||||
MigrationState.SQL_ONLY,
|
||||
MigrationState.SQL_PRIMARY_READ_ONLY,
|
||||
MigrationState.SQL_PRIMARY);
|
||||
|
||||
// In addition, we can always transition from a state to itself (useful when updating the map).
|
||||
for (MigrationState migrationState : MigrationState.values()) {
|
||||
builder.put(migrationState, migrationState);
|
||||
}
|
||||
Arrays.stream(MigrationState.values()).forEach(state -> builder.put(state, state));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -246,7 +269,7 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements
|
||||
* A provided map of transitions may be valid by itself (i.e. it shifts states properly, doesn't
|
||||
* skip states, and doesn't backtrack incorrectly) while still being invalid. In addition to the
|
||||
* transitions in the map being valid, the single transition from the current map at the current
|
||||
* time to the new map at the current time time must also be valid.
|
||||
* time to the new map at the current time must also be valid.
|
||||
*/
|
||||
private static void validateTransitionAtCurrentTime(
|
||||
TimedTransitionProperty<MigrationState, MigrationStateTransition> newTransitions) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package google.registry.persistence.transaction;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY_NO_ASYNC;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
@@ -23,6 +24,7 @@ import com.google.appengine.api.utils.SystemProperty.Environment.Value;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Suppliers;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.PrimaryDatabase;
|
||||
import google.registry.model.ofy.DatastoreTransactionManager;
|
||||
@@ -198,6 +200,22 @@ public final class TransactionManagerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that async actions (contact/host deletes and host renames) are allowed.
|
||||
*
|
||||
* <p>These are allowed at all times except during the {@link
|
||||
* DatabaseMigrationStateSchedule.MigrationState#DATASTORE_PRIMARY_NO_ASYNC} stage. Note that
|
||||
* {@link ReadOnlyModeException} may well be thrown during other read-only stages inside the
|
||||
* transaction manager; this method specifically checks only async actions.
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
public static void assertAsyncActionsAreAllowed() {
|
||||
if (DatabaseMigrationStateSchedule.getValueAtTime(DateTime.now(UTC))
|
||||
.equals(DATASTORE_PRIMARY_NO_ASYNC)) {
|
||||
throw new ReadOnlyModeException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Registry is currently undergoing maintenance and is in read-only mode. */
|
||||
public static class ReadOnlyModeException extends IllegalStateException {
|
||||
public ReadOnlyModeException() {
|
||||
|
||||
@@ -38,7 +38,9 @@ import java.io.ByteArrayInputStream;
|
||||
import java.net.URL;
|
||||
import java.security.Security;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.postgresql.util.PSQLException;
|
||||
|
||||
/** Container class to create and run remote commands against a Datastore instance. */
|
||||
@Parameters(separators = " =", commandDescription = "Command-line interface to the registry")
|
||||
@@ -178,14 +180,30 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
|
||||
|
||||
try {
|
||||
runCommand(command);
|
||||
} catch (RuntimeException ex) {
|
||||
if (Throwables.getRootCause(ex) instanceof LoginRequiredException) {
|
||||
} catch (RuntimeException e) {
|
||||
if (Throwables.getRootCause(e) instanceof LoginRequiredException) {
|
||||
System.err.println("===================================================================");
|
||||
System.err.println("You must login using 'nomulus login' prior to running this command.");
|
||||
System.err.println("===================================================================");
|
||||
System.exit(1);
|
||||
} else {
|
||||
throw ex;
|
||||
// See if this looks like the error we get when there's another instance of nomulus tool
|
||||
// running against SQL and give the user some additional guidance if so.
|
||||
Optional<Throwable> psqlException =
|
||||
Throwables.getCausalChain(e).stream()
|
||||
.filter(x -> x instanceof PSQLException)
|
||||
.findFirst();
|
||||
if (psqlException.isPresent() && psqlException.get().getMessage().contains("google:5432")) {
|
||||
e.printStackTrace();
|
||||
System.err.println("===================================================================");
|
||||
System.err.println(
|
||||
"This error is likely the result of having another instance of\n"
|
||||
+ "nomulus running at the same time. Check your system, shut down\n"
|
||||
+ "the other instance, and try again.");
|
||||
System.err.println("===================================================================");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ JOIN Domain d ON b.domainRepoId = d.repoId
|
||||
JOIN Tld t ON t.tldStrId = d.tld
|
||||
LEFT JOIN BillingCancellation c ON b.id = c.refOneTime.billingId
|
||||
LEFT JOIN BillingCancellation cr ON b.cancellationMatchingBillingEvent = cr.refRecurring.billingId
|
||||
WHERE r.billingIdentifier IS NOT NULL
|
||||
WHERE r.billingAccountMap IS NOT NULL
|
||||
AND r.type = 'REAL'
|
||||
AND t.invoicingEnabled IS TRUE
|
||||
AND b.billingTime BETWEEN CAST('%FIRST_TIMESTAMP_OF_MONTH%' AS timestamp) AND CAST('%LAST_TIMESTAMP_OF_MONTH%' AS timestamp)
|
||||
|
||||
@@ -24,6 +24,10 @@ import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.money.CurrencyUnit.CAD;
|
||||
import static org.joda.money.CurrencyUnit.JPY;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -55,6 +59,7 @@ import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import org.apache.beam.sdk.Pipeline.PipelineExecutionException;
|
||||
import org.apache.beam.sdk.coders.SerializableCoder;
|
||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
import org.apache.beam.sdk.testing.PAssert;
|
||||
@@ -294,6 +299,37 @@ class InvoicingPipelineTest {
|
||||
pipeline.run().waitUntilFinish();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_readFromCloudSqlMissingPAK() throws Exception {
|
||||
Registrar registrar = persistNewRegistrar("TheRegistrar");
|
||||
registrar =
|
||||
registrar
|
||||
.asBuilder()
|
||||
.setBillingAccountMap(ImmutableMap.of(USD, "789"))
|
||||
.setPoNumber(Optional.of("22446688"))
|
||||
.build();
|
||||
persistResource(registrar);
|
||||
Registry test =
|
||||
newRegistry("test", "_TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
|
||||
.asBuilder()
|
||||
.setInvoicingEnabled(true)
|
||||
.build();
|
||||
persistResource(test);
|
||||
DomainBase domain = persistActiveDomain("mycanadiandomain.test");
|
||||
|
||||
persistOneTimeBillingEvent(1, domain, registrar, Reason.RENEW, 3, Money.of(CAD, 20.5));
|
||||
PCollection<BillingEvent> billingEvents = InvoicingPipeline.readFromCloudSql(options, pipeline);
|
||||
billingEvents = billingEvents.apply(new ChangeDomainRepo());
|
||||
PAssert.that(billingEvents).empty();
|
||||
PipelineExecutionException thrown =
|
||||
assertThrows(PipelineExecutionException.class, () -> pipeline.run().waitUntilFinish());
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"Registrar TheRegistrar does not have a product account key for the currency unit:"
|
||||
+ " CAD");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_saveInvoiceCsv() throws Exception {
|
||||
InvoicingPipeline.saveInvoiceCsv(billingEvents, options);
|
||||
@@ -338,7 +374,7 @@ class InvoicingPipelineTest {
|
||||
+ "LEFT JOIN BillingCancellation c ON b.id = c.refOneTime.billingId\n"
|
||||
+ "LEFT JOIN BillingCancellation cr ON b.cancellationMatchingBillingEvent ="
|
||||
+ " cr.refRecurring.billingId\n"
|
||||
+ "WHERE r.billingIdentifier IS NOT NULL\n"
|
||||
+ "WHERE r.billingAccountMap IS NOT NULL\n"
|
||||
+ "AND r.type = 'REAL'\n"
|
||||
+ "AND t.invoicingEnabled IS TRUE\n"
|
||||
+ "AND b.billingTime BETWEEN CAST('2017-10-01' AS timestamp) AND CAST('2017-11-01'"
|
||||
@@ -362,18 +398,22 @@ class InvoicingPipelineTest {
|
||||
persistNewRegistrar("NewRegistrar");
|
||||
persistNewRegistrar("TheRegistrar");
|
||||
Registrar registrar1 = persistNewRegistrar("theRegistrar");
|
||||
registrar1 = registrar1.asBuilder().setBillingIdentifier(234L).build();
|
||||
registrar1 =
|
||||
registrar1
|
||||
.asBuilder()
|
||||
.setBillingAccountMap(ImmutableMap.of(JPY, "234", USD, "234"))
|
||||
.build();
|
||||
persistResource(registrar1);
|
||||
Registrar registrar2 = persistNewRegistrar("bestdomains");
|
||||
registrar2 =
|
||||
registrar2
|
||||
.asBuilder()
|
||||
.setBillingIdentifier(456L)
|
||||
.setBillingAccountMap(ImmutableMap.of(USD, "456"))
|
||||
.setPoNumber(Optional.of("116688"))
|
||||
.build();
|
||||
persistResource(registrar2);
|
||||
Registrar registrar3 = persistNewRegistrar("anotherRegistrar");
|
||||
registrar3 = registrar3.asBuilder().setBillingIdentifier(789L).build();
|
||||
registrar3 = registrar3.asBuilder().setBillingAccountMap(ImmutableMap.of(USD, "789")).build();
|
||||
persistResource(registrar3);
|
||||
|
||||
Registry test =
|
||||
@@ -397,10 +437,8 @@ class InvoicingPipelineTest {
|
||||
DomainBase domain6 = persistActiveDomain("locked.test");
|
||||
DomainBase domain7 = persistActiveDomain("update-prohibited.test");
|
||||
|
||||
persistOneTimeBillingEvent(
|
||||
1, domain1, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
||||
persistOneTimeBillingEvent(
|
||||
2, domain2, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
||||
persistOneTimeBillingEvent(1, domain1, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||
persistOneTimeBillingEvent(2, domain2, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||
persistOneTimeBillingEvent(
|
||||
3,
|
||||
domain3,
|
||||
@@ -410,31 +448,27 @@ class InvoicingPipelineTest {
|
||||
Money.ofMajor(CurrencyUnit.JPY, 70),
|
||||
DateTime.parse("2017-09-29T00:00:00.0Z"),
|
||||
DateTime.parse("2017-10-02T00:00:00.0Z"));
|
||||
persistOneTimeBillingEvent(
|
||||
4, domain4, registrar2, Reason.RENEW, 1, Money.of(CurrencyUnit.USD, 20.5));
|
||||
persistOneTimeBillingEvent(4, domain4, registrar2, Reason.RENEW, 1, Money.of(USD, 20.5));
|
||||
persistOneTimeBillingEvent(
|
||||
5,
|
||||
domain5,
|
||||
registrar3,
|
||||
Reason.CREATE,
|
||||
1,
|
||||
Money.of(CurrencyUnit.USD, 0),
|
||||
Money.of(USD, 0),
|
||||
DateTime.parse("2017-10-04T00:00:00.0Z"),
|
||||
DateTime.parse("2017-10-04T00:00:00.0Z"),
|
||||
Flag.SUNRISE,
|
||||
Flag.ANCHOR_TENANT);
|
||||
persistOneTimeBillingEvent(
|
||||
6, domain6, registrar1, Reason.SERVER_STATUS, 0, Money.of(CurrencyUnit.USD, 0));
|
||||
persistOneTimeBillingEvent(
|
||||
7, domain7, registrar1, Reason.SERVER_STATUS, 0, Money.of(CurrencyUnit.USD, 20));
|
||||
persistOneTimeBillingEvent(6, domain6, registrar1, Reason.SERVER_STATUS, 0, Money.of(USD, 0));
|
||||
persistOneTimeBillingEvent(7, domain7, registrar1, Reason.SERVER_STATUS, 0, Money.of(USD, 20));
|
||||
|
||||
// Add billing event for a non-billable registrar
|
||||
Registrar registrar4 = persistNewRegistrar("noBillRegistrar");
|
||||
registrar4 = registrar4.asBuilder().setBillingIdentifier(null).build();
|
||||
registrar4 = registrar4.asBuilder().setBillingAccountMap(null).build();
|
||||
persistResource(registrar4);
|
||||
DomainBase domain8 = persistActiveDomain("non-billable.test");
|
||||
persistOneTimeBillingEvent(
|
||||
8, domain8, registrar4, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
||||
persistOneTimeBillingEvent(8, domain8, registrar4, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||
|
||||
// Add billing event for a non-real registrar
|
||||
Registrar registrar5 = persistNewRegistrar("notRealRegistrar");
|
||||
@@ -442,19 +476,17 @@ class InvoicingPipelineTest {
|
||||
registrar5
|
||||
.asBuilder()
|
||||
.setIanaIdentifier(null)
|
||||
.setBillingIdentifier(456L)
|
||||
.setBillingAccountMap(ImmutableMap.of(USD, "456"))
|
||||
.setType(Registrar.Type.OTE)
|
||||
.build();
|
||||
persistResource(registrar5);
|
||||
DomainBase domain9 = persistActiveDomain("not-real.test");
|
||||
persistOneTimeBillingEvent(
|
||||
9, domain9, registrar5, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
||||
persistOneTimeBillingEvent(9, domain9, registrar5, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||
|
||||
// Add billing event for a non-invoicing TLD
|
||||
createTld("nobill");
|
||||
DomainBase domain10 = persistActiveDomain("test.nobill");
|
||||
persistOneTimeBillingEvent(
|
||||
10, domain10, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
||||
persistOneTimeBillingEvent(10, domain10, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||
|
||||
// Add billing event before October 2017
|
||||
DomainBase domain11 = persistActiveDomain("july.test");
|
||||
@@ -471,8 +503,7 @@ class InvoicingPipelineTest {
|
||||
// Add a billing event with a corresponding cancellation
|
||||
DomainBase domain12 = persistActiveDomain("cancel.test");
|
||||
OneTime oneTime =
|
||||
persistOneTimeBillingEvent(
|
||||
12, domain12, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
||||
persistOneTimeBillingEvent(12, domain12, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||
DomainHistory domainHistory = persistDomainHistory(domain12, registrar1);
|
||||
|
||||
Cancellation cancellation =
|
||||
@@ -507,8 +538,7 @@ class InvoicingPipelineTest {
|
||||
.build();
|
||||
persistResource(recurring);
|
||||
OneTime oneTimeRecurring =
|
||||
persistOneTimeBillingEvent(
|
||||
13, domain13, registrar1, Reason.RENEW, 3, Money.of(CurrencyUnit.USD, 20.5));
|
||||
persistOneTimeBillingEvent(13, domain13, registrar1, Reason.RENEW, 3, Money.of(USD, 20.5));
|
||||
oneTimeRecurring =
|
||||
oneTimeRecurring
|
||||
.asBuilder()
|
||||
|
||||
@@ -254,6 +254,15 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-delete");
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testModification_duringNoAsyncPhase() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
|
||||
EppException thrown = assertThrows(ReadOnlyModeEppException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testModification_duringReadOnlyPhase() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
|
||||
@@ -840,6 +840,15 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
doSuccessfulTest();
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testSuccess_inNoAsyncPhase() throws Exception {
|
||||
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
|
||||
persistContactsAndHosts();
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_create_response_noasync.xml", ImmutableMap.of("DOMAIN", "example.tld")));
|
||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_maxNumberOfNameservers() throws Exception {
|
||||
setEppInput("domain_create_13_nameservers.xml");
|
||||
|
||||
@@ -358,6 +358,15 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, HostResour
|
||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testModification_duringNoAsyncPhase() {
|
||||
persistActiveHost("ns1.example.tld");
|
||||
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
|
||||
EppException thrown = assertThrows(ReadOnlyModeEppException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||
}
|
||||
|
||||
private void assertOfyDeleteSuccess(String registrarId, String clientTrid, boolean isSuperuser)
|
||||
throws Exception {
|
||||
HostResource deletedHost = reloadResourceByForeignKey();
|
||||
|
||||
@@ -1355,7 +1355,43 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testModification_duringReadOnlyPhase() throws Exception {
|
||||
void testSuccess_nonHostRename_inNoAsyncPhase_succeeds() throws Exception {
|
||||
setEppInput("host_update_name_unchanged.xml");
|
||||
createTld("tld");
|
||||
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
|
||||
DomainBase domain = persistActiveDomain("example.tld");
|
||||
HostResource oldHost = persistActiveSubordinateHost(oldHostName(), domain);
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
// The example xml doesn't do a host rename, so reloading the host should work.
|
||||
assertAboutHosts()
|
||||
.that(reloadResourceByForeignKey())
|
||||
.hasLastSuperordinateChange(oldHost.getLastSuperordinateChange())
|
||||
.and()
|
||||
.hasSuperordinateDomain(domain.createVKey())
|
||||
.and()
|
||||
.hasPersistedCurrentSponsorRegistrarId("TheRegistrar")
|
||||
.and()
|
||||
.hasLastTransferTime(null)
|
||||
.and()
|
||||
.hasOnlyOneHistoryEntryWhich()
|
||||
.hasType(HistoryEntry.Type.HOST_UPDATE);
|
||||
assertDnsTasksEnqueued("ns1.example.tld");
|
||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testRename_duringNoAsyncPhase_fails() throws Exception {
|
||||
createTld("tld");
|
||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
||||
DatabaseHelper.setMigrationScheduleToDatastorePrimaryNoAsync(clock);
|
||||
EppException thrown = assertThrows(ReadOnlyModeEppException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||
}
|
||||
|
||||
@TestOfyOnly
|
||||
void testModification_duringReadOnlyPhase_fails() throws Exception {
|
||||
createTld("tld");
|
||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
||||
DatabaseHelper.setMigrationScheduleToDatastorePrimaryReadOnly(clock);
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.model.common;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_ONLY;
|
||||
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY;
|
||||
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY_NO_ASYNC;
|
||||
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.DATASTORE_PRIMARY_READ_ONLY;
|
||||
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.SQL_ONLY;
|
||||
import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.SQL_PRIMARY;
|
||||
@@ -71,10 +72,12 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
|
||||
runValidTransition(DATASTORE_ONLY, DATASTORE_PRIMARY);
|
||||
|
||||
runValidTransition(DATASTORE_PRIMARY, DATASTORE_ONLY);
|
||||
runValidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_READ_ONLY);
|
||||
runValidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_NO_ASYNC);
|
||||
runValidTransition(DATASTORE_PRIMARY_NO_ASYNC, DATASTORE_PRIMARY_READ_ONLY);
|
||||
|
||||
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, DATASTORE_ONLY);
|
||||
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, DATASTORE_PRIMARY);
|
||||
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, DATASTORE_PRIMARY_NO_ASYNC);
|
||||
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, SQL_PRIMARY_READ_ONLY);
|
||||
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, SQL_PRIMARY);
|
||||
|
||||
@@ -94,6 +97,7 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
|
||||
runInvalidTransition(DATASTORE_ONLY, SQL_PRIMARY);
|
||||
runInvalidTransition(DATASTORE_ONLY, SQL_ONLY);
|
||||
|
||||
runInvalidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_READ_ONLY);
|
||||
runInvalidTransition(DATASTORE_PRIMARY, SQL_PRIMARY_READ_ONLY);
|
||||
runInvalidTransition(DATASTORE_PRIMARY, SQL_PRIMARY);
|
||||
runInvalidTransition(DATASTORE_PRIMARY, SQL_ONLY);
|
||||
@@ -124,7 +128,8 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
|
||||
ImmutableSortedMap.<DateTime, MigrationState>naturalOrder()
|
||||
.put(START_OF_TIME, DATASTORE_ONLY)
|
||||
.put(startTime.plusHours(1), DATASTORE_PRIMARY)
|
||||
.put(startTime.plusHours(2), DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(startTime.plusHours(2), DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.put(startTime.plusHours(3), DATASTORE_PRIMARY_READ_ONLY)
|
||||
.build();
|
||||
assertThat(
|
||||
assertThrows(
|
||||
@@ -163,7 +168,8 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase {
|
||||
fakeClock.setTo(START_OF_TIME.plusDays(1));
|
||||
AllocationToken token =
|
||||
new AllocationToken.Builder().setToken("token").setTokenType(TokenType.SINGLE_USE).build();
|
||||
runValidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_READ_ONLY);
|
||||
runValidTransition(DATASTORE_PRIMARY, DATASTORE_PRIMARY_NO_ASYNC);
|
||||
runValidTransition(DATASTORE_PRIMARY_NO_ASYNC, DATASTORE_PRIMARY_READ_ONLY);
|
||||
assertThrows(ReadOnlyModeException.class, () -> persistResource(token));
|
||||
runValidTransition(DATASTORE_PRIMARY_READ_ONLY, SQL_PRIMARY_READ_ONLY);
|
||||
assertThrows(ReadOnlyModeException.class, () -> persistResource(token));
|
||||
|
||||
@@ -366,8 +366,9 @@ public class ReplicateToDatastoreActionTest {
|
||||
ImmutableSortedMap.<DateTime, MigrationState>naturalOrder()
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusHours(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.put(START_OF_TIME.plusHours(2), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusHours(3), MigrationState.SQL_PRIMARY)
|
||||
.put(START_OF_TIME.plusHours(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.put(START_OF_TIME.plusHours(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusHours(4), MigrationState.SQL_PRIMARY)
|
||||
.put(now.plusHours(1), MigrationState.SQL_PRIMARY_READ_ONLY)
|
||||
.put(now.plusHours(2), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(now.plusHours(3), MigrationState.DATASTORE_PRIMARY)
|
||||
|
||||
@@ -47,7 +47,9 @@ public class DatabaseMigrationScheduleTransitionConverterTest {
|
||||
MigrationState.DATASTORE_ONLY,
|
||||
DateTime.parse("2001-01-01T00:00:00.0Z"),
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
DateTime.parse("2002-01-01T00:00:00.0Z"),
|
||||
DateTime.parse("2002-01-01T01:00:00.0Z"),
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
|
||||
DateTime.parse("2002-01-01T02:00:00.0Z"),
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
DateTime.parse("2002-01-02T00:00:00.0Z"),
|
||||
MigrationState.SQL_PRIMARY,
|
||||
|
||||
@@ -1429,6 +1429,33 @@ public class DatabaseHelper {
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a DATASTORE_PRIMARY_NO_ASYNC state on the {@link DatabaseMigrationStateSchedule}.
|
||||
*
|
||||
* <p>In order to allow for tests to manipulate the clock how they need, we start the transitions
|
||||
* one millisecond after the clock's current time (in case the clock's current value is
|
||||
* START_OF_TIME). We then advance the clock one second so that we're in the
|
||||
* DATASTORE_PRIMARY_READ_ONLY phase.
|
||||
*
|
||||
* <p>We must use the current time, otherwise the setting of the migration state will fail due to
|
||||
* an invalid transition.
|
||||
*/
|
||||
public static void setMigrationScheduleToDatastorePrimaryNoAsync(FakeClock fakeClock) {
|
||||
DateTime now = fakeClock.nowUtc();
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
ImmutableSortedMap.of(
|
||||
START_OF_TIME,
|
||||
MigrationState.DATASTORE_ONLY,
|
||||
now.plusMillis(1),
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
now.plusMillis(2),
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC)));
|
||||
fakeClock.advanceBy(Duration.standardSeconds(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a DATASTORE_PRIMARY_READ_ONLY state on the {@link DatabaseMigrationStateSchedule}.
|
||||
*
|
||||
@@ -1452,6 +1479,8 @@ public class DatabaseHelper {
|
||||
now.plusMillis(1),
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
now.plusMillis(2),
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
|
||||
now.plusMillis(3),
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY)));
|
||||
fakeClock.advanceBy(Duration.standardSeconds(1));
|
||||
}
|
||||
@@ -1478,8 +1507,10 @@ public class DatabaseHelper {
|
||||
now.plusMillis(1),
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
now.plusMillis(2),
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
|
||||
now.plusMillis(3),
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
now.plusMillis(4),
|
||||
MigrationState.SQL_PRIMARY)));
|
||||
fakeClock.advanceBy(Duration.standardSeconds(1));
|
||||
}
|
||||
|
||||
@@ -54,10 +54,12 @@ public class GetDatabaseMigrationStateCommandTest
|
||||
now.plusHours(1),
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
now.plusHours(2),
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
|
||||
now.plusHours(3),
|
||||
MigrationState.SQL_PRIMARY,
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
now.plusHours(4),
|
||||
MigrationState.SQL_PRIMARY,
|
||||
now.plusHours(5),
|
||||
MigrationState.SQL_ONLY);
|
||||
jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(transitions));
|
||||
runCommand();
|
||||
|
||||
@@ -64,14 +64,21 @@ public class SetDatabaseMigrationStateCommandTest
|
||||
void testSuccess_fullSchedule() throws Exception {
|
||||
DateTime now = fakeClock.nowUtc();
|
||||
DateTime datastorePrimary = now.plusHours(1);
|
||||
DateTime datastorePrimaryReadOnly = now.plusHours(2);
|
||||
DateTime sqlPrimary = now.plusHours(3);
|
||||
DateTime sqlOnly = now.plusHours(4);
|
||||
DateTime datastorePrimaryNoAsync = now.plusHours(2);
|
||||
DateTime datastorePrimaryReadOnly = now.plusHours(3);
|
||||
DateTime sqlPrimary = now.plusHours(4);
|
||||
DateTime sqlOnly = now.plusHours(5);
|
||||
runCommandForced(
|
||||
String.format(
|
||||
"--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY,"
|
||||
+ "%s=DATASTORE_PRIMARY_READ_ONLY,%s=SQL_PRIMARY,%s=SQL_ONLY",
|
||||
START_OF_TIME, datastorePrimary, datastorePrimaryReadOnly, sqlPrimary, sqlOnly));
|
||||
+ "%s=DATASTORE_PRIMARY_NO_ASYNC,%s=DATASTORE_PRIMARY_READ_ONLY,"
|
||||
+ "%s=SQL_PRIMARY,%s=SQL_ONLY",
|
||||
START_OF_TIME,
|
||||
datastorePrimary,
|
||||
datastorePrimaryNoAsync,
|
||||
datastorePrimaryReadOnly,
|
||||
sqlPrimary,
|
||||
sqlOnly));
|
||||
assertThat(DatabaseMigrationStateSchedule.get().toValueMap())
|
||||
.containsExactlyEntriesIn(
|
||||
ImmutableSortedMap.of(
|
||||
@@ -79,6 +86,8 @@ public class SetDatabaseMigrationStateCommandTest
|
||||
MigrationState.DATASTORE_ONLY,
|
||||
datastorePrimary,
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
datastorePrimaryNoAsync,
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
|
||||
datastorePrimaryReadOnly,
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
sqlPrimary,
|
||||
@@ -110,8 +119,9 @@ public class SetDatabaseMigrationStateCommandTest
|
||||
runCommandForced(
|
||||
String.format(
|
||||
"--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY,"
|
||||
+ "%s=DATASTORE_PRIMARY_READ_ONLY,%s=DATASTORE_PRIMARY",
|
||||
START_OF_TIME, now.plusHours(1), now.plusHours(2), now.plusHours(3)));
|
||||
+ "%s=DATASTORE_PRIMARY_NO_ASYNC,%s=DATASTORE_PRIMARY_READ_ONLY,"
|
||||
+ "%s=DATASTORE_PRIMARY",
|
||||
START_OF_TIME, now.plusHours(1), now.plusHours(2), now.plusHours(3), now.plusHours(4)));
|
||||
assertThat(DatabaseMigrationStateSchedule.get().toValueMap())
|
||||
.containsExactlyEntriesIn(
|
||||
ImmutableSortedMap.of(
|
||||
@@ -120,8 +130,10 @@ public class SetDatabaseMigrationStateCommandTest
|
||||
now.plusHours(1),
|
||||
MigrationState.DATASTORE_PRIMARY,
|
||||
now.plusHours(2),
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
MigrationState.DATASTORE_PRIMARY_NO_ASYNC,
|
||||
now.plusHours(3),
|
||||
MigrationState.DATASTORE_PRIMARY_READ_ONLY,
|
||||
now.plusHours(4),
|
||||
MigrationState.DATASTORE_PRIMARY));
|
||||
}
|
||||
|
||||
@@ -152,9 +164,12 @@ public class SetDatabaseMigrationStateCommandTest
|
||||
() ->
|
||||
runCommandForced(
|
||||
String.format(
|
||||
"--migration_schedule=%s=DATASTORE_ONLY,"
|
||||
+ "%s=DATASTORE_PRIMARY,%s=DATASTORE_PRIMARY_READ_ONLY",
|
||||
START_OF_TIME, now.minusHours(2), now.minusHours(1)))))
|
||||
"--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY,"
|
||||
+ "%s=DATASTORE_PRIMARY_NO_ASYNC,%s=DATASTORE_PRIMARY_READ_ONLY",
|
||||
START_OF_TIME,
|
||||
now.minusHours(3),
|
||||
now.minusHours(2),
|
||||
now.minusHours(1)))))
|
||||
.hasMessageThat()
|
||||
.isEqualTo(
|
||||
"Cannot transition from current state-as-of-now DATASTORE_ONLY "
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<response>
|
||||
<result code="1000">
|
||||
<msg>Command completed successfully</msg>
|
||||
</result>
|
||||
<resData>
|
||||
<domain:creData
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>%DOMAIN%</domain:name>
|
||||
<domain:crDate>1999-04-03T22:00:01.0Z</domain:crDate>
|
||||
<domain:exDate>2001-04-03T22:00:01.0Z</domain:exDate>
|
||||
</domain:creData>
|
||||
</resData>
|
||||
<trID>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
<svTRID>server-trid</svTRID>
|
||||
</trID>
|
||||
</response>
|
||||
</epp>
|
||||
@@ -261,11 +261,11 @@ td.section {
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="property_name">generated on</td>
|
||||
<td class="property_value">2022-03-30 20:53:20.574091</td>
|
||||
<td class="property_value">2022-04-01 16:53:26.766163</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="property_name">last flyway file</td>
|
||||
<td id="lastFlywayFile" class="property_value">V115__add_renewal_columns_to_billing_recurrence.sql</td>
|
||||
<td id="lastFlywayFile" class="property_value">V116__add_renewal_column_to_allocation_token.sql</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -284,7 +284,7 @@ td.section {
|
||||
generated on
|
||||
</text>
|
||||
<text text-anchor="start" x="4055.5" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
2022-03-30 20:53:20.574091
|
||||
2022-04-01 16:53:26.766163
|
||||
</text>
|
||||
<polygon fill="none" stroke="#888888" points="3968,-4 3968,-44 4233,-44 4233,-4 3968,-4" /> <!-- allocationtoken_a08ccbef -->
|
||||
<g id="node1" class="node">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -113,3 +113,4 @@ V112__add_billingrecurrence_missing_indexes.sql
|
||||
V113__add_host_missing_indexes.sql
|
||||
V114__add_allocation_token_indexes.sql
|
||||
V115__add_renewal_columns_to_billing_recurrence.sql
|
||||
V116__add_renewal_column_to_allocation_token.sql
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Copyright 2022 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 "AllocationToken" add column renewal_price_behavior text default 'DEFAULT' not null;
|
||||
@@ -51,7 +51,8 @@ CREATE TABLE public."AllocationToken" (
|
||||
redemption_domain_repo_id text,
|
||||
token_status_transitions public.hstore,
|
||||
token_type text,
|
||||
redemption_domain_history_id bigint
|
||||
redemption_domain_history_id bigint,
|
||||
renewal_price_behavior text DEFAULT 'DEFAULT'::text NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user