Start Jolokia agents if configured without modifying JVM options [CORDA-1042] (#2541)

* Jolokia agents are loaded dynamically if configured
* Renamed exportJmxTo (never used) to jmxMonitoringHttpPort and take it from config
* Updated documentation and tests
This commit is contained in:
igor nitto 2018-02-15 17:10:07 +00:00 committed by GitHub
parent 2864ce1384
commit ed0cf91946
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 125 additions and 17 deletions

3
.idea/compiler.xml generated
View File

@ -39,6 +39,7 @@
<module name="cordformation_main" target="1.8" />
<module name="cordformation_runnodes" target="1.8" />
<module name="cordformation_test" target="1.8" />
<module name="core_extraResource" target="1.8" />
<module name="core_integrationTest" target="1.8" />
<module name="core_main" target="1.8" />
<module name="core_smokeTest" target="1.8" />
@ -165,4 +166,4 @@
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component>
</project>
</project>

View File

@ -2,6 +2,7 @@ buildscript {
// For sharing constants between builds
Properties constants = new Properties()
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
file("${project(':node').projectDir}/src/main/resources/build.properties").withInputStream { constants.load(it) }
// Our version: bump this on release.
ext.corda_release_version = "3.0-SNAPSHOT"
@ -40,7 +41,7 @@ buildscript {
ext.jackson_version = '2.9.3'
ext.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25'
ext.jolokia_version = '1.3.7'
ext.jolokia_version = constants.getProperty("jolokiaAgentVersion")
ext.assertj_version = '3.8.0'
ext.slf4j_version = '1.7.25'
ext.log4j_version = '2.9.1'
@ -71,6 +72,7 @@ buildscript {
ext.docker_compose_rule_version = '0.33.0'
ext.selenium_version = '3.8.1'
ext.ghostdriver_version = '2.1.0'
ext.eaagentloader_version = '1.0.3'
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131'

View File

@ -185,8 +185,8 @@ path to the node's base directory.
:port: The port to start SSH server on
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
:jmxMonitoringHttpPort: If set, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent on the corresponding port.
Default Jolokia access url is http://127.0.0.1:port/jolokia/
:transactionCacheSizeMegaBytes: Optionally specify how much memory should be used for caching of ledger transactions in memory.
Otherwise defaults to 8MB plus 5% of all heap memory above 300MB.

View File

@ -106,7 +106,7 @@ Here are a few ways to build dashboards and extract monitoring data for a node:
It can bridge any data input to any output using their plugin system, for example, Telegraf can
be configured to collect data from Jolokia and write to DataDog web api.
The Node configuration parameter `exportJMXTo` should be set to ``http`` to ensure a Jolokia agent is instrumented with
The Node configuration parameter `jmxMonitoringHttpPort` has to be present in order to ensure a Jolokia agent is instrumented with
the JVM run-time.
The following JMX statistics are exported:

View File

@ -172,6 +172,9 @@ dependencies {
// Jsh: Testing SSH server
integrationTestCompile group: 'com.jcraft', name: 'jsch', version: '0.1.54'
// AgentLoader: dynamic loading of JVM agents
compile group: 'com.ea.agentloader', name: 'ea-agent-loader', version: "${eaagentloader_version}"
// Jetty dependencies for NetworkMapClient test.
// Web stuff: for HTTP[S] servlets
testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
@ -183,7 +186,6 @@ dependencies {
testCompile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
testCompile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
// Jolokia JVM monitoring agent
runtime "org.jolokia:jolokia-jvm:${jolokia_version}:agent"
}

View File

@ -138,7 +138,7 @@ class AMQPBridgeTest {
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(artemisAddress).whenever(it).p2pAddress
doReturn("").whenever(it).exportJMXto
doReturn(null).whenever(it).jmxMonitoringHttpPort
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
}
artemisConfig.configureWithDevSSLCertificate()

View File

@ -222,7 +222,7 @@ class ProtonWrapperTests {
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress
doReturn("").whenever(it).exportJMXto
doReturn(null).whenever(it).jmxMonitoringHttpPort
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
}
artemisConfig.configureWithDevSSLCertificate()

View File

@ -59,6 +59,8 @@ import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSoftLockManager
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.NodeBuildProperties
import net.corda.node.utilities.JVMAgentRegistry
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -73,6 +75,7 @@ import rx.Observable
import rx.Scheduler
import java.io.IOException
import java.lang.reflect.InvocationTargetException
import java.nio.file.Paths
import java.security.KeyPair
import java.security.KeyStoreException
import java.security.PublicKey
@ -192,6 +195,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
check(started == null) { "Node has already been started" }
log.info("Node starting up ...")
initCertificate()
initialiseJVMAgents()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null)
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val identityService = makeIdentityService(identity.certificate)
@ -749,6 +753,22 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return NodeVaultService(platformClock, keyManagementService, stateLoader, hibernateConfig)
}
/** Load configured JVM agents */
private fun initialiseJVMAgents() {
configuration.jmxMonitoringHttpPort?.let { port ->
requireNotNull(NodeBuildProperties.JOLOKIA_AGENT_VERSION) {
"'jolokiaAgentVersion' missing from build properties"
}
log.info("Starting Jolokia agent on HTTP port: $port")
val libDir = Paths.get(configuration.baseDirectory.toString(), "drivers")
val jarFilePath = JVMAgentRegistry.resolveAgentJar(
"jolokia-jvm-${NodeBuildProperties.JOLOKIA_AGENT_VERSION}-agent.jar", libDir) ?:
throw Error("Unable to locate agent jar file")
log.info("Agent jar file: $jarFilePath")
JVMAgentRegistry.attach("jolokia", "port=$port", jarFilePath)
}
}
private inner class ServiceHubInternalImpl(
override val identityService: IdentityService,
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because

View File

@ -192,9 +192,9 @@ open class Node(configuration: NodeConfiguration,
val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc"
with(rpcOptions) {
rpcBroker = if (useSsl) {
ArtemisRpcBroker.withSsl(this.address!!, sslConfig, securityManager, certificateChainCheckPolicies, networkParameters.maxMessageSize, exportJMXto.isNotEmpty(), rpcBrokerDirectory)
ArtemisRpcBroker.withSsl(this.address!!, sslConfig, securityManager, certificateChainCheckPolicies, networkParameters.maxMessageSize, jmxMonitoringHttpPort != null, rpcBrokerDirectory)
} else {
ArtemisRpcBroker.withoutSsl(this.address!!, adminAddress!!, sslConfig, securityManager, certificateChainCheckPolicies, networkParameters.maxMessageSize, exportJMXto.isNotEmpty(), rpcBrokerDirectory)
ArtemisRpcBroker.withoutSsl(this.address!!, adminAddress!!, sslConfig, securityManager, certificateChainCheckPolicies, networkParameters.maxMessageSize, jmxMonitoringHttpPort != null, rpcBrokerDirectory)
}
}
return rpcBroker!!.addresses

View File

@ -25,7 +25,7 @@ val Int.MB: Long get() = this * 1024L * 1024L
interface NodeConfiguration : NodeSSLConfiguration {
val myLegalName: CordaX500Name
val emailAddress: String
val exportJMXto: String
val jmxMonitoringHttpPort: Int?
val dataSourceProperties: Properties
val rpcUsers: List<User>
val security: SecurityConfiguration?
@ -118,6 +118,7 @@ data class NodeConfigurationImpl(
/** This is not retrieved from the config file but rather from a command line argument. */
override val baseDirectory: Path,
override val myLegalName: CordaX500Name,
override val jmxMonitoringHttpPort: Int? = null,
override val emailAddress: String,
override val keyStorePassword: String,
override val trustStorePassword: String,
@ -184,7 +185,6 @@ data class NodeConfigurationImpl(
return errors
}
override val exportJMXto: String get() = "http"
override val transactionCacheSizeBytes: Long
get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes
override val attachmentContentCacheSizeBytes: Long

View File

@ -144,7 +144,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS)
// JMX enablement
if (config.exportJMXto.isNotEmpty()) {
if (config.jmxMonitoringHttpPort != null) {
isJMXManagementEnabled = true
isJMXUseBrokerName = true
}

View File

@ -0,0 +1,50 @@
package net.corda.node.utilities
import com.ea.agentloader.AgentLoader
import net.corda.core.internal.exists
import net.corda.core.internal.isRegularFile
import java.net.URLClassLoader
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.ConcurrentHashMap
/**
* Helper class for loading JVM agents dynamically
*/
object JVMAgentRegistry {
/**
* Names and options of loaded agents
*/
val loadedAgents = ConcurrentHashMap<String, String>()
/**
* Load and attach agent located at given [jar], unless [loadedAgents]
* indicate that one of its instance has been already loaded.
*/
fun attach(agentName: String, options: String, jar: Path) {
loadedAgents.computeIfAbsent(agentName.toLowerCase()) {
AgentLoader.loadAgent(jar.toString(), options)
options
}
}
/**
* Attempt finding location of jar for given agent by first searching into
* "drivers" directory of [nodeBaseDirectory] and then falling back to
* classpath. Returns null if no match is found.
*/
fun resolveAgentJar(jarFileName: String, driversDir: Path): Path? {
require(jarFileName.endsWith(".jar")) { "jarFileName does not have .jar suffix" }
val path = Paths.get(driversDir.toString(), jarFileName)
return if (path.exists() && path.isRegularFile()) {
path
} else {
(this::class.java.classLoader as? URLClassLoader)
?.urLs
?.map { Paths.get(it.path) }
?.firstOrNull { it.fileName.toString() == jarFileName }
}
}
}

View File

@ -0,0 +1,27 @@
package net.corda.node.utilities
import java.util.*
/**
* Expose properties defined in top-level 'constants.properties' file.
*/
object NodeBuildProperties {
// Note: initialization order is important
private val data by lazy {
Properties().apply {
NodeBuildProperties::class.java.getResourceAsStream("/build.properties")
?.let { load(it) }
}
}
/**
* Jolokia dependency version
*/
val JOLOKIA_AGENT_VERSION = get("jolokiaAgentVersion")
/**
* Get property value by name
*/
fun get(key: String): String? = data.getProperty(key)
}

View File

@ -0,0 +1,5 @@
# Build constants exported as resource file to make them visible in Node program
# Note: sadly, due to present limitation of IntelliJ-IDEA in processing resource files, these constants cannot be
# imported from top-level 'constants.properties' file
jolokiaAgentVersion=1.3.7

View File

@ -67,7 +67,7 @@ class ArtemisMessagingTest {
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(NetworkHostAndPort("0.0.0.0", serverPort)).whenever(it).p2pAddress
doReturn("").whenever(it).exportJMXto
doReturn(null).whenever(it).jmxMonitoringHttpPort
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
doReturn(5).whenever(it).messageRedeliveryDelaySeconds
}

View File

@ -91,10 +91,11 @@ class DriverTests {
@Test
fun `monitoring mode enables jolokia exporting of JMX metrics via HTTP JSON`() {
driver(DriverParameters(jmxPolicy = JmxPolicy(true))) {
driver(DriverParameters(startNodesInProcess = false)) {
// start another node so we gain access to node JMX metrics
startNode(providedName = DUMMY_REGULATOR_NAME).getOrThrow()
val webAddress = NetworkHostAndPort("localhost", 7006)
startNode(providedName = DUMMY_REGULATOR_NAME,
customOverrides = mapOf("jmxMonitoringHttpPort" to webAddress.port)).getOrThrow()
// request access to some JMX metrics via Jolokia HTTP/JSON
val api = HttpApi.fromHostAndPort(webAddress, "/jolokia/")
val versionAsJson = api.getJson<JSONObject>("/jolokia/version/")

View File

@ -469,7 +469,7 @@ private fun mockNodeConfiguration(): NodeConfiguration {
doReturn(null).whenever(it).notary
doReturn(DatabaseConfig()).whenever(it).database
doReturn("").whenever(it).emailAddress
doReturn("").whenever(it).exportJMXto
doReturn(null).whenever(it).jmxMonitoringHttpPort
doReturn(true).whenever(it).devMode
doReturn(null).whenever(it).compatibilityZoneURL
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies