Skip to content

Commit

Permalink
Set up database connection pool (#234)
Browse files Browse the repository at this point in the history
Set up database connection pool
  • Loading branch information
hstonec committed Aug 29, 2019
1 parent b5ef99a commit 487b695
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 23 deletions.
3 changes: 1 addition & 2 deletions core/build.gradle
Expand Up @@ -218,7 +218,6 @@ dependencies {
compile deps['org.bouncycastle:bcpg-jdk15on'] compile deps['org.bouncycastle:bcpg-jdk15on']
testCompile deps['org.bouncycastle:bcpkix-jdk15on'] testCompile deps['org.bouncycastle:bcpkix-jdk15on']
compile deps['org.bouncycastle:bcprov-jdk15on'] compile deps['org.bouncycastle:bcprov-jdk15on']
compile deps['org.hibernate:hibernate-core']
compile deps['org.joda:joda-money'] compile deps['org.joda:joda-money']
compile deps['org.json:json'] compile deps['org.json:json']
testCompile deps['org.mortbay.jetty:jetty'] testCompile deps['org.mortbay.jetty:jetty']
Expand Down Expand Up @@ -254,7 +253,7 @@ dependencies {
testCompile deps['org.hamcrest:hamcrest-all'] testCompile deps['org.hamcrest:hamcrest-all']
testCompile deps['org.hamcrest:hamcrest-core'] testCompile deps['org.hamcrest:hamcrest-core']
testCompile deps['org.hamcrest:hamcrest-library'] testCompile deps['org.hamcrest:hamcrest-library']
compile deps['org.hibernate:hibernate-core'] compile deps['org.hibernate:hibernate-hikaricp']
testCompile deps['junit:junit'] testCompile deps['junit:junit']
testCompile deps['org.mockito:mockito-core'] testCompile deps['org.mockito:mockito-core']
runtime deps['org.postgresql:postgresql'] runtime deps['org.postgresql:postgresql']
Expand Down
67 changes: 48 additions & 19 deletions core/src/main/java/google/registry/config/RegistryConfig.java
Expand Up @@ -112,7 +112,7 @@ public static String provideLogoFilename(RegistryConfigSettings config) {
} }


/** /**
* The product name of this specific registry. Used throughout the registrar console. * The product name of this specific registry. Used throughout the registrar console.
* *
* @see google.registry.ui.server.registrar.ConsoleUiAction * @see google.registry.ui.server.registrar.ConsoleUiAction
*/ */
Expand All @@ -123,11 +123,11 @@ public static String provideProductName(RegistryConfigSettings config) {
} }


/** /**
* Returns the roid suffix to be used for the roids of all contacts and hosts. E.g. a value of * Returns the roid suffix to be used for the roids of all contacts and hosts. E.g. a value of
* "ROID" would end up creating roids that look like "ABC123-ROID". * "ROID" would end up creating roids that look like "ABC123-ROID".
* *
* @see <a href="http://www.iana.org/assignments/epp-repository-ids/epp-repository-ids.xhtml"> * @see <a href="http://www.iana.org/assignments/epp-repository-ids/epp-repository-ids.xhtml">
* Extensible Provisioning Protocol (EPP) Repository Identifiers</a> * Extensible Provisioning Protocol (EPP) Repository Identifiers</a>
*/ */
@Provides @Provides
@Config("contactAndHostRoidSuffix") @Config("contactAndHostRoidSuffix")
Expand All @@ -136,7 +136,7 @@ public static String provideContactAndHostRoidSuffix(RegistryConfigSettings conf
} }


/** /**
* The e-mail address for questions about integrating with the registry. Used in the * The e-mail address for questions about integrating with the registry. Used in the
* "contact-us" section of the registrar console. * "contact-us" section of the registrar console.
* *
* @see google.registry.ui.server.registrar.ConsoleUiAction * @see google.registry.ui.server.registrar.ConsoleUiAction
Expand All @@ -148,7 +148,7 @@ public static String provideIntegrationEmail(RegistryConfigSettings config) {
} }


/** /**
* The e-mail address for general support. Used in the "contact-us" section of the registrar * The e-mail address for general support. Used in the "contact-us" section of the registrar
* console. * console.
* *
* @see google.registry.ui.server.registrar.ConsoleUiAction * @see google.registry.ui.server.registrar.ConsoleUiAction
Expand All @@ -160,7 +160,7 @@ public static String provideSupportEmail(RegistryConfigSettings config) {
} }


/** /**
* The "From" e-mail address for announcements. Used in the "contact-us" section of the * The "From" e-mail address for announcements. Used in the "contact-us" section of the
* registrar console. * registrar console.
* *
* @see google.registry.ui.server.registrar.ConsoleUiAction * @see google.registry.ui.server.registrar.ConsoleUiAction
Expand All @@ -172,7 +172,7 @@ public static String provideAnnouncementsEmail(RegistryConfigSettings config) {
} }


/** /**
* The contact phone number. Used in the "contact-us" section of the registrar console. * The contact phone number. Used in the "contact-us" section of the registrar console.
* *
* @see google.registry.ui.server.registrar.ConsoleUiAction * @see google.registry.ui.server.registrar.ConsoleUiAction
*/ */
Expand Down Expand Up @@ -1040,8 +1040,8 @@ public static Duration provideMetricsWriteInterval(RegistryConfigSettings config
} }


