mirror of
https://github.com/corda/corda.git
synced 2025-03-15 16:46:12 +00:00
ENT-2565 Make JMeter capsule usable as client (#1464)
* Make fat jar runnable as a client, improve handling of SSH parameters, fix up some dependencies. *Note*: running the fat jar as a server now requires the -s cmd line flag. * Picocli command class * Add additional search paths argument * Rewrite launcher to use PicoCLI arguments * Fix printing of help * fix tools:jmeter:run method to work with the new command line format. * Fix printing of help message without jmeter throwing loads of exceptions around. * Fix comments and add tests for non-trivial command line munging * Code review * Test for generated cmd line when running via gradle. * Rewrite launcher to handle jmeterProperties and rmi server port config explicitly * Modify ssh code to use properties passed in from JMeter launcher main. * Fix test * Transform server rmi local port to new format, fix/test reading. * Fix up jmeter:run to match new command line paramters * Formatting * Include server-rmi.config in jar * Code review
This commit is contained in:
parent
f2c5ab3b0f
commit
802c88e7ad
@ -48,10 +48,6 @@ for each host listed in jmeterHosts. Some additional ports are also opened based
|
||||
parts of the configuration to access the RMI registry and to allow return traffic
|
||||
from remote agents.
|
||||
|
||||
The SSH tunnels can be started independently with:
|
||||
|
||||
`./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
|
||||
@ -59,7 +55,7 @@ assumes that a posix sshagent process is being used, if it is not set, it assume
|
||||
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'"`
|
||||
`./gradlew tools:jmeter:run -PjmeterHosts="['hostname1', 'hostname2']" -PsshUser="'username'"`
|
||||
|
||||
#### Running locally with driver
|
||||
|
||||
|
@ -26,6 +26,9 @@ dependencies {
|
||||
compile "org.apache.logging.log4j:log4j-1.2-api:$log4j_version"
|
||||
compile "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
|
||||
//Picocli for command line interface
|
||||
compile "info.picocli:picocli:$picocli_version"
|
||||
|
||||
// JMeter
|
||||
ext.jmVersion = "3.3"
|
||||
|
||||
@ -71,21 +74,6 @@ task runServer(dependsOn: 'classes', type: JavaExec) {
|
||||
"-s" ]
|
||||
}
|
||||
|
||||
// Just start ssh tunnels, without JMeter.
|
||||
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+= "-Xssh"
|
||||
args+= Eval.me(jmeterHosts)
|
||||
}
|
||||
standardInput = System.in
|
||||
}
|
||||
|
||||
// Use the driver to launch 2 local nodes and the notary running the perftestcordapp.
|
||||
// To use for development of samplers and flows locally.
|
||||
task runDriver(dependsOn: 'classes', type: JavaExec) {
|
||||
@ -109,11 +97,7 @@ run {
|
||||
systemProperty "java.rmi.server.hostname", "localhost"
|
||||
systemProperty "jmeter.home", sourceSets.main.resources.getSrcDirs().first().getPath()
|
||||
// If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"
|
||||
args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties",
|
||||
"-d", sourceSets.main.resources.getSrcDirs().first().getPath() ]
|
||||
if ( project.hasProperty("jmeterArgs") ) {
|
||||
args+= Eval.me(jmeterArgs)
|
||||
}
|
||||
|
||||
if ( project.hasProperty("sshUser") ){
|
||||
args+= "-XsshUser"
|
||||
args+= Eval.me(sshUser)
|
||||
@ -122,6 +106,13 @@ run {
|
||||
args+= "-Xssh"
|
||||
args+= Eval.me(jmeterHosts)
|
||||
}
|
||||
|
||||
args+= [ "-XjmeterProperties", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties",
|
||||
"--",
|
||||
"-d", sourceSets.main.resources.getSrcDirs().first().getPath() ]
|
||||
if ( project.hasProperty("jmeterArgs") ) {
|
||||
args+= Eval.me(jmeterArgs)
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -149,6 +140,8 @@ task buildJMeterJAR(type: FatCapsule) {
|
||||
from(processResources) {
|
||||
include "log4j2.xml"
|
||||
include "*.properties"
|
||||
include "server-rmi.config"
|
||||
include "bin/*.properties"
|
||||
}
|
||||
|
||||
capsuleManifest {
|
||||
|
@ -1,10 +1,16 @@
|
||||
package com.r3.corda.jmeter
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import net.corda.core.internal.div
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.jmeter.JMeter
|
||||
import org.slf4j.LoggerFactory
|
||||
import picocli.CommandLine
|
||||
import java.io.File
|
||||
import java.lang.Exception
|
||||
import java.net.InetAddress
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.streams.asSequence
|
||||
|
||||
@ -15,71 +21,145 @@ import kotlin.streams.asSequence
|
||||
*/
|
||||
class Launcher {
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
val logger = LoggerFactory.getLogger(this::class.java)
|
||||
logger.info("Launcher called with ${args.toList()}")
|
||||
val jmeter = JMeter()
|
||||
val capsuleDir = System.getProperty("capsule.dir")
|
||||
if (capsuleDir != null) {
|
||||
// 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(capsuleDirPath).asSequence().filter {
|
||||
val filename = it.fileName.toString()
|
||||
filename.endsWith(".jar") && (filename.contains("corda") || filename.contains("jmeter", true))
|
||||
}.joinToString(";")
|
||||
logger.info("search_paths = $searchPath")
|
||||
System.setProperty("search_paths", searchPath)
|
||||
// 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(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()
|
||||
val jMeter = JMeter()
|
||||
val cmdLine = LauncherCommandLine()
|
||||
CommandLine.populateCommand(cmdLine, *args)
|
||||
|
||||
// If help has been requested, print our and jmeter command line help.
|
||||
if (cmdLine.helpRequested) {
|
||||
printHelp(cmdLine)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val capsuleDir = System.getProperty("capsule.dir")
|
||||
val searchPath = if (capsuleDir != null) prepareJMeterPropsCapsule(cmdLine, capsuleDir) else prepareJMeterPropsGradle(cmdLine)
|
||||
val (jMeterArgs, jMeterPropertiesFile, serverRmiMappings) = prepareJMeterArguments(cmdLine)
|
||||
if (!cmdLine.sshHosts.isEmpty()) {
|
||||
Ssh.createSshTunnels(cmdLine.sshHosts.toTypedArray(), jMeterPropertiesFile.toString(), serverRmiMappings, cmdLine.sshUser)
|
||||
}
|
||||
jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args)
|
||||
} else {
|
||||
val searchPath = Files.readAllLines(Paths.get(System.getProperty("search_paths_file"))).first()
|
||||
logger.info("search_paths = $searchPath")
|
||||
System.setProperty("search_paths", searchPath)
|
||||
jmeter.start(maybeOpenSshTunnels(args))
|
||||
jMeter.start(jMeterArgs.toTypedArray())
|
||||
} catch (e: Throwable) {
|
||||
println(e.message)
|
||||
printHelp(cmdLine)
|
||||
}
|
||||
}
|
||||
|
||||
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. Also get and remove the "-XsshUser" argument if it appears.
|
||||
var index = 0
|
||||
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 IllegalArgumentException(args.toList().toString())
|
||||
}
|
||||
userName = args[index]
|
||||
} else if (args[index] == "-Xssh") {
|
||||
// start ssh
|
||||
Ssh.createSshTunnels(args.copyOfRange(index + 1, args.size), userName, false)
|
||||
return returnArgs.toTypedArray()
|
||||
} else {
|
||||
returnArgs.add(args[index])
|
||||
}
|
||||
index++
|
||||
}
|
||||
return returnArgs.toTypedArray()
|
||||
internal data class JMeterArgsPlus(val jmeterArgs: Collection<String>, val jmeterPropertiesFile: Path, val serverRmiMappings: Map<String, Int>)
|
||||
|
||||
internal fun prepareJMeterPropsCapsule(cmdLine: LauncherCommandLine, capsuleDir: String): String {
|
||||
logger.info("Starting JMeter in capsule mode from $capsuleDir")
|
||||
val capsuleDirPath = Paths.get(capsuleDir)
|
||||
// Add all JMeter and Corda jars onto the JMeter search_paths, adding any search paths given by the user
|
||||
val searchPath = Files.list(capsuleDirPath).asSequence().filter {
|
||||
val filename = it.fileName.toString()
|
||||
filename.endsWith(".jar") && (filename.contains("corda") || filename.contains("jmeter", true))
|
||||
}.plus(listOf(cmdLine.additionalSearchPaths).filter { !it.isBlank() }).joinToString(";")
|
||||
logger.info("Generated search_paths = $searchPath")
|
||||
|
||||
// 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(capsuleDirPath / "lib" / "ext")
|
||||
Files.createDirectories(capsuleDirPath / "lib" / "junit")
|
||||
return searchPath
|
||||
}
|
||||
|
||||
internal fun prepareJMeterPropsGradle(cmdLine: LauncherCommandLine): String {
|
||||
// check that a search_paths_file has been provided as a system property and throw meaningful error otherwise
|
||||
val searchPathsFilename = System.getProperty("search_paths_file")
|
||||
if (searchPathsFilename.isNullOrBlank()) {
|
||||
throw LauncherException("System property search_paths_file must be set when running without capsule")
|
||||
}
|
||||
if (System.getProperty("jmeter.home").isNullOrBlank()) {
|
||||
throw LauncherException("System property jmeter.home must be set when running without capsule")
|
||||
}
|
||||
val searchPath = Files.readAllLines(Paths.get(searchPathsFilename)).first() + if (cmdLine.additionalSearchPaths.isBlank()) "" else ";${cmdLine.additionalSearchPaths}"
|
||||
logger.info("search_paths read from $searchPathsFilename: $searchPath")
|
||||
return searchPath
|
||||
}
|
||||
|
||||
internal fun prepareJMeterArguments(cmdLine: LauncherCommandLine): JMeterArgsPlus {
|
||||
val jMeterHome = File(System.getProperty("jmeter.home")).toPath()
|
||||
val jMeterPropertiesFile = if (cmdLine.jMeterProperties.isBlank()) {
|
||||
(jMeterHome / "jmeter.properties").toAbsolutePath()
|
||||
} else {
|
||||
File(cmdLine.jMeterProperties).toPath()
|
||||
}
|
||||
val serverRmiMappingFile = if (cmdLine.serverRmiMappings.isBlank()) {
|
||||
(jMeterHome / "server-rmi.config").toString()
|
||||
} else {
|
||||
cmdLine.serverRmiMappings
|
||||
}
|
||||
val serverRmiMappings = readHostAndPortMap(serverRmiMappingFile)
|
||||
|
||||
// we want to add the jMeter properties file here - it must not be part of the user specified jMeter
|
||||
// arguments
|
||||
val jMeterArgs = cmdLine.jMeterArguments.toMutableList()
|
||||
if ("-p" in jMeterArgs) {
|
||||
throw LauncherException("To choose jmeter.properties, use the -XjmeterProperties flag, not -p for JMeter arguments")
|
||||
}
|
||||
jMeterArgs.addAll(listOf("-p", jMeterPropertiesFile.toString()))
|
||||
|
||||
// if running as a server, add a server.rmi.port configuration file if the port is configured
|
||||
// if it isn't just carry on regardless (the user might not want to use ssh tunnels, we can't
|
||||
// know on the server side)
|
||||
if ("-s" in jMeterArgs) {
|
||||
val hostName = InetAddress.getLocalHost().hostName
|
||||
val port = serverRmiMappings[hostName]
|
||||
if (port != null) {
|
||||
val rmiPropsFile = (jMeterHome / "server-rmi.properties").toFile()
|
||||
rmiPropsFile.writeText("server.rmi.localport=$port")
|
||||
|
||||
jMeterArgs.addAll(listOf("-q", rmiPropsFile.toString()))
|
||||
}
|
||||
}
|
||||
return JMeterArgsPlus(jMeterArgs, jMeterPropertiesFile, serverRmiMappings)
|
||||
}
|
||||
|
||||
private fun printHelp(cmdLine: LauncherCommandLine) {
|
||||
// for JMeter to actually print the help, we need to provide it a jmeter.properties file - it can be empty,
|
||||
// but has to be present or we never see the help message. 🤦
|
||||
CommandLine.usage(cmdLine, System.out)
|
||||
val tmpDirPath = File(System.getProperty("java.io.tmpdir")).toPath() / "jmeter-emergency"
|
||||
val tmpDir = tmpDirPath.toFile()
|
||||
|
||||
try {
|
||||
if (tmpDir.exists()) {
|
||||
FileUtils.deleteDirectory(tmpDir)
|
||||
}
|
||||
// Create two dirs that JMeter expects, if they don't already exist.
|
||||
Files.createDirectories(tmpDirPath / "lib" / "ext")
|
||||
Files.createDirectories(tmpDirPath / "lib" / "junit")
|
||||
|
||||
// Set the JMeter home as a property rather than command line arg, due to inconsistent code in JMeter.
|
||||
System.setProperty("jmeter.home", tmpDirPath.toString())
|
||||
val tmpPropertyFile = (tmpDirPath / "jmeter.properties").toFile()
|
||||
tmpPropertyFile.writeText("")
|
||||
JMeter().start(arrayOf("--?", "-p", tmpPropertyFile.absolutePath))
|
||||
} finally {
|
||||
FileUtils.deleteDirectory(tmpDir)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun readHostAndPortMap(filename: String): Map<String, Int> {
|
||||
return File(filename).readLines().filter { !it.isBlank() && !it.startsWith("#") }.map {
|
||||
val hostAndPort = HostAndPort.fromString(it)
|
||||
hostAndPort.host to hostAndPort.port
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception class for error handling while running our JMeter launcher code.
|
||||
*/
|
||||
class LauncherException(message: String) : Exception(message)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.r3.corda.jmeter
|
||||
|
||||
import picocli.CommandLine
|
||||
|
||||
@CommandLine.Command(synopsisHeading = "", customSynopsis = arrayOf("Usage: jmeter-corda [OPTIONS] -- [<jMeter args>...]"), sortOptions = false)
|
||||
class LauncherCommandLine {
|
||||
@CommandLine.Option(names = arrayOf("-?", "-h", "--help"), usageHelp = true, description = arrayOf("Prints usage"))
|
||||
var helpRequested: Boolean = false
|
||||
|
||||
@CommandLine.Option(names = arrayOf("-Xssh"), description = arrayOf("List of hosts to create SSH tunnels to, separated by space",
|
||||
"Example: -Xssh <hostname1> [<hostname2> ...]"), arity = "1..*")
|
||||
var sshHosts: MutableList<String> = mutableListOf()
|
||||
|
||||
@CommandLine.Option(names = arrayOf("-XsshUser"), description = arrayOf(
|
||||
"Remote user account to use for ssh tunnels. This defaults to the current user."))
|
||||
var sshUser: String = System.getProperty("user.name")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = arrayOf("-XadditionalSearchPaths"),
|
||||
description = arrayOf(
|
||||
"A semicolon separated list of directories/jar files to search for plug-ins/samplers. This will be added to any search paths already in the properties",
|
||||
"Example:",
|
||||
"-XadditionalSearchPaths [<class dir>|<jar file>][;...]"))
|
||||
var additionalSearchPaths = ""
|
||||
|
||||
@CommandLine.Option(names = arrayOf("-XjmeterProperties"), description = arrayOf(
|
||||
"Path to a jmeter.properties file to replace the one in the jar. Use this instead of the -p flag of jMeter as the wrapping code needs access to the file as well."))
|
||||
var jMeterProperties = ""
|
||||
|
||||
@CommandLine.Option(names = arrayOf("-XserverRmiMappings"), description = arrayOf(
|
||||
"Path to a server RMI port mappings file.",
|
||||
"This file should have a line for each server that needs an ssh tunnel created to. Each line should have the unqualified server name, followed by a colon and the port number. Each host needs a different port number. Lines starting with # are comments and are ignored.",
|
||||
"Example line for host example-server.corda.net:",
|
||||
"example-server:10101"))
|
||||
var serverRmiMappings: String = ""
|
||||
|
||||
@CommandLine.Parameters(paramLabel = "<jMeter args>", description = arrayOf("All arguments after -- are passed to JMeter"))
|
||||
var jMeterArguments: MutableList<String> = mutableListOf()
|
||||
}
|
@ -5,8 +5,7 @@ import com.jcraft.jsch.Session
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -16,30 +15,7 @@ class Ssh {
|
||||
companion object {
|
||||
val log = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
@JvmStatic
|
||||
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 IllegalArgumentException(args.toList().toString())
|
||||
}
|
||||
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) {
|
||||
fun createSshTunnels(hosts: Array<String>, jmeterProperties: String, serverRmiMappings: Map<String, Int>, userName: String) {
|
||||
log.info("User name for ssh: ${userName}")
|
||||
val jsch = setupJSchWithSshAgent()
|
||||
val sessions = mutableListOf<Session>()
|
||||
@ -47,7 +23,7 @@ class Ssh {
|
||||
// Read jmeter.properties
|
||||
// For each host:port combo, map them to hosts from command line
|
||||
|
||||
val jmeterProps = loadProps("/jmeter.properties")
|
||||
val jmeterProps = loadProps(jmeterProperties)
|
||||
// The port the JMeter remote agents call back to on this client host.
|
||||
val clientRmiLocalPort = jmeterProps.getProperty("client.rmi.localport").toInt()
|
||||
// Remote RMI registry port.
|
||||
@ -55,43 +31,33 @@ 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() }
|
||||
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)
|
||||
try {
|
||||
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)
|
||||
|
||||
// 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")
|
||||
// For the remote host, load their specific property file, since it specifies remote RMI server port
|
||||
val unqualifiedHostName = remoteHost.substringBefore('.')
|
||||
val serverRmiLocalPort = serverRmiMappings[unqualifiedHostName]
|
||||
?: throw Launcher.Companion.LauncherException("No server RMI local port for $unqualifiedHostName in the server RMI mappings, can't create ssh tunnel")
|
||||
|
||||
val serverRmiLocalPort = hostProps.getProperty("server.rmi.localport", jmeterProps.getProperty("server.rmi.localport")).toInt()
|
||||
val session = connectToHost(jsch, remoteHost, userName)
|
||||
sessions += session
|
||||
|
||||
val session = connectToHost(jsch, remoteHost, userName)
|
||||
sessions += session
|
||||
// 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("localhost", localHostAndPort.port), NetworkHostAndPort("localhost", serverRmiPort))
|
||||
|
||||
// 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("localhost", 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("localhost", serverRmiLocalPort), NetworkHostAndPort("localhost", serverRmiLocalPort))
|
||||
|
||||
// For tunnelling the actual connection to the remote agent
|
||||
// ssh ${remoteHostAndPort.host} -L 0.0.0.0:$serverRmiLocalPort:localhost:$serverRmiLocalPort -N
|
||||
createOutboundTunnel(session, NetworkHostAndPort("localhost", 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("localhost", clientRmiLocalPort), NetworkHostAndPort("localhost", clientRmiLocalPort))
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
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()
|
||||
// For returning results to the client
|
||||
// ssh ${remoteHostAndPort.host} -R 0.0.0.0:clientRmiLocalPort:localhost:clientRmiLocalPort -N
|
||||
createInboundTunnel(session, NetworkHostAndPort("localhost", clientRmiLocalPort), NetworkHostAndPort("localhost", clientRmiLocalPort))
|
||||
}
|
||||
} else {
|
||||
} finally {
|
||||
addShutdownHook {
|
||||
sessions.forEach {
|
||||
log.info("Closing tunnels for ${it.host}")
|
||||
@ -103,7 +69,7 @@ class Ssh {
|
||||
|
||||
private fun loadProps(filename: String): Properties {
|
||||
val props = Properties()
|
||||
this::class.java.getResourceAsStream(filename).use {
|
||||
File(filename).inputStream().use {
|
||||
props.load(it)
|
||||
}
|
||||
return props
|
||||
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10101
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10102
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10103
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10101
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10102
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10103
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10104
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10100
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10101
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10102
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10103
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10104
|
@ -1 +0,0 @@
|
||||
server.rmi.localport=10100
|
11
tools/jmeter/src/main/resources/server-rmi.config
Normal file
11
tools/jmeter/src/main/resources/server-rmi.config
Normal file
@ -0,0 +1,11 @@
|
||||
# nodes in the corda performance test cluster
|
||||
performance-node-1:10101
|
||||
performance-node-2:10102
|
||||
performance-node-3:10103
|
||||
performance-node-4:10104
|
||||
performance-notary:10100
|
||||
|
||||
# nodes in the fban test cluster
|
||||
fbantesting-node-1:10101
|
||||
fbantesting-node-2:10102
|
||||
fbantesting-notary:10103
|
@ -0,0 +1,67 @@
|
||||
package com.r3.corda.jmeter
|
||||
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import picocli.CommandLine
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TestCommandLine {
|
||||
@Test
|
||||
fun checkWeGetJmeterArgs() {
|
||||
val args = arrayOf("-Xssh", "server1", "server2", "--", "-p", "/some/properties/file", "-P", "proxy")
|
||||
val cli = LauncherCommandLine()
|
||||
CommandLine.populateCommand(cli, *args)
|
||||
|
||||
assertEquals(cli.jMeterArguments, listOf("-p", "/some/properties/file", "-P", "proxy"))
|
||||
assertEquals(cli.helpRequested, false)
|
||||
assertEquals(cli.sshUser, System.getProperty("user.name"))
|
||||
assertEquals(cli.sshHosts, listOf("server1", "server2"))
|
||||
|
||||
val stream = ByteArrayOutputStream()
|
||||
val ps = PrintStream(stream)
|
||||
CommandLine.usage(cli, ps)
|
||||
assertEquals("""Usage: jmeter-corda [OPTIONS] -- [<jMeter args>...]
|
||||
[<jMeter args>...] All arguments after -- are passed to JMeter
|
||||
-?, -h, --help Prints usage
|
||||
-Xssh=<sshHosts>... List of hosts to create SSH tunnels to, separated by
|
||||
space
|
||||
Example: -Xssh <hostname1> [<hostname2> ...]
|
||||
-XsshUser=<sshUser> Remote user account to use for ssh tunnels. This
|
||||
defaults to the current user.
|
||||
-XadditionalSearchPaths=<additionalSearchPaths>
|
||||
A semicolon separated list of directories/jar files to
|
||||
search for plug-ins/samplers. This will be added to
|
||||
any search paths already in the properties
|
||||
Example:
|
||||
-XadditionalSearchPaths [<class dir>|<jar file>][;...]
|
||||
-XjmeterProperties=<jMeterProperties>
|
||||
Path to a jmeter.properties file to replace the one in
|
||||
the jar. Use this instead of the -p flag of jMeter as
|
||||
the wrapping code needs access to the file as well.
|
||||
-XserverRmiMappings=<serverRmiMappings>
|
||||
Path to a server RMI port mappings file.
|
||||
This file should have a line for each server that needs
|
||||
an ssh tunnel created to. Each line should have the
|
||||
unqualified server name, followed by a colon and the
|
||||
port number. Each host needs a different port number.
|
||||
Lines starting with # are comments and are ignored.
|
||||
Example line for host example-server.corda.net:
|
||||
example-server:10101
|
||||
""", stream.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDefaultArgs() {
|
||||
val cli = LauncherCommandLine()
|
||||
|
||||
assertTrue(cli.jMeterArguments.isEmpty())
|
||||
assertEquals(cli.helpRequested, false)
|
||||
assertEquals(cli.sshUser, System.getProperty("user.name"))
|
||||
assertTrue(cli.sshHosts.isEmpty())
|
||||
assertTrue(cli.jMeterProperties.isBlank())
|
||||
assertTrue(cli.serverRmiMappings.isBlank())
|
||||
assertTrue(cli.additionalSearchPaths.isBlank())
|
||||
}
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
package com.r3.corda.jmeter
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import picocli.CommandLine
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.net.InetAddress
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TestLauncherHelpers {
|
||||
@Rule
|
||||
@JvmField
|
||||
val temporaryFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun testJmeterPropPreppingCapsule() {
|
||||
val expectedSearchPaths = "${createFileInTmpFolder("corda-1.jar")};${createFileInTmpFolder("jmeter-2.jar")}"
|
||||
createFileInTmpFolder("foo.jar")
|
||||
|
||||
SystemPropertySetter("jmeter.home").use {
|
||||
val args = arrayOf("-Xssh", "server1", "server2", "--", "-P", "proxy")
|
||||
val cli = LauncherCommandLine()
|
||||
CommandLine.populateCommand(cli, *args)
|
||||
val searchPaths = Launcher.prepareJMeterPropsCapsule(cli, temporaryFolder.root.absolutePath)
|
||||
|
||||
assertEquals(temporaryFolder.root.absolutePath, System.getProperty("jmeter.home"))
|
||||
assertEquals(expectedSearchPaths, searchPaths)
|
||||
assertTrue((temporaryFolder.root.toPath() / "lib" / "ext").toFile().exists())
|
||||
assertTrue((temporaryFolder.root.toPath() / "lib" / "junit").toFile().exists())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCommandLineMungingCapsuleCustomSearchpath() {
|
||||
val expectedSearchPaths = "${createFileInTmpFolder("corda-1.jar")};${createFileInTmpFolder("jmeter-2.jar")}"
|
||||
createFileInTmpFolder("foo.jar")
|
||||
|
||||
SystemPropertySetter("jmeter.home").use {
|
||||
val args = arrayOf("-Xssh", "server1", "server2", "-XadditionalSearchPaths", "bar.jar", "--", "-P", "proxy")
|
||||
val cli = LauncherCommandLine()
|
||||
CommandLine.populateCommand(cli, *args)
|
||||
val searchPaths = Launcher.prepareJMeterPropsCapsule(cli, temporaryFolder.root.absolutePath)
|
||||
assertEquals("$expectedSearchPaths;bar.jar", searchPaths)
|
||||
assertEquals(temporaryFolder.root.absolutePath, System.getProperty("jmeter.home"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCommandLineMungingGradleCustomSearchPath() {
|
||||
val defaultSearchPaths = "/foo/bar/baz.jar;/some/class/folder"
|
||||
val searchPathFile = createFileInTmpFolder("search_paths.txt")
|
||||
File(searchPathFile).writeText(defaultSearchPaths)
|
||||
|
||||
SystemPropertySetter("search_paths_file", searchPathFile).use {
|
||||
SystemPropertySetter("jmeter.home", temporaryFolder.toString()).use {
|
||||
System.setProperty("search_paths_file", searchPathFile)
|
||||
|
||||
val args = arrayOf("-Xssh", "server1", "server2", "-XadditionalSearchPaths", "bar.jar", "--", "-P", "proxy")
|
||||
val cli = LauncherCommandLine()
|
||||
CommandLine.populateCommand(cli, *args)
|
||||
val searchPaths = Launcher.prepareJMeterPropsGradle(cli)
|
||||
assertEquals("$defaultSearchPaths;bar.jar", searchPaths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCommandLineMungingGradleTestExceptions() {
|
||||
val defaultSearchPaths = "/foo/bar/baz.jar;/some/class/folder"
|
||||
val searchPathFile = createFileInTmpFolder("search_paths.txt")
|
||||
File(searchPathFile).writeText(defaultSearchPaths)
|
||||
|
||||
SystemPropertySetter("search_paths_file", searchPathFile).use {
|
||||
SystemPropertySetter("jmeter.home").use {
|
||||
val args = arrayOf("-Xssh", "server1", "server2", "-XadditionalSearchPaths", "bar.jar", "--", "-P", "proxy")
|
||||
val cli = LauncherCommandLine()
|
||||
CommandLine.populateCommand(cli, *args)
|
||||
testForException("System property jmeter.home must be set when running without capsule") { Launcher.prepareJMeterPropsGradle(cli) }
|
||||
}
|
||||
}
|
||||
|
||||
SystemPropertySetter("search_paths_file").use {
|
||||
SystemPropertySetter("jmeter.home", temporaryFolder.root.toString()).use {
|
||||
val args = arrayOf("-Xssh", "server1", "server2", "-XadditionalSearchPaths", "bar.jar", "--", "-P", "proxy")
|
||||
val cli = LauncherCommandLine()
|
||||
CommandLine.populateCommand(cli, *args)
|
||||
testForException("System property search_paths_file must be set when running without capsule") { Launcher.prepareJMeterPropsGradle(cli) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRmiPortReading() {
|
||||
val rmiConfig = createFileInTmpFolder("rmi.conf")
|
||||
File(rmiConfig).writeText("#This is a comment\n \nfoo:21011\n \ntest:5150")
|
||||
|
||||
val result = Launcher.readHostAndPortMap(rmiConfig)
|
||||
assertEquals(mapOf("foo" to 21011, "test" to 5150), result)
|
||||
}
|
||||
|
||||
private fun createFileInTmpFolder(name: String, content: String = ""): String {
|
||||
val file = (temporaryFolder.root.toPath() / name).toFile()
|
||||
file.writeText(content)
|
||||
return file.absolutePath
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testJMeterArgPreparationServer() {
|
||||
SystemPropertySetter("jmeter.home", temporaryFolder.root.toString()).use {
|
||||
val defaultJmeterPropertiesFile = createFileInTmpFolder("jmeter.properties")
|
||||
createFileInTmpFolder("custom.properties")
|
||||
createFileInTmpFolder("server-rmi.config", "${InetAddress.getLocalHost().hostName}:10101")
|
||||
createFileInTmpFolder("nomatch-rmi.config", "notmatching:10101")
|
||||
|
||||
val cmdLine = LauncherCommandLine()
|
||||
cmdLine.jMeterArguments.addAll(listOf("-s"))
|
||||
|
||||
val result = Launcher.prepareJMeterArguments(cmdLine)
|
||||
assertEquals(defaultJmeterPropertiesFile, result.jmeterPropertiesFile.toString())
|
||||
assertEquals(mapOf(InetAddress.getLocalHost().hostName to 10101), result.serverRmiMappings)
|
||||
val expectedServerPropsFile = temporaryFolder.root.toPath() / "server-rmi.properties"
|
||||
assertEquals(listOf("-s", "-p", defaultJmeterPropertiesFile, "-q", expectedServerPropsFile.toString()), result.jmeterArgs)
|
||||
val serverPropsFile = expectedServerPropsFile.toFile()
|
||||
assertTrue(serverPropsFile.exists())
|
||||
assertEquals("server.rmi.localport=10101", serverPropsFile.readLines().first())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJMeterArgPreparationServerCustomProperties() {
|
||||
SystemPropertySetter("jmeter.home", temporaryFolder.root.toString()).use {
|
||||
createFileInTmpFolder("jmeter.properties")
|
||||
val customJmeterPropertiesFile = createFileInTmpFolder("custom.properties")
|
||||
createFileInTmpFolder("server-rmi.config", "${InetAddress.getLocalHost().hostName}:10101")
|
||||
val noMatchingServerRmiMapping = createFileInTmpFolder("nomatch-rmi.config", "notmatching:10101")
|
||||
|
||||
val cmdLine = LauncherCommandLine()
|
||||
cmdLine.jMeterArguments.addAll(listOf("-s"))
|
||||
cmdLine.jMeterProperties = customJmeterPropertiesFile
|
||||
cmdLine.serverRmiMappings = noMatchingServerRmiMapping
|
||||
|
||||
val result = Launcher.prepareJMeterArguments(cmdLine)
|
||||
assertEquals(customJmeterPropertiesFile, result.jmeterPropertiesFile.toString())
|
||||
assertEquals(mapOf("notmatching" to 10101), result.serverRmiMappings)
|
||||
val expectedServerPropsFile = temporaryFolder.root.toPath() / "server-rmi.properties"
|
||||
assertEquals(listOf("-s", "-p", customJmeterPropertiesFile), result.jmeterArgs)
|
||||
val serverPropsFile = expectedServerPropsFile.toFile()
|
||||
assertFalse(serverPropsFile.exists())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJMeterArgPreparationClient() {
|
||||
SystemPropertySetter("jmeter.home", temporaryFolder.root.toString()).use {
|
||||
val defaultJmeterPropertiesFile = createFileInTmpFolder("jmeter.properties")
|
||||
createFileInTmpFolder("custom.properties")
|
||||
createFileInTmpFolder("server-rmi.config", "${InetAddress.getLocalHost().hostName}:10101")
|
||||
createFileInTmpFolder("nomatch-rmi.config", "notmatching:10101")
|
||||
|
||||
val cmdLine = LauncherCommandLine()
|
||||
|
||||
val result = Launcher.prepareJMeterArguments(cmdLine)
|
||||
assertEquals(defaultJmeterPropertiesFile, result.jmeterPropertiesFile.toString())
|
||||
assertEquals(mapOf(InetAddress.getLocalHost().hostName to 10101), result.serverRmiMappings)
|
||||
val expectedServerPropsFile = temporaryFolder.root.toPath() / "server-rmi.properties"
|
||||
assertEquals(listOf("-p", defaultJmeterPropertiesFile), result.jmeterArgs)
|
||||
val serverPropsFile = expectedServerPropsFile.toFile()
|
||||
assertFalse(serverPropsFile.exists())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJMeterArgPreparationClientCustomProperties() {
|
||||
SystemPropertySetter("jmeter.home", temporaryFolder.root.toString()).use {
|
||||
createFileInTmpFolder("jmeter.properties")
|
||||
val customJmeterPropertiesFile = createFileInTmpFolder("custom.properties")
|
||||
createFileInTmpFolder("server-rmi.config", "${InetAddress.getLocalHost().hostName}:10101")
|
||||
val noMatchingServerRmiMapping = createFileInTmpFolder("nomatch-rmi.config", "notmatching:10101")
|
||||
|
||||
val cmdLine = LauncherCommandLine()
|
||||
cmdLine.jMeterProperties = customJmeterPropertiesFile
|
||||
cmdLine.serverRmiMappings = noMatchingServerRmiMapping
|
||||
|
||||
val result = Launcher.prepareJMeterArguments(cmdLine)
|
||||
assertEquals(customJmeterPropertiesFile, result.jmeterPropertiesFile.toString())
|
||||
assertEquals(mapOf("notmatching" to 10101), result.serverRmiMappings)
|
||||
val expectedServerPropsFile = temporaryFolder.root.toPath() / "server-rmi.properties"
|
||||
assertEquals(listOf("-p", customJmeterPropertiesFile), result.jmeterArgs)
|
||||
val serverPropsFile = expectedServerPropsFile.toFile()
|
||||
assertFalse(serverPropsFile.exists())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJMeterArgPreparationException() {
|
||||
SystemPropertySetter("jmeter.home", temporaryFolder.root.toString()).use {
|
||||
createFileInTmpFolder("jmeter.properties")
|
||||
val customJmeterPropertiesFile = createFileInTmpFolder("custom.properties")
|
||||
createFileInTmpFolder("server-rmi.config", "${InetAddress.getLocalHost().hostName}:10101")
|
||||
val cmdLine = LauncherCommandLine()
|
||||
cmdLine.jMeterArguments.addAll(listOf("-p", customJmeterPropertiesFile))
|
||||
testForException("To choose jmeter.properties, use the -XjmeterProperties flag, not -p for JMeter arguments") { Launcher.prepareJMeterArguments(cmdLine) }
|
||||
}
|
||||
}
|
||||
|
||||
class SystemPropertySetter(private val propertyName: String, private val propertyValue: String? = null) : Closeable {
|
||||
private val originalValue: String? = System.getProperty(propertyName).also { setOrRemove(propertyValue) }
|
||||
|
||||
override fun close() {
|
||||
setOrRemove(originalValue)
|
||||
}
|
||||
|
||||
private fun setOrRemove(value: String?) {
|
||||
if (value == null) {
|
||||
System.clearProperty(propertyName)
|
||||
} else {
|
||||
System.setProperty(propertyName, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSystemPropertySetter() {
|
||||
val propertyName = "mytest.property"
|
||||
assertNull(System.getProperty(propertyName))
|
||||
SystemPropertySetter(propertyName, "foo").use {
|
||||
assertEquals("foo", System.getProperty(propertyName))
|
||||
SystemPropertySetter(propertyName, "bar").use {
|
||||
assertEquals("bar", System.getProperty(propertyName))
|
||||
|
||||
}
|
||||
assertEquals("foo", System.getProperty(propertyName))
|
||||
}
|
||||
assertNull(System.getProperty(propertyName))
|
||||
}
|
||||
|
||||
companion object {
|
||||
inline fun testForException(expectedMessage: String, block: () -> Unit) {
|
||||
var exceptionThrown = false
|
||||
try {
|
||||
block()
|
||||
} catch (e: Launcher.Companion.LauncherException) {
|
||||
exceptionThrown = true
|
||||
assertNotNull(e.message)
|
||||
assertTrue(e.message!!.contains(expectedMessage))
|
||||
}
|
||||
assertTrue(exceptionThrown, "Expected exception has not been thrown")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user