diff --git a/core/src/main/java/google/registry/model/DatabaseMigrationUtils.java b/core/src/main/java/google/registry/model/DatabaseMigrationUtils.java
deleted file mode 100644
index 0612ee883..000000000
--- a/core/src/main/java/google/registry/model/DatabaseMigrationUtils.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 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;
-
-import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
-
-import com.google.common.flogger.FluentLogger;
-import google.registry.config.RegistryEnvironment;
-import google.registry.model.common.DatabaseTransitionSchedule;
-import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabase;
-import google.registry.model.common.DatabaseTransitionSchedule.TransitionId;
-
-/** Utility methods related to migrating dual-read/dual-write entities. */
-public class DatabaseMigrationUtils {
-
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- /** Throws exceptions only in unit tests, otherwise only logs exceptions. */
- public static void suppressExceptionUnlessInTest(Runnable work, String message) {
- try {
- work.run();
- } catch (Exception e) {
- if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)) {
- throw e;
- }
- logger.atWarning().withCause(e).log(message);
- }
- }
-
- /** Gets the value for the database currently considered primary. */
- public static PrimaryDatabase getPrimaryDatabase(TransitionId transitionId) {
- return DatabaseTransitionSchedule.getCached(transitionId)
- .map(DatabaseTransitionSchedule::getPrimaryDatabase)
- .orElse(PrimaryDatabase.DATASTORE);
- }
-
- public static boolean isDatastore(TransitionId transitionId) {
- return tm().transactNew(() -> DatabaseMigrationUtils.getPrimaryDatabase(transitionId))
- .equals(PrimaryDatabase.DATASTORE);
- }
-
- private DatabaseMigrationUtils() {}
-}
diff --git a/core/src/main/java/google/registry/model/EntityClasses.java b/core/src/main/java/google/registry/model/EntityClasses.java
index 8aa0fc1f7..ed2d08976 100644
--- a/core/src/main/java/google/registry/model/EntityClasses.java
+++ b/core/src/main/java/google/registry/model/EntityClasses.java
@@ -17,8 +17,7 @@ package google.registry.model;
import com.google.common.collect.ImmutableSet;
import google.registry.model.billing.BillingEvent;
import google.registry.model.common.Cursor;
-import google.registry.model.common.DatabaseMigrationStateWrapper;
-import google.registry.model.common.DatabaseTransitionSchedule;
+import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.common.GaeUserIdConverter;
import google.registry.model.contact.ContactHistory;
@@ -76,8 +75,7 @@ public final class EntityClasses {
ContactHistory.class,
ContactResource.class,
Cursor.class,
- DatabaseMigrationStateWrapper.class,
- DatabaseTransitionSchedule.class,
+ DatabaseMigrationStateSchedule.class,
DomainBase.class,
DomainHistory.class,
EntityGroupRoot.class,
diff --git a/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java b/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java
new file mode 100644
index 000000000..0976868db
--- /dev/null
+++ b/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java
@@ -0,0 +1,232 @@
+// 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.ofyTm;
+import static google.registry.util.DateTimeUtils.START_OF_TIME;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSortedMap;
+import com.googlecode.objectify.annotation.Embed;
+import com.googlecode.objectify.annotation.Entity;
+import com.googlecode.objectify.annotation.Mapify;
+import google.registry.model.annotations.InCrossTld;
+import google.registry.model.common.TimedTransitionProperty.TimeMapper;
+import google.registry.model.common.TimedTransitionProperty.TimedTransition;
+import google.registry.schema.replay.DatastoreOnlyEntity;
+import java.time.Duration;
+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 Datastore throughout the entire migration so as to have a single point
+ * of access.
+ */
+@Entity
+@InCrossTld
+public class DatabaseMigrationStateSchedule extends CrossTldSingleton
+ implements DatastoreOnlyEntity {
+
+ public enum PrimaryDatabase {
+ CLOUD_SQL,
+ 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_ONLY(PrimaryDatabase.DATASTORE, false),
+ DATASTORE_PRIMARY(PrimaryDatabase.DATASTORE, false),
+ DATASTORE_PRIMARY_READ_ONLY(PrimaryDatabase.DATASTORE, true),
+ SQL_PRIMARY_READ_ONLY(PrimaryDatabase.CLOUD_SQL, true),
+ SQL_PRIMARY(PrimaryDatabase.CLOUD_SQL, false),
+ SQL_ONLY(PrimaryDatabase.CLOUD_SQL, false);
+
+ private final PrimaryDatabase primaryDatabase;
+ private final boolean isReadOnly;
+
+ public PrimaryDatabase getPrimaryDatabase() {
+ return primaryDatabase;
+ }
+
+ public boolean isReadOnly() {
+ return isReadOnly;
+ }
+
+ MigrationState(PrimaryDatabase primaryDatabase, boolean isReadOnly) {
+ this.primaryDatabase = primaryDatabase;
+ this.isReadOnly = isReadOnly;
+ }
+ }
+
+ @Embed
+ public static class MigrationStateTransition extends TimedTransition {
+ private MigrationState migrationState;
+
+ @Override
+ protected MigrationState getValue() {
+ return migrationState;
+ }
+
+ @Override
+ protected void setValue(MigrationState migrationState) {
+ this.migrationState = migrationState;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ private static final LoadingCache<
+ Class,
+ TimedTransitionProperty>
+ // Each instance should cache the migration schedule for five minutes before reloading
+ CACHE =
+ CacheBuilder.newBuilder()
+ .expireAfterWrite(Duration.ofMinutes(5))
+ .build(
+ new CacheLoader<
+ Class,
+ TimedTransitionProperty>() {
+ @Override
+ public TimedTransitionProperty load(
+ Class unused) {
+ return 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_READ_ONLY)
+ .putAll(
+ MigrationState.DATASTORE_PRIMARY_READ_ONLY,
+ MigrationState.DATASTORE_ONLY,
+ MigrationState.DATASTORE_PRIMARY,
+ 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);
+ // 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);
+ }
+ 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),
+ MigrationStateTransition.class);
+
+ @Mapify(TimeMapper.class)
+ TimedTransitionProperty migrationTransitions =
+ TimedTransitionProperty.forMapify(
+ MigrationState.DATASTORE_ONLY, MigrationStateTransition.class);
+
+ // Required for Objectify initialization
+ private DatabaseMigrationStateSchedule() {}
+
+ @VisibleForTesting
+ DatabaseMigrationStateSchedule(
+ TimedTransitionProperty migrationTransitions) {
+ this.migrationTransitions = migrationTransitions;
+ }
+
+ /** Sets and persists to Datastore the provided migration transition schedule. */
+ public static void set(ImmutableSortedMap migrationTransitionMap) {
+ ofyTm().assertInTransaction();
+ TimedTransitionProperty transitions =
+ TimedTransitionProperty.make(
+ migrationTransitionMap,
+ MigrationStateTransition.class,
+ VALID_STATE_TRANSITIONS,
+ "validStateTransitions",
+ MigrationState.DATASTORE_ONLY,
+ "migrationTransitionMap must start with DATASTORE_ONLY");
+ validateTransitionAtCurrentTime(transitions);
+ ofyTm().put(new DatabaseMigrationStateSchedule(transitions));
+ CACHE.invalidateAll();
+ }
+
+ /** Loads the currently-set migration schedule from the cache, or the default if none exists. */
+ public static TimedTransitionProperty get() {
+ return CACHE.getUnchecked(DatabaseMigrationStateSchedule.class);
+ }
+
+ /** Loads the currently-set migration schedule from Datastore, or the default if none exists. */
+ @VisibleForTesting
+ static TimedTransitionProperty getUncached() {
+ return ofyTm()
+ .transact(
+ () ->
+ ofyTm()
+ .loadSingleton(DatabaseMigrationStateSchedule.class)
+ .map(s -> s.migrationTransitions)
+ .orElse(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 time must also be valid.
+ */
+ private static void validateTransitionAtCurrentTime(
+ TimedTransitionProperty newTransitions) {
+ MigrationState currentValue = getUncached().getValueAtTime(ofyTm().getTransactionTime());
+ MigrationState nextCurrentValue = newTransitions.getValueAtTime(ofyTm().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/model/common/DatabaseMigrationStateWrapper.java b/core/src/main/java/google/registry/model/common/DatabaseMigrationStateWrapper.java
deleted file mode 100644
index 1063651c9..000000000
--- a/core/src/main/java/google/registry/model/common/DatabaseMigrationStateWrapper.java
+++ /dev/null
@@ -1,122 +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.ofyTm;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.googlecode.objectify.annotation.Entity;
-import google.registry.model.annotations.InCrossTld;
-import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabase;
-import google.registry.schema.replay.DatastoreOnlyEntity;
-
-/**
- * A wrapper object representing the current stage of the Registry 3.0 Cloud SQL migration.
- *
- *
The entity is stored in Datastore throughout the entire migration so as to have a single point
- * of access (avoiding a two-phase commit problem).
- */
-@Entity
-@InCrossTld
-public class DatabaseMigrationStateWrapper extends CrossTldSingleton
- implements DatastoreOnlyEntity {
-
- /**
- * 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_ONLY(PrimaryDatabase.DATASTORE, false),
- DATASTORE_PRIMARY(PrimaryDatabase.DATASTORE, false),
- DATASTORE_PRIMARY_READ_ONLY(PrimaryDatabase.DATASTORE, true),
- SQL_PRIMARY(PrimaryDatabase.CLOUD_SQL, false),
- SQL_ONLY(PrimaryDatabase.CLOUD_SQL, false);
-
- private final PrimaryDatabase primaryDatabase;
- private final boolean readOnly;
-
- public PrimaryDatabase getPrimaryDatabase() {
- return primaryDatabase;
- }
-
- public boolean isReadOnly() {
- return readOnly;
- }
-
- MigrationState(PrimaryDatabase primaryDatabase, boolean readOnly) {
- this.primaryDatabase = primaryDatabase;
- this.readOnly = readOnly;
- }
- }
-
- private MigrationState migrationState;
-
- // Required for Objectify initialization
- private DatabaseMigrationStateWrapper() {}
-
- DatabaseMigrationStateWrapper(MigrationState migrationState) {
- this.migrationState = migrationState;
- }
-
- // The valid state transitions. Basically, at state N, state N+1 is valid as well as all previous
- // states, with one type of exception: when in either of the SQL states, we can only move back
- // one step so that we can make sure that any modifications have been replayed back to Datastore.
- private static final ImmutableMap>
- VALID_STATE_TRANSITIONS =
- ImmutableMap.of(
- MigrationState.DATASTORE_ONLY,
- ImmutableSet.of(MigrationState.DATASTORE_PRIMARY),
- MigrationState.DATASTORE_PRIMARY,
- ImmutableSet.of(
- MigrationState.DATASTORE_ONLY, MigrationState.DATASTORE_PRIMARY_READ_ONLY),
- MigrationState.DATASTORE_PRIMARY_READ_ONLY,
- ImmutableSet.of(
- MigrationState.DATASTORE_ONLY,
- MigrationState.DATASTORE_PRIMARY,
- MigrationState.SQL_PRIMARY),
- MigrationState.SQL_PRIMARY,
- ImmutableSet.of(MigrationState.DATASTORE_PRIMARY_READ_ONLY, MigrationState.SQL_ONLY),
- MigrationState.SQL_ONLY,
- ImmutableSet.of(MigrationState.SQL_PRIMARY));
-
- private static boolean isValidStateTransition(MigrationState from, MigrationState to) {
- return VALID_STATE_TRANSITIONS.get(from).contains(to);
- }
-
- /** Sets and persists to Datastore the current database migration state. */
- public static void set(MigrationState newState) {
- MigrationState currentState = get();
- checkArgument(
- isValidStateTransition(currentState, newState),
- "Moving from migration state %s to %s is not a valid transition",
- currentState,
- newState);
- DatabaseMigrationStateWrapper wrapper = new DatabaseMigrationStateWrapper(newState);
- ofyTm().transact(() -> ofyTm().put(wrapper));
- }
-
- /** Retrieves the current state of the migration (or DATASTORE_ONLY if it hasn't started). */
- public static MigrationState get() {
- return ofyTm()
- .transact(
- () ->
- ofyTm()
- .loadSingleton(DatabaseMigrationStateWrapper.class)
- .map(s -> s.migrationState)
- .orElse(MigrationState.DATASTORE_ONLY));
- }
-}
diff --git a/core/src/main/java/google/registry/model/common/DatabaseTransitionSchedule.java b/core/src/main/java/google/registry/model/common/DatabaseTransitionSchedule.java
deleted file mode 100644
index 199840b64..000000000
--- a/core/src/main/java/google/registry/model/common/DatabaseTransitionSchedule.java
+++ /dev/null
@@ -1,166 +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.checkNotNull;
-import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
-import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
-import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
-import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
-
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableSortedMap;
-import com.googlecode.objectify.Key;
-import com.googlecode.objectify.annotation.Embed;
-import com.googlecode.objectify.annotation.Entity;
-import com.googlecode.objectify.annotation.Id;
-import com.googlecode.objectify.annotation.Mapify;
-import com.googlecode.objectify.annotation.Parent;
-import google.registry.model.ImmutableObject;
-import google.registry.model.UpdateAutoTimestamp;
-import google.registry.model.annotations.InCrossTld;
-import google.registry.model.common.TimedTransitionProperty.TimeMapper;
-import google.registry.model.common.TimedTransitionProperty.TimedTransition;
-import google.registry.model.registry.label.PremiumList;
-import google.registry.model.registry.label.ReservedList;
-import google.registry.model.smd.SignedMarkRevocationList;
-import google.registry.model.tmch.ClaimsList;
-import google.registry.persistence.VKey;
-import google.registry.schema.replay.DatastoreOnlyEntity;
-import java.util.Optional;
-import javax.annotation.concurrent.Immutable;
-import org.joda.time.DateTime;
-
-@Entity
-@Immutable
-@InCrossTld
-public class DatabaseTransitionSchedule extends ImmutableObject implements DatastoreOnlyEntity {
-
- /**
- * The name of the database to be treated as the primary database. The first entry in the schedule
- * will always be Datastore.
- */
- public enum PrimaryDatabase {
- CLOUD_SQL,
- DATASTORE
- }
-
- /** The id of the transition schedule. */
- public enum TransitionId {
- /** The schedule for migration of {@link ClaimsList} entities. */
- CLAIMS_LIST,
- /** The schedule for the migration of {@link PremiumList} and {@link ReservedList}. */
- DOMAIN_LABEL_LISTS,
- /** The schedule for the migration of the {@link SignedMarkRevocationList} entity. */
- SIGNED_MARK_REVOCATION_LIST,
- /** The schedule for all asynchronously-replayed entities, ones not dually-written. */
- REPLAYED_ENTITIES,
- }
-
- /**
- * The transition to a specified primary database at a specific point in time, for use in a
- * TimedTransitionProperty.
- */
- @Embed
- public static class PrimaryDatabaseTransition extends TimedTransition {
- private PrimaryDatabase primaryDatabase;
-
- @Override
- protected PrimaryDatabase getValue() {
- return primaryDatabase;
- }
-
- @Override
- protected void setValue(PrimaryDatabase primaryDatabase) {
- this.primaryDatabase = primaryDatabase;
- }
- }
-
- @Parent Key parent = getCrossTldKey();
-
- @Id String transitionId;
-
- /** An automatically managed timestamp of when this schedule was last written to Datastore. */
- UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null);
-
- /** A property that tracks the primary database for a dual-read/dual-write database migration. */
- @Mapify(TimeMapper.class)
- TimedTransitionProperty databaseTransitions =
- TimedTransitionProperty.forMapify(PrimaryDatabase.DATASTORE, PrimaryDatabaseTransition.class);
-
- /** A cache that loads the {@link DatabaseTransitionSchedule} for a given id. */
- private static final LoadingCache> CACHE =
- CacheBuilder.newBuilder()
- .expireAfterWrite(
- java.time.Duration.ofMillis(getSingletonCacheRefreshDuration().getMillis()))
- .build(
- new CacheLoader>() {
- @Override
- public Optional load(TransitionId transitionId) {
- return DatabaseTransitionSchedule.get(transitionId);
- }
- });
-
- public static DatabaseTransitionSchedule create(
- TransitionId transitionId,
- TimedTransitionProperty databaseTransitions) {
- checkNotNull(transitionId, "Id cannot be null");
- checkNotNull(databaseTransitions, "databaseTransitions cannot be null");
- databaseTransitions.checkValidity();
- DatabaseTransitionSchedule instance = new DatabaseTransitionSchedule();
- instance.transitionId = transitionId.name();
- instance.databaseTransitions = databaseTransitions;
- return instance;
- }
-
- /** Returns the database that is indicated as primary at the given time. */
- public PrimaryDatabase getPrimaryDatabase() {
- return databaseTransitions.getValueAtTime(tm().getTransactionTime());
- }
-
- /** Returns the database transitions as a map of start time to primary database. */
- public ImmutableSortedMap getDatabaseTransitions() {
- return databaseTransitions.toValueMap();
- }
-
- /**
- * Returns the current cached schedule for the given id.
- *
- *
WARNING: The schedule returned by this method could be up to 10 minutes out of date.
- */
- public static Optional getCached(TransitionId id) {
- return CACHE.getUnchecked(id);
- }
-
- /** Returns the schedule for a given id. */
- public static Optional get(TransitionId transitionId) {
- VKey key =
- VKey.create(
- DatabaseTransitionSchedule.class,
- transitionId,
- Key.create(getCrossTldKey(), DatabaseTransitionSchedule.class, transitionId.name()));
-
- return ofyTm().transact(() -> ofyTm().loadByKeyIfPresent(key));
- }
-
- @Override
- public String toString() {
- return String.format(
- "%s(last updated at %s): %s",
- transitionId, lastUpdateTime.getTimestamp(), databaseTransitions.toValueMap());
- }
-}
diff --git a/core/src/main/java/google/registry/rdap/RdapActionBase.java b/core/src/main/java/google/registry/rdap/RdapActionBase.java
index 8f24822a1..4ea60e07b 100644
--- a/core/src/main/java/google/registry/rdap/RdapActionBase.java
+++ b/core/src/main/java/google/registry/rdap/RdapActionBase.java
@@ -17,7 +17,6 @@ package google.registry.rdap;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
-import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Actions.getPathForAction;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
@@ -29,10 +28,7 @@ import com.google.common.net.MediaType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import google.registry.config.RegistryConfig.Config;
-import google.registry.model.DatabaseMigrationUtils;
import google.registry.model.EppResource;
-import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabase;
-import google.registry.model.common.DatabaseTransitionSchedule.TransitionId;
import google.registry.model.registrar.Registrar;
import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.ErrorResponse;
@@ -261,10 +257,4 @@ public abstract class RdapActionBase implements Runnable {
return rdapJsonFormatter.getRequestTime();
}
- static boolean isDatastore() {
- return tm().transact(
- () ->
- DatabaseMigrationUtils.getPrimaryDatabase(TransitionId.REPLAYED_ENTITIES)
- .equals(PrimaryDatabase.DATASTORE));
- }
}
diff --git a/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java b/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java
index 46013dcd2..ed64d50e6 100644
--- a/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java
+++ b/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java
@@ -19,6 +19,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
+import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -204,7 +205,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
// need it.
int querySizeLimit = RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize;
RdapResultSet resultSet;
- if (isDatastore()) {
+ if (tm().isOfy()) {
Query query =
auditedOfy()
.load()
@@ -260,7 +261,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
// Don't use queryItems, because it doesn't handle pending deletes.
int querySizeLimit = RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize;
RdapResultSet resultSet;
- if (isDatastore()) {
+ if (tm().isOfy()) {
Query query = auditedOfy().load().type(DomainBase.class).filter("tld", tld);
if (cursorString.isPresent()) {
query = query.filter("fullyQualifiedDomainName >", cursorString.get());
@@ -337,7 +338,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
// incomplete result set if a search asks for something like "ns*", but we need to enforce a
// limit in order to avoid arbitrarily long-running queries.
Optional desiredRegistrar = getDesiredRegistrar();
- if (isDatastore()) {
+ if (tm().isOfy()) {
Query query =
queryItems(
HostResource.class,
@@ -472,7 +473,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
private DomainSearchResponse searchByNameserverIp(final InetAddress inetAddress) {
Optional desiredRegistrar = getDesiredRegistrar();
ImmutableSet> hostKeys;
- if (isDatastore()) {
+ if (tm().isOfy()) {
Query query =
queryItems(
HostResource.class,
@@ -544,7 +545,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
int numHostKeysSearched = 0;
for (List> chunk : Iterables.partition(hostKeys, 30)) {
numHostKeysSearched += chunk.size();
- if (isDatastore()) {
+ if (tm().isOfy()) {
Query query =
auditedOfy()
.load()
diff --git a/core/src/main/java/google/registry/rdap/RdapEntitySearchAction.java b/core/src/main/java/google/registry/rdap/RdapEntitySearchAction.java
index 93419aef7..9aacb2821 100644
--- a/core/src/main/java/google/registry/rdap/RdapEntitySearchAction.java
+++ b/core/src/main/java/google/registry/rdap/RdapEntitySearchAction.java
@@ -262,7 +262,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|| (cursorType == CursorType.REGISTRAR)) {
resultSet = RdapResultSet.create(ImmutableList.of());
} else {
- if (isDatastore()) {
+ if (tm().isOfy()) {
Query query =
queryItems(
ContactResource.class,
@@ -386,7 +386,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
if (subtype == Subtype.REGISTRARS) {
contactResultSet = RdapResultSet.create(ImmutableList.of());
} else {
- if (isDatastore()) {
+ if (tm().isOfy()) {
contactResultSet =
getMatchingResources(
queryItemsByKey(
diff --git a/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java b/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java
index 58b3c8c19..7972e7c6d 100644
--- a/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java
+++ b/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java
@@ -16,6 +16,7 @@ package google.registry.rdap;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
+import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -220,7 +221,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
private NameserverSearchResponse searchByNameUsingPrefix(RdapSearchPattern partialStringQuery) {
// Add 1 so we can detect truncation.
int querySizeLimit = getStandardQuerySizeLimit();
- if (isDatastore()) {
+ if (tm().isOfy()) {
Query query =
queryItems(
HostResource.class,
@@ -254,7 +255,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
// Add 1 so we can detect truncation.
int querySizeLimit = getStandardQuerySizeLimit();
RdapResultSet rdapResultSet;
- if (isDatastore()) {
+ if (tm().isOfy()) {
Query query =
queryItems(
HostResource.class,
diff --git a/core/src/main/java/google/registry/tools/GetDatabaseTransitionScheduleCommand.java b/core/src/main/java/google/registry/tools/GetDatabaseTransitionScheduleCommand.java
deleted file mode 100644
index 53822df0b..000000000
--- a/core/src/main/java/google/registry/tools/GetDatabaseTransitionScheduleCommand.java
+++ /dev/null
@@ -1,44 +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.util.PreconditionsUtils.checkArgumentPresent;
-
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
-import google.registry.model.common.DatabaseTransitionSchedule;
-import google.registry.model.common.DatabaseTransitionSchedule.TransitionId;
-import java.util.List;
-
-/** Command to show the {@link DatabaseTransitionSchedule} for a transition id. */
-@Parameters(separators = " =", commandDescription = "Show database transition schedule")
-final class GetDatabaseTransitionScheduleCommand implements CommandWithRemoteApi {
-
- @Parameter(description = "Transition id(s) for the schedules to get", required = true)
- private List mainParameters;
-
- @Override
- public void run() {
- for (TransitionId transitionId : mainParameters) {
- DatabaseTransitionSchedule schedule =
- checkArgumentPresent(
- DatabaseTransitionSchedule.get(transitionId),
- "A database transition schedule for %s does not exist",
- transitionId);
-
- System.out.println(schedule);
- }
- }
-}
diff --git a/core/src/main/java/google/registry/tools/RegistryTool.java b/core/src/main/java/google/registry/tools/RegistryTool.java
index 591bdb20e..2e2d596de 100644
--- a/core/src/main/java/google/registry/tools/RegistryTool.java
+++ b/core/src/main/java/google/registry/tools/RegistryTool.java
@@ -69,7 +69,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_transition_schedule", GetDatabaseTransitionScheduleCommand.class)
.put("get_domain", GetDomainCommand.class)
.put("get_history_entries", GetHistoryEntriesCommand.class)
.put("get_host", GetHostCommand.class)
@@ -111,7 +110,6 @@ public final class RegistryTool {
.put("resave_epp_resource", ResaveEppResourceCommand.class)
.put("save_sql_credential", SaveSqlCredentialCommand.class)
.put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class)
- .put("set_database_transition_schedule", SetDatabaseTransitionScheduleCommand.class)
.put("set_num_instances", SetNumInstancesCommand.class)
.put("set_sql_replay_checkpoint", SetSqlReplayCheckpointCommand.class)
.put("setup_ote", SetupOteCommand.class)
diff --git a/core/src/main/java/google/registry/tools/SetDatabaseTransitionScheduleCommand.java b/core/src/main/java/google/registry/tools/SetDatabaseTransitionScheduleCommand.java
deleted file mode 100644
index 6e5f13e43..000000000
--- a/core/src/main/java/google/registry/tools/SetDatabaseTransitionScheduleCommand.java
+++ /dev/null
@@ -1,69 +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.ofyTm;
-
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
-import com.google.common.collect.ImmutableSortedMap;
-import google.registry.model.common.DatabaseTransitionSchedule;
-import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabase;
-import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabaseTransition;
-import google.registry.model.common.DatabaseTransitionSchedule.TransitionId;
-import google.registry.model.common.TimedTransitionProperty;
-import google.registry.tools.params.TransitionListParameter.PrimaryDatabaseTransitions;
-import org.joda.time.DateTime;
-
-/** Command to update {@link DatabaseTransitionSchedule}. */
-@Parameters(
- separators = " =",
- commandDescription = "Set the database transition schedule for transition id.")
-public class SetDatabaseTransitionScheduleCommand extends ConfirmingCommand
- implements CommandWithRemoteApi {
-
- @Parameter(
- names = "--transition_schedule",
- converter = PrimaryDatabaseTransitions.class,
- validateWith = PrimaryDatabaseTransitions.class,
- description =
- "Comma-delimited list of database transitions, of the form"
- + "