mirror of
https://github.com/google/nomulus
synced 2026-09-02 22:27:07 +00:00
Add a no-async actions DB migration phase (#1579)
* Add a no-async actions DB migration phase This needs to be set several hours prior to entering the READONLY stage. This is not a read-only stage; all synchronous actions under Datastore (such as domain creates) will continue to succeed. The only thing that will fail is host deletes, host renames, and contact deletes, as these three actions require a mapreduce to run before they are complete, and we don't want mapreduces hanging around and executing during what is supposed to be a short duration READONLY period.
This commit is contained in:
@@ -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);
|
||||
|
||||
+9
-3
@@ -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)
|
||||
|
||||
+3
-1
@@ -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();
|
||||
|
||||
+26
-11
@@ -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 "
|
||||
|
||||
+19
@@ -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>
|
||||
Reference in New Issue
Block a user