Fix ssh for windows (#121)

* Make ssh tunnels work with Pageant on windows and allow specifying explicit
ssh remote user

* Update comments
This commit is contained in:
Christian Sailer 2017-11-23 10:06:39 +00:00 committed by rick.parker
parent f382639ed8
commit 2b217b6eea
5 changed files with 75 additions and 18 deletions

View File

@ -7,7 +7,7 @@ the Corda node via RPC.
To run up the JMeter UI, using the jmeter.properties in the resources folder,
type the following:
./gradlew tools:jmeter:run
`./gradlew tools:jmeter:run`
You can then open the example script in "Example Flow Properties.jmx" via the File -> Open menu option. You need to
configure the host, ports, user name and password in the Java Sampler that correspond to your chosen target Corda node.
@ -15,7 +15,7 @@ Simply running from the UI will result in the RPC client running inside the UI J
If you wish to pass additional arguments to JMeter, you can do this:
./gradlew tools:jmeter:run -PjmeterArgs="['-n', '-Ljmeter.engine=DEBUG']"
`./gradlew tools:jmeter:run -PjmeterArgs="['-n', '-Ljmeter.engine=DEBUG']"`
The intention is to run against a remote Corda node or nodes, hosted on servers rather than desktops. To
this end, we leverage the JMeter ability to run remote agents that actually execute the tests, with these
@ -26,7 +26,7 @@ The remote agents then run close to the nodes, so the latency of RPC calls is mi
A Capsule (http://www.capsule.io) based launchable JAR is created that can be run with the simple command line
java -jar jmeter-corda-<version>.jar
`java -jar jmeter-corda-<version>.jar`
Embedded in the JAR is all of the corda code for flows and RPC, as well as the jmeter.propeties. This
JAR will also include a properties file based on the hostname in the JMeter configuration,
@ -34,7 +34,7 @@ so we allocate different SSH tunneled port numbers this way.
To launch JMeter with the tunnels automatically created:
./gradlew tools:jmeter:run -PjmeterHosts="['hostname1', 'hostname2']"
`./gradlew tools:jmeter:run -PjmeterHosts="['hostname1', 'hostname2']"`
The list of hostnames should be of at least length one, with a maximum equal to the length of the remote_hosts
option in jmeter.properties. We effectively "zip" together the hostnames and that list to build the SSH tunnels.
@ -45,4 +45,13 @@ from remote agents.
The SSH tunnels can be started independently with:
./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']"
`./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']"`
For the ssh tunneling to work, an ssh agent must be running on your local machine with the
appropriate private key loaded. If the environment variable `SSH_AUTH_SOCK` is set, the code
assumes that a posix sshagent process is being used, if it is not set, it assumes that
[Pageant](https://www.ssh.com/ssh/putty/putty-manuals/0.68/Chapter9.html) is in use. If the
remote user name is different from the current user name, `-XsshUser <remote user name>`
can be used to set this, or in the gradle call:
`./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']" -PsshUser="'username'"`

View File

@ -13,6 +13,7 @@ dependencies {
compile group: 'com.jcraft', name: 'jsch.agentproxy.core', version: '0.0.9'
compile group: 'com.jcraft', name: 'jsch.agentproxy.sshagent', version: '0.0.9'
compile group: 'com.jcraft', name: 'jsch.agentproxy.usocket-jna', version: '0.0.9'
compile group: 'com.jcraft', name: 'jsch.agentproxy.pageant', version: '0.0.9'
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
@ -60,8 +61,13 @@ task(runServer, dependsOn: 'classes', type: JavaExec) {
task(runSsh, dependsOn: 'classes', type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'com.r3.corda.jmeter.Ssh'
if ( project.hasProperty("sshUser") ){
args+= "-XsshUser"
args+= Eval.me(sshUser)
}
if ( project.hasProperty("jmeterHosts") ) {
args Eval.me(jmeterHosts)
args+= "-Xssh"
args+= Eval.me(jmeterHosts)
}
standardInput = System.in
}
@ -85,6 +91,10 @@ run {
if ( project.hasProperty("jmeterArgs") ) {
args+= Eval.me(jmeterHosts)
}
if ( project.hasProperty("sshUser") ){
args+= "-XsshUser"
args+= Eval.me(sshUser)
}
if ( project.hasProperty("jmeterHosts") ) {
args+= "-Xssh"
args+= Eval.me(jmeterHosts)

View File

@ -5,6 +5,7 @@ import com.jcraft.jsch.Identity
import com.jcraft.jsch.IdentityRepository
import com.jcraft.jsch.JSch
import com.jcraft.jsch.agentproxy.AgentProxy
import com.jcraft.jsch.agentproxy.connector.PageantConnector
import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector
import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory
import org.slf4j.LoggerFactory
@ -16,7 +17,11 @@ private val log = LoggerFactory.getLogger(Ssh::class.java)
* Creates a new [JSch] instance with identities loaded from the running SSH agent.
*/
fun setupJSchWithSshAgent(): JSch {
val connector = SSHAgentConnector(JNAUSocketFactory())
val connector =
if (System.getenv("SSH_AUTH_SOCK") == null)
PageantConnector()
else
SSHAgentConnector(JNAUSocketFactory())
val agentProxy = AgentProxy(connector)
val identities = agentProxy.identities
require(identities.isNotEmpty()) { "No SSH identities found, please add one to the agent" }

View File

@ -1,5 +1,6 @@
package com.r3.corda.jmeter
import com.sun.javaws.exceptions.InvalidArgumentException
import net.corda.core.internal.div
import org.apache.jmeter.JMeter
import org.slf4j.LoggerFactory
@ -54,19 +55,29 @@ class Launcher {
}
}
private fun maybeOpenSshTunnels(args: Array<String>): Array<String> {
fun maybeOpenSshTunnels(args: Array<String>): Array<String> {
// We trim the args at the point "-Xssh" appears in the array of args. Anything after that is a host to
// SSH tunnel to.
// SSH tunnel to. Also get and remove the "-XsshUser" argument if it appears.
var index = 0
for (arg in args) {
if (arg == "-Xssh") {
var userName = System.getProperty("user.name")
val returnArgs = mutableListOf<String>()
while (index < args.size) {
if (args[index] == "-XsshUser") {
++index
if (index == args.size || args[index].startsWith("-")) {
throw InvalidArgumentException(args)
}
userName = args[index]
} else if (args[index] == "-Xssh") {
// start ssh
Ssh.main(args.copyOfRange(index + 1, args.size), false)
return if (index == 0) emptyArray() else args.copyOfRange(0, index)
Ssh.createSshTunnels(args.copyOfRange(index + 1, args.size), userName, false)
return returnArgs.toTypedArray()
} else {
returnArgs.add(args[index])
}
index++
}
return args
return returnArgs.toTypedArray()
}
}
}

View File

@ -2,6 +2,7 @@ package com.r3.corda.jmeter
import com.jcraft.jsch.JSch
import com.jcraft.jsch.Session
import com.sun.javaws.exceptions.InvalidArgumentException
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.addShutdownHook
import org.slf4j.LoggerFactory
@ -17,9 +18,30 @@ class Ssh {
val log = LoggerFactory.getLogger(this::class.java)
@JvmStatic
@JvmOverloads
fun main(args: Array<String>, wait: Boolean = true) {
val userName = System.getProperty("user.name")
fun main(args: Array<String>) {
// parse the args and call createSshTunnels
// the only arguments recognised are "-XsshUser" for the remote user name
// and "-Xssh" - everything after this will be treated as a remote host name
var userName = System.getProperty("user.name")
var index = 0
while (index < args.size) {
if (args[index] == "-XsshUser") {
++index
if (index == args.size || args[index].startsWith("-")) {
throw InvalidArgumentException(args)
}
userName = args[index]
} else if (args[index] == "-Xssh") {
createSshTunnels(args.copyOfRange(index + 1, args.size), userName, true)
return
}
}
log.info("Nothing to be done - did you specify hosts to tunnel to with -Xssh?")
}
fun createSshTunnels(hosts: Array<String>, userName: String, wait: Boolean) {
log.info("User name for ssh: ${userName}")
val jsch = setupJSchWithSshAgent()
val sessions = mutableListOf<Session>()
@ -34,7 +56,7 @@ class Ssh {
// 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 ->
hosts.zip(localHostsAndPorts) { remoteHost, localHostAndPortString ->
// Actual remote host and port we will tunnel to.
log.info("Creating tunnels for $remoteHost")
val localHostAndPort = NetworkHostAndPort.parse(localHostAndPortString)