SSH tunnelling utility. Property files per remote host.

This commit is contained in:
rick.parker 2017-11-10 08:33:55 +00:00
parent 3b565f52f7
commit 766c0b23d2
3 changed files with 120 additions and 6 deletions

View File

@ -3,6 +3,7 @@ package net.corda.jmeter
import net.corda.core.internal.div
import org.apache.jmeter.JMeter
import org.slf4j.LoggerFactory
import java.net.InetAddress
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.streams.asSequence
@ -18,8 +19,9 @@ class Launcher {
// We are running under Capsule, so assume we want a JMeter slave server to be controlled from
// elsewhere.
logger.info("Starting JMeter in server mode from $capsuleDir")
val capsuleDirPath = Paths.get(capsuleDir)
// Add all JMeter and Corda jars onto the JMeter search_paths
val searchPath = Files.list(Paths.get(capsuleDir)).asSequence().filter {
val searchPath = Files.list(capsuleDirPath).asSequence().filter {
val filename = it.fileName.toString()
filename.endsWith(".jar") && (filename.contains("corda") || filename.contains("jmeter", true))
}.joinToString(";")
@ -28,9 +30,19 @@ class Launcher {
// Set the JMeter home as a property rather than command line arg, due to inconsistent code in JMeter.
System.setProperty("jmeter.home", capsuleDir)
// Create two dirs that JMeter expects, if they don't already exist.
Files.createDirectories(Paths.get(capsuleDir) / "lib" / "ext")
Files.createDirectories(Paths.get(capsuleDir) / "lib" / "junit")
jmeter.start(arrayOf("-s", "-p", "$capsuleDir/jmeter.properties") + args)
Files.createDirectories(capsuleDirPath / "lib" / "ext")
Files.createDirectories(capsuleDirPath / "lib" / "junit")
// Now see if we have a hostname specific property file, and if so, add it.
val hostName = InetAddress.getLocalHost().hostName
val hostSpecificConfigFile = capsuleDirPath / "$hostName.properties"
logger.info("Attempting to use host-specific properties file $hostSpecificConfigFile")
val extraArgs = if (Files.exists(hostSpecificConfigFile)) {
logger.info("Found host-specific properties file")
arrayOf("-q", hostSpecificConfigFile.toString())
} else {
emptyArray()
}
jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args)
} else {
jmeter.start(args)
}

View File

@ -0,0 +1,102 @@
package net.corda.jmeter
import com.jcraft.jsch.JSch
import com.jcraft.jsch.Session
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.loadtest.setupJSchWithSshAgent
import org.slf4j.LoggerFactory
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.*
class Ssh {
companion object {
val log = LoggerFactory.getLogger(this::class.java)
@JvmStatic
fun main(args: Array<String>) {
val userName = System.getProperty("user.name")
val jsch = setupJSchWithSshAgent()
val sessions = mutableListOf<Session>()
// Read jmeter.properties
// For each host:port combo, map them to hosts from command line
val jmeterProps = loadProps("/jmeter.properties")
// The port the JMeter remote agents call back to on this client host.
val clientRmiLocalPort = jmeterProps.getProperty("client.rmi.localport").toInt()
// TODO: Where is this value used? Just on the remote agent to set up the RMI registry?
val serverRmiPort = jmeterProps.getProperty("server.rmi.port", "1099").toInt()
// Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel).
val localHostsAndPorts = jmeterProps.getProperty("remote_hosts", "").split(',').map { it.trim() }
args.zip(localHostsAndPorts) { remoteHost, localHostAndPortString ->
// Actual remote host and port we will tunnel to.
log.info("Creating tunnels for $remoteHost")
val localHostAndPort = NetworkHostAndPort.parse(localHostAndPortString)
// For the remote host, load their specific property file, since it specifies remote RMI server port
val unqualifiedHostName = remoteHost.substringBefore('.')
val hostProps = loadProps("/$unqualifiedHostName.properties")
val serverRmiLocalPort = hostProps.getProperty("server.rmi.localport", jmeterProps.getProperty("server.rmi.localport")).toInt()
val session = connectToHost(jsch, remoteHost, userName)
sessions += session
// TODO: maybe check the local host is actually "localhost"?
// For tunnelling the RMI registry on the remote agent
// ssh ${remoteHostAndPort.host} -L 0.0.0.0:${localHostAndPort.port}:localhost:$serverRmiPort -N
createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", localHostAndPort.port), NetworkHostAndPort("localhost", serverRmiPort))
// For tunnelling the actual connection to the remote agent
// ssh ${remoteHostAndPort.host} -L 0.0.0.0:$serverRmiLocalPort:localhost:$serverRmiLocalPort -N
createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", serverRmiLocalPort), NetworkHostAndPort("localhost", serverRmiLocalPort))
// For returning results to the client
// ssh ${remoteHostAndPort.host} -R 0.0.0.0:clientRmiLocalPort:localhost:clientRmiLocalPort -N
createInboundTunnel(session, NetworkHostAndPort("0.0.0.0", clientRmiLocalPort), NetworkHostAndPort("localhost", clientRmiLocalPort))
}
val input = BufferedReader(InputStreamReader(System.`in`))
do {
log.info("Type 'quit' to exit cleanly.")
} while (input.readLine() != "quit")
sessions.forEach {
log.info("Closing tunnels for ${it.host}")
it.disconnect()
}
}
private fun loadProps(filename: String): Properties {
val props = Properties()
this::class.java.getResourceAsStream(filename).use {
props.load(it)
}
return props
}
fun connectToHost(jSch: JSch, remoteHost: String, remoteUserName: String): Session {
val session = jSch.getSession(remoteUserName, remoteHost, 22)
// We don't check the host fingerprints because they may change often
session.setConfig("StrictHostKeyChecking", "no")
log.info("Connecting to $remoteHost...")
session.connect()
log.info("Connected to $remoteHost!")
return session
}
fun createOutboundTunnel(session: Session, local: NetworkHostAndPort, remote: NetworkHostAndPort) {
log.info("Creating outbound tunnel from $local to $remote with ${session.host}...")
session.setPortForwardingL(local.host, local.port, remote.host, remote.port)
log.info("Tunnel created!")
}
fun createInboundTunnel(session: Session, local: NetworkHostAndPort, remote: NetworkHostAndPort) {
log.info("Creating inbound tunnel from $remote to $local on ${session.host}...")
session.setPortForwardingR(remote.host, remote.port, local.host, local.port)
log.info("Tunnel created!")
}
}
}

View File

@ -15,9 +15,9 @@
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">1000</stringProp>
<stringProp name="LoopController.loops">10</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">2</stringProp>
<stringProp name="ThreadGroup.num_threads">3</stringProp>
<stringProp name="ThreadGroup.ramp_time"></stringProp>
<longProp name="ThreadGroup.start_time">1509455820000</longProp>
<longProp name="ThreadGroup.end_time">1509455820000</longProp>