mirror of
https://github.com/google/nomulus.git
synced 2025-07-21 10:16:07 +02:00
Add support for nomulus tool to connect to Cloud SQL (#303)
This commit is contained in:
parent
022f397cd9
commit
a694e247cd
6 changed files with 106 additions and 29 deletions
|
@ -15,10 +15,7 @@
|
||||||
package google.registry.model.transaction;
|
package google.registry.model.transaction;
|
||||||
|
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import google.registry.persistence.PersistenceModule.AppEngineEmf;
|
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.EntityManagerFactory;
|
import javax.persistence.EntityManagerFactory;
|
||||||
import javax.persistence.EntityTransaction;
|
import javax.persistence.EntityTransaction;
|
||||||
|
@ -26,7 +23,6 @@ import javax.persistence.PersistenceException;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
/** Implementation of {@link JpaTransactionManager} for JPA compatible database. */
|
/** Implementation of {@link JpaTransactionManager} for JPA compatible database. */
|
||||||
@Singleton
|
|
||||||
public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||||
|
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
@ -39,8 +35,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||||
private final ThreadLocal<TransactionInfo> transactionInfo =
|
private final ThreadLocal<TransactionInfo> transactionInfo =
|
||||||
ThreadLocal.withInitial(TransactionInfo::new);
|
ThreadLocal.withInitial(TransactionInfo::new);
|
||||||
|
|
||||||
@Inject
|
public JpaTransactionManagerImpl(EntityManagerFactory emf, Clock clock) {
|
||||||
JpaTransactionManagerImpl(@AppEngineEmf EntityManagerFactory emf, Clock clock) {
|
|
||||||
this.emf = emf;
|
this.emf = emf;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,15 @@ public class TransactionManagerFactory {
|
||||||
return new DatastoreTransactionManager(null);
|
return new DatastoreTransactionManager(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets jpaTm to the implementation for Nomulus tool. Note that this method should be only used by
|
||||||
|
* {@link google.registry.tools.RegistryCli} to initialize jpaTm.
|
||||||
|
*/
|
||||||
|
public static void initForTool() {
|
||||||
|
// TODO(shicong): Uncomment the line below when we set up Cloud SQL instance in all environments
|
||||||
|
// jpaTm = DaggerPersistenceComponent.create().nomulusToolJpaTransactionManager();
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns {@link TransactionManager} instance. */
|
/** Returns {@link TransactionManager} instance. */
|
||||||
public static TransactionManager tm() {
|
public static TransactionManager tm() {
|
||||||
return TM;
|
return TM;
|
||||||
|
@ -56,11 +65,6 @@ public class TransactionManagerFactory {
|
||||||
|
|
||||||
/** Returns {@link JpaTransactionManager} instance. */
|
/** Returns {@link JpaTransactionManager} instance. */
|
||||||
public static JpaTransactionManager jpaTm() {
|
public static JpaTransactionManager jpaTm() {
|
||||||
// TODO: Returns corresponding TransactionManager based on the runtime environment.
|
|
||||||
// We have 3 kinds of runtime environment:
|
|
||||||
// 1. App Engine
|
|
||||||
// 2. Local JVM used by nomulus tool
|
|
||||||
// 3. Unit test
|
|
||||||
return jpaTm;
|
return jpaTm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,9 @@ import dagger.Component;
|
||||||
import google.registry.config.CredentialModule;
|
import google.registry.config.CredentialModule;
|
||||||
import google.registry.config.RegistryConfig.ConfigModule;
|
import google.registry.config.RegistryConfig.ConfigModule;
|
||||||
import google.registry.keyring.kms.KmsModule;
|
import google.registry.keyring.kms.KmsModule;
|
||||||
import google.registry.model.transaction.JpaTransactionManagerImpl;
|
import google.registry.model.transaction.JpaTransactionManager;
|
||||||
|
import google.registry.persistence.PersistenceModule.AppEngineJpaTm;
|
||||||
|
import google.registry.persistence.PersistenceModule.NomulusToolJpaTm;
|
||||||
import google.registry.util.UtilsModule;
|
import google.registry.util.UtilsModule;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javax.persistence.EntityManagerFactory;
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
@ -34,5 +36,10 @@ import javax.persistence.EntityManagerFactory;
|
||||||
UtilsModule.class
|
UtilsModule.class
|
||||||
})
|
})
|
||||||
public interface PersistenceComponent {
|
public interface PersistenceComponent {
|
||||||
JpaTransactionManagerImpl jpaTransactionManager();
|
|
||||||
|
@AppEngineJpaTm
|
||||||
|
JpaTransactionManager appEngineJpaTransactionManager();
|
||||||
|
|
||||||
|
@NomulusToolJpaTm
|
||||||
|
JpaTransactionManager nomulusToolJpaTransactionManager();
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,14 @@ import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.keyring.kms.KmsKeyring;
|
import google.registry.keyring.kms.KmsKeyring;
|
||||||
|
import google.registry.model.transaction.JpaTransactionManager;
|
||||||
|
import google.registry.model.transaction.JpaTransactionManagerImpl;
|
||||||
|
import google.registry.util.Clock;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import javax.inject.Qualifier;
|
import javax.inject.Qualifier;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import javax.persistence.EntityManagerFactory;
|
import javax.persistence.EntityManagerFactory;
|
||||||
import javax.persistence.Persistence;
|
import javax.persistence.Persistence;
|
||||||
import org.hibernate.cfg.Environment;
|
import org.hibernate.cfg.Environment;
|
||||||
|
@ -77,24 +80,55 @@ public class PersistenceModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@AppEngineEmf
|
@Singleton
|
||||||
public static EntityManagerFactory providesAppEngineEntityManagerFactory(
|
@AppEngineJpaTm
|
||||||
|
public static JpaTransactionManager providesAppEngineJpaTm(
|
||||||
@Config("cloudSqlJdbcUrl") String jdbcUrl,
|
@Config("cloudSqlJdbcUrl") String jdbcUrl,
|
||||||
@Config("cloudSqlUsername") String username,
|
@Config("cloudSqlUsername") String username,
|
||||||
@Config("cloudSqlInstanceConnectionName") String instanceConnectionName,
|
@Config("cloudSqlInstanceConnectionName") String instanceConnectionName,
|
||||||
KmsKeyring kmsKeyring,
|
KmsKeyring kmsKeyring,
|
||||||
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs) {
|
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs,
|
||||||
String password = kmsKeyring.getCloudSqlPassword();
|
Clock clock) {
|
||||||
|
|
||||||
HashMap<String, String> overrides = Maps.newHashMap(defaultConfigs);
|
HashMap<String, String> overrides = Maps.newHashMap(defaultConfigs);
|
||||||
|
|
||||||
// For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections.
|
// For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections.
|
||||||
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
|
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
|
||||||
overrides.put("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
|
overrides.put("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
|
||||||
overrides.put("cloudSqlInstance", instanceConnectionName);
|
overrides.put("cloudSqlInstance", instanceConnectionName);
|
||||||
|
|
||||||
EntityManagerFactory emf = create(jdbcUrl, username, password, ImmutableMap.copyOf(overrides));
|
overrides.put(Environment.URL, jdbcUrl);
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(emf::close));
|
overrides.put(Environment.USER, username);
|
||||||
return emf;
|
overrides.put(Environment.PASS, kmsKeyring.getCloudSqlPassword());
|
||||||
|
|
||||||
|
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@NomulusToolJpaTm
|
||||||
|
public static JpaTransactionManager providesNomulusToolJpaTm(
|
||||||
|
@Config("toolsCloudSqlJdbcUrl") String jdbcUrl,
|
||||||
|
@Config("toolsCloudSqlUsername") String username,
|
||||||
|
@Config("cloudSqlInstanceConnectionName") String instanceConnectionName,
|
||||||
|
KmsKeyring kmsKeyring,
|
||||||
|
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs,
|
||||||
|
Clock clock) {
|
||||||
|
|
||||||
|
// Cloud SQL JDBC Socket Factory library requires the jdbc url to include all connection
|
||||||
|
// information, otherwise the connection initialization will fail. See here for more details:
|
||||||
|
// https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory
|
||||||
|
String fullJdbcUrl =
|
||||||
|
new StringBuilder(jdbcUrl)
|
||||||
|
.append("?cloudSqlInstance=" + instanceConnectionName)
|
||||||
|
.append("&socketFactory=com.google.cloud.sql.postgres.SocketFactory")
|
||||||
|
.append("&user=" + username)
|
||||||
|
.append("&password=" + kmsKeyring.getCloudSqlPassword())
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
HashMap<String, String> overrides = Maps.newHashMap(defaultConfigs);
|
||||||
|
overrides.put(Environment.URL, fullJdbcUrl);
|
||||||
|
|
||||||
|
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs the {@link EntityManagerFactory} instance. */
|
/** Constructs the {@link EntityManagerFactory} instance. */
|
||||||
|
@ -106,29 +140,36 @@ public class PersistenceModule {
|
||||||
properties.put(Environment.USER, username);
|
properties.put(Environment.USER, username);
|
||||||
properties.put(Environment.PASS, password);
|
properties.put(Environment.PASS, password);
|
||||||
|
|
||||||
|
return create(ImmutableMap.copyOf(properties));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EntityManagerFactory create(Map<String, String> properties) {
|
||||||
// If there are no annotated classes, we can create the EntityManagerFactory from the generic
|
// 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
|
// 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.
|
// of annotated classes defined in the configuration, it does not override them.
|
||||||
EntityManagerFactory emf =
|
EntityManagerFactory emf =
|
||||||
Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME, properties);
|
Persistence.createEntityManagerFactory(
|
||||||
|
PERSISTENCE_UNIT_NAME, ImmutableMap.copyOf(properties));
|
||||||
checkState(
|
checkState(
|
||||||
emf != null,
|
emf != null,
|
||||||
"Persistence.createEntityManagerFactory() returns a null EntityManagerFactory");
|
"Persistence.createEntityManagerFactory() returns a null EntityManagerFactory");
|
||||||
return emf;
|
return emf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Dagger qualifier for the {@link EntityManagerFactory} used for App Engine application. */
|
/** Dagger qualifier for {@link JpaTransactionManager} used for App Engine application. */
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@interface AppEngineJpaTm {}
|
||||||
public @interface AppEngineEmf {}
|
|
||||||
|
/** Dagger qualifier for {@link JpaTransactionManager} used for Nomulus tool. */
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@interface NomulusToolJpaTm {}
|
||||||
|
|
||||||
/** Dagger qualifier for the default Hibernate configurations. */
|
/** Dagger qualifier for the default Hibernate configurations. */
|
||||||
// TODO(shicong): Change annotations in this class to none public or put them in a top level
|
// TODO(shicong): Change annotations in this class to none public or put them in a top level
|
||||||
// package
|
// package
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface DefaultHibernateConfigs {}
|
public @interface DefaultHibernateConfigs {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
// 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.tools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for commands that use Cloud Sql.
|
||||||
|
*
|
||||||
|
* <p>Just implementing this is sufficient to use Cloud Sql; {@link RegistryTool} will install it as
|
||||||
|
* needed.
|
||||||
|
*/
|
||||||
|
interface CommandWithCloudSql extends CommandWithRemoteApi {}
|
|
@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import google.registry.config.RegistryConfig;
|
import google.registry.config.RegistryConfig;
|
||||||
import google.registry.model.ofy.ObjectifyService;
|
import google.registry.model.ofy.ObjectifyService;
|
||||||
|
import google.registry.model.transaction.TransactionManagerFactory;
|
||||||
import google.registry.tools.AuthModule.LoginRequiredException;
|
import google.registry.tools.AuthModule.LoginRequiredException;
|
||||||
import google.registry.tools.params.ParameterFactory;
|
import google.registry.tools.params.ParameterFactory;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
@ -210,6 +211,8 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommandWithRemoteApis need to have the remote api installed to work.
|
// CommandWithRemoteApis need to have the remote api installed to work.
|
||||||
|
// CommandWithCloudSql extends CommandWithRemoteApi so the command will also get the remote
|
||||||
|
// api installed. This is because the DB password is stored in Datastore.
|
||||||
if (command instanceof CommandWithRemoteApi) {
|
if (command instanceof CommandWithRemoteApi) {
|
||||||
if (installer == null) {
|
if (installer == null) {
|
||||||
installer = new RemoteApiInstaller();
|
installer = new RemoteApiInstaller();
|
||||||
|
@ -233,6 +236,10 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
|
||||||
ofy().clearSessionCache();
|
ofy().clearSessionCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command instanceof CommandWithCloudSql) {
|
||||||
|
TransactionManagerFactory.initForTool();
|
||||||
|
}
|
||||||
|
|
||||||
command.run();
|
command.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue