From 1c0d507bc359df8579e1d43999feee62dbe4b18a Mon Sep 17 00:00:00 2001 From: Lai Jiang Date: Mon, 8 May 2023 14:36:28 -0400 Subject: [PATCH] Delete DatabaseMigrationStateSchedule (#2001) We have been using it as a poor man's timed flag that triggers a system behavior change after a certain time. We have no foreseeable future use for it now that the DNS pull queue related code is deleted. If in the future a need for such a flag arises, we are better off implementing a proper flag system than hijacking this class any way. --- .../DatabaseMigrationStateSchedule.java | 275 ------------------ ...eMigrationScheduleTransitionConverter.java | 37 --- .../GetDatabaseMigrationStateCommand.java | 34 --- .../google/registry/tools/RegistryTool.java | 2 - .../SetDatabaseMigrationStateCommand.java | 70 ----- .../tools/params/TransitionListParameter.java | 9 - .../main/resources/META-INF/persistence.xml | 2 - .../DatabaseMigrationStateScheduleTest.java | 187 ------------ ...rationScheduleTransitionConverterTest.java | 88 ------ .../JpaEntityCoverageExtension.java | 9 - .../registry/testing/DatabaseHelper.java | 43 --- .../GetDatabaseMigrationStateCommandTest.java | 66 ----- .../SetDatabaseMigrationStateCommandTest.java | 181 ------------ .../sql/schema/db-schema.sql.generated | 6 - 14 files changed, 1009 deletions(-) delete mode 100644 core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java delete mode 100644 core/src/main/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverter.java delete mode 100644 core/src/main/java/google/registry/tools/GetDatabaseMigrationStateCommand.java delete mode 100644 core/src/main/java/google/registry/tools/SetDatabaseMigrationStateCommand.java delete mode 100644 core/src/test/java/google/registry/model/common/DatabaseMigrationStateScheduleTest.java delete mode 100644 core/src/test/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverterTest.java delete mode 100644 core/src/test/java/google/registry/tools/GetDatabaseMigrationStateCommandTest.java delete mode 100644 core/src/test/java/google/registry/tools/SetDatabaseMigrationStateCommandTest.java diff --git a/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java b/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java deleted file mode 100644 index 4f1375ca8..000000000 --- a/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2021 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. - -package google.registry.model.common; - -import static com.google.common.base.Preconditions.checkArgument; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.START_OF_TIME; - -import com.github.benmanes.caffeine.cache.LoadingCache; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSortedMap; -import com.google.common.flogger.FluentLogger; -import google.registry.config.RegistryEnvironment; -import google.registry.model.CacheUtils; -import google.registry.model.annotations.DeleteAfterMigration; -import java.time.Duration; -import java.util.Arrays; -import javax.persistence.Entity; -import javax.persistence.PersistenceException; -import org.joda.time.DateTime; - -/** - * A wrapper object representing the stage-to-time mapping of the Registry 3.0 Cloud SQL migration. - * - *

The entity is stored in SQL throughout the entire migration so as to have a single point of - * access. - */ -@DeleteAfterMigration -@Entity -public class DatabaseMigrationStateSchedule extends CrossTldSingleton { - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - private static boolean useUncachedForTest = false; - - public enum PrimaryDatabase { - CLOUD_SQL, - DATASTORE - } - - public enum ReplayDirection { - NO_REPLAY, - DATASTORE_TO_SQL, - SQL_TO_DATASTORE - } - - /** - * The current phase of the migration plus information about which database to use and whether or - * 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), - - /** Toggles SQL Sequence based allocateId */ - 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), - - /** Use SQL-based DNS update flow instead of the pull queue-based one. */ - DNS_SQL(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY); - - private final PrimaryDatabase primaryDatabase; - private final boolean isReadOnly; - private final ReplayDirection replayDirection; - - public PrimaryDatabase getPrimaryDatabase() { - return primaryDatabase; - } - - public boolean isReadOnly() { - return isReadOnly; - } - - public ReplayDirection getReplayDirection() { - return replayDirection; - } - - MigrationState( - PrimaryDatabase primaryDatabase, boolean isReadOnly, ReplayDirection replayDirection) { - this.primaryDatabase = primaryDatabase; - this.isReadOnly = isReadOnly; - this.replayDirection = replayDirection; - } - } - - /** - * Cache of the current migration schedule. The key is meaningless; this is essentially a memoized - * Supplier that can be reset for testing purposes and after writes. - */ - @VisibleForTesting - public static final LoadingCache< - Class, TimedTransitionProperty> - // Each instance should cache the migration schedule for five minutes before reloading - CACHE = - CacheUtils.newCacheBuilder(Duration.ofMinutes(5)) - .build(singletonClazz -> DatabaseMigrationStateSchedule.getUncached()); - - // Restrictions on the state transitions, e.g. no going from DATASTORE_ONLY to SQL_ONLY - private static final ImmutableMultimap VALID_STATE_TRANSITIONS = - createValidStateTransitions(); - - /** - * The valid state transitions. Generally, one can advance the state one step or move backward any - * number of steps, as long as the step we're moving back to has the same primary database as the - * one we're in. Otherwise, we must move to the corresponding READ_ONLY stage first. - */ - private static ImmutableMultimap createValidStateTransitions() { - ImmutableMultimap.Builder builder = - new ImmutableMultimap.Builder() - .put(MigrationState.DATASTORE_ONLY, MigrationState.DATASTORE_PRIMARY) - .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( - MigrationState.SQL_PRIMARY_READ_ONLY, - MigrationState.DATASTORE_PRIMARY_READ_ONLY, - MigrationState.SQL_PRIMARY) - .putAll( - MigrationState.SQL_PRIMARY, - MigrationState.SQL_PRIMARY_READ_ONLY, - MigrationState.SQL_ONLY) - .putAll( - MigrationState.SQL_ONLY, - MigrationState.SQL_PRIMARY_READ_ONLY, - MigrationState.SQL_PRIMARY) - .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, - MigrationState.DNS_SQL) - .putAll(MigrationState.DNS_SQL, MigrationState.NORDN_SQL); - - // 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)); - return builder.build(); - } - - // Default map to return if we have never saved any -- only use Datastore. - @VisibleForTesting - public static final TimedTransitionProperty DEFAULT_TRANSITION_MAP = - TimedTransitionProperty.fromValueMap( - ImmutableSortedMap.of(START_OF_TIME, MigrationState.DATASTORE_ONLY)); - - @VisibleForTesting - public TimedTransitionProperty migrationTransitions = - TimedTransitionProperty.withInitialValue(MigrationState.DATASTORE_ONLY); - - // Required for Hibernate initialization - protected DatabaseMigrationStateSchedule() {} - - @VisibleForTesting - public DatabaseMigrationStateSchedule( - TimedTransitionProperty migrationTransitions) { - this.migrationTransitions = migrationTransitions; - } - - /** Sets and persists to SQL the provided migration transition schedule. */ - public static void set(ImmutableSortedMap migrationTransitionMap) { - tm().assertInTransaction(); - TimedTransitionProperty transitions = - TimedTransitionProperty.make( - migrationTransitionMap, - VALID_STATE_TRANSITIONS, - "validStateTransitions", - MigrationState.DATASTORE_ONLY, - "migrationTransitionMap must start with DATASTORE_ONLY"); - validateTransitionAtCurrentTime(transitions); - tm().put(new DatabaseMigrationStateSchedule(transitions)); - CACHE.invalidateAll(); - } - - @VisibleForTesting - public static void useUncachedForTest() { - useUncachedForTest = true; - } - - /** Loads the currently-set migration schedule from the cache, or the default if none exists. */ - public static TimedTransitionProperty get() { - return CACHE.get(DatabaseMigrationStateSchedule.class); - } - - /** Returns the database migration status at the given time. */ - public static MigrationState getValueAtTime(DateTime dateTime) { - return useUncachedForTest - ? getUncached().getValueAtTime(dateTime) - : get().getValueAtTime(dateTime); - } - - /** Loads the currently-set migration schedule from SQL, or the default if none exists. */ - @VisibleForTesting - static TimedTransitionProperty getUncached() { - return tm().transact( - () -> { - try { - return tm().loadSingleton(DatabaseMigrationStateSchedule.class) - .map(s -> s.migrationTransitions) - .orElse(DEFAULT_TRANSITION_MAP); - } catch (PersistenceException e) { - if (!RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)) { - throw e; - } - logger.atWarning().withCause(e).log( - "Error when retrieving migration schedule; this should only happen in tests."); - return DEFAULT_TRANSITION_MAP; - } - }); - } - - /** - * 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 must also be valid. - */ - private static void validateTransitionAtCurrentTime( - TimedTransitionProperty newTransitions) { - MigrationState currentValue = getUncached().getValueAtTime(tm().getTransactionTime()); - MigrationState nextCurrentValue = newTransitions.getValueAtTime(tm().getTransactionTime()); - checkArgument( - VALID_STATE_TRANSITIONS.get(currentValue).contains(nextCurrentValue), - "Cannot transition from current state-as-of-now %s to new state-as-of-now %s", - currentValue, - nextCurrentValue); - } -} diff --git a/core/src/main/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverter.java b/core/src/main/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverter.java deleted file mode 100644 index 04722089c..000000000 --- a/core/src/main/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2021 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. - -package google.registry.persistence.converter; - -import google.registry.model.annotations.DeleteAfterMigration; -import google.registry.model.common.DatabaseMigrationStateSchedule; -import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState; -import javax.persistence.Converter; - -/** JPA converter for {@link DatabaseMigrationStateSchedule} transitions. */ -@DeleteAfterMigration -@Converter(autoApply = true) -public class DatabaseMigrationScheduleTransitionConverter - extends TimedTransitionPropertyConverterBase { - - @Override - protected String convertValueToString(MigrationState value) { - return value.name(); - } - - @Override - protected MigrationState convertStringToValue(String string) { - return MigrationState.valueOf(string); - } -} diff --git a/core/src/main/java/google/registry/tools/GetDatabaseMigrationStateCommand.java b/core/src/main/java/google/registry/tools/GetDatabaseMigrationStateCommand.java deleted file mode 100644 index 16cc2c6cc..000000000 --- a/core/src/main/java/google/registry/tools/GetDatabaseMigrationStateCommand.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2021 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. - -package google.registry.tools; - -import com.beust.jcommander.Parameters; -import google.registry.model.annotations.DeleteAfterMigration; -import google.registry.model.common.DatabaseMigrationStateSchedule; -import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState; -import google.registry.model.common.TimedTransitionProperty; - -/** A command to check the current Registry 3.0 migration state of the database. */ -@DeleteAfterMigration -@Parameters(separators = " =", commandDescription = "Check current Registry 3.0 migration state") -public class GetDatabaseMigrationStateCommand implements Command { - - @Override - public void run() throws Exception { - TimedTransitionProperty migrationSchedule = - DatabaseMigrationStateSchedule.get(); - System.out.printf("Current migration schedule: %s%n", migrationSchedule.toValueMap()); - } -} diff --git a/core/src/main/java/google/registry/tools/RegistryTool.java b/core/src/main/java/google/registry/tools/RegistryTool.java index 214e81e20..3bd783da2 100644 --- a/core/src/main/java/google/registry/tools/RegistryTool.java +++ b/core/src/main/java/google/registry/tools/RegistryTool.java @@ -68,7 +68,6 @@ public final class RegistryTool { .put("get_allocation_token", GetAllocationTokenCommand.class) .put("get_claims_list", GetClaimsListCommand.class) .put("get_contact", GetContactCommand.class) - .put("get_database_migration_state", GetDatabaseMigrationStateCommand.class) .put("get_domain", GetDomainCommand.class) .put("get_history_entries", GetHistoryEntriesCommand.class) .put("get_host", GetHostCommand.class) @@ -98,7 +97,6 @@ public final class RegistryTool { .put("renew_domain", RenewDomainCommand.class) .put("save_sql_credential", SaveSqlCredentialCommand.class) .put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class) - .put("set_database_migration_state", SetDatabaseMigrationStateCommand.class) .put("setup_ote", SetupOteCommand.class) .put("uniform_rapid_suspension", UniformRapidSuspensionCommand.class) .put("unlock_domain", UnlockDomainCommand.class) diff --git a/core/src/main/java/google/registry/tools/SetDatabaseMigrationStateCommand.java b/core/src/main/java/google/registry/tools/SetDatabaseMigrationStateCommand.java deleted file mode 100644 index e50a9e8a4..000000000 --- a/core/src/main/java/google/registry/tools/SetDatabaseMigrationStateCommand.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2021 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. - -package google.registry.tools; - -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.google.common.collect.ImmutableSortedMap; -import google.registry.model.annotations.DeleteAfterMigration; -import google.registry.model.common.DatabaseMigrationStateSchedule; -import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState; -import google.registry.tools.params.TransitionListParameter.MigrationStateTransitions; -import org.joda.time.DateTime; - -/** Command to set the Registry 3.0 database migration state schedule. */ -@DeleteAfterMigration -@Parameters( - separators = " =", - commandDescription = "Set the current database migration state schedule.") -public class SetDatabaseMigrationStateCommand extends ConfirmingCommand { - - private static final String WARNING_MESSAGE = - "Attempting to change the schedule with an effect that would take place within the next 10 " - + "minutes. The cache expiration duration is 5 minutes so this MAY BE DANGEROUS.\n"; - - @Parameter( - names = "--migration_schedule", - converter = MigrationStateTransitions.class, - validateWith = MigrationStateTransitions.class, - required = true, - description = - "Comma-delimited list of database transitions, of the form" - + "

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 SQL_PRIMARY phase. - * - *