/** /**
* The global automatic transfer length for contacts. After this amount of time has * The global automatic transfer length for contacts. After this amount of time has elapsed, the
* elapsed, the transfer is automatically approved. * transfer is automatically approved.
* *
* @see google.registry.flows.contact.ContactTransferRequestFlow * @see google.registry.flows.contact.ContactTransferRequestFlow
*/ */
Expand Down Expand Up @@ -1196,7 +1196,7 @@ static RegistryConfigSettings provideRegistryConfigSettings() {
/** /**
* Provides the OAuth scopes that authentication logic should detect on access tokens. * Provides the OAuth scopes that authentication logic should detect on access tokens.
* *
* <p>This list should be a superset of the required OAuth scope set provided below. Note that * <p>This list should be a superset of the required OAuth scope set provided below. Note that
* ideally, this setting would not be required and all scopes on an access token would be * ideally, this setting would not be required and all scopes on an access token would be
* detected automatically, but that is not the case due to the way {@code OAuthService} works. * detected automatically, but that is not the case due to the way {@code OAuthService} works.
* *
Expand Down Expand Up @@ -1297,9 +1297,7 @@ public static String provideRdapTosStaticUrl(RegistryConfigSettings config) {
} }
} }


/** /** Returns the App Engine project ID, which is based off the environment name. */
* Returns the App Engine project ID, which is based off the environment name.
*/
public static String getProjectId() { public static String getProjectId() {
return CONFIG_SETTINGS.get().appEngine.projectId; return CONFIG_SETTINGS.get().appEngine.projectId;
} }
Expand Down Expand Up @@ -1451,20 +1449,51 @@ public static String getDefaultRegistrarWhoisServer() {
return CONFIG_SETTINGS.get().registryPolicy.defaultRegistrarWhoisServer; return CONFIG_SETTINGS.get().registryPolicy.defaultRegistrarWhoisServer;
} }


/** /** Returns the number of {@code EppResourceIndex} buckets to be used. */
* Returns the number of {@code EppResourceIndex} buckets to be used.
*/
public static int getEppResourceIndexBucketCount() { public static int getEppResourceIndexBucketCount() {
return CONFIG_SETTINGS.get().datastore.eppResourceIndexBucketsNum; return CONFIG_SETTINGS.get().datastore.eppResourceIndexBucketsNum;
} }


/** /** Returns the base retry duration that gets doubled after each failure within {@code Ofy}. */
* Returns the base retry duration that gets doubled after each failure within {@code Ofy}.
*/
public static Duration getBaseOfyRetryDuration() { public static Duration getBaseOfyRetryDuration() {
return Duration.millis(CONFIG_SETTINGS.get().datastore.baseOfyRetryMillis); return Duration.millis(CONFIG_SETTINGS.get().datastore.baseOfyRetryMillis);
} }


/** Returns the default database transaction isolation. */
public static String getHibernateConnectionIsolation() {
return CONFIG_SETTINGS.get().hibernate.connectionIsolation;
}

/** Returns true if hibernate.show_sql is enabled. */
public static String getHibernateLogSqlQueries() {
return CONFIG_SETTINGS.get().hibernate.logSqlQueries;
}

/** Returns true if schema modification is allowed. */
public static String getHibernateHbm2ddlAuto() {
return CONFIG_SETTINGS.get().hibernate.hbm2ddlAuto;
}

/** Returns the connection timeout for HikariCP. */
public static String getHibernateHikariConnectionTimeout() {
return CONFIG_SETTINGS.get().hibernate.hikariConnectionTimeout;
}

/** Returns the minimum idle connections for HikariCP. */
public static String getHibernateHikariMinimumIdle() {
return CONFIG_SETTINGS.get().hibernate.hikariMinimumIdle;
}

/** Returns the maximum pool size for HikariCP. */
public static String getHibernateHikariMaximumPoolSize() {
return CONFIG_SETTINGS.get().hibernate.hikariMaximumPoolSize;
}

/** Returns the idle timeout for HikariCP. */
public static String getHibernateHikariIdleTimeout() {
return CONFIG_SETTINGS.get().hibernate.hikariIdleTimeout;
}

