diff --git a/core/WEB-INF/appengine-generated/local_db.bin b/core/WEB-INF/appengine-generated/local_db.bin deleted file mode 100644 index 44061b4bb..000000000 Binary files a/core/WEB-INF/appengine-generated/local_db.bin and /dev/null differ diff --git a/core/src/main/java/google/registry/model/EntityClasses.java b/core/src/main/java/google/registry/model/EntityClasses.java index 5600701ca..bdd013911 100644 --- a/core/src/main/java/google/registry/model/EntityClasses.java +++ b/core/src/main/java/google/registry/model/EntityClasses.java @@ -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, diff --git a/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java b/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java index 94624db7d..513808691 100644 --- a/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java +++ b/core/src/main/java/google/registry/model/common/DatabaseMigrationStateSchedule.java @@ -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 { 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 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 migrationTransitionMap) { - ofyTm().assertInTransaction(); + jpaTm().assertInTransaction(); TimedTransitionProperty 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 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 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", diff --git a/core/src/main/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverter.java b/core/src/main/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverter.java new file mode 100644 index 000000000..979ae3a33 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverter.java @@ -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 { + + @Override + Map.Entry convertToDatabaseMapEntry( + Map.Entry entry) { + return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue().name()); + } + + @Override + Map.Entry convertToEntityMapEntry(Map.Entry entry) { + return Maps.immutableEntry( + DateTime.parse(entry.getKey()), MigrationState.valueOf(entry.getValue())); + } + + @Override + Class getTimedTransitionSubclass() { + return MigrationStateTransition.class; + } +} diff --git a/core/src/main/java/google/registry/tools/SetDatabaseMigrationStateCommand.java b/core/src/main/java/google/registry/tools/SetDatabaseMigrationStateCommand.java index 5a43e8bdf..199d3a78d 100644 --- a/core/src/main/java/google/registry/tools/SetDatabaseMigrationStateCommand.java +++ b/core/src/main/java/google/registry/tools/SetDatabaseMigrationStateCommand.java @@ -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); } } diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 428cd342e..c8b6fdb35 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -42,6 +42,7 @@ google.registry.model.billing.BillingEvent$OneTime google.registry.model.billing.BillingEvent$Recurring google.registry.model.common.Cursor + google.registry.model.common.DatabaseMigrationStateSchedule google.registry.model.contact.ContactHistory google.registry.model.contact.ContactResource google.registry.model.domain.DomainBase @@ -85,6 +86,7 @@ google.registry.persistence.converter.CreateAutoTimestampConverter google.registry.persistence.converter.CurrencyToBillingConverter google.registry.persistence.converter.CurrencyUnitConverter + google.registry.persistence.converter.DatabaseMigrationScheduleTransitionConverter google.registry.persistence.converter.DateTimeConverter google.registry.persistence.converter.DurationConverter google.registry.persistence.converter.InetAddressSetConverter diff --git a/core/src/test/java/google/registry/backup/ReplayCommitLogsToSqlActionTest.java b/core/src/test/java/google/registry/backup/ReplayCommitLogsToSqlActionTest.java index 9c050db24..eb0ed1647 100644 --- a/core/src/test/java/google/registry/backup/ReplayCommitLogsToSqlActionTest.java +++ b/core/src/test/java/google/registry/backup/ReplayCommitLogsToSqlActionTest.java @@ -135,7 +135,7 @@ public class ReplayCommitLogsToSqlActionTest { action.diffLister.gcsUtils = gcsUtils; action.diffLister.executorProvider = MoreExecutors::newDirectExecutorService; action.diffLister.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - ofyTm() + jpaTm() .transact( () -> DatabaseMigrationStateSchedule.set( @@ -469,7 +469,7 @@ public class ReplayCommitLogsToSqlActionTest { @Test void testFailure_notEnabled() { - ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(DEFAULT_TRANSITION_MAP.toValueMap())); + jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(DEFAULT_TRANSITION_MAP.toValueMap())); action.run(); assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT); assertThat(response.getPayload()) diff --git a/core/src/test/java/google/registry/beam/initsql/BackupTestStore.java b/core/src/test/java/google/registry/beam/initsql/BackupTestStore.java index 96042347a..8aaa67184 100644 --- a/core/src/test/java/google/registry/beam/initsql/BackupTestStore.java +++ b/core/src/test/java/google/registry/beam/initsql/BackupTestStore.java @@ -63,7 +63,7 @@ public final class BackupTestStore implements AutoCloseable { this.fakeClock = fakeClock; this.appEngine = new AppEngineExtension.Builder() - .withDatastore() + .withDatastoreAndCloudSql() .withoutCannedData() .withClock(fakeClock) .build(); diff --git a/core/src/test/java/google/registry/model/common/DatabaseMigrationStateScheduleTest.java b/core/src/test/java/google/registry/model/common/DatabaseMigrationStateScheduleTest.java index e954ea608..72fb79423 100644 --- a/core/src/test/java/google/registry/model/common/DatabaseMigrationStateScheduleTest.java +++ b/core/src/test/java/google/registry/model/common/DatabaseMigrationStateScheduleTest.java @@ -21,7 +21,7 @@ import static google.registry.model.common.DatabaseMigrationStateSchedule.Migrat import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.SQL_ONLY; import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.SQL_PRIMARY; import static google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState.SQL_PRIMARY_READ_ONLY; -import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.junit.Assert.assertThrows; @@ -36,6 +36,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +/** Tests for {@link DatabaseMigrationStateSchedule}. */ public class DatabaseMigrationStateScheduleTest extends EntityTestCase { @BeforeEach @@ -123,7 +124,7 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase { assertThat( assertThrows( IllegalArgumentException.class, - () -> ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(nowInvalidMap)))) + () -> jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(nowInvalidMap)))) .hasMessageThat() .isEqualTo( "Cannot transition from current state-as-of-now DATASTORE_ONLY " @@ -139,7 +140,7 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase { DatabaseMigrationStateSchedule.set( DatabaseMigrationStateSchedule.DEFAULT_TRANSITION_MAP.toValueMap()))) .hasMessageThat() - .isEqualTo("Must be called in a transaction"); + .isEqualTo("Not in a transaction"); } @Test @@ -154,7 +155,7 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase { private void runValidTransition(MigrationState from, MigrationState to) { ImmutableSortedMap transitions = createMapEndingWithTransition(from, to); - ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(transitions)); + jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(transitions)); assertThat(DatabaseMigrationStateSchedule.getUncached().toValueMap()) .containsExactlyEntriesIn(transitions); } @@ -165,7 +166,7 @@ public class DatabaseMigrationStateScheduleTest extends EntityTestCase { assertThat( assertThrows( IllegalArgumentException.class, - () -> ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(transitions)))) + () -> jpaTm().transact(() -> DatabaseMigrationStateSchedule.set(transitions)))) .hasMessageThat() .isEqualTo( String.format("validStateTransitions map cannot transition from %s to %s.", from, to)); diff --git a/core/src/test/java/google/registry/model/ofy/DatastoreTransactionManagerTest.java b/core/src/test/java/google/registry/model/ofy/DatastoreTransactionManagerTest.java index 982bb3eb6..dfabb6c67 100644 --- a/core/src/test/java/google/registry/model/ofy/DatastoreTransactionManagerTest.java +++ b/core/src/test/java/google/registry/model/ofy/DatastoreTransactionManagerTest.java @@ -38,7 +38,7 @@ public class DatastoreTransactionManagerTest { @RegisterExtension public final AppEngineExtension appEngine = AppEngineExtension.builder() - .withDatastore() + .withDatastoreAndCloudSql() .withOfyTestEntities(InCrossTldTestEntity.class) .build(); diff --git a/core/src/test/java/google/registry/model/ofy/EntityWritePrioritiesTest.java b/core/src/test/java/google/registry/model/ofy/EntityWritePrioritiesTest.java index 662b98a6f..578ab0e86 100644 --- a/core/src/test/java/google/registry/model/ofy/EntityWritePrioritiesTest.java +++ b/core/src/test/java/google/registry/model/ofy/EntityWritePrioritiesTest.java @@ -28,7 +28,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; class EntityWritePrioritiesTest { @RegisterExtension - AppEngineExtension appEngine = new AppEngineExtension.Builder().withDatastore().build(); + AppEngineExtension appEngine = + new AppEngineExtension.Builder().withDatastoreAndCloudSql().build(); @Test void testGetPriority() { diff --git a/core/src/test/java/google/registry/model/replay/ReplicateToDatastoreActionTest.java b/core/src/test/java/google/registry/model/replay/ReplicateToDatastoreActionTest.java index a44cecc5c..566e86af9 100644 --- a/core/src/test/java/google/registry/model/replay/ReplicateToDatastoreActionTest.java +++ b/core/src/test/java/google/registry/model/replay/ReplicateToDatastoreActionTest.java @@ -196,7 +196,7 @@ public class ReplicateToDatastoreActionTest { void testNotInMigrationState_doesNothing() { // set a schedule that backtracks the current status to DATASTORE_PRIMARY_READ_ONLY DateTime now = fakeClock.nowUtc(); - ofyTm() + jpaTm() .transact( () -> DatabaseMigrationStateSchedule.set( diff --git a/core/src/test/java/google/registry/model/smd/SignedMarkRevocationListDaoTest.java b/core/src/test/java/google/registry/model/smd/SignedMarkRevocationListDaoTest.java index d89b28d02..7e3bf248c 100644 --- a/core/src/test/java/google/registry/model/smd/SignedMarkRevocationListDaoTest.java +++ b/core/src/test/java/google/registry/model/smd/SignedMarkRevocationListDaoTest.java @@ -19,16 +19,13 @@ import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableO import com.google.common.collect.ImmutableMap; import google.registry.model.EntityTestCase; -import google.registry.persistence.transaction.JpaTestRules; -import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; public class SignedMarkRevocationListDaoTest extends EntityTestCase { - @RegisterExtension - final JpaIntegrationWithCoverageExtension jpa = - new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension(); + public SignedMarkRevocationListDaoTest() { + super(JpaEntityCoverageCheck.ENABLED); + } @Test void testSave_success() { diff --git a/core/src/test/java/google/registry/model/translators/VKeyTranslatorFactoryTest.java b/core/src/test/java/google/registry/model/translators/VKeyTranslatorFactoryTest.java index d2d2892a0..3cf7733fe 100644 --- a/core/src/test/java/google/registry/model/translators/VKeyTranslatorFactoryTest.java +++ b/core/src/test/java/google/registry/model/translators/VKeyTranslatorFactoryTest.java @@ -36,7 +36,10 @@ public class VKeyTranslatorFactoryTest { @RegisterExtension public final AppEngineExtension appEngine = - AppEngineExtension.builder().withDatastore().withOfyTestEntities(TestObject.class).build(); + AppEngineExtension.builder() + .withDatastoreAndCloudSql() + .withOfyTestEntities(TestObject.class) + .build(); VKeyTranslatorFactoryTest() {} diff --git a/core/src/test/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverterTest.java b/core/src/test/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverterTest.java index fba4a2a8f..237980cd3 100644 --- a/core/src/test/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverterTest.java +++ b/core/src/test/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverterTest.java @@ -40,7 +40,6 @@ public class AllocationTokenStatusTransitionConverterTest { @RegisterExtension public final JpaUnitTestExtension jpa = new JpaTestRules.Builder() - .withInitScript("sql/flyway/V14__load_extension_for_hstore.sql") .withEntityClass(AllocationTokenStatusTransitionConverterTestEntity.class) .buildUnitTestRule(); diff --git a/core/src/test/java/google/registry/persistence/converter/BillingCostTransitionConverterTest.java b/core/src/test/java/google/registry/persistence/converter/BillingCostTransitionConverterTest.java index 6091dbd67..dd4f94c12 100644 --- a/core/src/test/java/google/registry/persistence/converter/BillingCostTransitionConverterTest.java +++ b/core/src/test/java/google/registry/persistence/converter/BillingCostTransitionConverterTest.java @@ -38,7 +38,6 @@ public class BillingCostTransitionConverterTest { @RegisterExtension public final JpaUnitTestExtension jpa = new JpaTestRules.Builder() - .withInitScript("sql/flyway/V14__load_extension_for_hstore.sql") .withEntityClass(TestEntity.class) .buildUnitTestRule(); diff --git a/core/src/test/java/google/registry/persistence/converter/CurrencyToBillingConverterTest.java b/core/src/test/java/google/registry/persistence/converter/CurrencyToBillingConverterTest.java index 28bbf2a80..2637fc6d4 100644 --- a/core/src/test/java/google/registry/persistence/converter/CurrencyToBillingConverterTest.java +++ b/core/src/test/java/google/registry/persistence/converter/CurrencyToBillingConverterTest.java @@ -35,7 +35,6 @@ public class CurrencyToBillingConverterTest { @RegisterExtension public final JpaUnitTestExtension jpaExtension = new JpaTestRules.Builder() - .withInitScript("sql/flyway/V14__load_extension_for_hstore.sql") .withEntityClass(TestEntity.class) .buildUnitTestRule(); diff --git a/core/src/test/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverterTest.java b/core/src/test/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverterTest.java new file mode 100644 index 000000000..67652dec3 --- /dev/null +++ b/core/src/test/java/google/registry/persistence/converter/DatabaseMigrationScheduleTransitionConverterTest.java @@ -0,0 +1,88 @@ +// 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 static com.google.common.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.util.DateTimeUtils.START_OF_TIME; + +import com.google.common.collect.ImmutableSortedMap; +import google.registry.model.ImmutableObject; +import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState; +import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationStateTransition; +import google.registry.model.common.TimedTransitionProperty; +import google.registry.persistence.transaction.JpaTestRules; +import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestExtension; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.joda.time.DateTime; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Unit tests for {@link DatabaseMigrationScheduleTransitionConverter}. */ +public class DatabaseMigrationScheduleTransitionConverterTest { + + @RegisterExtension + public final JpaUnitTestExtension jpa = + new JpaTestRules.Builder() + .withEntityClass(DatabaseMigrationScheduleTransitionConverterTestEntity.class) + .buildUnitTestRule(); + + private static final ImmutableSortedMap values = + ImmutableSortedMap.of( + START_OF_TIME, + MigrationState.DATASTORE_ONLY, + DateTime.parse("2001-01-01T00:00:00.0Z"), + MigrationState.DATASTORE_PRIMARY, + DateTime.parse("2002-01-01T00:00:00.0Z"), + MigrationState.DATASTORE_PRIMARY_READ_ONLY, + DateTime.parse("2002-01-02T00:00:00.0Z"), + MigrationState.SQL_PRIMARY, + DateTime.parse("2002-01-03T00:00:00.0Z"), + MigrationState.SQL_ONLY); + + @Test + void roundTripConversion_returnsSameTimedTransitionProperty() { + TimedTransitionProperty timedTransitionProperty = + TimedTransitionProperty.fromValueMap(values, MigrationStateTransition.class); + DatabaseMigrationScheduleTransitionConverterTestEntity testEntity = + new DatabaseMigrationScheduleTransitionConverterTestEntity(timedTransitionProperty); + jpaTm().transact(() -> jpaTm().insert(testEntity)); + DatabaseMigrationScheduleTransitionConverterTestEntity persisted = + jpaTm() + .transact( + () -> + jpaTm() + .getEntityManager() + .find(DatabaseMigrationScheduleTransitionConverterTestEntity.class, "id")); + assertThat(persisted.timedTransitionProperty).containsExactlyEntriesIn(timedTransitionProperty); + } + + @Entity + private static class DatabaseMigrationScheduleTransitionConverterTestEntity + extends ImmutableObject { + + @Id String name = "id"; + + TimedTransitionProperty timedTransitionProperty; + + private DatabaseMigrationScheduleTransitionConverterTestEntity() {} + + private DatabaseMigrationScheduleTransitionConverterTestEntity( + TimedTransitionProperty timedTransitionProperty) { + this.timedTransitionProperty = timedTransitionProperty; + } + } +} diff --git a/core/src/test/java/google/registry/persistence/converter/StringMapConverterBaseTest.java b/core/src/test/java/google/registry/persistence/converter/StringMapConverterBaseTest.java index bcaf88a2a..7b72e349a 100644 --- a/core/src/test/java/google/registry/persistence/converter/StringMapConverterBaseTest.java +++ b/core/src/test/java/google/registry/persistence/converter/StringMapConverterBaseTest.java @@ -37,7 +37,6 @@ public class StringMapConverterBaseTest { @RegisterExtension public final JpaUnitTestExtension jpaExtension = new JpaTestRules.Builder() - .withInitScript("sql/flyway/V14__load_extension_for_hstore.sql") .withEntityClass(TestStringMapConverter.class, TestEntity.class) .buildUnitTestRule(); diff --git a/core/src/test/java/google/registry/persistence/converter/TimedTransitionPropertyConverterBaseTest.java b/core/src/test/java/google/registry/persistence/converter/TimedTransitionPropertyConverterBaseTest.java index 4f69ee1a1..fea496e16 100644 --- a/core/src/test/java/google/registry/persistence/converter/TimedTransitionPropertyConverterBaseTest.java +++ b/core/src/test/java/google/registry/persistence/converter/TimedTransitionPropertyConverterBaseTest.java @@ -41,7 +41,6 @@ class TimedTransitionPropertyConverterBaseTest { @RegisterExtension public final JpaUnitTestExtension jpa = new JpaTestRules.Builder() - .withInitScript("sql/flyway/V14__load_extension_for_hstore.sql") .withEntityClass(TestTimedTransitionPropertyConverter.class, TestEntity.class) .buildUnitTestRule(); diff --git a/core/src/test/java/google/registry/persistence/converter/TldStateTransitionConverterTest.java b/core/src/test/java/google/registry/persistence/converter/TldStateTransitionConverterTest.java index b78bbdf12..2f808a62f 100644 --- a/core/src/test/java/google/registry/persistence/converter/TldStateTransitionConverterTest.java +++ b/core/src/test/java/google/registry/persistence/converter/TldStateTransitionConverterTest.java @@ -37,7 +37,6 @@ class TldStateTransitionConverterTest { @RegisterExtension public final JpaUnitTestExtension jpa = new JpaTestRules.Builder() - .withInitScript("sql/flyway/V14__load_extension_for_hstore.sql") .withEntityClass(TestEntity.class) .buildUnitTestRule(); diff --git a/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverageExtension.java b/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverageExtension.java index 7790683b5..a2c3666a5 100644 --- a/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverageExtension.java +++ b/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverageExtension.java @@ -39,15 +39,14 @@ import org.junit.jupiter.api.extension.ExtensionContext; */ public class JpaEntityCoverageExtension implements BeforeEachCallback, AfterEachCallback { - // TODO(weiminyu): update this set when entities written to Cloud SQL and tests are added. private static final ImmutableSet IGNORE_ENTITIES = ImmutableSet.of( - "DelegationSignerData", - "DesignatedContact", - "GracePeriod", - "RegistrarContact", - - // TransactionEntity is trivial, its persistence is tested in TransactionTest. + // DatabaseMigrationStateSchedule is persisted in tests, however any test that sets it + // needs to remove it in order to avoid affecting any other tests running in the same JVM. + // TODO(gbrodman): remove this when we implement proper read-only modes for the + // transaction managers. + "DatabaseMigrationStateSchedule", + // TransactionEntity is trivial; its persistence is tested in TransactionTest. "TransactionEntity"); private static final ImmutableSet> ALL_JPA_ENTITIES = diff --git a/core/src/test/java/google/registry/persistence/transaction/JpaTestRules.java b/core/src/test/java/google/registry/persistence/transaction/JpaTestRules.java index ce12a7d83..de7090dc3 100644 --- a/core/src/test/java/google/registry/persistence/transaction/JpaTestRules.java +++ b/core/src/test/java/google/registry/persistence/transaction/JpaTestRules.java @@ -41,6 +41,8 @@ import org.junit.jupiter.api.extension.ExtensionContext; public class JpaTestRules { private static final String GOLDEN_SCHEMA_SQL_PATH = "sql/schema/nomulus.golden.sql"; + private static final String HSTORE_EXTENSION_SQL_PATH = + "sql/flyway/V14__load_extension_for_hstore.sql"; /** * Junit rule for integration tests with JPA framework, when the underlying database is populated @@ -174,7 +176,8 @@ public class JpaTestRules { "Unit tests must not depend on the Nomulus schema."); return new JpaUnitTestExtension( clock == null ? new FakeClock(DateTime.now(UTC)) : clock, - Optional.ofNullable(initScript), + // Use the hstore extension by default so we can save the migration schedule + Optional.of(initScript == null ? HSTORE_EXTENSION_SQL_PATH : initScript), ImmutableList.copyOf(extraEntityClasses), ImmutableMap.copyOf(userProperties)); } diff --git a/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerExtension.java b/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerExtension.java index 106fa1ee8..5c621a5ae 100644 --- a/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerExtension.java +++ b/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerExtension.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Streams; import com.google.common.io.Resources; +import google.registry.model.common.DatabaseMigrationStateSchedule; import google.registry.persistence.HibernateSchemaExporter; import google.registry.persistence.NomulusPostgreSql; import google.registry.persistence.PersistenceModule; @@ -341,9 +342,12 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft } private ImmutableList> getTestEntities() { - // We have to add the TransactionEntity to extra entities, as this is required by the - // transaction replication mechanism. - return Stream.concat(extraEntityClasses.stream(), Stream.of(TransactionEntity.class)) + // We have to add the DatabaseMigrationStateSchedule and TransactionEntity classes to extra + // entities, as they are required by the transaction manager factory and transaction replication + // mechanism, respectively. + return Stream.concat( + extraEntityClasses.stream(), + Stream.of(DatabaseMigrationStateSchedule.class, TransactionEntity.class)) .collect(toImmutableList()); } } diff --git a/core/src/test/java/google/registry/reporting/billing/GenerateInvoicesActionTest.java b/core/src/test/java/google/registry/reporting/billing/GenerateInvoicesActionTest.java index 3c8db1b99..3e74bdf27 100644 --- a/core/src/test/java/google/registry/reporting/billing/GenerateInvoicesActionTest.java +++ b/core/src/test/java/google/registry/reporting/billing/GenerateInvoicesActionTest.java @@ -37,7 +37,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; class GenerateInvoicesActionTest extends BeamActionTestBase { @RegisterExtension - final AppEngineExtension appEngine = AppEngineExtension.builder().withTaskQueue().build(); + final AppEngineExtension appEngine = + AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build(); private final BillingEmailUtils emailUtils = mock(BillingEmailUtils.class); private FakeClock clock = new FakeClock(); diff --git a/core/src/test/java/google/registry/testing/AppEngineExtension.java b/core/src/test/java/google/registry/testing/AppEngineExtension.java index 92f53674d..ff91d53ac 100644 --- a/core/src/test/java/google/registry/testing/AppEngineExtension.java +++ b/core/src/test/java/google/registry/testing/AppEngineExtension.java @@ -159,12 +159,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa return this; } - /** Turns on Datastore only, for use by test data generators. */ - public Builder withDatastore() { - rule.withDatastore = true; - return this; - } - /** Turns on Cloud SQL only, for use by test data generators. */ public Builder withCloudSql() { rule.withCloudSql = true; @@ -372,7 +366,8 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa checkArgumentNotNull(context, "The ExtensionContext must not be null"); setUp(); if (withCloudSql) { - JpaTestRules.Builder builder = new JpaTestRules.Builder(); + JpaTestRules.Builder builder = + new JpaTestRules.Builder().withEntityClass(jpaTestEntities.toArray(new Class[0])); if (clock != null) { builder.withClock(clock); } @@ -380,8 +375,7 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa jpaIntegrationWithCoverageExtension = builder.buildIntegrationWithCoverageExtension(); jpaIntegrationWithCoverageExtension.beforeEach(context); } else if (withJpaUnitTest) { - jpaUnitTestRule = - builder.withEntityClass(jpaTestEntities.toArray(new Class[0])).buildUnitTestRule(); + jpaUnitTestRule = builder.buildUnitTestRule(); jpaUnitTestRule.beforeEach(context); } else { jpaIntegrationTestRule = builder.buildIntegrationTestRule(); @@ -391,8 +385,10 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa if (isWithDatastoreAndCloudSql()) { injectTmForDualDatabaseTest(context); } - if (!withoutCannedData && (tm().isOfy() || (withCloudSql && !withJpaUnitTest))) { - loadInitialData(); + if (withDatastore || withCloudSql) { + if (!withoutCannedData && (tm().isOfy() || (withCloudSql && !withJpaUnitTest))) { + loadInitialData(); + } } } diff --git a/core/src/test/java/google/registry/testing/DatabaseHelper.java b/core/src/test/java/google/registry/testing/DatabaseHelper.java index 839701909..1fb737bc4 100644 --- a/core/src/test/java/google/registry/testing/DatabaseHelper.java +++ b/core/src/test/java/google/registry/testing/DatabaseHelper.java @@ -35,7 +35,6 @@ import static google.registry.model.ResourceTransferUtils.createTransferResponse import static google.registry.model.ofy.ObjectifyService.auditedOfy; import static google.registry.model.tld.Registry.TldState.GENERAL_AVAILABILITY; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; -import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerUtil.ofyTmOrDoNothing; import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm; @@ -1373,7 +1372,7 @@ public class DatabaseHelper { */ public static void setMigrationScheduleToSqlPrimary(FakeClock fakeClock) { DateTime now = fakeClock.nowUtc(); - ofyTm() + jpaTm() .transact( () -> DatabaseMigrationStateSchedule.set( @@ -1392,12 +1391,12 @@ public class DatabaseHelper { /** 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 - ofyTm() + jpaTm() .transact( () -> - ofyTm() + jpaTm() .loadSingleton(DatabaseMigrationStateSchedule.class) - .ifPresent(ofyTm()::delete)); + .ifPresent(jpaTm()::delete)); DatabaseMigrationStateSchedule.CACHE.invalidateAll(); } diff --git a/core/src/test/java/google/registry/tools/GetDatabaseMigrationStateCommandTest.java b/core/src/test/java/google/registry/tools/GetDatabaseMigrationStateCommandTest.java index ab171558b..88ae00f4e 100644 --- a/core/src/test/java/google/registry/tools/GetDatabaseMigrationStateCommandTest.java +++ b/core/src/test/java/google/registry/tools/GetDatabaseMigrationStateCommandTest.java @@ -15,7 +15,7 @@ package google.registry.tools; import static google.registry.model.common.DatabaseMigrationStateSchedule.DEFAULT_TRANSITION_MAP; -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.collect.ImmutableSortedMap; @@ -59,7 +59,7 @@ public class GetDatabaseMigrationStateCommandTest MigrationState.SQL_PRIMARY, now.plusHours(4), MigrationState.SQL_ONLY); - ofyTm().transact(() -> DatabaseMigrationStateSchedule.set(transitions)); + jpaTm().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 index 9b701dc5b..42eb2fca6 100644 --- a/core/src/test/java/google/registry/tools/SetDatabaseMigrationStateCommandTest.java +++ b/core/src/test/java/google/registry/tools/SetDatabaseMigrationStateCommandTest.java @@ -17,7 +17,7 @@ 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.ofyTm; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -44,15 +44,15 @@ public class SetDatabaseMigrationStateCommandTest @TestOfyAndSql void testSuccess_setsBasicSchedule() throws Exception { assertThat(DatabaseMigrationStateSchedule.get()).isEqualTo(DEFAULT_TRANSITION_MAP); - assertThat(ofyTm().transact(() -> ofyTm().loadSingleton(DatabaseMigrationStateSchedule.class))) + assertThat(jpaTm().transact(() -> jpaTm().loadSingleton(DatabaseMigrationStateSchedule.class))) .isEmpty(); runCommandForced("--migration_schedule=1970-01-01T00:00:00.000Z=DATASTORE_ONLY"); // use a raw ofy call to check what's in the DB - ofyTm() + jpaTm() .transact( () -> assertThat( - ofyTm() + jpaTm() .loadSingleton(DatabaseMigrationStateSchedule.class) .get() .migrationTransitions) diff --git a/core/src/test/resources/google/registry/export/backup_kinds.txt b/core/src/test/resources/google/registry/export/backup_kinds.txt index 1bb2b7568..c0ea3fbab 100644 --- a/core/src/test/resources/google/registry/export/backup_kinds.txt +++ b/core/src/test/resources/google/registry/export/backup_kinds.txt @@ -2,7 +2,6 @@ AllocationToken Cancellation ContactResource Cursor -DatabaseMigrationStateSchedule DomainBase EntityGroupRoot EppResourceIndex diff --git a/core/src/test/resources/google/registry/export/crosstld_kinds.txt b/core/src/test/resources/google/registry/export/crosstld_kinds.txt index 7cbfa8d9b..df8299d80 100644 --- a/core/src/test/resources/google/registry/export/crosstld_kinds.txt +++ b/core/src/test/resources/google/registry/export/crosstld_kinds.txt @@ -1,7 +1,6 @@ ClaimsList ClaimsListSingleton Cursor -DatabaseMigrationStateSchedule Registrar RegistrarContact Registry diff --git a/core/src/test/resources/google/registry/model/schema.txt b/core/src/test/resources/google/registry/model/schema.txt index 96b482418..f55bb5c0c 100644 --- a/core/src/test/resources/google/registry/model/schema.txt +++ b/core/src/test/resources/google/registry/model/schema.txt @@ -78,23 +78,6 @@ class google.registry.model.common.Cursor { google.registry.model.UpdateAutoTimestamp lastUpdateTime; org.joda.time.DateTime cursorTime; } -class google.registry.model.common.DatabaseMigrationStateSchedule { - @Id long id; - @Parent com.googlecode.objectify.Key parent; - google.registry.model.common.TimedTransitionProperty migrationTransitions; -} -enum google.registry.model.common.DatabaseMigrationStateSchedule$MigrationState { - DATASTORE_ONLY; - DATASTORE_PRIMARY; - DATASTORE_PRIMARY_READ_ONLY; - SQL_ONLY; - SQL_PRIMARY; - SQL_PRIMARY_READ_ONLY; -} -class google.registry.model.common.DatabaseMigrationStateSchedule$MigrationStateTransition { - google.registry.model.common.DatabaseMigrationStateSchedule$MigrationState migrationState; - org.joda.time.DateTime transitionTime; -} class google.registry.model.common.EntityGroupRoot { @Id java.lang.String id; google.registry.model.UpdateAutoTimestamp updateTimestamp; diff --git a/core/src/test/resources/google/registry/persistence/transaction/test_schema.sql b/core/src/test/resources/google/registry/persistence/transaction/test_schema.sql index 1edc161f7..7a29bd5e7 100644 --- a/core/src/test/resources/google/registry/persistence/transaction/test_schema.sql +++ b/core/src/test/resources/google/registry/persistence/transaction/test_schema.sql @@ -12,6 +12,8 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA public; + CREATE TABLE Person ( age INT NOT NULL ); diff --git a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html index 47b793d7e..b7257027e 100644 --- a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html @@ -261,7 +261,7 @@ td.section { generated on - 2021-08-10 16:04:36.360808 + 2021-08-10 20:50:09.993881 last flyway file @@ -284,7 +284,7 @@ td.section { generated on - 2021-08-10 16:04:36.360808 + 2021-08-10 20:50:09.993881 diff --git a/db/src/main/resources/sql/er_diagram/full_er_diagram.html b/db/src/main/resources/sql/er_diagram/full_er_diagram.html index 363a72986..555f433e7 100644 --- a/db/src/main/resources/sql/er_diagram/full_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/full_er_diagram.html @@ -261,7 +261,7 @@ td.section { generated on - 2021-08-10 16:04:34.151068 + 2021-08-10 20:50:07.996141 last flyway file @@ -284,7 +284,7 @@ td.section { generated on - 2021-08-10 16:04:34.151068 + 2021-08-10 20:50:07.996141 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 e0ea31f8c..5a96e4ded 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -238,6 +238,12 @@ 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,