We must use the current time, otherwise the setting of the migration state will fail due to - * an invalid transition. - */ - public static void setMigrationScheduleToSqlPrimary(FakeClock fakeClock) { - DateTime now = fakeClock.nowUtc(); - tm().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, - now.plusMillis(3), - MigrationState.DATASTORE_PRIMARY_READ_ONLY, - now.plusMillis(4), - MigrationState.SQL_PRIMARY))); - fakeClock.advanceBy(Duration.standardSeconds(1)); - } - - /** Removes the database migration schedule, in essence transitioning to DATASTORE_ONLY. */ - public static void removeDatabaseMigrationSchedule() { - // use the raw calls because going SQL_PRIMARY -> DATASTORE_ONLY is not valid - tm().transact( - () -> - tm().put( - new DatabaseMigrationStateSchedule( - DatabaseMigrationStateSchedule.DEFAULT_TRANSITION_MAP))); - DatabaseMigrationStateSchedule.CACHE.invalidateAll(); - } - private static ImmutableList getDnsRefreshRequests(TargetType type, String... names) { return tm().transact( () -> diff --git a/core/src/test/java/google/registry/tools/GetDatabaseMigrationStateCommandTest.java b/core/src/test/java/google/registry/tools/GetDatabaseMigrationStateCommandTest.java deleted file mode 100644 index a2e9f88e1..000000000 --- a/core/src/test/java/google/registry/tools/GetDatabaseMigrationStateCommandTest.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2021 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. - -package google.registry.tools; - -import static google.registry.model.common.DatabaseMigrationStateSchedule.DEFAULT_TRANSITION_MAP; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.START_OF_TIME; - -import com.google.common.collect.ImmutableSortedMap; -import google.registry.model.common.DatabaseMigrationStateSchedule; -import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState; -import google.registry.testing.DatabaseHelper; -import org.joda.time.DateTime; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -/** Tests for {@link GetDatabaseMigrationStateCommand}. */ -public class GetDatabaseMigrationStateCommandTest - extends CommandTestCase { - - @AfterEach - void afterEach() { - DatabaseHelper.removeDatabaseMigrationSchedule(); - } - - @Test - void testInitial_returnsDatastoreOnly() throws Exception { - runCommand(); - assertStdoutIs( - String.format("Current migration schedule: %s\n", DEFAULT_TRANSITION_MAP.toValueMap())); - } - - @Test - void testFullSchedule() throws Exception { - DateTime now = fakeClock.nowUtc(); - ImmutableSortedMap transitions = - ImmutableSortedMap.of( - START_OF_TIME, - MigrationState.DATASTORE_ONLY, - now.plusHours(1), - MigrationState.DATASTORE_PRIMARY, - now.plusHours(2), - MigrationState.DATASTORE_PRIMARY_NO_ASYNC, - now.plusHours(3), - MigrationState.DATASTORE_PRIMARY_READ_ONLY, - now.plusHours(4), - MigrationState.SQL_PRIMARY, - now.plusHours(5), - MigrationState.SQL_ONLY); - tm().transact(() -> DatabaseMigrationStateSchedule.set(transitions)); - runCommand(); - assertStdoutIs(String.format("Current migration schedule: %s\n", transitions)); - } -} diff --git a/core/src/test/java/google/registry/tools/SetDatabaseMigrationStateCommandTest.java b/core/src/test/java/google/registry/tools/SetDatabaseMigrationStateCommandTest.java deleted file mode 100644 index 24e770105..000000000 --- a/core/src/test/java/google/registry/tools/SetDatabaseMigrationStateCommandTest.java +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2021 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. - -package google.registry.tools; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static google.registry.model.common.DatabaseMigrationStateSchedule.DEFAULT_TRANSITION_MAP; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.START_OF_TIME; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.beust.jcommander.ParameterException; -import com.google.common.collect.ImmutableSortedMap; -import google.registry.model.common.DatabaseMigrationStateSchedule; -import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState; -import google.registry.testing.DatabaseHelper; -import org.joda.time.DateTime; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -/** Tests for {@link SetDatabaseMigrationStateCommand}. */ -public class SetDatabaseMigrationStateCommandTest - extends CommandTestCase { - - @AfterEach - void afterEach() { - DatabaseHelper.removeDatabaseMigrationSchedule(); - } - - @Test - void testSuccess_setsBasicSchedule() throws Exception { - assertThat(DatabaseMigrationStateSchedule.get()).isEqualTo(DEFAULT_TRANSITION_MAP); - assertThat(tm().transact(() -> tm().loadSingleton(DatabaseMigrationStateSchedule.class))) - .isEmpty(); - runCommandForced("--migration_schedule=1970-01-01T00:00:00.000Z=DATASTORE_ONLY"); - tm().transact( - () -> - assertThat( - tm().loadSingleton(DatabaseMigrationStateSchedule.class) - .get() - .migrationTransitions) - .isEqualTo(DEFAULT_TRANSITION_MAP)); - assertThat(DatabaseMigrationStateSchedule.get()).isEqualTo(DEFAULT_TRANSITION_MAP); - } - - @Test - void testSuccess_fullSchedule() throws Exception { - DateTime now = fakeClock.nowUtc(); - DateTime datastorePrimary = now.plusHours(1); - 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_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( - START_OF_TIME, - MigrationState.DATASTORE_ONLY, - datastorePrimary, - MigrationState.DATASTORE_PRIMARY, - datastorePrimaryNoAsync, - MigrationState.DATASTORE_PRIMARY_NO_ASYNC, - datastorePrimaryReadOnly, - MigrationState.DATASTORE_PRIMARY_READ_ONLY, - sqlPrimary, - MigrationState.SQL_PRIMARY, - sqlOnly, - MigrationState.SQL_ONLY)); - } - - @Test - void testSuccess_warnsOnChangeSoon() throws Exception { - DateTime now = fakeClock.nowUtc(); - runCommandForced( - String.format( - "--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY", - START_OF_TIME, now.plusMinutes(1))); - assertThat(DatabaseMigrationStateSchedule.get().toValueMap()) - .containsExactlyEntriesIn( - ImmutableSortedMap.of( - START_OF_TIME, - MigrationState.DATASTORE_ONLY, - now.plusMinutes(1), - MigrationState.DATASTORE_PRIMARY)); - assertInStdout("MAY BE DANGEROUS"); - } - - @Test - void testSuccess_goesBackward() throws Exception { - DateTime now = fakeClock.nowUtc(); - runCommandForced( - String.format( - "--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY," - + "%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( - START_OF_TIME, - MigrationState.DATASTORE_ONLY, - now.plusHours(1), - MigrationState.DATASTORE_PRIMARY, - now.plusHours(2), - MigrationState.DATASTORE_PRIMARY_NO_ASYNC, - now.plusHours(3), - MigrationState.DATASTORE_PRIMARY_READ_ONLY, - now.plusHours(4), - MigrationState.DATASTORE_PRIMARY)); - } - - @Test - void testFailure_invalidTransition() { - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> - runCommandForced( - String.format( - "--migration_schedule=%s=DATASTORE_ONLY,%s=DATASTORE_PRIMARY_READ_ONLY", - START_OF_TIME, START_OF_TIME.plusHours(1)))); - assertThat(thrown) - .hasMessageThat() - .isEqualTo( - "validStateTransitions map cannot transition from DATASTORE_ONLY " - + "to DATASTORE_PRIMARY_READ_ONLY."); - } - - @Test - void testFailure_invalidTransitionFromOldToNew() { - // The map we pass in is valid by itself, but we can't go from DATASTORE_ONLY now to - // DATASTORE_PRIMARY_READ_ONLY now - DateTime now = fakeClock.nowUtc(); - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> - runCommandForced( - String.format( - "--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)))); - assertThat(thrown) - .hasMessageThat() - .isEqualTo( - "Cannot transition from current state-as-of-now DATASTORE_ONLY " - + "to new state-as-of-now DATASTORE_PRIMARY_READ_ONLY"); - } - - @Test - void testFailure_invalidParams() { - assertThrows(ParameterException.class, this::runCommandForced); - assertThrows(ParameterException.class, () -> runCommandForced("--migration_schedule=FOOBAR")); - assertThrows( - ParameterException.class, - () -> runCommandForced("--migration_schedule=1970-01-01T00:00:00.000Z=FOOBAR")); - } -} diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated index 8d641e199..f92084056 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -241,12 +241,6 @@ primary key (scope, type) ); - create table "DatabaseMigrationStateSchedule" ( - id int8 not null, - migration_transitions hstore, - primary key (id) - ); - create table "DelegationSignerData" ( algorithm int4 not null, digest bytea not null,