Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a utility to create a SSH tunnel for the database #1237

Open
wants to merge 1 commit into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package ubic.gemma.persistence.util;

import lombok.Setter;
import lombok.extern.apachecommons.CommonsLog;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.context.SmartLifecycle;
import org.springframework.util.Assert;

import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
* Establishes a local SSH tunnel.
* @author poirigui
*/
@CommonsLog
@Setter
public class LocalSshTunnel implements SmartLifecycle {

private String host;
@Nullable
private Integer port;

// for the tunnel
private int localPort;
private String remoteHost;
private int remotePort;

private boolean autoStart = false;

private Process tunnelProcess;

@Override
public void start() {
Assert.isTrue( host != null && localPort > 0 && remoteHost != null && remotePort > 0 );
try {
String[] args = new String[] { "ssh", "-L", localPort + ":" + remoteHost + ":" + remotePort, host };
if ( port != null ) {
args = ArrayUtils.addAll( args, "-p", String.valueOf( port ) );
}
tunnelProcess = Runtime.getRuntime().exec( args );
// quickly check if the process exited
if ( tunnelProcess.waitFor( 100, TimeUnit.MILLISECONDS ) ) {
throw new RuntimeException( IOUtils.toString( tunnelProcess.getErrorStream(), StandardCharsets.UTF_8 ) );
}
} catch ( IOException | InterruptedException e ) {
throw new RuntimeException( e );
}
log.info( String.format( "Established a SSH tunnel to %s%s: %d -> %s:%d.",
host, port != null ? ":" + port : "", localPort, remoteHost, remotePort ) );
}

@Override
public void stop() {
tunnelProcess.destroy();
}

@Override
public boolean isRunning() {
return tunnelProcess != null && tunnelProcess.isAlive();
}

@Override
public boolean isAutoStartup() {
return autoStart;
}

@Override
public void stop( Runnable callback ) {
stop();
}

@Override
public int getPhase() {
return 0;
}
}
14 changes: 14 additions & 0 deletions gemma-core/src/main/resources/default.properties
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ gemma.db.user=gemmauser
gemma.db.password=XXXXXX
# Maximum size for the connections pool
gemma.db.maximumPoolSize=10
# Establish an SSH tunnel to connect to the database (only if the dev profile is active)
gemma.db.tunnel.enabled=false
gemma.db.tunnel.host=
gemma.db.tunnel.port=
gemma.db.tunnel.localPort=0
gemma.db.tunnel.remoteHost=
gemma.db.tunnel.remotePort=0
############################################################
# SECURITY
# Used to elevate authorities for some methods.
Expand Down Expand Up @@ -205,6 +212,13 @@ gemma.testdb.agent.userName=gemmaAgent
gemma.testdb.agent.password=XXXXXXXX
# Initialize the test database, this can be disabled to make integration tests faster
gemma.testdb.initialize=true
# Establish an SSH tunnel to connect to the test database (only if the dev profile is active)
gemma.testdb.tunnel.enabled=false
gemma.testdb.tunnel.host=
gemma.testdb.tunnel.port=
gemma.testdb.tunnel.localPort=0
gemma.testdb.tunnel.remoteHost=
gemma.testdb.tunnel.remotePort=0
#the external database id to exclude by default in phenocarta
gemma.neurocarta.exluded_database_id=85
# Featured external databases in Gemma Web About page and Gemma REST main endpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,35 @@
<prop key="sessionVariables">sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'</prop>
</util:properties>

<beans profile="dev">
<bean id="tunnel" class="ubic.gemma.persistence.util.LocalSshTunnel">
<property name="host" value="${gemma.db.tunnel.host}"/>
<property name="port" value="${gemma.db.tunnel.port}"/>
<property name="localPort" value="${gemma.db.tunnel.localPort}"/>
<property name="remoteHost" value="${gemma.db.tunnel.remoteHost}"/>
<property name="remotePort" value="${gemma.db.tunnel.remotePort}"/>
<property name="autoStart" value="${gemma.db.tunnel.enabled}"/>
</bean>
<bean id="testTunnel" class="ubic.gemma.persistence.util.LocalSshTunnel">
<property name="host" value="${gemma.testdb.tunnel.host}"/>
<property name="port" value="${gemma.testdb.tunnel.port}"/>
<property name="localPort" value="${gemma.testdb.tunnel.localPort}"/>
<property name="remoteHost" value="${gemma.testdb.tunnel.remoteHost}"/>
<property name="remotePort" value="${gemma.testdb.tunnel.remotePort}"/>
<property name="autoStart" value="${gemma.testdb.tunnel.enabled}"/>
</bean>
</beans>

<beans profile="!dev">
<!-- placeholders to satisfy depends-on -->
<bean id="tunnel" class="java.lang.Object"/>
<bean id="testTunnel" class="java.lang.Object"/>
</beans>

<!-- we use the same database for production and development -->
<beans profile="production,dev">
<!-- Database connection information -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close" depends-on="tunnel">
<property name="poolName" value="gemma"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="${gemma.db.user}"/>
Expand All @@ -36,7 +61,7 @@
</beans>

<beans profile="test,testdb">
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close" depends-on="testTunnel">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="${gemma.testdb.user}"/>
<property name="password" value="${gemma.testdb.password}"/>
Expand Down