/** Returns the roid suffix to be used for the roids of all contacts and hosts. */ /** Returns the roid suffix to be used for the roids of all contacts and hosts. */
public static String getContactAndHostRoidSuffix() { public static String getContactAndHostRoidSuffix() {
return CONFIG_SETTINGS.get().registryPolicy.contactAndHostRoidSuffix; return CONFIG_SETTINGS.get().registryPolicy.contactAndHostRoidSuffix;
Expand Down
Expand Up @@ -25,6 +25,7 @@ public class RegistryConfigSettings {
public CredentialOAuth credentialOAuth; public CredentialOAuth credentialOAuth;
public RegistryPolicy registryPolicy; public RegistryPolicy registryPolicy;
public Datastore datastore; public Datastore datastore;
public Hibernate hibernate;
public CloudDns cloudDns; public CloudDns cloudDns;
public Caching caching; public Caching caching;
public IcannReporting icannReporting; public IcannReporting icannReporting;
Expand Down Expand Up @@ -105,6 +106,17 @@ public static class Datastore {
public int baseOfyRetryMillis; public int baseOfyRetryMillis;
} }


/** Configuration for Hibernate. */
public static class Hibernate {
public String connectionIsolation;
public String logSqlQueries;
public String hbm2ddlAuto;
public String hikariConnectionTimeout;
public String hikariMinimumIdle;
public String hikariMaximumPoolSize;
public String hikariIdleTimeout;
}

/** Configuration for Apache Beam (Cloud Dataflow). */ /** Configuration for Apache Beam (Cloud Dataflow). */
public static class Beam { public static class Beam {
public String defaultJobZone; public String defaultJobZone;
Expand Down
Expand Up @@ -191,6 +191,26 @@ datastore:
# doubles after each failure). # doubles after each failure).
baseOfyRetryMillis: 100 baseOfyRetryMillis: 100


hibernate:
# Make 'SERIALIZABLE' the default isolation level to ensure correctness.
#
# Entities that are never involved in multi-table transactions may use optimistic
# locks and a less strict isolation level. We may lower individual transaction's
# isolation level using a framework-dependent method.
#
# Alternatively, if a use case calls for, we may also use a lower isolation level
# but lock tables explicitly, either using framework-dependent API, or execute
# "select table for update" statements directly.
connectionIsolation: TRANSACTION_SERIALIZABLE
# Whether to log all SQL queries to App Engine logs. Overridable at runtime.
logSqlQueries: false

# Connection pool configurations.
hikariConnectionTimeout: 20000
hikariMinimumIdle: 0
hikariMaximumPoolSize: 20
hikariIdleTimeout: 300000

cloudDns: cloudDns:
# Set both properties to null in Production. # Set both properties to null in Production.
# The root url for the Cloud DNS API. Set this to a non-null value to # The root url for the Cloud DNS API. Set this to a non-null value to
Expand Down
@@ -0,0 +1,67 @@
// 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.config.RegistryConfig.getHibernateConnectionIsolation;
import static google.registry.config.RegistryConfig.getHibernateHikariConnectionTimeout;
import static google.registry.config.RegistryConfig.getHibernateHikariIdleTimeout;
import static google.registry.config.RegistryConfig.getHibernateHikariMaximumPoolSize;
import static google.registry.config.RegistryConfig.getHibernateHikariMinimumIdle;
import static google.registry.config.RegistryConfig.getHibernateLogSqlQueries;

import com.google.common.collect.ImmutableMap;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.hibernate.cfg.Environment;

/** Factory class to provide {@link EntityManagerFactory} instance. */
public class EntityManagerFactoryProvider {
// This name must be the same as the one defined in persistence.xml.
public static final String PERSISTENCE_UNIT_NAME = "nomulus";
public static final String HIKARI_CONNECTION_TIMEOUT = "hibernate.hikari.connectionTimeout";
public static final String HIKARI_MINIMUM_IDLE = "hibernate.hikari.minimumIdle";
public static final String HIKARI_MAXIMUM_POOL_SIZE = "hibernate.hikari.maximumPoolSize";
public static final String HIKARI_IDLE_TIMEOUT = "hibernate.hikari.idleTimeout";

private static ImmutableMap<String, String> getDefaultProperties() {
ImmutableMap.Builder<String, String> properties = ImmutableMap.builder();

properties.put(Environment.DRIVER, "org.postgresql.Driver");
properties.put(
Environment.CONNECTION_PROVIDER,
"org.hibernate.hikaricp.internal.HikariCPConnectionProvider");
// Whether to automatically validate and export schema DDL to the database when the
// SessionFactory is created. Setting it to 'none' to turn off the feature.
properties.put(Environment.HBM2DDL_AUTO, "none");

properties.put(Environment.ISOLATION, getHibernateConnectionIsolation());
properties.put(Environment.SHOW_SQL, getHibernateLogSqlQueries());
properties.put(HIKARI_CONNECTION_TIMEOUT, getHibernateHikariConnectionTimeout());
properties.put(HIKARI_MINIMUM_IDLE, getHibernateHikariMinimumIdle());
properties.put(HIKARI_MAXIMUM_POOL_SIZE, getHibernateHikariMaximumPoolSize());
properties.put(HIKARI_IDLE_TIMEOUT, getHibernateHikariIdleTimeout());
return properties.build();
}

/** Constructs the {@link EntityManagerFactory} instance. */
public static EntityManagerFactory create(String jdbcUrl, String username, String password) {
ImmutableMap.Builder<String, String> properties = ImmutableMap.builder();
properties.putAll(getDefaultProperties());
properties.put(Environment.URL, jdbcUrl);
properties.put(Environment.USER, username);
properties.put(Environment.PASS, password);
return Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME, properties.build());
}
}
28 changes: 28 additions & 0 deletions core/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
version="2.2">
<persistence-unit name="nomulus" transaction-type="RESOURCE_LOCAL">
<description>
Persistence unit for the Nomulus Cloud SQL database.
</description>
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

<!--
All JPA entities must be enumerated here. JPA does not support auto detection.
Note that Hibernate's auto detection functionality (hibernate.archive.autodection)
does not meet our needs. It only scans archives, not the 'classes' folders. So we
are left with two options:
* Move tests to another (sub)project. This is not a big problem, but feels unnatural.
* Use Hibernate's ServiceRegistry for bootstrapping (not JPA-compliant)
-->
<class>google.registry.model.domain.DomainBase</class>
<class>google.registry.schema.tmch.ClaimsList</class>

<!-- TODO(weiminyu): check out application-layer validation. -->
<validation-mode>NONE</validation-mode>
</persistence-unit>
</persistence>
@@ -0,0 +1,55 @@
// 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 javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.testcontainers.containers.PostgreSQLContainer;

/** Unit tests for {@link EntityManagerFactoryProvider}. */
@RunWith(JUnit4.class)
public class EntityManagerFactoryProviderTest {
@Rule public PostgreSQLContainer database = new PostgreSQLContainer();

private EntityManagerFactory emf;

@Before
public void init() {
emf =
EntityManagerFactoryProvider.create(
database.getJdbcUrl(), database.getUsername(), database.getPassword());
}

@After
public void destroy() {
emf.close();
emf = null;
}

@Test
public void testConnectToDatabase_success() {
EntityManager em = emf.createEntityManager();
assertThat(em.isOpen()).isTrue();
em.close();
}
}
4 changes: 2 additions & 2 deletions dependencies.gradle
Expand Up @@ -115,7 +115,7 @@ ext {
'org.hamcrest:hamcrest-all:1.3', 'org.hamcrest:hamcrest-all:1.3',
'org.hamcrest:hamcrest-core:1.3', 'org.hamcrest:hamcrest-core:1.3',
'org.hamcrest:hamcrest-library:1.3', 'org.hamcrest:hamcrest-library:1.3',
'org.hibernate:hibernate-core:5.4.4.Final', 'org.hibernate:hibernate-hikaricp:5.4.4.Final',
'org.joda:joda-money:0.10.0', 'org.joda:joda-money:0.10.0',
'org.json:json:20160810', 'org.json:json:20160810',
'org.mockito:mockito-core:2.25.0', 'org.mockito:mockito-core:2.25.0',
Expand All @@ -125,8 +125,8 @@ ext {
'org.seleniumhq.selenium:selenium-chrome-driver:3.141.59', 'org.seleniumhq.selenium:selenium-chrome-driver:3.141.59',
'org.seleniumhq.selenium:selenium-java:3.141.59', 'org.seleniumhq.selenium:selenium-java:3.141.59',
'org.seleniumhq.selenium:selenium-remote-driver:3.141.59', 'org.seleniumhq.selenium:selenium-remote-driver:3.141.59',
'org.testcontainers:postgresql:1.11.3',
'org.testcontainers:selenium:1.10.7', 'org.testcontainers:selenium:1.10.7',
'org.testcontainers:postgresql:1.8.3',
'org.yaml:snakeyaml:1.17', 'org.yaml:snakeyaml:1.17',
'xerces:xmlParserAPIs:2.6.2', 'xerces:xmlParserAPIs:2.6.2',
'xpp3:xpp3:1.1.4c' 'xpp3:xpp3:1.1.4c'
Expand Down

0 comments on commit 487b695

Please sign in to comment.