Store DatabaseMigrationSchedule in SQL instead of Datastore (#1269)

* Store DatabaseMigrationSchedule in SQL instead of Datastore

This requires messing around with some of the JPA unit test rule
creation since it requires saving / retrieving the schedule pretty much
always (which itself includes the hstore extension).
This commit is contained in:
gbrodman
2021-08-12 15:57:31 -06:00
committed by GitHub
parent 60469479a4
commit 7f733cd16d
36 changed files with 239 additions and 122 deletions
@@ -17,7 +17,6 @@ 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.DatabaseMigrationStateSchedule;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.common.GaeUserIdConverter;
import google.registry.model.contact.ContactHistory;
@@ -72,7 +71,6 @@ public final class EntityClasses {
ContactHistory.class,
ContactResource.class,
Cursor.class,
DatabaseMigrationStateSchedule.class,
DomainBase.class,
DomainHistory.class,
EntityGroupRoot.class,
@@ -15,9 +15,7 @@
package google.registry.model.common;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.annotations.VisibleForTesting;
@@ -26,16 +24,13 @@ 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.Key;
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 com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryEnvironment;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.model.replay.DatastoreOnlyEntity;
import google.registry.model.replay.SqlOnlyEntity;
import java.time.Duration;
import java.util.Optional;
import javax.persistence.Entity;
import javax.persistence.PersistenceException;
import org.joda.time.DateTime;
/**
@@ -45,9 +40,9 @@ import org.joda.time.DateTime;
* of access.
*/
@Entity
@InCrossTld
public class DatabaseMigrationStateSchedule extends CrossTldSingleton
implements DatastoreOnlyEntity {
public class DatabaseMigrationStateSchedule extends CrossTldSingleton implements SqlOnlyEntity {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public enum PrimaryDatabase {
CLOUD_SQL,
@@ -96,12 +91,11 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton
}
}
@Embed
public static class MigrationStateTransition extends TimedTransition<MigrationState> {
private MigrationState migrationState;
@Override
protected MigrationState getValue() {
public MigrationState getValue() {
return migrationState;
}
@@ -185,7 +179,6 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton
MigrationStateTransition.class);
@VisibleForTesting
@Mapify(TimeMapper.class)
public TimedTransitionProperty<MigrationState, MigrationStateTransition> migrationTransitions =
TimedTransitionProperty.forMapify(
MigrationState.DATASTORE_ONLY, MigrationStateTransition.class);
@@ -201,7 +194,7 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton
/** Sets and persists to Datastore the provided migration transition schedule. */
public static void set(ImmutableSortedMap<DateTime, MigrationState> migrationTransitionMap) {
ofyTm().assertInTransaction();
jpaTm().assertInTransaction();
TimedTransitionProperty<MigrationState, MigrationStateTransition> transitions =
TimedTransitionProperty.make(
migrationTransitionMap,
@@ -211,7 +204,7 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton
MigrationState.DATASTORE_ONLY,
"migrationTransitionMap must start with DATASTORE_ONLY");
validateTransitionAtCurrentTime(transitions);
ofyTm().put(new DatabaseMigrationStateSchedule(transitions));
jpaTm().put(new DatabaseMigrationStateSchedule(transitions));
CACHE.invalidateAll();
}
@@ -228,20 +221,23 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton
/** Loads the currently-set migration schedule from Datastore, or the default if none exists. */
@VisibleForTesting
static TimedTransitionProperty<MigrationState, MigrationStateTransition> getUncached() {
return Optional.ofNullable(
auditedOfy()
.doTransactionless(
() ->
auditedOfy()
.load()
.key(
Key.create(
getCrossTldKey(),
DatabaseMigrationStateSchedule.class,
CrossTldSingleton.SINGLETON_ID))
.now()))
.map(s -> s.migrationTransitions)
.orElse(DEFAULT_TRANSITION_MAP);
return jpaTm()
.transactNew(
() -> {
try {
return jpaTm()
.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;
}
});
}
/**
@@ -252,8 +248,8 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton
*/
private static void validateTransitionAtCurrentTime(
TimedTransitionProperty<MigrationState, MigrationStateTransition> newTransitions) {
MigrationState currentValue = getUncached().getValueAtTime(ofyTm().getTransactionTime());
MigrationState nextCurrentValue = newTransitions.getValueAtTime(ofyTm().getTransactionTime());
MigrationState currentValue = getUncached().getValueAtTime(jpaTm().getTransactionTime());
MigrationState nextCurrentValue = newTransitions.getValueAtTime(jpaTm().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",
@@ -0,0 +1,46 @@
// 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 com.google.common.collect.Maps;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationStateTransition;
import java.util.Map;
import javax.persistence.Converter;
import org.joda.time.DateTime;
/** JPA converter for {@link DatabaseMigrationStateSchedule} transitions. */
@Converter(autoApply = true)
public class DatabaseMigrationScheduleTransitionConverter
extends TimedTransitionPropertyConverterBase<MigrationState, MigrationStateTransition> {
@Override
Map.Entry<String, String> convertToDatabaseMapEntry(
Map.Entry<DateTime, MigrationStateTransition> entry) {
return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue().name());
}
@Override
Map.Entry<DateTime, MigrationState> convertToEntityMapEntry(Map.Entry<String, String> entry) {
return Maps.immutableEntry(
DateTime.parse(entry.getKey()), MigrationState.valueOf(entry.getValue()));
}
@Override
Class<MigrationStateTransition> getTimedTransitionSubclass() {
return MigrationStateTransition.class;
}
}
@@ -14,7 +14,7 @@
package google.registry.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -47,11 +47,11 @@ public class SetDatabaseMigrationStateCommand extends ConfirmingCommand
@Override
protected String prompt() {
return ofyTm()
return jpaTm()
.transact(
() -> {
StringBuilder result = new StringBuilder();
DateTime now = ofyTm().getTransactionTime();
DateTime now = jpaTm().getTransactionTime();
DateTime nextTransition = transitionSchedule.ceilingKey(now);
if (nextTransition != null && nextTransition.isBefore(now.plusMinutes(10))) {
result.append(WARNING_MESSAGE);
@@ -64,7 +64,7 @@ public class SetDatabaseMigrationStateCommand extends ConfirmingCommand
@Override
protected String execute() {
ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(transitionSchedule));
jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(transitionSchedule));
return String.format("Successfully set new migration state schedule %s", transitionSchedule);
}
}
@@ -42,6 +42,7 @@
<class>google.registry.model.billing.BillingEvent$OneTime</class>
<class>google.registry.model.billing.BillingEvent$Recurring</class>
<class>google.registry.model.common.Cursor</class>
<class>google.registry.model.common.DatabaseMigrationStateSchedule</class>
<class>google.registry.model.contact.ContactHistory</class>
<class>google.registry.model.contact.ContactResource</class>
<class>google.registry.model.domain.DomainBase</class>
@@ -85,6 +86,7 @@
<class>google.registry.persistence.converter.CreateAutoTimestampConverter</class>
<class>google.registry.persistence.converter.CurrencyToBillingConverter</class>
<class>google.registry.persistence.converter.CurrencyUnitConverter</class>
<class>google.registry.persistence.converter.DatabaseMigrationScheduleTransitionConverter</class>
<class>google.registry.persistence.converter.DateTimeConverter</class>
<class>google.registry.persistence.converter.DurationConverter</class>
<class>google.registry.persistence.converter.InetAddressSetConverter</class>