CORDA-3028: Add Node Diagnostics Info RPC Call (#5271)

* CORDA-3028: Add Node Diagnostics Info RPC Call - Initial commit

* CORDA-3028: Add Node Diagnostics Info RPC Call - Changes after code review

* CORDA-3028: Add Node Diagnostics Info RPC Call - Additional code review update and documentation

* CORDA-3028: Add Node Diagnostics Info RPC Call - Additional changes to documentation

* CORDA-3028: Add Node Diagnostics Info RPC Call - Another change to documentation

* CORDA-3028: Add Node Diagnostics Info RPC Call - More changes to documentation

* CORDA-3028: Add Node Diagnostics Info RPC Call - Comment change
This commit is contained in:
gwr3com 2019-07-08 14:43:50 +01:00 committed by Anthony Keenan
parent 6d3a6a3998
commit 933330bc4c
7 changed files with 149 additions and 2 deletions

View File

@ -0,0 +1,15 @@
package net.corda.core.cordapp
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
@CordaSerializable
data class CordappInfo(val type: String,
val name: String,
val shortName: String,
val minimumPlatformVersion: Int,
val targetPlatformVersion: Int,
val version: String,
val vendor: String,
val licence: String,
val jarHash: SecureHash.SHA256)

View File

@ -12,6 +12,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeDiagnosticInfo
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
@ -280,6 +281,11 @@ interface CordaRPCOps : RPCOps {
/** Returns Node's NodeInfo, assuming this will not change while the node is running. */ /** Returns Node's NodeInfo, assuming this will not change while the node is running. */
fun nodeInfo(): NodeInfo fun nodeInfo(): NodeInfo
/**
* Returns Node's NodeDiagnosticInfo, including the version details as well as the information about installed CorDapps.
*/
fun nodeDiagnosticInfo(): NodeDiagnosticInfo
/** /**
* Returns network's notary identities, assuming this will not change while the node is running. * Returns network's notary identities, assuming this will not change while the node is running.
* *

View File

@ -0,0 +1,11 @@
package net.corda.core.node
import net.corda.core.cordapp.CordappInfo
import net.corda.core.serialization.CordaSerializable
@CordaSerializable
data class NodeDiagnosticInfo(val version: String,
val revision: String,
val platformVersion: Int,
val vendor: String,
val cordapps: List<CordappInfo>)

View File

@ -17,8 +17,10 @@ The key RPC operations exposed by the node are:
* Start one of the node's registered flows * Start one of the node's registered flows
* ``CordaRPCOps.startTrackedFlowDynamic`` * ``CordaRPCOps.startTrackedFlowDynamic``
* As above, but also returns a progress handle for the flow * As above, but also returns a progress handle for the flow
* ``CordaRPCOps.nodeDiagnosticInfo``
* Returns diagnostic information about the node, including the version and CorDapp details
* ``CordaRPCOps.nodeInfo`` * ``CordaRPCOps.nodeInfo``
* Returns information about the node * Returns the network map entry of the node, including its address and identity details as well as the platform version information
* ``CordaRPCOps.currentNodeTime`` * ``CordaRPCOps.currentNodeTime``
* Returns the current time according to the node's clock * Returns the current time according to the node's clock
* ``CordaRPCOps.partyFromKey/CordaRPCOps.wellKnownPartyFromX500Name`` * ``CordaRPCOps.partyFromKey/CordaRPCOps.wellKnownPartyFromX500Name``

View File

@ -242,3 +242,40 @@ If the above holds, Corda components will benefit from the following:
* A timely recovery from deletion or corruption of configuration files (e.g., ``node.conf``, ``node-info`` files, etc.), database drivers, CorDapps binaries and configuration, and certificate directories, provided backups are available to restore from. * A timely recovery from deletion or corruption of configuration files (e.g., ``node.conf``, ``node-info`` files, etc.), database drivers, CorDapps binaries and configuration, and certificate directories, provided backups are available to restore from.
.. warning:: Private keys used to sign transactions should be preserved with the utmost care. The recommendation is to keep at least two separate copies on a storage not connected to the Internet. .. warning:: Private keys used to sign transactions should be preserved with the utmost care. The recommendation is to keep at least two separate copies on a storage not connected to the Internet.
Checking node version and installed CorDapps
--------------------------------------------
A ``nodeDiagnosticInfo`` RPC call can be made to obtain version information about the Corda platform running on the node. The returned ``NodeDiagnosticInfo`` object also includes information about the CorDapps installed on the node.
The RPC call is also available as the ``run nodeDiagnosticInfo`` command executable from the Corda shell that can be accessed via the local terminal, SSH, or as the standalone shell.
Example
+++++++
Here is a sample output displayed by the ``run nodeDiagnosticInfo`` command executed from the Corda shell:
.. code-block:: none
version: "|corda_version|"
revision: "d7e4a0050049be357999f57f69d8bca41a2b8274"
platformVersion: 4
vendor: "Corda Open Source"
cordapps:
- type: "Contract CorDapp"
name: "corda-finance-contracts-|corda_version|"
shortName: "Corda Finance Demo"
minimumPlatformVersion: 1
targetPlatformVersion: 4
version: "1"
vendor: "R3"
licence: "Open Source (Apache 2)"
jarHash: "570EEB9DF4B43680586F3BE663F9C5844518BC2E410EAF9904E8DEE930B7E45C"
- type: "Workflow CorDapp"
name: "corda-finance-workflows-|corda_version|"
shortName: "Corda Finance Demo"
minimumPlatformVersion: 1
targetPlatformVersion: 4
version: "1"
vendor: "R3"
licence: "Open Source (Apache 2)"
jarHash: "6EA4E0B36010F1DD27B5677F3686B4713BA40C316804A4188DCA20F477FDB23F"

View File

@ -0,0 +1,46 @@
package net.corda.node
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.internal.FINANCE_CONTRACTS_CORDAPP
import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class NodeRPCTests {
private val CORDA_VERSION_REGEX = "\\d+(\\.\\d+)?(-\\w+)?".toRegex() // e.g. "5.0-SNAPSHOT"
private val CORDA_VENDOR = "Corda Open Source"
private val CORDAPPS = listOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)
private val CORDAPP_TYPES = setOf("Contract CorDapp", "Workflow CorDapp")
private val CORDAPP_CONTRACTS_NAME_REGEX = "corda-finance-contracts-$CORDA_VERSION_REGEX".toRegex()
private val CORDAPP_WORKFLOWS_NAME_REGEX = "corda-finance-workflows-$CORDA_VERSION_REGEX".toRegex()
private val CORDAPP_SHORT_NAME = "Corda Finance Demo"
private val CORDAPP_VENDOR = "R3"
private val CORDAPP_LICENCE = "Open Source (Apache 2)"
private val HEXADECIMAL_REGEX = "[0-9a-fA-F]+".toRegex()
@Test
fun `run nodeDiagnosticInfo`() {
driver(DriverParameters(notarySpecs = emptyList(), cordappsForAllNodes = CORDAPPS)) {
val nodeDiagnosticInfo = startNode().get().rpc.nodeDiagnosticInfo()
assertTrue(nodeDiagnosticInfo.version.matches(CORDA_VERSION_REGEX))
assertTrue(nodeDiagnosticInfo.revision.matches(HEXADECIMAL_REGEX))
assertEquals(PLATFORM_VERSION, nodeDiagnosticInfo.platformVersion)
assertEquals(CORDA_VENDOR, nodeDiagnosticInfo.vendor)
assertEquals(CORDAPPS.size, nodeDiagnosticInfo.cordapps.size)
assertEquals(CORDAPP_TYPES, nodeDiagnosticInfo.cordapps.map { it.type }.toSet())
assertTrue(nodeDiagnosticInfo.cordapps.any { it.name.matches(CORDAPP_CONTRACTS_NAME_REGEX) })
assertTrue(nodeDiagnosticInfo.cordapps.any { it.name.matches(CORDAPP_WORKFLOWS_NAME_REGEX) })
val cordappInfo = nodeDiagnosticInfo.cordapps.first()
assertEquals(CORDAPP_SHORT_NAME, cordappInfo.shortName)
assertTrue(cordappInfo.version.all { it.isDigit() })
assertEquals(CORDAPP_VENDOR, cordappInfo.vendor)
assertEquals(CORDAPP_LICENCE, cordappInfo.licence)
assertTrue(cordappInfo.minimumPlatformVersion <= PLATFORM_VERSION)
assertTrue(cordappInfo.targetPlatformVersion <= PLATFORM_VERSION)
assertTrue(cordappInfo.jarHash.toString().matches(HEXADECIMAL_REGEX))
}
}
}

View File

@ -1,11 +1,14 @@
package net.corda.node.internal package net.corda.node.internal
import net.corda.client.rpc.notUsed import net.corda.client.rpc.notUsed
import net.corda.common.logging.CordaVersion
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext import net.corda.core.context.InvocationContext
import net.corda.core.context.InvocationOrigin import net.corda.core.context.InvocationOrigin
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappInfo
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
@ -21,6 +24,7 @@ import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.sign import net.corda.core.internal.sign
import net.corda.core.messaging.* import net.corda.core.messaging.*
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeDiagnosticInfo
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
@ -165,6 +169,32 @@ internal class CordaRPCOpsImpl(
return services.myInfo return services.myInfo
} }
override fun nodeDiagnosticInfo(): NodeDiagnosticInfo {
return NodeDiagnosticInfo(
version = CordaVersion.releaseVersion,
revision = CordaVersion.revision,
platformVersion = CordaVersion.platformVersion,
vendor = CordaVersion.vendor,
cordapps = services.cordappProvider.cordapps
.filter { !it.jarPath.toString().endsWith("corda-core-${CordaVersion.releaseVersion}.jar") }
.map { CordappInfo(
type = when (it.info) {
is Cordapp.Info.Contract -> "Contract CorDapp"
is Cordapp.Info.Workflow -> "Workflow CorDapp"
else -> "CorDapp"
},
name = it.name,
shortName = it.info.shortName,
minimumPlatformVersion = it.minimumPlatformVersion,
targetPlatformVersion = it.targetPlatformVersion,
version = it.info.version,
vendor = it.info.vendor,
licence = it.info.licence,
jarHash = it.jarHash)
}
)
}
override fun notaryIdentities(): List<Party> { override fun notaryIdentities(): List<Party> {
return services.networkMapCache.notaryIdentities return services.networkMapCache.notaryIdentities
} }
@ -373,4 +403,4 @@ internal class CordaRPCOpsImpl(
private inline fun <reified TARGET> Class<*>.checkIsA() { private inline fun <reified TARGET> Class<*>.checkIsA() {
require(TARGET::class.java.isAssignableFrom(this)) { "$name is not a ${TARGET::class.java.name}" } require(TARGET::class.java.isAssignableFrom(this)) { "$name is not a ${TARGET::class.java.name}" }
} }
} }