1
0
mirror of https://github.com/google/nomulus synced 2026-05-24 08:41:48 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Michael Muller
ebc8d54f94 Add persistence.xml to the war files (#293)
* Add persistence.xml to the war files
* Always use the DummyJpaTransactionManager

Use the DJTM until we get all of the dependencies set up for all of the
environments.

This shouldn't affect any of the unit tests, these use the
JpaTransactionManagerRule to set up a local database and connection.

This fixes the App Engine build.
2019-10-06 21:32:27 -04:00
sarahcaseybot
e9ce389269 Implement UpdateAutoTimestampConverter (#298)
* Implement a JPA-based converter for UpdateAutoTimestamp, allowing us to persist instances of this class.
2019-10-04 16:45:32 -04:00
Michael Muller
e07be9780e Implement CreateAutoTimestampConverter (#282)
* Implement CreateAutoTimestampConverter

Implement a JPA-based converter for CreateAutoTimestamp, allowing us to
persist instances of this class.

Note that converters appear to be required to convert to and from database
types that are generally known to JDBC.  For example, conversion to Timestamp
works, conversion to OffsetDateTime does not (even though this works through
the JDBC interface directly).
2019-10-04 16:31:14 -04:00
Michael Muller
31b5abb9a1 Improve error handling for environment property (#295)
* Improve error handling for environment property

Improve the error messages that we get for a bad or missing environment
property.  Move the property processing into the main build so that we do it
only once and configure all of the appengine deployment tasks to check that a
project has been defined and print a friendly error if it hasn't.

Note that even if the check isn't configured, this change prevents deployment
because the gcpProject will be set to null.  It just won't print as useful an
error message.

Tested: ran appengineDeployAll with and without the environment property, ran
"build" to verify that we don't get any complaints for non-deployment targets.

* Changes for review.

* Changes for review

* Changes for review.

* Changes for review.
2019-10-04 13:23:36 -04:00
Michael Muller
aa4e242a34 Give JpaTransactionManagerRule more parameters (#292)
* Give JpaTransactionManagerRule more parameters

Allow users of the rule to add annotated classes and properties, both useful
for testing.

* Change in response to review.

* Changes for review.

* Move test EntityManagerFactory create method

Move the test create method into the JpaTransactionManagerRuleTest.

* Remove nomulus SQL dialect from G.S.S.Command

Remove NomulusPostgreSQLDialect from GenerateSqlSchemaCommand (it has been
moved to its own top-level class).
2019-10-03 16:14:28 -04:00
Weimin Yu
964f264c9d Add maven-publish task for SQL schema jar (#289)
* Add maven-publish task for SQL schema jar

Add task to publish SQL schema jar with flyway scripts and
golden schema to a maven repo. This will be used
for pre-release testing in the future.

This task is not part of build and needs to be invoked explicitly.

User needs to provide schema_jar_repo and schema_version
properties.

* Merge branch 'master' of https://github.com/google/nomulus into publish-schema-jar

* Add maven-publish task for SQL schema jar

Add task to publish SQL schema jar with flyway scripts and
golden schema to a maven repo. This will be used
for pre-release testing in the future.

This task is not part of build and needs to be invoked explicitly.

User needs to provide schema_jar_repo and schema_version
properties.
2019-10-02 14:27:36 -04:00
gbrodman
cc018a6dac Don't crash on a null completion timestamp (#296)
* Don't crash on a null completion timestamp

* optional
2019-10-02 12:53:22 -04:00
Weimin Yu
c25adbbd9c Restrict nomulus user access to flyway table (#297)
* Restrict nomulus user access to flyway table

The regular read-write user should not have write permissions to
the flyway metadata table.
2019-10-02 11:05:46 -04:00
17 changed files with 549 additions and 79 deletions

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
apply from: "${rootDir.path}/projects.gradle"
apply plugin: 'war'
def environment = rootProject.environment
@@ -26,14 +25,19 @@ project.convention.plugins['war'].webAppDirName =
apply plugin: 'com.google.cloud.tools.appengine'
def coreResourcesDir = "${rootDir}/core/build/resources/main"
// Get the web.xml file for the service.
war {
webInf {
from "../../core/src/main/java/google/registry/env/common/${project.name}/WEB-INF"
from("${coreResourcesDir}/META-INF/persistence.xml") {
into "classes/META-INF"
}
}
}
def coreResourcesDir = "${rootDir}/core/build/resources/main"
war {
from("${coreResourcesDir}/google/registry/ui/html") {
include "*.html"
@@ -68,6 +72,10 @@ appengine {
if (!rootProject.prodOrSandboxEnv) {
version = 'GCLOUD_CONFIG'
}
// Don't set gcpProject directly, it gets overriden in ./build.gradle.
// Do -P environment={crash,alpha} instead. For sandbox/production,
// use Spinnaker.
projectId = gcpProject
}
}
@@ -92,4 +100,15 @@ explodeWar.doLast {
rootProject.deploy.dependsOn appengineDeployAll
rootProject.stage.dependsOn appengineStage
appengineDeployAll.dependsOn rootProject.verifyDeployment
// Impose verification for all of the deployment tasks. We haven't found a
// better way to do this other than to apply to each of them independently.
// If a new task gets added, it will still fail if "environment" is not defined
// because gcpProject is null. We just won't get as friendly an error message.
appengineDeployAll.configure rootProject.verifyDeploymentConfig
appengineDeploy.configure rootProject.verifyDeploymentConfig
appengineDeployCron.configure rootProject.verifyDeploymentConfig
appengineDeployDispatch.configure rootProject.verifyDeploymentConfig
appengineDeployDos.configure rootProject.verifyDeploymentConfig
appengineDeployIndex.configure rootProject.verifyDeploymentConfig
appengineDeployQueue.configure rootProject.verifyDeploymentConfig

View File

@@ -109,21 +109,64 @@ task deploy {
description = 'Deploys all services to App Engine.'
}
task verifyDeployment {
group = 'deployment'
description = 'Ensure that one cannot deploy to production or sandbox.'
doFirst {
if (rootProject.prodOrSandboxEnv) {
throw new GradleException("Cannot deploy to production or sandbox.");
}
}
}
task stage {
group = 'deployment'
description = 'Generates application directories for all services.'
}
// App-engine environment configuration. We set up all of the variables in
// the root project.
def environments = ['production', 'sandbox', 'alpha', 'crash']
// TODO(mmuller): Move this into internal specialization code.
def projects = ['production': 'domain-registry',
'sandbox' : 'domain-registry-sandbox',
'alpha' : 'domain-registry-alpha',
'crash' : 'domain-registry-crash']
def gcpProject = null
if (environment == '') {
// Keep the project null, this will prevent deployment. Set the
// environment to "alpha" because other code needs this property to
// explode the war file.
environment = 'alpha'
} else if (environment != 'production' && environment != 'sandbox') {
gcpProject = projects[environment]
if (gcpProject == null) {
throw new GradleException("-Penvironment must be one of ${environments}.")
}
}
rootProject.ext.environment = environment
rootProject.ext.gcpProject = gcpProject
rootProject.ext.prodOrSandboxEnv = environment in ['production', 'sandbox']
// Function to verify that the deployment parameters have been set.
def verifyDeploymentParams() {
if (prodOrSandboxEnv) {
// Do not deploy to prod or sandbox. Print a prominent error in bright red.
System.err.println('\033[31;1m-----------------------------------------------------------------')
System.err.println('*** DANGER WILL ROBINSON!')
System.err.println('*** You may not deploy to production or sandbox from gradle. Do a')
System.err.println('*** release from Spinnaker, see deployment playbook.')
System.err.println('-----------------------------------------------------------------')
throw new GradleException('Aborting. See prominent error above.')
} else if (gcpProject == null) {
def error = 'You must specify -P environment={alpha,crash}'
System.err.println("\033[33;1m${error}\033[0m")
throw GradleException("Aborting: ${error}")
}
}
// Closure that we can just drop into all of our deployment tasks.
rootProject.ext.verifyDeploymentConfig = {
doFirst { verifyDeploymentParams() }
}
// Subproject configuration.
allprojects {
// Skip no-op project
if (project.name == 'services') return

View File

@@ -14,11 +14,8 @@
package google.registry.model.transaction;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
import com.google.common.annotations.VisibleForTesting;
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.persistence.DaggerPersistenceComponent;
/** Factory class to create {@link TransactionManager} instance. */
// TODO: Rename this to PersistenceFactory and move to persistence package.
@@ -30,11 +27,13 @@ public class TransactionManagerFactory {
private TransactionManagerFactory() {}
private static JpaTransactionManager createJpaTransactionManager() {
if (SystemProperty.environment.value() == Value.Production) {
return DaggerPersistenceComponent.create().jpaTransactionManager();
} else {
return DummyJpaTransactionManager.create();
}
// TODO(shicong): There is currently no environment where we want to create a real JPA
// transaction manager here. The unit tests that require one are all set up using
// JpaTransactionManagerRule which launches its own PostgreSQL instance. When we actually have
// PostgreSQL tables in production, ensure that all of the test environments are set up
// correctly and restore the code that creates a JpaTransactionManager when
// RegistryEnvironment.get() != UNITTEST.
return DummyJpaTransactionManager.create();
}
private static TransactionManager createTransactionManager() {

View File

@@ -0,0 +1,50 @@
// Copyright 2019 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;
import static com.google.common.base.MoreObjects.firstNonNull;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.CreateAutoTimestamp;
import google.registry.util.DateTimeUtils;
import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import org.joda.time.DateTime;
/** JPA converter to for storing/retrieving CreateAutoTimestamp objects. */
@Converter
public class CreateAutoTimestampConverter
implements AttributeConverter<CreateAutoTimestamp, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(CreateAutoTimestamp entity) {
DateTime dateTime =
firstNonNull(((CreateAutoTimestamp) entity).getTimestamp(), jpaTm().getTransactionTime());
return Timestamp.from(DateTimeUtils.toZonedDateTime(dateTime).toInstant());
}
@Override
@Nullable
public CreateAutoTimestamp convertToEntityAttribute(@Nullable Timestamp columnValue) {
if (columnValue == null) {
return null;
}
ZonedDateTime zdt = ZonedDateTime.ofInstant(columnValue.toInstant(), ZoneOffset.UTC);
return CreateAutoTimestamp.create(DateTimeUtils.toJodaDateTime(zdt));
}
}

View File

@@ -0,0 +1,27 @@
// Copyright 2019 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;
import java.sql.Types;
import org.hibernate.dialect.PostgreSQL95Dialect;
/** Nomulus mapping rules for column types in Postgresql. */
public class NomulusPostgreSQLDialect extends PostgreSQL95Dialect {
public NomulusPostgreSQLDialect() {
super();
registerColumnType(Types.VARCHAR, "text");
registerColumnType(Types.TIMESTAMP_WITH_TIMEZONE, "timestamptz");
registerColumnType(Types.TIMESTAMP, "timestamptz");
}
}

View File

@@ -70,6 +70,7 @@ public class PersistenceModule {
properties.put(HIKARI_MINIMUM_IDLE, getHibernateHikariMinimumIdle());
properties.put(HIKARI_MAXIMUM_POOL_SIZE, getHibernateHikariMaximumPoolSize());
properties.put(HIKARI_IDLE_TIMEOUT, getHibernateHikariIdleTimeout());
properties.put(Environment.DIALECT, NomulusPostgreSQLDialect.class.getName());
return properties.build();
}
@@ -102,8 +103,13 @@ public class PersistenceModule {
properties.put(Environment.URL, jdbcUrl);
properties.put(Environment.USER, username);
properties.put(Environment.PASS, password);
// If there are no annotated classes, we can create the EntityManagerFactory from the generic
// method. Otherwise we have to use a more tailored approach. Note that this adds to the set
// of annotated classes defined in the configuration, it does not override them.
EntityManagerFactory emf =
Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME, properties);
checkState(
emf != null,
"Persistence.createEntityManagerFactory() returns a null EntityManagerFactory");

View File

@@ -0,0 +1,46 @@
// Copyright 2019 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;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.util.DateTimeUtils;
import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/** JPA converter for storing/retrieving UpdateAutoTimestamp objects. */
@Converter
public class UpdateAutoTimestampConverter
implements AttributeConverter<UpdateAutoTimestamp, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(UpdateAutoTimestamp entity) {
return Timestamp.from(DateTimeUtils.toZonedDateTime(jpaTm().getTransactionTime()).toInstant());
}
@Override
@Nullable
public UpdateAutoTimestamp convertToEntityAttribute(@Nullable Timestamp columnValue) {
if (columnValue == null) {
return null;
}
ZonedDateTime zdt = ZonedDateTime.ofInstant(columnValue.toInstant(), ZoneOffset.UTC);
return UpdateAutoTimestamp.create(DateTimeUtils.toJodaDateTime(zdt));
}
}

View File

@@ -21,7 +21,9 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.util.DateTimeUtils;
import java.time.ZonedDateTime;
import java.util.Optional;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
@@ -149,8 +151,9 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
return toJodaDateTime(creationTimestamp);
}
public DateTime getCompletionTimestamp() {
return toJodaDateTime(completionTimestamp);
/** Returns the completion timestamp, or empty if this lock has not been completed yet. */
public Optional<DateTime> getCompletionTimestamp() {
return Optional.ofNullable(completionTimestamp).map(DateTimeUtils::toJodaDateTime);
}
public String getVerificationCode() {

View File

@@ -28,20 +28,19 @@ import google.registry.model.eppcommon.Trid;
import google.registry.model.transfer.BaseTransferObject;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.NomulusNamingStrategy;
import google.registry.persistence.NomulusPostgreSQLDialect;
import google.registry.schema.domain.RegistryLock;
import google.registry.schema.tld.PremiumList;
import google.registry.schema.tmch.ClaimsList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Types;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.PostgreSQL95Dialect;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
import org.joda.time.Period;
@@ -198,13 +197,4 @@ public class GenerateSqlSchemaCommand implements Command {
}
}
}
/** Nomulus mapping rules for column types in Postgresql. */
public static class NomulusPostgreSQLDialect extends PostgreSQL95Dialect {
public NomulusPostgreSQLDialect() {
super();
registerColumnType(Types.VARCHAR, "text");
registerColumnType(Types.TIMESTAMP, "timestamptz");
}
}
}

View File

@@ -16,9 +16,19 @@ package google.registry.model.transaction;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import google.registry.persistence.PersistenceModule;
import google.registry.testing.FakeClock;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
import org.joda.time.DateTime;
import org.junit.rules.ExternalResource;
import org.junit.rules.RuleChain;
@@ -41,12 +51,20 @@ public class JpaTransactionManagerRule extends ExternalResource {
private final DateTime now = DateTime.now(UTC);
private final FakeClock clock = new FakeClock(now);
private final String initScript;
private final ImmutableList<Class> extraEntityClasses;
private final ImmutableMap userProperties;
private JdbcDatabaseContainer database;
private EntityManagerFactory emf;
private JpaTransactionManager cachedTm;
private JpaTransactionManagerRule(String initScript) {
private JpaTransactionManagerRule(
String initScript,
ImmutableList<Class> extraEntityClasses,
ImmutableMap<String, String> userProperties) {
this.initScript = initScript;
this.extraEntityClasses = extraEntityClasses;
this.userProperties = userProperties;
}
/** Wraps {@link JpaTransactionManagerRule} in a {@link PostgreSQLContainer}. */
@@ -60,12 +78,21 @@ public class JpaTransactionManagerRule extends ExternalResource {
@Override
public void before() {
ImmutableMap properties = PersistenceModule.providesDefaultDatabaseConfigs();
if (!userProperties.isEmpty()) {
// If there are user properties, create a new properties object with these added.
ImmutableMap.Builder builder = properties.builder();
builder.putAll(userProperties);
properties = builder.build();
}
emf =
PersistenceModule.create(
createEntityManagerFactory(
database.getJdbcUrl(),
database.getUsername(),
database.getPassword(),
PersistenceModule.providesDefaultDatabaseConfigs());
properties,
extraEntityClasses);
JpaTransactionManagerImpl txnManager = new JpaTransactionManagerImpl(emf, clock);
cachedTm = TransactionManagerFactory.jpaTm;
TransactionManagerFactory.jpaTm = txnManager;
@@ -80,6 +107,24 @@ public class JpaTransactionManagerRule extends ExternalResource {
cachedTm = null;
}
/** Constructs the {@link EntityManagerFactory} instance. */
private static EntityManagerFactory createEntityManagerFactory(
String jdbcUrl,
String username,
String password,
ImmutableMap<String, String> configs,
ImmutableList<Class> extraEntityClasses) {
HashMap<String, String> properties = Maps.newHashMap(configs);
properties.put(Environment.URL, jdbcUrl);
properties.put(Environment.USER, username);
properties.put(Environment.PASS, password);
MetadataSources metadataSources =
new MetadataSources(new StandardServiceRegistryBuilder().applySettings(properties).build());
extraEntityClasses.forEach(metadataSources::addAnnotatedClass);
return metadataSources.buildMetadata().getSessionFactoryBuilder().build();
}
/** Returns the {@link FakeClock} used by the underlying {@link JpaTransactionManagerImpl}. */
public FakeClock getTxnClock() {
return clock;
@@ -88,6 +133,8 @@ public class JpaTransactionManagerRule extends ExternalResource {
/** Builder for {@link JpaTransactionManagerRule}. */
public static class Builder {
private String initScript;
private List<Class> extraEntityClasses = new ArrayList<Class>();
private Map<String, String> userProperties = new HashMap<String, String>();
/**
* Sets the SQL script to be used to initialize the database. If not set,
@@ -98,12 +145,27 @@ public class JpaTransactionManagerRule extends ExternalResource {
return this;
}
/** Adds an annotated class to the known entities for the database. */
public Builder withEntityClass(Class clazz) {
this.extraEntityClasses.add(clazz);
return this;
}
/** Adds the specified property to those used to initialize the transaction manager. */
public Builder withProperty(String name, String value) {
this.userProperties.put(name, value);
return this;
}
/** Builds a {@link JpaTransactionManagerRule} instance. */
public JpaTransactionManagerRule build() {
if (initScript == null) {
initScript = SCHEMA_GOLDEN_SQL;
}
return new JpaTransactionManagerRule(initScript);
return new JpaTransactionManagerRule(
initScript,
ImmutableList.copyOf(extraEntityClasses),
ImmutableMap.copyOf(userProperties));
}
}
}

View File

@@ -18,8 +18,12 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.JUnitBackports.assertThrows;
import google.registry.model.ImmutableObject;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PersistenceException;
import org.hibernate.cfg.Environment;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,7 +35,10 @@ public class JpaTransactionManagerRuleTest {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder().build();
new JpaTransactionManagerRule.Builder()
.withEntityClass(TestEntity.class)
.withProperty(Environment.HBM2DDL_AUTO, "update")
.build();
@Test
public void verifiesRuleWorks() {
@@ -56,4 +63,28 @@ public class JpaTransactionManagerRuleTest {
assertThat(results).isEmpty();
});
}
@Test
public void testExtraParameters() {
// This test verifies that 1) withEntityClass() has registered TestEntity and 2) The table
// has been created, implying withProperty(HBM2DDL_AUTO, "update") worked.
TestEntity original = new TestEntity("key", "value");
jpaTm().transact(() -> jpaTm().getEntityManager().persist(original));
TestEntity retrieved =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "key"));
assertThat(retrieved).isEqualTo(original);
}
@Entity(name = "TestEntity") // Specify name to avoid nested class naming issues.
static class TestEntity extends ImmutableObject {
@Id String key;
String value;
TestEntity(String key, String value) {
this.key = key;
this.value = value;
}
TestEntity() {}
}
}

View File

@@ -0,0 +1,91 @@
// Copyright 2019 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;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.transaction.JpaTransactionManagerRule;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.Environment;
import org.joda.time.DateTime;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.testcontainers.containers.PostgreSQLContainer;
@RunWith(JUnit4.class)
public class CreateAutoTimestampConverterTest {
@ClassRule
public static PostgreSQLContainer postgres =
new PostgreSQLContainer()
.withDatabaseName("postgres")
.withUsername("postgres")
.withPassword("domain-registry");
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder()
.withEntityClass(TestEntity.class)
.withProperty(Environment.HBM2DDL_AUTO, "update")
.build();
public CreateAutoTimestampConverterTest() {}
@Test
public void testTypeConversion() {
CreateAutoTimestamp ts = CreateAutoTimestamp.create(DateTime.parse("2019-09-9T11:39:00Z"));
TestEntity ent = new TestEntity("myinst", ts);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
TestEntity result =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst"));
assertThat(result).isEqualTo(new TestEntity("myinst", ts));
}
@Test
public void testAutoInitialization() {
CreateAutoTimestamp ts = CreateAutoTimestamp.create(null);
TestEntity ent = new TestEntity("autoinit", ts);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
TestEntity result =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "autoinit"));
assertThat(result.cat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
public static class TestEntity extends ImmutableObject {
@Id String name;
@Convert(converter = CreateAutoTimestampConverter.class)
CreateAutoTimestamp cat;
public TestEntity() {}
public TestEntity(String name, CreateAutoTimestamp cat) {
this.name = name;
this.cat = cat;
}
}
}

View File

@@ -0,0 +1,102 @@
// Copyright 2019 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;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.ImmutableObject;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.transaction.JpaTransactionManagerRule;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.Environment;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.testcontainers.containers.PostgreSQLContainer;
@RunWith(JUnit4.class)
public class UpdateAutoTimestampConverterTest {
@ClassRule
public static PostgreSQLContainer postgres =
new PostgreSQLContainer()
.withDatabaseName("postgres")
.withUsername("postgres")
.withPassword("domain-registry");
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder()
.withEntityClass(TestEntity.class)
.withProperty(Environment.HBM2DDL_AUTO, "update")
.build();
public UpdateAutoTimestampConverterTest() {}
@Test
public void testTypeConversion() {
TestEntity ent = new TestEntity("myinst", null);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
TestEntity result =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst"));
assertThat(result.name).isEqualTo("myinst");
assertThat(result.uat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
}
@Test
public void testTimeChangesOnSubsequentTransactions() {
TestEntity ent1 = new TestEntity("myinst1", null);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent1));
TestEntity result1 =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst1"));
jpaTmRule.getTxnClock().advanceOneMilli();
TestEntity ent2 = new TestEntity("myinst2", result1.uat);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent2));
TestEntity result2 =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst2"));
assertThat(result1.uat.getTimestamp()).isNotEqualTo(result2.uat.getTimestamp());
assertThat(result2.uat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
public static class TestEntity extends ImmutableObject {
@Id String name;
@Convert(converter = UpdateAutoTimestampConverter.class)
UpdateAutoTimestamp uat;
public TestEntity() {}
public TestEntity(String name, UpdateAutoTimestamp uat) {
this.name = name;
this.uat = uat;
}
}
}

View File

@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import com.google.common.collect.ImmutableList
plugins {
id "org.flywaydb.flyway" version "6.0.1"
id 'maven-publish'
}
ext {
@@ -75,6 +74,35 @@ ext {
}
}
task schemaJar(type: Jar) {
archiveBaseName = 'schema'
from(sourceSets.main.resources) {
include 'sql/flyway/**'
include 'sql/schema/nomulus.golden.sql'
}
}
artifacts {
archives schemaJar
}
publishing {
repositories {
maven {
url project.schema_jar_repo
}
}
publications {
schemaOrmPublication(MavenPublication) {
groupId 'google.registry'
artifactId 'schema'
version project.schema_version
artifact schemaJar
}
}
}
flyway {
def accessInfo = project.ext.getJdbcAccessInfo()

View File

@@ -12,10 +12,12 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Script to create a user with read-write permission to all tables.
-- Script to create a user with read-write permission to all tables (except for
-- WRITE permissions to flyway_schema_history).
CREATE USER :username ENCRYPTED PASSWORD :'password';
GRANT CONNECT ON DATABASE postgres TO :username;
GRANT USAGE ON SCHEMA public TO :username;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :username;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO :username;
REVOKE INSERT, UPDATE, DELETE ON TABLE public.flyway_schema_history FROM :username;

View File

@@ -8,6 +8,9 @@ verboseTestOutput=false
flowDocsFile=
enableDependencyLocking=true
# GAE Environment for deployment and staging.
environment=
# Cloud SQL properties
# A registry environment name (e.g., 'alpha') or a host[:port] string
@@ -15,3 +18,8 @@ dbServer=
dbName=postgres
dbUser=
dbPassword=
# Maven repository of the Cloud SQL schema jar, which contains the
# SQL DDL scripts.
schema_jar_repo=
schema_version=

View File

@@ -1,37 +0,0 @@
// Copyright 2019 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.
def environments = ['production', 'sandbox', 'alpha', 'crash']
def projects = ['production': 'domain-registry',
'sandbox' : 'domain-registry-sandbox',
'alpha' : 'domain-registry-alpha',
'crash' : 'domain-registry-crash']
def environment = rootProject.findProperty("environment")
if (environment == null) {
environment = 'production'
}
def gcpProject = projects[environment]
if (gcpProject == null) {
throw new GradleException("-Penvironment must be one of ${environments}.")
}
rootProject.ext.environment = environment
rootProject.ext.gcpProject = gcpProject
rootProject.ext.prodOrSandboxEnv = ['production', 'sandbox'].contains(environment)