diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/LocalSshTunnel.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/LocalSshTunnel.java
new file mode 100644
index 0000000000..ab839b263b
--- /dev/null
+++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/LocalSshTunnel.java
@@ -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;
+ }
+}
diff --git a/gemma-core/src/main/resources/default.properties b/gemma-core/src/main/resources/default.properties
index f27e7170c4..9da196ff26 100755
--- a/gemma-core/src/main/resources/default.properties
+++ b/gemma-core/src/main/resources/default.properties
@@ -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.
@@ -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
diff --git a/gemma-core/src/main/resources/ubic/gemma/applicationContext-dataSource.xml b/gemma-core/src/main/resources/ubic/gemma/applicationContext-dataSource.xml
index d28d0d40d1..0993f94311 100644
--- a/gemma-core/src/main/resources/ubic/gemma/applicationContext-dataSource.xml
+++ b/gemma-core/src/main/resources/ubic/gemma/applicationContext-dataSource.xml
@@ -12,10 +12,35 @@
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -36,7 +61,7 @@
-
+