From e75d45d45dc1e59b98a4f5224058980d31984f64 Mon Sep 17 00:00:00 2001 From: gbrodman Date: Wed, 2 Jun 2021 14:06:28 -0400 Subject: [PATCH] Use a TimedTransitionProperty for the DB migration schedule (#1186) This includes the following changes: - Convert the single-valued database migration state to a timed transition property, meaning that we can switch all instances over at the same time and schedule it in advance - Use a "cache" (technically an expiring memoized supplier) when retrieving the database migration state value - Delete the old DatabaseTransitionSchedule because it is no longer necessary. We took the idea from that and used it for the new DatabaseMigrationStateSchedule, though we cannot reuse the entity itself because the structure is fundamentally different. - Removed references to the DatabaseTransitionSchedule, mainly in the getter/setter commands+tests and a few odd references elsewhere. --- .../model/DatabaseMigrationUtils.java | 55 ----- .../google/registry/model/EntityClasses.java | 6 +- .../DatabaseMigrationStateSchedule.java | 232 ++++++++++++++++++ .../common/DatabaseMigrationStateWrapper.java | 122 --------- .../common/DatabaseTransitionSchedule.java | 166 ------------- .../google/registry/rdap/RdapActionBase.java | 10 - .../registry/rdap/RdapDomainSearchAction.java | 11 +- .../registry/rdap/RdapEntitySearchAction.java | 4 +- .../rdap/RdapNameserverSearchAction.java | 5 +- .../GetDatabaseTransitionScheduleCommand.java | 44 ---- .../google/registry/tools/RegistryTool.java | 2 - .../SetDatabaseTransitionScheduleCommand.java | 69 ------ .../tools/params/TransitionIdParameter.java | 23 -- .../tools/params/TransitionListParameter.java | 2 +- .../DatabaseMigrationStateScheduleTest.java | 173 +++++++++++++ .../DatabaseMigrationStateWrapperTest.java | 111 --------- .../DatabaseTransitionScheduleTest.java | 79 ------ .../registry/testing/AppEngineExtension.java | 22 +- ...DatabaseTransitionScheduleCommandTest.java | 132 ---------- ...DatabaseTransitionScheduleCommandTest.java | 134 ---------- .../google/registry/export/backup_kinds.txt | 3 +- .../google/registry/export/crosstld_kinds.txt | 3 +- .../google/registry/model/schema.txt | 21 +- 23 files changed, 429 insertions(+), 1000 deletions(-) delete mode 100644 core/src/main/java/google/registry/model/DatabaseMigrationUtils.java create mode 100644 core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java delete mode 100644 core/src/main/java/google/registry/model/common/DatabaseMigrationStateWrapper.java delete mode 100644 core/src/main/java/google/registry/model/common/DatabaseTransitionSchedule.java delete mode 100644 core/src/main/java/google/registry/tools/GetDatabaseTransitionScheduleCommand.java delete mode 100644 core/src/main/java/google/registry/tools/SetDatabaseTransitionScheduleCommand.java delete mode 100644 core/src/main/java/google/registry/tools/params/TransitionIdParameter.java create mode 100644 core/src/test/java/google/registry/model/common/DatabaseMigrationStateScheduleTest.java delete mode 100644 core/src/test/java/google/registry/model/common/DatabaseMigrationStateWrapperTest.java delete mode 100644 core/src/test/java/google/registry/model/common/DatabaseTransitionScheduleTest.java delete mode 100644 core/src/test/java/google/registry/tools/GetDatabaseTransitionScheduleCommandTest.java delete mode 100644 core/src/test/java/google/registry/tools/SetDatabaseTransitionScheduleCommandTest.java 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" - + "