Integrate transaction persistence into JpaTM (#717)

* Integrate transaction persistence into JpaTM

Store the serialized transaction whenever we commit from the JPA transaction
manager.  This change also adds:

-   The Transaction table.
-   The TransactionEntity which is stored in it.
-   Changes to the test infrastructure to register the TransactionEntity for
    tests where we don't load the nomulus schema.
-   A new configuration variable to allow us to turn the transaction
    persistence functionality on and off (default is "off").

* Changes for review.

* Incremented sequence number of flyway file
This commit is contained in:
Michael Muller 2020-07-28 19:23:44 -04:00 committed by GitHub
parent a56713e4be
commit a802be2a9b
13 changed files with 250 additions and 16 deletions

View file

@ -42,7 +42,13 @@ public class JpaEntityCoverageExtension implements BeforeEachCallback, AfterEach
// TODO(weiminyu): update this set when entities written to Cloud SQL and tests are added.
private static final ImmutableSet<String> IGNORE_ENTITIES =
ImmutableSet.of(
"DelegationSignerData", "DesignatedContact", "GracePeriod", "RegistrarContact");
"DelegationSignerData",
"DesignatedContact",
"GracePeriod",
"RegistrarContact",
// TransactionEntity is trivial, its persistence is tested in TransactionTest.
"TransactionEntity");
private static final ImmutableSet<Class> ALL_JPA_ENTITIES =
PersistenceXmlUtility.getManagedClasses().stream()

View file

@ -165,10 +165,10 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft
}
executeSql(readSqlInClassPath(DB_CLEANUP_SQL_PATH));
initScriptPath.ifPresent(path -> executeSql(readSqlInClassPath(path)));
if (!extraEntityClasses.isEmpty()) {
if (!includeNomulusSchema) {
File tempSqlFile = File.createTempFile("tempSqlFile", ".sql");
tempSqlFile.deleteOnExit();
exporter.export(extraEntityClasses, tempSqlFile);
exporter.export(getTestEntities(), tempSqlFile);
executeSql(new String(Files.readAllBytes(tempSqlFile.toPath()), StandardCharsets.UTF_8));
}
@ -187,11 +187,7 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft
assertReasonableNumDbConnections();
emf =
createEntityManagerFactory(
getJdbcUrl(),
database.getUsername(),
database.getPassword(),
properties,
extraEntityClasses);
getJdbcUrl(), database.getUsername(), database.getPassword(), properties);
emfEntityHash = entityHash;
}
@ -309,11 +305,7 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft
/** Constructs the {@link EntityManagerFactory} instance. */
private EntityManagerFactory createEntityManagerFactory(
String jdbcUrl,
String username,
String password,
ImmutableMap<String, String> configs,
ImmutableList<Class> extraEntityClasses) {
String jdbcUrl, String username, String password, ImmutableMap<String, String> configs) {
HashMap<String, String> properties = Maps.newHashMap(configs);
properties.put(Environment.URL, jdbcUrl);
properties.put(Environment.USER, username);
@ -342,7 +334,14 @@ abstract class JpaTransactionManagerExtension implements BeforeEachCallback, Aft
descriptor.getManagedClassNames().addAll(nonEntityClasses);
}
extraEntityClasses.stream().map(Class::getName).forEach(descriptor::addClasses);
getTestEntities().stream().map(Class::getName).forEach(descriptor::addClasses);
return Bootstrap.getEntityManagerFactoryBuilder(descriptor, properties).build();
}
private ImmutableList<Class> 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))
.collect(toImmutableList());
}
}

View file

@ -15,16 +15,19 @@
package google.registry.persistence.transaction;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static org.junit.Assert.assertThrows;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import google.registry.testing.AppEngineExtension;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
import org.junit.jupiter.api.BeforeEach;
@ -99,6 +102,51 @@ public class TransactionTest {
StreamCorruptedException.class, () -> Transaction.deserialize(new byte[] {1, 2, 3, 4}));
}
@Test
public void testTransactionSerialization() throws IOException {
RegistryConfig.overrideCloudSqlReplicateTransactions(true);
try {
jpaTm()
.transact(
() -> {
jpaTm().saveNew(fooEntity);
jpaTm().saveNew(barEntity);
});
TransactionEntity txnEnt =
jpaTm().transact(() -> jpaTm().load(VKey.createSql(TransactionEntity.class, 1L)));
Transaction txn = Transaction.deserialize(txnEnt.contents);
txn.writeToDatastore();
ofyTm()
.transact(
() -> {
assertThat(ofyTm().load(fooEntity.key())).isEqualTo(fooEntity);
assertThat(ofyTm().load(barEntity.key())).isEqualTo(barEntity);
});
// Verify that no transaction was persisted for the load transaction.
assertThat(
jpaTm()
.transact(() -> jpaTm().checkExists(VKey.createSql(TransactionEntity.class, 2L))))
.isFalse();
} finally {
RegistryConfig.overrideCloudSqlReplicateTransactions(false);
}
}
@Test
public void testTransactionSerializationDisabledByDefault() {
jpaTm()
.transact(
() -> {
jpaTm().saveNew(fooEntity);
jpaTm().saveNew(barEntity);
});
assertThat(
jpaTm()
.transact(() -> jpaTm().checkExists(VKey.createSql(TransactionEntity.class, 1L))))
.isFalse();
}
@Entity(name = "TxnTestEntity")
@javax.persistence.Entity(name = "TestEntity")
private static class TestEntity extends ImmutableObject {