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.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeDiagnosticInfo
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
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. */
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.
*

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
* ``CordaRPCOps.startTrackedFlowDynamic``
* 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``
* 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``
* Returns the current time according to the node's clock
* ``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.
.. 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
import net.corda.client.rpc.notUsed
import net.corda.common.logging.CordaVersion
import net.corda.core.CordaRuntimeException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext
import net.corda.core.context.InvocationOrigin
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.flows.FlowInitiator
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.messaging.*
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeDiagnosticInfo
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
@ -165,6 +169,32 @@ internal class CordaRPCOpsImpl(
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> {
return services.networkMapCache.notaryIdentities
}
@ -373,4 +403,4 @@ internal class CordaRPCOpsImpl(
private inline fun <reified TARGET> Class<*>.checkIsA() {
require(TARGET::class.java.isAssignableFrom(this)) { "$name is not a ${TARGET::class.java.name}" }
}
}
}