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:
Christian Sailer 2018-10-16 08:14:55 +01:00 committed by GitHub
parent f2c5ab3b0f
commit 802c88e7ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 552 additions and 153 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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

View File

@ -1 +0,0 @@
server.rmi.localport=10101

View File

@ -1 +0,0 @@
server.rmi.localport=10102

View File

@ -1 +0,0 @@
server.rmi.localport=10103

View File

@ -1 +0,0 @@
server.rmi.localport=10101

View File

@ -1 +0,0 @@
server.rmi.localport=10102

View File

@ -1 +0,0 @@
server.rmi.localport=10103

View File

@ -1 +0,0 @@
server.rmi.localport=10104

View File

@ -1 +0,0 @@
server.rmi.localport=10100

View File

@ -1 +0,0 @@
server.rmi.localport=10101

View File

@ -1 +0,0 @@
server.rmi.localport=10102

View File

@ -1 +0,0 @@
server.rmi.localport=10103

View File

@ -1 +0,0 @@
server.rmi.localport=10104

View File

@ -1 +0,0 @@
server.rmi.localport=10100

View 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

View File

@ -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())
}
}

View File

@ -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")
}
}
}