Merge pull request #12 from corda/chrisbarratt/CORE-9302-switch-to-Corda-beta1-release-build
CORE-9302 Switch to corda beta1 release build
This commit is contained in:
commit
1eb51b7fcc
4
.ci/Jenkinsfile
vendored
4
.ci/Jenkinsfile
vendored
@ -1,5 +1,5 @@
|
||||
@Library('corda-shared-build-pipeline-steps@chrisbarratt/CORE-8075-create-pipeline-for-running-the-autotester') _
|
||||
|
||||
@Library('corda-shared-build-pipeline-steps@5.0') _
|
||||
|
||||
cordaPipeline(
|
||||
nexusAppId: 'com.corda.CSDE-Java.5.0',
|
||||
publishRepoPrefix: '',
|
||||
|
@ -1,5 +1,8 @@
|
||||
# CSDE-cordapp-template-java
|
||||
|
||||
## Note: This cut of CSDE is work in progress and has not been released yet, hence may not function as expected.
|
||||
|
||||
|
||||
To help make the process of prototyping Cordapps on Developer Preview 2 more straight forward we have developed the Cordapp Standard Development Environment (CSDE).
|
||||
|
||||
The CSDE is obtained by cloning this CSDE-Cordapp-Template-java to your local machine. The CSDE provides:
|
||||
|
122
build.gradle
122
build.gradle
@ -3,11 +3,6 @@ import static org.gradle.api.JavaVersion.VERSION_11
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
|
||||
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
|
||||
// These extend existing build environment so that CPB and CPK files can be built.
|
||||
// This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
|
||||
// required by Corda.
|
||||
id 'net.corda.plugins.cordapp-cpb2'
|
||||
id 'net.corda.cordapp.cordapp-configuration'
|
||||
|
||||
id 'org.jetbrains.kotlin.plugin.jpa'
|
||||
@ -18,110 +13,39 @@ plugins {
|
||||
id 'csde'
|
||||
}
|
||||
|
||||
group 'com.r3.hellocorda'
|
||||
version '1.0-SNAPSHOT'
|
||||
allprojects {
|
||||
group 'com.r3.hellocorda'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
def javaVersion = VERSION_11
|
||||
def javaVersion = VERSION_11
|
||||
|
||||
// The CordApp section.
|
||||
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
|
||||
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the component’s
|
||||
// subproject.
|
||||
// This is required by the corda plugins to build the CorDapp.
|
||||
cordapp {
|
||||
// "targetPlatformVersion" and "minimumPlatformVersion" are intended to specify the preferred
|
||||
// and earliest versions of the Corda platform that the CorDapp will run on respectively.
|
||||
// Enforced versioning has not implemented yet so we need to pass in a dummy value for now.
|
||||
// The platform version will correspond to and be roughly equivalent to the Corda API version.
|
||||
targetPlatformVersion platformVersion.toInteger()
|
||||
minimumPlatformVersion platformVersion.toInteger()
|
||||
|
||||
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
|
||||
// Declares the type and metadata of the CPK (this CPB has one CPK).
|
||||
workflow {
|
||||
name "ModuleNameHere"
|
||||
versionId 1
|
||||
vendor "VendorNameHere"
|
||||
// Declare the set of Java compiler options we need to build a CorDapp.
|
||||
tasks.withType(JavaCompile) {
|
||||
// -parameters - Needed for reflection and serialization to work correctly.
|
||||
options.compilerArgs += [
|
||||
"-parameters"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Declare the set of Kotlin compiler options we need to build a CorDapp.
|
||||
tasks.withType(JavaCompile) {
|
||||
|
||||
// -parameters - Needed for reflection and serialization to work correctly.
|
||||
options.compilerArgs += [
|
||||
"-parameters"
|
||||
]
|
||||
}
|
||||
|
||||
repositories {
|
||||
// All dependencies are held in Maven Central
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = "$artifactoryContextUrl/"
|
||||
repositories {
|
||||
// All dependencies are held in Maven Central
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
// Declare dependencies for the modules we will use.
|
||||
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
|
||||
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
|
||||
dependencies {
|
||||
// We need a version of kotlin-stdlib-jdk8 built as an OSGi bundle, this is "kotlin-stdlib-jdk8-osgi".
|
||||
// R3 builds kotlin-stdlib-jdk8-osgi from Kotlin's kotlin-stdlib-jdk8.
|
||||
// NB:
|
||||
// Kotlin's kotlin-osgi-bundle does not provide all of the Kotlin API that is required,
|
||||
// There is no kotlin-stdlib-jdk11, but one is not needed even though we are targetting Java 11.
|
||||
cordaProvided 'net.corda.kotlin:kotlin-stdlib-jdk8-osgi'
|
||||
tasks.withType(Test).configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
|
||||
// Corda API specified.
|
||||
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
|
||||
|
||||
// If using transistive dependencies this will provide most of Corda-API:
|
||||
// cordaProvided 'net.corda:corda-application'
|
||||
|
||||
// Alternatively we can explicitly specify all our Corda-API dependencies:
|
||||
cordaProvided 'net.corda:corda-base'
|
||||
cordaProvided 'net.corda:corda-application'
|
||||
cordaProvided 'net.corda:corda-crypto'
|
||||
cordaProvided 'net.corda:corda-membership'
|
||||
// cordaProvided 'net.corda:corda-persistence'
|
||||
cordaProvided 'net.corda:corda-serialization'
|
||||
|
||||
// Not yet fully implemented:
|
||||
// cordaProvided 'net.corda:corda-ledger'
|
||||
|
||||
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
|
||||
cordaProvided 'org.slf4j:slf4j-api'
|
||||
|
||||
// Dependencies Required By Test Tooling
|
||||
testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
|
||||
testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
|
||||
|
||||
// 3rd party libraries
|
||||
// Required
|
||||
testImplementation "org.slf4j:slf4j-simple:2.0.0"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
// Optional but used by exmaple tests.
|
||||
testImplementation "org.mockito:mockito-core:$mockitoVersion"
|
||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifactId "corda-CSDE-java-sample"
|
||||
groupId project.group
|
||||
artifact jar
|
||||
}
|
||||
maven(MavenPublication) {
|
||||
artifactId "corda-CSDE-java-sample"
|
||||
groupId project.group
|
||||
artifact jar
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,6 @@ plugins {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = "$artifactoryContextUrl/"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -1,6 +1,4 @@
|
||||
jacksonVersion = 2.13.3
|
||||
jacksonVersion = 2.13.4
|
||||
unirestVersion=3.13.10
|
||||
cordaApiVersion=5.0.0.505-Beta1.0-HC00
|
||||
|
||||
# R3 internal repository
|
||||
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00
|
||||
cordaApiVersion=5.0.0.523-Fox1.0
|
||||
|
@ -1,4 +1,24 @@
|
||||
import com.r3.csde.CsdeRpcInterface
|
||||
// Note, IntelliJ does not recognise the imported Java Classes, hence they are
|
||||
// highlighted in Red. However, they are recognised in the gradle compilation.
|
||||
|
||||
|
||||
// todo: look at the declaration of the script variables, can they be combined with the declaration of the Project Context
|
||||
// todo: investigate adding corda-cli to the class path then executing it directly - might not work as gradle has to set up the jar file, so its not their when you start.
|
||||
// Todo: write a test flow runner helper function??
|
||||
// todo: rename deployCPIsHelper
|
||||
// todo: add proper logging, rather than reading Stdout
|
||||
// todo: add test corda running/live task
|
||||
// todo: add a test to check docker is running and display error if not + halt start corda
|
||||
// todo: add a clean corda task.
|
||||
|
||||
|
||||
import com.r3.csde.CordaLifeCycleHelper
|
||||
import com.r3.csde.ProjectContext
|
||||
import com.r3.csde.DeployCPIsHelper
|
||||
import com.r3.csde.BuildCPIsHelper
|
||||
import com.r3.csde.ProjectUtils
|
||||
import com.r3.csde.CordaStatusQueries
|
||||
import com.r3.csde.CreateAndRegisterVNodesHelper
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
@ -18,21 +38,37 @@ configurations {
|
||||
canBeResolved = true
|
||||
}
|
||||
|
||||
notaryServerCPB {
|
||||
canBeConsumed = false
|
||||
canBeResolved = true
|
||||
}
|
||||
}
|
||||
|
||||
// Dependencies for supporting tools
|
||||
dependencies {
|
||||
combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion"
|
||||
myPostgresJDBC 'org.postgresql:postgresql:42.4.1'
|
||||
notaryServerCPB("com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-server:$cordaNotaryPluginsVersion") {
|
||||
artifact {
|
||||
classifier = 'package'
|
||||
extension = 'cpb'
|
||||
}
|
||||
}
|
||||
|
||||
implementation "org.codehaus.groovy:groovy-json:3.0.9"
|
||||
}
|
||||
|
||||
// task groupings
|
||||
def cordaGroup = 'csde-corda' // corda lifecycle tasks
|
||||
def cordappGroup = 'csde-cordapp' // tasks to build and deploy corDapps
|
||||
def queriesGroup = 'csde-queries' // tasks which query corda status
|
||||
def supportingGroup = 'supporting' // tasks which should be hidden from the csde user
|
||||
|
||||
def pluginGroupName = "CSDE"
|
||||
def pluginImplGroupName = "other"
|
||||
def cordaBinDir= System.getProperty('user.home') + "/.corda/corda5"
|
||||
def cordaCliBinDir = System.getProperty('user.home') + "/.corda/cli"
|
||||
|
||||
def cordaBinDir = System.getenv("CSDE_CORDA_BIN") ?: System.getProperty('user.home') + "/.corda/corda5"
|
||||
def cordaCliBinDir = System.getenv("CSDE_CORDA_CLI") ?:System.getProperty('user.home') + "/.corda/cli"
|
||||
def cordaJDBCDir = cordaBinDir + "/jdbcDrivers"
|
||||
def cordaNotaryServerDir = cordaBinDir + "/notaryserver"
|
||||
def signingCertAlias="gradle-plugin-default-key"
|
||||
// Get error if this is not a autotyped object
|
||||
// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem"
|
||||
@ -41,25 +77,75 @@ def keystoreAlias = "my-signing-key"
|
||||
def keystoreFName = devEnvWorkspace + "/signingkeys.pfx"
|
||||
def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem"
|
||||
def combiWorkerPidCacheFile = devEnvWorkspace + "/CordaPID.dat"
|
||||
// todo: can we rely on the build directory always being /workflow/build? aslo, is the
|
||||
// workflow directory the correct place to build the cpb to. shoudl it be the main build directory.
|
||||
def workflowBuildDir = rootDir.toString() + "/workflows/build"
|
||||
|
||||
|
||||
// Need to read things from cordapp plugin
|
||||
def cpiName = 'cpi name'
|
||||
// todo: Need to read things from cordapp plugin - the cordapp names will be changed by the user
|
||||
def appCpiName = 'cpi name'
|
||||
def notaryCpiName = 'CSDE Notary Server CPI'
|
||||
|
||||
def csdeHelper = new CsdeRpcInterface(project,
|
||||
|
||||
// todo: there should be a better way to set up these project context variables.
|
||||
def projectContext = new ProjectContext(project,
|
||||
cordaClusterURL.toString(),
|
||||
cordaRpcUser,
|
||||
cordaRpcPasswd,
|
||||
devEnvWorkspace,
|
||||
// todo: why is this not obtained in the groovy def's abouve - its inconsistent.
|
||||
new String("${System.getProperty("java.home")}/bin"),
|
||||
dbContainerName,
|
||||
cordaJDBCDir,
|
||||
combiWorkerPidCacheFile
|
||||
combiWorkerPidCacheFile,
|
||||
signingCertAlias,
|
||||
signingCertFName,
|
||||
keystoreAlias,
|
||||
keystoreFName,
|
||||
keystoreCertFName,
|
||||
appCpiName,
|
||||
notaryCpiName,
|
||||
devEnvWorkspace,
|
||||
cordaCliBinDir,
|
||||
cordaNotaryServerDir,
|
||||
workflowBuildDir,
|
||||
cordaNotaryPluginsVersion
|
||||
)
|
||||
|
||||
def utils = new ProjectUtils()
|
||||
|
||||
// Initiate workspace folder
|
||||
|
||||
tasks.register('projInit') {
|
||||
group = supportingGroup
|
||||
doLast {
|
||||
mkdir devEnvWorkspace
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CordaLifeCycle tasks
|
||||
|
||||
def cordaLifeCycle = new CordaLifeCycleHelper(projectContext)
|
||||
|
||||
tasks.register("startCorda") {
|
||||
group = cordaGroup
|
||||
dependsOn('getDevCordaLite', 'getPostgresJDBC')
|
||||
doLast {
|
||||
mkdir devEnvWorkspace
|
||||
cordaLifeCycle.startCorda()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("stopCorda") {
|
||||
group = cordaGroup
|
||||
doLast {
|
||||
cordaLifeCycle.stopCorda()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("getPostgresJDBC") {
|
||||
group = pluginImplGroupName
|
||||
group = supportingGroup
|
||||
doLast {
|
||||
copy {
|
||||
from configurations.myPostgresJDBC
|
||||
@ -68,183 +154,99 @@ tasks.register("getPostgresJDBC") {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('projInit') {
|
||||
group = pluginImplGroupName
|
||||
doLast {
|
||||
mkdir devEnvWorkspace
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("createGroupPolicy") {
|
||||
group = pluginImplGroupName
|
||||
dependsOn('projInit')
|
||||
doLast {
|
||||
|
||||
def groupPolicyFName = new String("${devEnvWorkspace}/GroupPolicy.json")
|
||||
def devnetFName = new String("$rootDir/config/dev-net.json")
|
||||
File groupPolicyFile = new File(groupPolicyFName)
|
||||
File devnetFile = new File(devnetFName)
|
||||
if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) {
|
||||
def configX500Ids = csdeHelper.getConfigX500Ids()
|
||||
|
||||
println("createGroupPolicy: Creating a GroupPolicy")
|
||||
|
||||
javaexec {
|
||||
classpath = files("$cordaCliBinDir/corda-cli.jar")
|
||||
jvmArgs = ["-Dpf4j.pluginsDir=$cordaCliBinDir/plugins/"]
|
||||
standardOutput = new FileOutputStream(groupPolicyFName)
|
||||
LinkedList<String> myArgs = new LinkedList<String>()
|
||||
myArgs.add("mgm")
|
||||
myArgs.add("groupPolicy")
|
||||
configX500Ids.forEach {
|
||||
myArgs.add("--name")
|
||||
myArgs.add("$it")
|
||||
}
|
||||
|
||||
myArgs.add("--endpoint-protocol=1")
|
||||
myArgs.add("--endpoint=http://localhost:1080")
|
||||
args = myArgs
|
||||
}
|
||||
|
||||
} else {
|
||||
println("createPolicyTask: everything up to date; nothing to do.")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("getDevCordaLite", Copy) {
|
||||
group = pluginImplGroupName
|
||||
group = supportingGroup
|
||||
from configurations.combinedWorker
|
||||
into cordaBinDir
|
||||
}
|
||||
|
||||
tasks.register('createKeystore') {
|
||||
group = pluginImplGroupName
|
||||
dependsOn('projInit')
|
||||
doLast {
|
||||
File keystoreFile = new File(keystoreFName)
|
||||
if(!keystoreFile.exists()) {
|
||||
println('createKeystore: Create a keystore')
|
||||
exec {
|
||||
commandLine "${System.getProperty("java.home")}/bin/keytool", "-genkeypair",
|
||||
"-alias", keystoreAlias,
|
||||
"-keystore", keystoreFName,
|
||||
"-storepass", "keystore password",
|
||||
"-dname", "CN=CPI Example - My Signing Key, O=CorpOrgCorp, L=London, C=GB",
|
||||
"-keyalg", "RSA",
|
||||
"-storetype", "pkcs12",
|
||||
"-validity", "4000"
|
||||
}
|
||||
// Need to add the default signing key to the keystore
|
||||
exec {
|
||||
commandLine "${System.getProperty("java.home")}/bin/keytool", "-importcert",
|
||||
"-keystore", keystoreFName,
|
||||
"-storepass", "keystore password",
|
||||
"-noprompt",
|
||||
"-alias", signingCertAlias,
|
||||
"-file", signingCertFName
|
||||
}
|
||||
// keytool -exportcert -rfc -alias "signing key 1" -keystore signingkeys.pfx -storepass "keystore password" -file signingkey1.pem
|
||||
exec {
|
||||
commandLine "${System.getProperty("java.home")}/bin/keytool",
|
||||
"-exportcert", "-rfc", "-alias", keystoreAlias,
|
||||
"-keystore", keystoreFName,
|
||||
"-storepass", "keystore password",
|
||||
"-file", keystoreCertFName
|
||||
}
|
||||
}
|
||||
else {
|
||||
println('createKeystore: keystore already created; nothing to do.')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// Corda status queries
|
||||
|
||||
def cordaStatusQueries = new CordaStatusQueries(projectContext)
|
||||
|
||||
tasks.register('buildCPI') {
|
||||
group = pluginGroupName
|
||||
dependsOn('build', 'createGroupPolicy', 'createKeystore')
|
||||
|
||||
doLast{
|
||||
def cpiFile= buildDir.toString() + "/" + project.archivesBaseName + "-" + project.version + ".cpi"
|
||||
delete { delete cpiFile }
|
||||
File srcDir
|
||||
srcDir = file('build/libs')
|
||||
|
||||
// Create a file collection using a closure
|
||||
def collection = layout.files { srcDir.listFiles() }
|
||||
def cpbs = collection.filter { it.getName().endsWith(".cpb") }
|
||||
|
||||
javaexec {
|
||||
classpath = files("$cordaCliBinDir/corda-cli.jar")
|
||||
jvmArgs = ["-Dpf4j.pluginsDir=$cordaCliBinDir/plugins/"]
|
||||
args = ['package', 'create-cpi',
|
||||
'--cpb', cpbs.singleFile.absolutePath,
|
||||
'--group-policy', "${devEnvWorkspace}/GroupPolicy.json",
|
||||
'--cpi-name', cpiName,
|
||||
'--cpi-version', project.version,
|
||||
'--file', cpiFile,
|
||||
'--keystore', "${devEnvWorkspace}/signingkeys.pfx",
|
||||
'--storepass', 'keystore password',
|
||||
'--key', 'my-signing-key' ]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tasks.register("deployCPI") {
|
||||
group = pluginImplGroupName
|
||||
dependsOn('buildCPI')
|
||||
doLast {
|
||||
csdeHelper.uploadCertificate(signingCertAlias, signingCertFName)
|
||||
csdeHelper.uploadCertificate(keystoreAlias, keystoreCertFName)
|
||||
csdeHelper.deployCPI("${buildDir}/${project.archivesBaseName}-${project.version}.cpi", cpiName, project.version)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("createAndRegVNodes") {
|
||||
group = pluginImplGroupName
|
||||
dependsOn('deployCPI')
|
||||
doLast {
|
||||
csdeHelper.createAndRegVNodes()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('listVNodes') {
|
||||
group = pluginGroupName
|
||||
group = queriesGroup
|
||||
doLast {
|
||||
csdeHelper.listVNodes()
|
||||
cordaStatusQueries.listVNodes()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('listCPIs') {
|
||||
group = pluginImplGroupName
|
||||
group = queriesGroup
|
||||
doLast {
|
||||
csdeHelper.listCPIs()
|
||||
cordaStatusQueries.listCPIs()
|
||||
}
|
||||
}
|
||||
|
||||
// Build CPI tasks
|
||||
|
||||
def buildCPIsHelper = new BuildCPIsHelper(projectContext)
|
||||
|
||||
tasks.register("1-createGroupPolicy") {
|
||||
group = cordappGroup
|
||||
dependsOn('projInit')
|
||||
doLast {
|
||||
buildCPIsHelper.createGroupPolicy()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("getNotaryServerCPB", Copy) {
|
||||
group = supportingGroup
|
||||
from configurations.notaryServerCPB
|
||||
into cordaNotaryServerDir
|
||||
}
|
||||
|
||||
tasks.register('2-createKeystore') {
|
||||
group = cordappGroup
|
||||
dependsOn('projInit')
|
||||
doLast {
|
||||
buildCPIsHelper.createKeyStore()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('3-buildCPIs') {
|
||||
group = cordappGroup
|
||||
def dependsOnTasks = subprojects.collect {it.tasks.findByName("build") }
|
||||
dependsOnTasks.add('1-createGroupPolicy')
|
||||
dependsOnTasks.add('2-createKeystore')
|
||||
dependsOnTasks.add('getNotaryServerCPB')
|
||||
dependsOn dependsOnTasks
|
||||
doLast{
|
||||
buildCPIsHelper.buildCPIs()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// deploy CPI tasks
|
||||
|
||||
def deployCPIsHelper = new DeployCPIsHelper(projectContext)
|
||||
|
||||
tasks.register("4-deployCPIs") {
|
||||
group = cordappGroup
|
||||
dependsOn('3-buildCPIs')
|
||||
doLast {
|
||||
deployCPIsHelper.deployCPIs()
|
||||
}
|
||||
}
|
||||
|
||||
// create and register Vnodes Tasks
|
||||
|
||||
def createAndRegisterVNodesHelper = new CreateAndRegisterVNodesHelper(projectContext)
|
||||
|
||||
tasks.register("5-createAndRegVNodes") {
|
||||
group = cordappGroup
|
||||
dependsOn('4-deployCPIs')
|
||||
doLast {
|
||||
createAndRegisterVNodesHelper.createAndRegVNodes()
|
||||
}
|
||||
}
|
||||
|
||||
// Empty task, just acts as the Task user entry point task.
|
||||
tasks.register('deployCordapp') {
|
||||
group = pluginGroupName
|
||||
dependsOn("createAndRegVNodes")
|
||||
}
|
||||
|
||||
tasks.register("startCorda") {
|
||||
group = pluginGroupName
|
||||
dependsOn('getDevCordaLite', 'getPostgresJDBC')
|
||||
doLast {
|
||||
mkdir devEnvWorkspace
|
||||
csdeHelper.startCorda()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("stopCorda") {
|
||||
group = pluginGroupName
|
||||
doLast {
|
||||
csdeHelper.stopCorda()
|
||||
}
|
||||
tasks.register('quickDeployCordapp') {
|
||||
group = cordappGroup
|
||||
dependsOn("5-createAndRegVNodes")
|
||||
}
|
||||
|
||||
|
||||
|
270
buildSrc/src/main/java/com/r3/csde/BuildCPIsHelper.java
Normal file
270
buildSrc/src/main/java/com/r3/csde/BuildCPIsHelper.java
Normal file
@ -0,0 +1,270 @@
|
||||
package com.r3.csde;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class BuildCPIsHelper {
|
||||
|
||||
public ProjectContext pc;
|
||||
public ProjectUtils utils ;
|
||||
public BuildCPIsHelper(ProjectContext _pc) {
|
||||
pc = _pc;
|
||||
utils = new ProjectUtils(pc);
|
||||
}
|
||||
|
||||
public void createGroupPolicy() throws IOException {
|
||||
|
||||
File groupPolicyFile = new File(String.format("%s/GroupPolicy.json", pc.devEnvWorkspace));
|
||||
File devnetFile = new File(String.format("%s/config/dev-net.json", pc.project.getRootDir()));
|
||||
|
||||
if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) {
|
||||
|
||||
pc.out.println("createGroupPolicy: Creating a GroupPolicy");
|
||||
|
||||
LinkedList<String> configX500Ids = utils.getConfigX500Ids(pc.X500ConfigFile);
|
||||
LinkedList<String> commandList = new LinkedList<>();
|
||||
|
||||
commandList.add(String.format("%s/java", pc.javaBinDir));
|
||||
commandList.add(String.format("-Dpf4j.pluginsDir=%s/plugins/", pc.cordaCliBinDir));
|
||||
commandList.add("-jar");
|
||||
commandList.add(String.format("%s/corda-cli.jar", pc.cordaCliBinDir));
|
||||
commandList.add("mgm");
|
||||
commandList.add("groupPolicy");
|
||||
for (String id : configX500Ids) {
|
||||
commandList.add("--name");
|
||||
commandList.add(id);
|
||||
}
|
||||
commandList.add("--endpoint-protocol=1");
|
||||
commandList.add("--endpoint=http://localhost:1080");
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(commandList);
|
||||
pb.redirectErrorStream(true);
|
||||
Process proc = pb.start();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
|
||||
|
||||
// todo add exception catching
|
||||
FileWriter fileWriter = new FileWriter(groupPolicyFile);
|
||||
String line;
|
||||
while (( line = reader.readLine()) != null){
|
||||
fileWriter.write(line + "\n");
|
||||
}
|
||||
fileWriter.close();
|
||||
|
||||
} else {
|
||||
pc.out.println("createPolicyTask: everything up to date; nothing to do.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void createKeyStore() throws IOException, InterruptedException {
|
||||
|
||||
File keystoreFile = new File(pc.keystoreFName);
|
||||
if(!keystoreFile.exists()) {
|
||||
pc.out.println("createKeystore: Create a keystore");
|
||||
|
||||
generateKeyPair();
|
||||
addDefaultSigningKey();
|
||||
exportCert();
|
||||
|
||||
} else {
|
||||
pc.out.println("createKeystore: keystore already created; nothing to do.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void generateKeyPair() throws IOException, InterruptedException {
|
||||
|
||||
LinkedList<String> cmdArray = new LinkedList<>();
|
||||
|
||||
cmdArray.add(pc.javaBinDir + "/keytool");
|
||||
cmdArray.add("-genkeypair");
|
||||
cmdArray.add("-alias");
|
||||
cmdArray.add(pc.keystoreAlias);
|
||||
cmdArray.add("-keystore");
|
||||
cmdArray.add(pc.keystoreFName);
|
||||
cmdArray.add("-storepass");
|
||||
cmdArray.add("keystore password");
|
||||
cmdArray.add("-dname");
|
||||
cmdArray.add("CN=CPI Example - My Signing Key, O=CorpOrgCorp, L=London, C=GB");
|
||||
cmdArray.add("-keyalg");
|
||||
cmdArray.add("RSA");
|
||||
cmdArray.add("-storetype");
|
||||
cmdArray.add("pkcs12");
|
||||
cmdArray.add("-validity");
|
||||
cmdArray.add("4000");
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmdArray);
|
||||
pb.redirectErrorStream(true);
|
||||
Process proc = pb.start();
|
||||
proc.waitFor();
|
||||
|
||||
}
|
||||
|
||||
private void addDefaultSigningKey() throws IOException, InterruptedException {
|
||||
|
||||
LinkedList<String> cmdArray = new LinkedList<>();
|
||||
|
||||
cmdArray.add(pc.javaBinDir + "/keytool");
|
||||
cmdArray.add("-importcert");
|
||||
cmdArray.add("-keystore");
|
||||
cmdArray.add(pc.keystoreFName);
|
||||
cmdArray.add("-storepass");
|
||||
cmdArray.add("keystore password");
|
||||
cmdArray.add("-noprompt");
|
||||
cmdArray.add("-alias");
|
||||
cmdArray.add(pc.signingCertAlias);
|
||||
cmdArray.add("-file");
|
||||
cmdArray.add(pc.signingCertFName);
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmdArray);
|
||||
pb.redirectErrorStream(true);
|
||||
Process proc = pb.start();
|
||||
proc.waitFor();
|
||||
}
|
||||
|
||||
private void exportCert() throws IOException, InterruptedException {
|
||||
|
||||
LinkedList<String> cmdArray = new LinkedList<>();
|
||||
|
||||
cmdArray.add(pc.javaBinDir + "/keytool");
|
||||
cmdArray.add("-exportcert");
|
||||
cmdArray.add("-rfc");
|
||||
cmdArray.add("-alias");
|
||||
cmdArray.add(pc.keystoreAlias);
|
||||
cmdArray.add("-keystore");
|
||||
cmdArray.add(pc.keystoreFName);
|
||||
cmdArray.add("-storepass");
|
||||
cmdArray.add("keystore password");
|
||||
cmdArray.add("-file");
|
||||
cmdArray.add(pc.keystoreCertFName);
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmdArray);
|
||||
pb.redirectErrorStream(true);
|
||||
Process proc = pb.start();
|
||||
proc.waitFor();
|
||||
|
||||
}
|
||||
|
||||
public void buildCPIs() throws IOException, InterruptedException, CsdeException {
|
||||
createCorDappCPI();
|
||||
createNotaryCPI();
|
||||
}
|
||||
|
||||
private void createCorDappCPI() throws IOException, InterruptedException, CsdeException {
|
||||
|
||||
String appCPIFilePath = pc.workflowBuildDir + "/" +
|
||||
pc.project.getRootProject().getName() + "-" +
|
||||
pc.project.getVersion() + ".cpi";
|
||||
|
||||
File appCPIFile = new File(appCPIFilePath);
|
||||
appCPIFile.delete();
|
||||
|
||||
File srcDir = new File(pc.workflowBuildDir + "/libs");
|
||||
File[] appCPBs = srcDir.listFiles(( x , name ) -> name.endsWith(".cpb"));
|
||||
if (appCPBs == null) throw new CsdeException("Expecting exactly one CPB but no CPB found.");
|
||||
if (appCPBs.length != 1) throw new CsdeException("Expecting exactly one CPB but more than one found.");
|
||||
|
||||
pc.out.println("appCpbs:");
|
||||
pc.out.println(appCPBs[0].getAbsolutePath());
|
||||
|
||||
LinkedList<String> commandList = new LinkedList<>();
|
||||
|
||||
commandList.add(String.format("%s/java", pc.javaBinDir));
|
||||
commandList.add(String.format("-Dpf4j.pluginsDir=%s/plugins/", pc.cordaCliBinDir));
|
||||
commandList.add("-jar");
|
||||
commandList.add(String.format("%s/corda-cli.jar", pc.cordaCliBinDir));
|
||||
commandList.add("package");
|
||||
commandList.add("create-cpi");
|
||||
commandList.add("--cpb");
|
||||
commandList.add(appCPBs[0].getAbsolutePath());
|
||||
commandList.add("--group-policy");
|
||||
commandList.add(pc.devEnvWorkspace + "/GroupPolicy.json");
|
||||
commandList.add("--cpi-name");
|
||||
commandList.add(pc.appCPIName);
|
||||
commandList.add("--cpi-version");
|
||||
commandList.add(pc.project.getVersion().toString());
|
||||
commandList.add("--file");
|
||||
commandList.add(appCPIFilePath);
|
||||
commandList.add("--keystore");
|
||||
commandList.add(pc.devEnvWorkspace + "/signingkeys.pfx");
|
||||
commandList.add("--storepass");
|
||||
commandList.add("keystore password");
|
||||
commandList.add("--key");
|
||||
commandList.add("my-signing-key"); // todo: should be passed as context property
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(commandList);
|
||||
pb.redirectErrorStream(true);
|
||||
Process proc = pb.start();
|
||||
proc.waitFor();
|
||||
|
||||
// todo: work out how to capture error code better than the following code
|
||||
|
||||
// BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
|
||||
// File tempOutputFile = new File(String.format("%s/tempOutput.txt", pc.devEnvWorkspace));
|
||||
// tempOutputFile.delete();
|
||||
// FileWriter fileWriter = new FileWriter(tempOutputFile);
|
||||
// String line;
|
||||
// while (( line = reader.readLine()) != null){
|
||||
// fileWriter.write(line + "\n");
|
||||
// }
|
||||
// fileWriter.close();
|
||||
|
||||
}
|
||||
|
||||
private void createNotaryCPI() throws CsdeException, IOException, InterruptedException {
|
||||
|
||||
String notaryCPIFilePath = pc.workflowBuildDir + "/" +
|
||||
pc.notaryCPIName.replace(' ', '-').toLowerCase() + "-" +
|
||||
pc.project.getVersion() + ".cpi";
|
||||
|
||||
File notaryCPIFile = new File(notaryCPIFilePath);
|
||||
notaryCPIFile.delete();
|
||||
|
||||
File srcDir = new File(pc.cordaNotaryServiceDir);
|
||||
File[] notaryCPBs = srcDir.listFiles(( x , name ) -> name.endsWith(".cpb") && name.contains(pc.cordaNotaryPluginsVersion));
|
||||
if (notaryCPBs == null) throw new CsdeException("Expecting exactly one notary CPB but no CPB found.");
|
||||
if (notaryCPBs.length != 1) throw new CsdeException("Expecting exactly one notary CPB but more than one found.");
|
||||
|
||||
pc.out.println("notaryCpbs:");
|
||||
pc.out.println(notaryCPBs[0]);
|
||||
|
||||
LinkedList<String> commandList = new LinkedList<>();
|
||||
|
||||
commandList.add(String.format("%s/java", pc.javaBinDir));
|
||||
commandList.add(String.format("-Dpf4j.pluginsDir=%s/plugins/", pc.cordaCliBinDir));
|
||||
commandList.add("-jar");
|
||||
commandList.add(String.format("%s/corda-cli.jar", pc.cordaCliBinDir));
|
||||
commandList.add("package");
|
||||
commandList.add("create-cpi");
|
||||
commandList.add("--cpb");
|
||||
commandList.add(notaryCPBs[0].getAbsolutePath());
|
||||
commandList.add("--group-policy");
|
||||
commandList.add(pc.devEnvWorkspace + "/GroupPolicy.json");
|
||||
commandList.add("--cpi-name");
|
||||
commandList.add(pc.notaryCPIName);
|
||||
commandList.add("--cpi-version");
|
||||
commandList.add(pc.project.getVersion().toString());
|
||||
commandList.add("--file");
|
||||
commandList.add(notaryCPIFilePath);
|
||||
commandList.add("--keystore");
|
||||
commandList.add(pc.devEnvWorkspace + "/signingkeys.pfx");
|
||||
commandList.add("--storepass");
|
||||
commandList.add("keystore password");
|
||||
commandList.add("--key");
|
||||
commandList.add("my-signing-key");
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(commandList);
|
||||
pb.redirectErrorStream(true);
|
||||
Process proc = pb.start();
|
||||
proc.waitFor();
|
||||
|
||||
}
|
||||
|
||||
// todo: this might be needed for improved logging
|
||||
private void printCmdArray(LinkedList<String> cmdArray) {
|
||||
for (int i = 0; i < cmdArray.size(); i++) {
|
||||
pc.out.print(cmdArray.get(i) + " ");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
94
buildSrc/src/main/java/com/r3/csde/CordaLifeCycleHelper.java
Normal file
94
buildSrc/src/main/java/com/r3/csde/CordaLifeCycleHelper.java
Normal file
@ -0,0 +1,94 @@
|
||||
package com.r3.csde;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* Manages Bringing corda up, testing for liveness and taking corda down
|
||||
*/
|
||||
|
||||
public class CordaLifeCycleHelper {
|
||||
|
||||
ProjectContext pc;
|
||||
ProjectUtils utils;
|
||||
|
||||
public CordaLifeCycleHelper(ProjectContext _pc) {
|
||||
pc = _pc;
|
||||
utils = new ProjectUtils(pc);
|
||||
}
|
||||
|
||||
|
||||
public void startCorda() throws IOException {
|
||||
PrintStream pidStore = new PrintStream(new FileOutputStream(pc.cordaPidCache));
|
||||
File combinedWorkerJar = pc.project.getConfigurations().getByName("combinedWorker").getSingleFile();
|
||||
|
||||
|
||||
// todo: make consistent with other ProcessBuilder set ups (use cmdArray)
|
||||
new ProcessBuilder(
|
||||
"docker",
|
||||
"run", "-d", "--rm",
|
||||
"-p", "5432:5432",
|
||||
"--name", pc.dbContainerName,
|
||||
"-e", "POSTGRES_DB=cordacluster",
|
||||
"-e", "POSTGRES_USER=postgres",
|
||||
"-e", "POSTGRES_PASSWORD=password",
|
||||
"postgres:latest").start();
|
||||
// todo: is there a better way of doing this - ie poll for readiness
|
||||
utils.rpcWait(10000);
|
||||
|
||||
ProcessBuilder procBuild = new ProcessBuilder(pc.javaBinDir + "/java",
|
||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
|
||||
"-Dco.paralleluniverse.fibers.verifyInstrumentation=true",
|
||||
"-jar",
|
||||
combinedWorkerJar.toString(),
|
||||
"--instanceId=0",
|
||||
"-mbus.busType=DATABASE",
|
||||
"-spassphrase=password",
|
||||
"-ssalt=salt",
|
||||
"-spassphrase=password",
|
||||
"-ssalt=salt",
|
||||
"-ddatabase.user=user",
|
||||
"-ddatabase.pass=password",
|
||||
"-ddatabase.jdbc.url=jdbc:postgresql://localhost:5432/cordacluster",
|
||||
"-ddatabase.jdbc.directory="+pc.JDBCDir);
|
||||
|
||||
|
||||
procBuild.redirectErrorStream(true);
|
||||
Process proc = procBuild.start();
|
||||
pidStore.print(proc.pid());
|
||||
pc.out.println("Corda Process-id="+proc.pid());
|
||||
|
||||
// todo: should poll for readiness before returning
|
||||
// Chris comment - We probably do not want to poll for readiness here.
|
||||
// The combined-worker takes serveral minutes to come up.
|
||||
// It might be better to warn the user of that and have the readiness detection and polling logic used in other tasks involved in creating v-nodes and deploying the CPI.
|
||||
// Matt comment - I'm not sure I agree, we need to investigate
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void stopCorda() throws IOException, CsdeException {
|
||||
File cordaPIDFile = new File(pc.cordaPidCache);
|
||||
if(cordaPIDFile.exists()) {
|
||||
Scanner sc = new Scanner(cordaPIDFile);
|
||||
long pid = sc.nextLong();
|
||||
pc.out.println("pid to kill=" + pid);
|
||||
|
||||
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
|
||||
new ProcessBuilder("Powershell", "-Command", "Stop-Process", "-Id", Long.toString(pid), "-PassThru").start();
|
||||
} else {
|
||||
new ProcessBuilder("kill", "-9", Long.toString(pid)).start();
|
||||
}
|
||||
|
||||
Process proc = new ProcessBuilder("docker", "stop", pc.dbContainerName).start();
|
||||
|
||||
cordaPIDFile.delete();
|
||||
}
|
||||
else {
|
||||
throw new CsdeException("Cannot stop the Combined worker\nCached process ID file " + pc.cordaPidCache + " missing.\nWas the combined worker not started?");
|
||||
}
|
||||
}
|
||||
}
|
63
buildSrc/src/main/java/com/r3/csde/CordaStatusQueries.java
Normal file
63
buildSrc/src/main/java/com/r3/csde/CordaStatusQueries.java
Normal file
@ -0,0 +1,63 @@
|
||||
package com.r3.csde;
|
||||
|
||||
import kong.unirest.JsonNode;
|
||||
import kong.unirest.Unirest;
|
||||
import kong.unirest.json.JSONArray;
|
||||
import kong.unirest.json.JSONObject;
|
||||
import kong.unirest.HttpResponse;
|
||||
|
||||
public class CordaStatusQueries {
|
||||
|
||||
ProjectContext pc;
|
||||
public CordaStatusQueries(ProjectContext _pc){ pc = _pc; }
|
||||
|
||||
|
||||
public HttpResponse<JsonNode> getVNodeInfo() {
|
||||
Unirest.config().verifySsl(false);
|
||||
return Unirest.get(pc.baseURL + "/api/v1/virtualnode/")
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJson();
|
||||
}
|
||||
public void listVNodesVerbose() {
|
||||
HttpResponse<JsonNode> vnodeResponse = getVNodeInfo();
|
||||
pc.out.println("VNodes:\n" + vnodeResponse.getBody().toPrettyString());
|
||||
}
|
||||
|
||||
// X500Name, shorthash, cpiname
|
||||
public void listVNodes() {
|
||||
HttpResponse<JsonNode> vnodeResponse = getVNodeInfo();
|
||||
|
||||
JSONArray virtualNodesJson = (JSONArray) vnodeResponse.getBody().getObject().get("virtualNodes");
|
||||
pc.out.println("X500 Name\tHolding identity short hash\tCPI Name");
|
||||
for(Object o: virtualNodesJson){
|
||||
if(o instanceof JSONObject) {
|
||||
JSONObject idObj = ((JSONObject) o).getJSONObject("holdingIdentity");
|
||||
JSONObject cpiObj = ((JSONObject) o).getJSONObject("cpiIdentifier");
|
||||
pc.out.print("\"" + idObj.get("x500Name") + "\"");
|
||||
pc.out.print("\t\"" + idObj.get("shortHash") + "\"");
|
||||
pc.out.println("\t\"" + cpiObj.get("cpiName") + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HttpResponse<JsonNode> getCpiInfo() {
|
||||
Unirest.config().verifySsl(false);
|
||||
return Unirest.get(pc.baseURL + "/api/v1/cpi/")
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJson();
|
||||
}
|
||||
|
||||
public void listCPIs() {
|
||||
HttpResponse<JsonNode> cpiResponse = getCpiInfo();
|
||||
JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
|
||||
|
||||
for(Object o: jArray){
|
||||
if(o instanceof JSONObject) {
|
||||
JSONObject idObj = ((JSONObject) o).getJSONObject("id");
|
||||
pc.out.print("cpiName=" + idObj.get("cpiName"));
|
||||
pc.out.println(", cpiVersion=" + idObj.get("cpiVersion"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,289 @@
|
||||
package com.r3.csde;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.JsonNode;
|
||||
import kong.unirest.Unirest;
|
||||
import kong.unirest.json.JSONArray;
|
||||
import kong.unirest.json.JSONObject;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static java.net.HttpURLConnection.*;
|
||||
|
||||
public class CreateAndRegisterVNodesHelper {
|
||||
|
||||
ProjectContext pc;
|
||||
ProjectUtils utils;
|
||||
CordaStatusQueries queries;
|
||||
|
||||
public CreateAndRegisterVNodesHelper(ProjectContext _pc) {
|
||||
pc = _pc;
|
||||
queries = new CordaStatusQueries(pc);
|
||||
utils = new ProjectUtils(pc);
|
||||
}
|
||||
|
||||
public void createAndRegVNodes() throws IOException, CsdeException, ConfigurationException {
|
||||
Unirest.config().verifySsl(false);
|
||||
String appCpiCheckSum = getLastCPIUploadChkSum( pc.CPIUploadStatusFName );
|
||||
String notaryCpiCheckSum = getLastCPIUploadChkSum( pc.CPIUploadStatusFName, "-NotaryServer" );
|
||||
|
||||
LinkedList<String> x500Ids = utils.getConfigX500Ids(pc.X500ConfigFile);
|
||||
|
||||
// For each identity check that it already exists.
|
||||
Set<MemberX500Name> existingX500 = new HashSet<>();
|
||||
HttpResponse<JsonNode> vnodeListResponse = queries.getVNodeInfo();
|
||||
|
||||
JSONArray virtualNodesJson = (JSONArray) vnodeListResponse.getBody().getObject().get("virtualNodes");
|
||||
for(Object o: virtualNodesJson){
|
||||
if(o instanceof JSONObject) {
|
||||
JSONObject idObj = ((JSONObject) o).getJSONObject("holdingIdentity");
|
||||
String x500id = (String) idObj.get("x500Name");
|
||||
existingX500.add(MemberX500Name.parse( x500id) );
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, CompletableFuture<HttpResponse<JsonNode>>> responses = new LinkedHashMap<>();
|
||||
|
||||
// Create the VNodes
|
||||
for(String x500id: x500Ids) {
|
||||
if(!existingX500.contains(MemberX500Name.parse(x500id) )) {
|
||||
String cpiCheckSum = getNotaryRepresentatives().containsKey(x500id) ? notaryCpiCheckSum : appCpiCheckSum;
|
||||
|
||||
pc.out.println("Creating VNode for x500id=\"" + x500id + "\" cpi checksum=" + cpiCheckSum);
|
||||
responses.put(x500id, Unirest
|
||||
.post(pc.baseURL + "/api/v1/virtualnode")
|
||||
.body("{ \"request\" : { \"cpiFileChecksum\": " + cpiCheckSum + ", \"x500Name\": \"" + x500id + "\" } }")
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJsonAsync()
|
||||
);
|
||||
}
|
||||
else {
|
||||
pc.out.println("Not creating a vnode for \"" + x500id + "\", vnode already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
pc.out.println("Waiting for VNode creation results...");
|
||||
|
||||
for (Map.Entry<String, CompletableFuture<HttpResponse<JsonNode>>> response: responses.entrySet()) {
|
||||
try {
|
||||
HttpResponse<JsonNode> jsonNode = response.getValue().get();
|
||||
// need to check this and report errors.
|
||||
// 200/HTTP_OK - OK
|
||||
// 409/HTTP_CONFLICT - Vnode already exists
|
||||
// 500/HTTP_INTERNAL_ERROR
|
||||
// - Can mean that the request timed out.
|
||||
// - However, the cluster may still have created the V-node successfully, so we want to poll later.
|
||||
pc.out.println("Vnode creation end point status:" + jsonNode.getStatus());
|
||||
switch(jsonNode.getStatus()) {
|
||||
case HTTP_OK: break;
|
||||
case HTTP_CONFLICT: break;
|
||||
case HTTP_INTERNAL_ERROR: break;
|
||||
default:
|
||||
utils.reportError(jsonNode);
|
||||
}
|
||||
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw new CsdeException("Unexpected exception while waiting for response to " +
|
||||
"membership submission for holding identity" + response.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> OKHoldingX500AndShortIds = pollForVNodeShortHoldingHashIds(x500Ids, 60, 5000);
|
||||
|
||||
// Register the VNodes
|
||||
responses.clear();
|
||||
|
||||
for(String okId: OKHoldingX500AndShortIds.keySet()) {
|
||||
responses.put(okId, Unirest
|
||||
.post(pc.baseURL + "/api/v1/membership/" + OKHoldingX500AndShortIds.get(okId))
|
||||
.body(getMemberRegistrationBody(okId))
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJsonAsync( response ->
|
||||
pc.out.println("Vnode membership submission for \"" + okId + "\"" +
|
||||
System.lineSeparator() + response.getBody().toPrettyString()))
|
||||
);
|
||||
}
|
||||
|
||||
pc.out.println("Vnode membership requests submitted, waiting for acknowledgement from MGM...");
|
||||
|
||||
for (Map.Entry<String, CompletableFuture<HttpResponse<JsonNode>>> response: responses.entrySet()) {
|
||||
try {
|
||||
response.getValue().get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw new CsdeException("Unexpected exception while waiting for response to " +
|
||||
"membership submission for holding identity" + response.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
pollForCompleteMembershipRegistration(OKHoldingX500AndShortIds);
|
||||
}
|
||||
|
||||
public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName) throws IOException, NullPointerException {
|
||||
return getLastCPIUploadChkSum(CPIUploadStatusFName, "");
|
||||
}
|
||||
|
||||
public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName,
|
||||
String uploadStatusQualifier) throws IOException, NullPointerException {
|
||||
|
||||
String qualifiedCPIUploadStatusFName =
|
||||
CPIUploadStatusFName.replace(".json", uploadStatusQualifier + ".json");
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
FileInputStream in = new FileInputStream(qualifiedCPIUploadStatusFName);
|
||||
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
||||
|
||||
String checksum = jsonNode.get("cpiFileChecksum").toString();
|
||||
if(checksum == null || checksum.equals("null")) {
|
||||
throw new NullPointerException("Missing cpiFileChecksum in file " +
|
||||
qualifiedCPIUploadStatusFName + " with contents:" + jsonNode);
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
// KV pairs of representative x500 name and corresponding notary service x500 name
|
||||
public Map<String, String> getNotaryRepresentatives() throws IOException, ConfigurationException {
|
||||
if (pc.notaryRepresentatives == null) {
|
||||
pc.notaryRepresentatives = new HashMap<>();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
FileInputStream in = new FileInputStream(pc.X500ConfigFile);
|
||||
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
||||
|
||||
List<String> identities = utils.getConfigX500Ids(pc.X500ConfigFile);
|
||||
|
||||
for (com.fasterxml.jackson.databind.JsonNode notary : jsonNode.get("notaries")) {
|
||||
|
||||
String svcX500Id = utils.jsonNodeToString(notary.get("serviceX500Name"));
|
||||
|
||||
com.fasterxml.jackson.databind.JsonNode repsForThisService = notary.get("representatives");
|
||||
|
||||
if (repsForThisService.isEmpty()) {
|
||||
throw new ConfigurationException(
|
||||
"Notary service \"" + svcX500Id + "\" must have at least one representative.");
|
||||
} else if (repsForThisService.size() > 1) {
|
||||
// Temporary restriction while the MGM only supports a 1-1 association
|
||||
throw new ConfigurationException(
|
||||
"Notary service \"" + svcX500Id + "\" can only have a single representative at this time.");
|
||||
}
|
||||
|
||||
for (com.fasterxml.jackson.databind.JsonNode representative : repsForThisService) {
|
||||
|
||||
String repAsString = utils.jsonNodeToString(representative);
|
||||
|
||||
if (identities.contains(repAsString)) {
|
||||
pc.notaryRepresentatives.put(repAsString, svcX500Id);
|
||||
} else {
|
||||
throw new ConfigurationException(
|
||||
"Notary representative \"" + repAsString + "\" is not a valid identity");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pc.notaryRepresentatives;
|
||||
}
|
||||
|
||||
private String getMemberRegistrationBody(String memberX500Name) throws ConfigurationException, IOException {
|
||||
Map<String, String> notaryReps = getNotaryRepresentatives();
|
||||
|
||||
String context = "\"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\"" + (
|
||||
notaryReps.containsKey(memberX500Name)
|
||||
? ", \"corda.roles.0\" : \"notary\", " +
|
||||
"\"corda.notary.service.name\" : \"" + notaryReps.get(memberX500Name) + "\", " +
|
||||
// This will need revisiting in the long term when additional protocols are added, and will
|
||||
// need to be specified in config. We will also need to review the hard-coded name once
|
||||
// notary plugin selection logic is re-instated in CORE-7248.
|
||||
"\"corda.notary.service.plugin\" : \"net.corda.notary.NonValidatingNotary\""
|
||||
: ""
|
||||
);
|
||||
|
||||
return "{ \"memberRegistrationRequest\": { \"action\": \"requestJoin\", \"context\": { " + context + " } } }";
|
||||
}
|
||||
|
||||
|
||||
Map<String, String> pollForVNodeShortHoldingHashIds(List<String> x500Ids, int retryCount, int coolDownMs ) throws CsdeException {
|
||||
HashMap<String, String> x500NameToShortHashes = new HashMap<>();
|
||||
Set<String> vnodesToCheck = new HashSet<String>(x500Ids);
|
||||
while(!vnodesToCheck.isEmpty() && retryCount-- > 0) {
|
||||
utils.rpcWait(coolDownMs);
|
||||
kong.unirest.json.JSONArray virtualNodes = (JSONArray) queries.getVNodeInfo().getBody().getObject().get("virtualNodes");
|
||||
Map<String, String> vnodesMap = new HashMap<String, String>();
|
||||
for (Object virtualNode : virtualNodes) {
|
||||
if (virtualNode instanceof JSONObject) {
|
||||
JSONObject idObj = ((JSONObject) virtualNode).getJSONObject("holdingIdentity");
|
||||
vnodesMap.put(idObj.get("x500Name").toString(), idObj.get("shortHash").toString());
|
||||
}
|
||||
}
|
||||
for(String x500Name: vnodesToCheck) {
|
||||
if(vnodesMap.containsKey(x500Name)) {
|
||||
x500NameToShortHashes.put(x500Name, vnodesMap.get(x500Name));
|
||||
}
|
||||
}
|
||||
vnodesMap.keySet().forEach(vnodesToCheck::remove);
|
||||
}
|
||||
if(!vnodesToCheck.isEmpty()) {
|
||||
throw new CsdeException("VNode creation timed out. Not all expected vnodes were reported as created:" + vnodesToCheck.toString());
|
||||
}
|
||||
return x500NameToShortHashes;
|
||||
}
|
||||
|
||||
private void pollForCompleteMembershipRegistration(Map<String, String> X500ToShortIdHash) throws CsdeException {
|
||||
HashSet<String> vnodesToCheck = new HashSet<String>(X500ToShortIdHash.keySet());
|
||||
LinkedList<String> approved = new LinkedList<String>();
|
||||
while (!vnodesToCheck.isEmpty()) {
|
||||
utils.rpcWait(2000);
|
||||
approved.clear();
|
||||
for (String vnodeX500 : vnodesToCheck) {
|
||||
try {
|
||||
pc.out.println("Checking membership registration progress for v-node '" + vnodeX500 + "':");
|
||||
HttpResponse<JsonNode> statusResponse = Unirest
|
||||
.get(pc.baseURL + "/api/v1/membership/" + X500ToShortIdHash.get(vnodeX500) + "/")
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJson();
|
||||
if (isMembershipRegComplete(statusResponse)) {
|
||||
approved.add(vnodeX500);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new CsdeException("Error when registering V-Node '" + vnodeX500 + "'", e);
|
||||
}
|
||||
}
|
||||
approved.forEach(vnodesToCheck::remove);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isMembershipRegComplete(HttpResponse<JsonNode> response) throws CsdeException {
|
||||
if(response.getStatus() == HTTP_OK) {
|
||||
JsonNode responseBody = response.getBody();
|
||||
pc.out.println(responseBody.toPrettyString());
|
||||
if(responseBody.getArray().length() > 0) {
|
||||
JSONObject memRegStatusInfo = (JSONObject) responseBody
|
||||
.getArray()
|
||||
.getJSONObject(0);
|
||||
String memRegStatus = memRegStatusInfo.get("registrationStatus").toString();
|
||||
if (memRegStatus.equals("DECLINED")) {
|
||||
throw new CsdeException("V-Node membership registration declined by Corda");
|
||||
}
|
||||
return memRegStatus.equals("APPROVED");
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
utils.reportError(response);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package com.r3.csde;
|
||||
|
||||
public class CsdeException extends Exception {
|
||||
public CsdeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
public CsdeException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
@ -1,415 +0,0 @@
|
||||
package com.r3.csde;
|
||||
|
||||
import kong.unirest.json.JSONArray;
|
||||
import org.gradle.api.Project;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import kong.unirest.Unirest;
|
||||
import kong.unirest.json.JSONObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.io.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import static java.lang.Thread.sleep;
|
||||
|
||||
public class CsdeRpcInterface {
|
||||
private Project project;
|
||||
private String baseURL = "https://localhost:8888";
|
||||
private String rpcUser = "admin";
|
||||
private String rpcPasswd = "admin";
|
||||
private String workspaceDir = "workspace";
|
||||
static private int retryWaitMs = 1000;
|
||||
static PrintStream out = System.out;
|
||||
static private String CPIUploadStatusBaseName = "CPIFileStatus.json";
|
||||
static private String CPIUploadStatusFName;
|
||||
static private String X500ConfigFile = "config/dev-net.json";
|
||||
static private String javaBinDir;
|
||||
static private String cordaPidCache = "CordaPIDCache.dat";
|
||||
static private String dbContainerName;
|
||||
private String JDBCDir;
|
||||
private String combinedWorkerBinRe;
|
||||
|
||||
public CsdeRpcInterface() {
|
||||
}
|
||||
|
||||
public CsdeRpcInterface (Project inProject,
|
||||
String inBaseUrl,
|
||||
String inRpcUser,
|
||||
String inRpcPasswd,
|
||||
String inWorkspaceDir,
|
||||
String inJavaBinDir,
|
||||
String inDbContainerName,
|
||||
String inJDBCDir,
|
||||
String inCordaPidCache
|
||||
) {
|
||||
project = inProject;
|
||||
baseURL = inBaseUrl;
|
||||
rpcUser = inRpcUser;
|
||||
rpcPasswd = inRpcPasswd;
|
||||
workspaceDir = inWorkspaceDir;
|
||||
javaBinDir = inJavaBinDir;
|
||||
cordaPidCache = inCordaPidCache;
|
||||
dbContainerName = inDbContainerName;
|
||||
JDBCDir = inJDBCDir;
|
||||
CPIUploadStatusFName = workspaceDir +"/"+ CPIUploadStatusBaseName;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static private void rpcWait(int millis) {
|
||||
try {
|
||||
sleep(millis);
|
||||
}
|
||||
catch(InterruptedException e) {
|
||||
throw new UnsupportedOperationException("Interrupts not supported.", e);
|
||||
}
|
||||
}
|
||||
|
||||
static private void rpcWait() {
|
||||
rpcWait(retryWaitMs);
|
||||
}
|
||||
|
||||
public LinkedList<String> getConfigX500Ids() throws IOException {
|
||||
LinkedList<String> x500Ids = new LinkedList<>();
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
|
||||
FileInputStream in = new FileInputStream(X500ConfigFile);
|
||||
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
||||
for( com.fasterxml.jackson.databind.JsonNode identity: jsonNode.get("identities")) {
|
||||
String idAsString = identity.toString();
|
||||
x500Ids.add(idAsString.substring(1,idAsString.length()-1));
|
||||
}
|
||||
return x500Ids;
|
||||
}
|
||||
|
||||
static public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName) throws IOException, NullPointerException {
|
||||
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
FileInputStream in = new FileInputStream(CPIUploadStatusFName);
|
||||
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
||||
|
||||
|
||||
String checksum = jsonNode.get("cpiFileChecksum").toString();
|
||||
if(checksum == null || checksum.equals("null")) {
|
||||
throw new NullPointerException("Missing cpiFileChecksum in file " + CPIUploadStatusFName+ " with contents:" + jsonNode);
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
|
||||
public void reportError(@NotNull kong.unirest.HttpResponse<kong.unirest.JsonNode> response) throws CsdeException {
|
||||
|
||||
out.println("*** *** ***");
|
||||
out.println("Unexpected response from Corda");
|
||||
out.println("Status="+ response.getStatus());
|
||||
out.println("*** Headers ***\n"+ response.getHeaders());
|
||||
out.println("*** Body ***\n"+ response.getBody());
|
||||
out.println("*** *** ***");
|
||||
throw new CsdeException("Error: unexpected response from Corda.");
|
||||
}
|
||||
|
||||
public void downloadFile(String url, String targetPath) {
|
||||
Unirest.get(url)
|
||||
.asFile(targetPath)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
public kong.unirest.HttpResponse<kong.unirest.JsonNode> getVNodeInfo() {
|
||||
Unirest.config().verifySsl(false);
|
||||
return Unirest.get(baseURL + "/api/v1/virtualnode/")
|
||||
.basicAuth(rpcUser, rpcPasswd)
|
||||
.asJson();
|
||||
}
|
||||
|
||||
public void listVNodesVerbose() {
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = getVNodeInfo();
|
||||
out.println("VNodes:\n" + vnodeResponse.getBody().toPrettyString());
|
||||
}
|
||||
|
||||
// X500Name, cpiname, shorthash,
|
||||
public void listVNodes() {
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = getVNodeInfo();
|
||||
|
||||
kong.unirest.json.JSONArray virtualNodesJson = (JSONArray) vnodeResponse.getBody().getObject().get("virtualNodes");
|
||||
out.println("X500 Name\tHolding identity short hash");
|
||||
for(Object o: virtualNodesJson){
|
||||
if(o instanceof kong.unirest.json.JSONObject) {
|
||||
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("holdingIdentity");
|
||||
out.print("\"" + idObj.get("x500Name") + "\"");
|
||||
out.println("\t\"" + idObj.get("shortHash") + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public kong.unirest.HttpResponse<kong.unirest.JsonNode> getCpiInfo() {
|
||||
Unirest.config().verifySsl(false);
|
||||
return Unirest.get(baseURL + "/api/v1/cpi/")
|
||||
.basicAuth(rpcUser, rpcPasswd)
|
||||
.asJson();
|
||||
|
||||
}
|
||||
|
||||
public void listCPIs() {
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> cpiResponse = getCpiInfo();
|
||||
kong.unirest.json.JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
|
||||
|
||||
for(Object o: jArray){
|
||||
if(o instanceof kong.unirest.json.JSONObject) {
|
||||
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("id");
|
||||
out.print("cpiName=" + idObj.get("cpiName"));
|
||||
out.println(", cpiVersion=" + idObj.get("cpiVersion"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void uploadCertificate(String certAlias, String certFName) {
|
||||
Unirest.config().verifySsl(false);
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadResponse = Unirest.put(baseURL + "/api/v1/certificates/cluster/code-signer")
|
||||
.field("alias", certAlias)
|
||||
.field("certificate", new File(certFName))
|
||||
.basicAuth(rpcUser, rpcPasswd)
|
||||
.asJson();
|
||||
out.println("Certificate/key upload, alias "+certAlias+" certificate/key file "+certFName);
|
||||
out.println(uploadResponse.getBody().toPrettyString());
|
||||
}
|
||||
|
||||
public void forceuploadCPI(String cpiFName) throws FileNotFoundException, CsdeException {
|
||||
Unirest.config().verifySsl(false);
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> jsonResponse = Unirest.post(baseURL + "/api/v1/maintenance/virtualnode/forcecpiupload/")
|
||||
.field("upload", new File(cpiFName))
|
||||
.basicAuth(rpcUser, rpcPasswd)
|
||||
.asJson();
|
||||
|
||||
if(jsonResponse.getStatus() == 200) {
|
||||
String id = (String) jsonResponse.getBody().getObject().get("id");
|
||||
out.println("get id:\n" +id);
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = uploadStatus(id);
|
||||
|
||||
if (statusResponse.getStatus() == 200) {
|
||||
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(CPIUploadStatusFName));
|
||||
cpiUploadStatus.print(statusResponse.getBody());
|
||||
out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
|
||||
} else {
|
||||
reportError(statusResponse);
|
||||
}
|
||||
}
|
||||
else {
|
||||
reportError(jsonResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean uploadStatusRetry(kong.unirest.HttpResponse<kong.unirest.JsonNode> response) {
|
||||
int status = response.getStatus();
|
||||
kong.unirest.JsonNode body = response.getBody();
|
||||
// Do not retry on success
|
||||
if(status == 200) {
|
||||
// Keep retrying until we get "OK" may move through "Validateing upload", "Persisting CPI"
|
||||
return !(body.getObject().get("status").equals("OK"));
|
||||
}
|
||||
else if (status == 400){
|
||||
JSONObject details = response.getBody().getObject().getJSONObject("details");
|
||||
if( details != null ){
|
||||
String code = (String) details.getString("code");
|
||||
return !code.equals("BAD_REQUEST");
|
||||
}
|
||||
else {
|
||||
// 400 otherwise means some transient problem
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadStatus(String requestId) {
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = null;
|
||||
do {
|
||||
rpcWait(1000);
|
||||
statusResponse = Unirest
|
||||
.get(baseURL + "/api/v1/cpi/status/" + requestId + "/")
|
||||
.basicAuth(rpcUser, rpcPasswd)
|
||||
.asJson();
|
||||
out.println("Upload status="+statusResponse.getStatus()+", status query response:\n"+statusResponse.getBody().toPrettyString());
|
||||
}
|
||||
while(uploadStatusRetry(statusResponse));
|
||||
|
||||
return statusResponse;
|
||||
}
|
||||
|
||||
public void deployCPI(String cpiFName, String cpiName, String cpiVersion) throws FileNotFoundException, CsdeException {
|
||||
Unirest.config().verifySsl(false);
|
||||
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> cpiResponse = getCpiInfo();
|
||||
kong.unirest.json.JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
|
||||
|
||||
|
||||
int matches = 0;
|
||||
for(Object o: jArray.toList() ) {
|
||||
if(o instanceof JSONObject) {
|
||||
JSONObject idObj = ((JSONObject) o).getJSONObject("id");
|
||||
if((idObj.get("cpiName").toString().equals(cpiName)
|
||||
&& idObj.get("cpiVersion").toString().equals(cpiVersion))) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
out.println("Matching CPIS="+matches);
|
||||
|
||||
|
||||
if(matches == 0) {
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadResponse = Unirest.post(baseURL + "/api/v1/cpi/")
|
||||
.field("upload", new File(cpiFName))
|
||||
.basicAuth(rpcUser, rpcPasswd)
|
||||
.asJson();
|
||||
|
||||
kong.unirest.JsonNode body = uploadResponse.getBody();
|
||||
|
||||
int status = uploadResponse.getStatus();
|
||||
|
||||
out.println("Upload Status:" + status);
|
||||
out.println("Pretty print the body\n" + body.toPrettyString());
|
||||
|
||||
// We expect the id field to be a string.
|
||||
if (status == 200) {
|
||||
String id = (String) body.getObject().get("id");
|
||||
out.println("get id:\n" + id);
|
||||
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = uploadStatus(id);
|
||||
if (statusResponse.getStatus() == 200) {
|
||||
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(CPIUploadStatusFName));
|
||||
cpiUploadStatus.print(statusResponse.getBody());
|
||||
out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
|
||||
} else {
|
||||
reportError(statusResponse);
|
||||
}
|
||||
} else {
|
||||
reportError(uploadResponse);
|
||||
}
|
||||
}
|
||||
else {
|
||||
out.println("CPI already uploaded doing a 'force' upload.");
|
||||
forceuploadCPI(cpiFName);
|
||||
}
|
||||
}
|
||||
|
||||
public void createAndRegVNodes() throws IOException, CsdeException{
|
||||
Unirest.config().verifySsl(false);
|
||||
String cpiCheckSum = getLastCPIUploadChkSum( CPIUploadStatusFName );
|
||||
|
||||
LinkedList<String> x500Ids = getConfigX500Ids();
|
||||
LinkedList<String> OKHoldingShortIds = new LinkedList<>();
|
||||
|
||||
// For each identity check that it already exists.
|
||||
Set<MemberX500Name> existingX500 = new HashSet<>();
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeListResponse = getVNodeInfo();
|
||||
|
||||
kong.unirest.json.JSONArray virtualNodesJson = (JSONArray) vnodeListResponse.getBody().getObject().get("virtualNodes");
|
||||
for(Object o: virtualNodesJson){
|
||||
if(o instanceof kong.unirest.json.JSONObject) {
|
||||
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("holdingIdentity");
|
||||
String x500id = (String) idObj.get("x500Name");
|
||||
existingX500.add(MemberX500Name.parse( x500id) );
|
||||
}
|
||||
}
|
||||
|
||||
// Create the VNodes
|
||||
for(String x500id: x500Ids) {
|
||||
if(!existingX500.contains(MemberX500Name.parse(x500id) )) {
|
||||
out.println("Creating VNode for x500id=\"" + x500id + "\" cpi checksum=" + cpiCheckSum);
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> jsonNode = Unirest.post(baseURL + "/api/v1/virtualnode")
|
||||
.body("{ \"request\" : { \"cpiFileChecksum\": " + cpiCheckSum + ", \"x500Name\": \"" + x500id + "\" } }")
|
||||
.basicAuth(rpcUser, rpcPasswd)
|
||||
.asJson();
|
||||
// Logging.
|
||||
|
||||
// need to check this and report errors.
|
||||
// 200 - OK
|
||||
// 409 - Vnode already exists
|
||||
if (jsonNode.getStatus() != 409) {
|
||||
if (jsonNode.getStatus() != 200) {
|
||||
reportError(jsonNode);
|
||||
} else {
|
||||
JSONObject thing = jsonNode.getBody().getObject().getJSONObject("holdingIdentity");
|
||||
String shortHash = (String) thing.get("shortHash");
|
||||
OKHoldingShortIds.add(shortHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
out.println("Not creating a vnode for \"" + x500id + "\", vnode already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
// Register the VNodes
|
||||
for(String shortHoldingIdHash: OKHoldingShortIds) {
|
||||
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = Unirest.post(baseURL + "/api/v1/membership/" + shortHoldingIdHash)
|
||||
.body("{ \"memberRegistrationRequest\": { \"action\": \"requestJoin\", \"context\": { \"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\" } } }")
|
||||
.basicAuth(rpcUser, rpcPasswd)
|
||||
.asJson();
|
||||
|
||||
out.println("Vnode membership submission:\n" + vnodeResponse.getBody().toPrettyString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void startCorda() throws IOException {
|
||||
PrintStream pidStore = new PrintStream(new FileOutputStream(cordaPidCache));
|
||||
File combinedWorkerJar = project.getConfigurations().getByName("combinedWorker").getSingleFile();
|
||||
|
||||
new ProcessBuilder(
|
||||
"docker",
|
||||
"run", "-d", "--rm",
|
||||
"-p", "5432:5432",
|
||||
"--name", dbContainerName,
|
||||
"-e", "POSTGRES_DB=cordacluster",
|
||||
"-e", "POSTGRES_USER=postgres",
|
||||
"-e", "POSTGRES_PASSWORD=password",
|
||||
"postgres:latest").start();
|
||||
rpcWait(10000);
|
||||
|
||||
ProcessBuilder procBuild = new ProcessBuilder(javaBinDir + "/java",
|
||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
|
||||
"-Dco.paralleluniverse.fibers.verifyInstrumentation=true",
|
||||
"-jar",
|
||||
combinedWorkerJar.toString(),
|
||||
"--instanceId=0",
|
||||
"-mbus.busType=DATABASE",
|
||||
"-spassphrase=password",
|
||||
"-ssalt=salt",
|
||||
"-spassphrase=password",
|
||||
"-ssalt=salt",
|
||||
"-ddatabase.user=user",
|
||||
"-ddatabase.pass=password",
|
||||
"-ddatabase.jdbc.url=jdbc:postgresql://localhost:5432/cordacluster",
|
||||
"-ddatabase.jdbc.directory="+JDBCDir);
|
||||
|
||||
|
||||
procBuild.redirectErrorStream(true);
|
||||
Process proc = procBuild.start();
|
||||
pidStore.print(proc.pid());
|
||||
out.println("Corda Process-id="+proc.pid());
|
||||
}
|
||||
|
||||
public void stopCorda() throws IOException, NoPidFile {
|
||||
File cordaPIDFile = new File(cordaPidCache);
|
||||
if(cordaPIDFile.exists()) {
|
||||
Scanner sc = new Scanner(cordaPIDFile);
|
||||
long pid = sc.nextLong();
|
||||
out.println("pid to kill=" + pid);
|
||||
|
||||
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
|
||||
new ProcessBuilder("Powershell", "-Command", "Stop-Process", "-Id", Long.toString(pid), "-PassThru").start();
|
||||
} else {
|
||||
new ProcessBuilder("kill", "-9", Long.toString(pid)).start();
|
||||
}
|
||||
|
||||
Process proc = new ProcessBuilder("docker", "stop", dbContainerName).start();
|
||||
|
||||
cordaPIDFile.delete();
|
||||
}
|
||||
else {
|
||||
throw new NoPidFile("Cannot stop the Combined worker\nCached process ID file " + cordaPidCache + " missing.\nWas the combined worker not started?");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
188
buildSrc/src/main/java/com/r3/csde/DeployCPIsHelper.java
Normal file
188
buildSrc/src/main/java/com/r3/csde/DeployCPIsHelper.java
Normal file
@ -0,0 +1,188 @@
|
||||
package com.r3.csde;
|
||||
|
||||
import kong.unirest.JsonNode;
|
||||
import kong.unirest.Unirest;
|
||||
import kong.unirest.json.JSONArray;
|
||||
import kong.unirest.json.JSONObject;
|
||||
import kong.unirest.HttpResponse;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
||||
import static java.net.HttpURLConnection.HTTP_OK;
|
||||
|
||||
public class DeployCPIsHelper {
|
||||
|
||||
public DeployCPIsHelper() {
|
||||
}
|
||||
ProjectContext pc;
|
||||
CordaStatusQueries queries;
|
||||
ProjectUtils utils;
|
||||
|
||||
public DeployCPIsHelper(ProjectContext _pc) {
|
||||
pc = _pc;
|
||||
queries = new CordaStatusQueries(pc);
|
||||
utils = new ProjectUtils(pc);
|
||||
}
|
||||
|
||||
public void deployCPIs() throws FileNotFoundException, CsdeException{
|
||||
|
||||
uploadCertificate(pc.signingCertAlias, pc.signingCertFName);
|
||||
uploadCertificate(pc.keystoreAlias, pc.keystoreCertFName);
|
||||
|
||||
// todo: make consistent with other string building code - remove String.format
|
||||
String appCPILocation = String.format("%s/%s-%s.cpi",
|
||||
pc.workflowBuildDir,
|
||||
pc.project.getName(),
|
||||
pc.project.getVersion());
|
||||
deployCPI(appCPILocation, pc.appCPIName,pc.project.getVersion().toString());
|
||||
|
||||
String notaryCPILocation = String.format("%s/%s-%s.cpi",
|
||||
pc.workflowBuildDir,
|
||||
pc.notaryCPIName.replace(' ','-').toLowerCase(),
|
||||
pc.project.getVersion());
|
||||
deployCPI(notaryCPILocation,
|
||||
pc.notaryCPIName,
|
||||
pc.project.getVersion().toString(),
|
||||
"-NotaryServer" );
|
||||
|
||||
}
|
||||
|
||||
public void uploadCertificate(String certAlias, String certFName) {
|
||||
Unirest.config().verifySsl(false);
|
||||
HttpResponse<JsonNode> uploadResponse = Unirest.put(pc.baseURL + "/api/v1/certificates/cluster/code-signer")
|
||||
.field("alias", certAlias)
|
||||
.field("certificate", new File(certFName))
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJson();
|
||||
pc.out.println("Certificate/key upload, alias "+certAlias+" certificate/key file "+certFName);
|
||||
pc.out.println(uploadResponse.getBody().toPrettyString());
|
||||
}
|
||||
|
||||
public void forceuploadCPI(String cpiFName) throws FileNotFoundException, CsdeException {
|
||||
forceuploadCPI(cpiFName, "");
|
||||
}
|
||||
|
||||
public void forceuploadCPI(String cpiFName, String uploadStatusQualifier) throws FileNotFoundException, CsdeException {
|
||||
Unirest.config().verifySsl(false);
|
||||
HttpResponse<JsonNode> jsonResponse = Unirest.post(pc.baseURL + "/api/v1/maintenance/virtualnode/forcecpiupload/")
|
||||
.field("upload", new File(cpiFName))
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJson();
|
||||
|
||||
if(jsonResponse.getStatus() == HTTP_OK) {
|
||||
String id = (String) jsonResponse.getBody().getObject().get("id");
|
||||
pc.out.println("get id:\n" +id);
|
||||
HttpResponse<JsonNode> statusResponse = uploadStatus(id);
|
||||
|
||||
if (statusResponse.getStatus() == HTTP_OK) {
|
||||
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(
|
||||
pc.CPIUploadStatusFName.replace(".json", uploadStatusQualifier + ".json" )));
|
||||
cpiUploadStatus.print(statusResponse.getBody());
|
||||
pc.out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
|
||||
} else {
|
||||
utils.reportError(statusResponse);
|
||||
}
|
||||
}
|
||||
else {
|
||||
utils.reportError(jsonResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean uploadStatusRetry(HttpResponse<JsonNode> response) {
|
||||
int status = response.getStatus();
|
||||
JsonNode body = response.getBody();
|
||||
// Do not retry on success // todo: need to think through the possible outcomes here - what if the bodyTitle is null, it won't retry
|
||||
if(status == HTTP_OK) {
|
||||
// Keep retrying until we get "OK" may move through "Validating upload", "Persisting CPI"
|
||||
return !(body.getObject().get("status").equals("OK"));
|
||||
}
|
||||
else if (status == HTTP_BAD_REQUEST){
|
||||
String bodyTitle = response.getBody().getObject().getString("title");
|
||||
return bodyTitle != null && bodyTitle.matches("No such requestId=[-0-9a-f]+");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public HttpResponse<JsonNode> uploadStatus(String requestId) {
|
||||
HttpResponse<JsonNode> statusResponse = null;
|
||||
do {
|
||||
utils.rpcWait(1000);
|
||||
statusResponse = Unirest
|
||||
.get(pc.baseURL + "/api/v1/cpi/status/" + requestId + "/")
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJson();
|
||||
pc.out.println("Upload status="+statusResponse.getStatus()+", status query response:\n"+statusResponse.getBody().toPrettyString());
|
||||
}
|
||||
while(uploadStatusRetry(statusResponse));
|
||||
|
||||
return statusResponse;
|
||||
}
|
||||
|
||||
public void deployCPI(String cpiFName, String cpiName, String cpiVersion) throws FileNotFoundException, CsdeException {
|
||||
deployCPI(cpiFName, cpiName, cpiVersion, "");
|
||||
}
|
||||
|
||||
public void deployCPI(String cpiFName,
|
||||
String cpiName,
|
||||
String cpiVersion,
|
||||
String uploadStatusQualifier) throws FileNotFoundException, CsdeException {
|
||||
// todo: where is the primary instance declared?
|
||||
Unirest.config().verifySsl(false);
|
||||
|
||||
HttpResponse<JsonNode> cpiResponse = queries.getCpiInfo();
|
||||
JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
|
||||
|
||||
int matches = 0;
|
||||
for(Object o: jArray.toList() ) {
|
||||
if(o instanceof JSONObject) {
|
||||
JSONObject idObj = ((JSONObject) o).getJSONObject("id");
|
||||
if((idObj.get("cpiName").toString().equals(cpiName)
|
||||
&& idObj.get("cpiVersion").toString().equals(cpiVersion))) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
pc.out.println("Matching CPIS="+matches);
|
||||
|
||||
if(matches == 0) {
|
||||
HttpResponse<JsonNode> uploadResponse = Unirest.post(pc.baseURL + "/api/v1/cpi/")
|
||||
.field("upload", new File(cpiFName))
|
||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||
.asJson();
|
||||
|
||||
JsonNode body = uploadResponse.getBody();
|
||||
|
||||
int status = uploadResponse.getStatus();
|
||||
|
||||
pc.out.println("Upload Status:" + status);
|
||||
pc.out.println("Pretty print the body\n" + body.toPrettyString());
|
||||
|
||||
// We expect the id field to be a string.
|
||||
if (status == HTTP_OK) {
|
||||
String id = (String) body.getObject().get("id");
|
||||
pc.out.println("get id:\n" + id);
|
||||
|
||||
HttpResponse<JsonNode> statusResponse = uploadStatus(id);
|
||||
if (statusResponse.getStatus() == HTTP_OK) {
|
||||
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(
|
||||
pc.CPIUploadStatusFName.replace(".json", uploadStatusQualifier + ".json" )));
|
||||
cpiUploadStatus.print(statusResponse.getBody());
|
||||
pc.out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
|
||||
} else {
|
||||
utils.reportError(statusResponse);
|
||||
}
|
||||
} else {
|
||||
utils.reportError(uploadResponse);
|
||||
}
|
||||
}
|
||||
else {
|
||||
pc.out.println("CPI already uploaded doing a 'force' upload.");
|
||||
forceuploadCPI(cpiFName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.r3.csde;
|
||||
|
||||
public class NoPidFile extends Exception {
|
||||
public NoPidFile(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
83
buildSrc/src/main/java/com/r3/csde/ProjectContext.java
Normal file
83
buildSrc/src/main/java/com/r3/csde/ProjectContext.java
Normal file
@ -0,0 +1,83 @@
|
||||
package com.r3.csde;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.Map;
|
||||
|
||||
public class ProjectContext {
|
||||
Project project;
|
||||
String baseURL = "https://localhost:8888";
|
||||
String rpcUser = "admin";
|
||||
String rpcPasswd = "admin";
|
||||
String workspaceDir = "workspace";
|
||||
int retryWaitMs = 1000;
|
||||
PrintStream out = System.out;
|
||||
String CPIUploadStatusBaseName = "CPIFileStatus.json";
|
||||
String CPIUploadStatusFName;
|
||||
String X500ConfigFile = "config/dev-net.json";
|
||||
String javaBinDir;
|
||||
String cordaPidCache = "CordaPIDCache.dat";
|
||||
String dbContainerName;
|
||||
String JDBCDir;
|
||||
String combinedWorkerBinRe;
|
||||
Map<String, String> notaryRepresentatives = null;
|
||||
String signingCertAlias;
|
||||
String signingCertFName;
|
||||
String keystoreAlias;
|
||||
String keystoreFName;
|
||||
String keystoreCertFName;
|
||||
String appCPIName;
|
||||
String notaryCPIName;
|
||||
String devEnvWorkspace;
|
||||
String cordaCliBinDir;
|
||||
String cordaNotaryServiceDir;
|
||||
String workflowBuildDir;
|
||||
String cordaNotaryPluginsVersion;
|
||||
|
||||
public ProjectContext (Project inProject,
|
||||
String inBaseUrl,
|
||||
String inRpcUser,
|
||||
String inRpcPasswd,
|
||||
String inWorkspaceDir,
|
||||
String inJavaBinDir,
|
||||
String inDbContainerName,
|
||||
String inJDBCDir,
|
||||
String inCordaPidCache,
|
||||
String inSigningCertAlias,
|
||||
String inSigningCertFName,
|
||||
String inKeystoreAlias,
|
||||
String inKeystoreFName,
|
||||
String inKeystoreCertFName,
|
||||
String inAppCPIName,
|
||||
String inNotaryCPIName,
|
||||
String inDevEnvWorkspace,
|
||||
String inCordaCLiBinDir,
|
||||
String inCordaNotaryServiceDir,
|
||||
String inWorkflowBuildDir,
|
||||
String inCordaNotaryPluginsVersion
|
||||
) {
|
||||
project = inProject;
|
||||
baseURL = inBaseUrl;
|
||||
rpcUser = inRpcUser;
|
||||
rpcPasswd = inRpcPasswd;
|
||||
workspaceDir = inWorkspaceDir;
|
||||
javaBinDir = inJavaBinDir;
|
||||
cordaPidCache = inCordaPidCache;
|
||||
dbContainerName = inDbContainerName;
|
||||
JDBCDir = inJDBCDir;
|
||||
CPIUploadStatusFName = workspaceDir + "/" + CPIUploadStatusBaseName;
|
||||
signingCertAlias = inSigningCertAlias;
|
||||
signingCertFName = inSigningCertFName;
|
||||
keystoreAlias = inKeystoreAlias;
|
||||
keystoreFName = inKeystoreFName;
|
||||
keystoreCertFName = inKeystoreCertFName;
|
||||
appCPIName = inAppCPIName;
|
||||
notaryCPIName = inNotaryCPIName;
|
||||
devEnvWorkspace = inDevEnvWorkspace;
|
||||
cordaCliBinDir = inCordaCLiBinDir;
|
||||
cordaNotaryServiceDir = inCordaNotaryServiceDir;
|
||||
workflowBuildDir = inWorkflowBuildDir;
|
||||
cordaNotaryPluginsVersion = inCordaNotaryPluginsVersion;
|
||||
}
|
||||
}
|
71
buildSrc/src/main/java/com/r3/csde/ProjectUtils.java
Normal file
71
buildSrc/src/main/java/com/r3/csde/ProjectUtils.java
Normal file
@ -0,0 +1,71 @@
|
||||
package com.r3.csde;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.JsonNode;
|
||||
import kong.unirest.Unirest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static java.lang.Thread.sleep;
|
||||
|
||||
public class ProjectUtils {
|
||||
|
||||
ProjectContext pc;
|
||||
ProjectUtils(ProjectContext _pc) {
|
||||
pc = _pc;
|
||||
}
|
||||
|
||||
|
||||
void rpcWait(int millis) {
|
||||
try {
|
||||
sleep(millis);
|
||||
}
|
||||
catch(InterruptedException e) {
|
||||
throw new UnsupportedOperationException("Interrupts not supported.", e);
|
||||
}
|
||||
}
|
||||
|
||||
void rpcWait() {
|
||||
rpcWait( pc.retryWaitMs);
|
||||
}
|
||||
|
||||
public LinkedList<String> getConfigX500Ids(String configFile) throws IOException {
|
||||
LinkedList<String> x500Ids = new LinkedList<>();
|
||||
// com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
|
||||
FileInputStream in = new FileInputStream(configFile);
|
||||
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
||||
for( com.fasterxml.jackson.databind.JsonNode identity: jsonNode.get("identities")) {
|
||||
x500Ids.add(jsonNodeToString(identity));
|
||||
}
|
||||
return x500Ids;
|
||||
}
|
||||
|
||||
public String jsonNodeToString(com.fasterxml.jackson.databind.JsonNode jsonNode) {
|
||||
String jsonString = jsonNode.toString();
|
||||
return jsonString.substring(1, jsonString.length()-1);
|
||||
}
|
||||
|
||||
public void downloadFile(String url, String targetPath) {
|
||||
Unirest.get(url)
|
||||
.asFile(targetPath)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
public void reportError(@NotNull HttpResponse<JsonNode> response) throws CsdeException {
|
||||
|
||||
pc.out.println("*** *** ***");
|
||||
pc.out.println("Unexpected response from Corda");
|
||||
pc.out.println("Status="+ response.getStatus());
|
||||
pc.out.println("*** Headers ***\n"+ response.getHeaders());
|
||||
pc.out.println("*** Body ***\n"+ response.getBody());
|
||||
pc.out.println("*** *** ***");
|
||||
throw new CsdeException("Error: unexpected response from Corda.");
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
import com.r3.csde.CsdeRpcInterface;
|
||||
|
||||
public class CsdeRpcInterfaceTests {
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
{
|
||||
"identities" : [
|
||||
"CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
|
||||
"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
|
||||
"CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB"
|
||||
]
|
||||
"identities" : [
|
||||
"CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
|
||||
"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
|
||||
"CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB",
|
||||
"CN=NotaryRep1, OU=Test Dept, O=R3, L=London, C=GB"],
|
||||
"notaries" : [
|
||||
{
|
||||
"serviceX500Name": "CN=NotaryService, OU=Test Dept, O=R3, L=London, C=GB",
|
||||
"representatives": ["CN=NotaryRep1, OU=Test Dept, O=R3, L=London, C=GB"]
|
||||
}]
|
||||
}
|
||||
|
91
contracts/build.gradle
Normal file
91
contracts/build.gradle
Normal file
@ -0,0 +1,91 @@
|
||||
|
||||
plugins {
|
||||
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
|
||||
// These extend existing build environment so that CPB and CPK files can be built.
|
||||
// This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
|
||||
// required by Corda.
|
||||
id 'net.corda.plugins.cordapp-cpb2'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
// Declare dependencies for the modules we will use.
|
||||
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
|
||||
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
|
||||
dependencies {
|
||||
// We need a version of kotlin-stdlib-jdk8 built as an OSGi bundle, this is "kotlin-stdlib-jdk8-osgi".
|
||||
// R3 builds kotlin-stdlib-jdk8-osgi from Kotlin's kotlin-stdlib-jdk8.
|
||||
// NB:
|
||||
// Kotlin's kotlin-osgi-bundle does not provide all of the Kotlin API that is required,
|
||||
// There is no kotlin-stdlib-jdk11, but one is not needed even though we are targetting Java 11.
|
||||
cordaProvided 'net.corda.kotlin:kotlin-stdlib-jdk8-osgi'
|
||||
|
||||
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
|
||||
// Corda API specified.
|
||||
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
|
||||
|
||||
// If using transistive dependencies this will provide most of Corda-API:
|
||||
// cordaProvided 'net.corda:corda-application'
|
||||
|
||||
// Alternatively we can explicitly specify all our Corda-API dependencies:
|
||||
cordaProvided 'net.corda:corda-base'
|
||||
cordaProvided 'net.corda:corda-application'
|
||||
cordaProvided 'net.corda:corda-crypto'
|
||||
cordaProvided 'net.corda:corda-membership'
|
||||
// cordaProvided 'net.corda:corda-persistence'
|
||||
cordaProvided 'net.corda:corda-serialization'
|
||||
cordaProvided 'net.corda:corda-ledger-utxo'
|
||||
cordaProvided 'net.corda:corda-ledger-consensual'
|
||||
|
||||
// CorDapps that use the UTXO ledger must include at least one notary client plugin
|
||||
cordapp "com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-client:$cordaNotaryPluginsVersion"
|
||||
|
||||
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
|
||||
cordaProvided 'org.slf4j:slf4j-api'
|
||||
|
||||
// This are shared so should be here.
|
||||
// Dependencies Required By Test Tooling
|
||||
testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
|
||||
testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
|
||||
|
||||
// 3rd party libraries
|
||||
// Required
|
||||
testImplementation "org.slf4j:slf4j-simple:2.0.0"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
// Optional but used by exmaple tests.
|
||||
testImplementation "org.mockito:mockito-core:$mockitoVersion"
|
||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
}
|
||||
|
||||
// The CordApp section.
|
||||
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
|
||||
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the component’s
|
||||
// subproject.
|
||||
// This is required by the corda plugins to build the CorDapp.
|
||||
cordapp {
|
||||
// "targetPlatformVersion" and "minimumPlatformVersion" are intended to specify the preferred
|
||||
// and earliest versions of the Corda platform that the CorDapp will run on respectively.
|
||||
// Enforced versioning has not implemented yet so we need to pass in a dummy value for now.
|
||||
// The platform version will correspond to and be roughly equivalent to the Corda API version.
|
||||
targetPlatformVersion = platformVersion.toInteger()
|
||||
minimumPlatformVersion = platformVersion.toInteger()
|
||||
|
||||
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
|
||||
// Declares the type and metadata of the CPK (this CPB has one CPK).
|
||||
contract {
|
||||
name "ContractsModuleNameHere"
|
||||
versionId 1
|
||||
vendor "VendorNameHere"
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
from components.cordapp
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,18 @@ kotlin.code.style=official
|
||||
|
||||
# Specify the version of the Corda-API to use.
|
||||
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
|
||||
# cordaApiVersion=5.0.0.190-DevPreview-2
|
||||
cordaApiVersion=5.0.0.505-Beta1.0-HC00
|
||||
cordaApiVersion=5.0.0.523-Fox1.0
|
||||
|
||||
# Settings For Development Utilities
|
||||
combinedWorkerVersion=5.0.0.0-Fox1.0
|
||||
simulatorVersion=5.0.0.0-Fox1.0
|
||||
|
||||
# Specify the version of the notary plugins to use.
|
||||
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
|
||||
cordaNotaryPluginsVersion=5.0.0.0-Fox1.0
|
||||
|
||||
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
|
||||
cordaPluginsVersion=7.0.0
|
||||
cordaPluginsVersion=7.0.1
|
||||
|
||||
# For the time being this just needs to be set to a dummy value.
|
||||
platformVersion = 999
|
||||
@ -24,15 +31,9 @@ mockitoKotlinVersion=4.0.0
|
||||
mockitoVersion=4.6.1
|
||||
hamcrestVersion=2.2
|
||||
|
||||
# Settings For Development Utilities
|
||||
combinedWorkerVersion=5.0.0.0-Beta1.0-HC00
|
||||
simulatorVersion=5.0.0.0-Beta1.0-HC00
|
||||
|
||||
cordaClusterURL=https://localhost:8888
|
||||
cordaRpcUser=admin
|
||||
cordaRpcPasswd=admin
|
||||
devEnvWorkspace=workspace
|
||||
dbContainerName=CSDEpostgresql
|
||||
|
||||
# R3 internal repository
|
||||
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00
|
@ -2,9 +2,7 @@ pluginManagement {
|
||||
// Declare the repositories where plugins are stored.
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven {
|
||||
url = "$artifactoryContextUrl/"
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
|
||||
@ -21,5 +19,9 @@ pluginManagement {
|
||||
|
||||
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
|
||||
rootProject.name = 'csde-cordapp-template-java'
|
||||
include ':workflows'
|
||||
include ':contracts'
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
package com.r3.developers.csdetemplate;
|
||||
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
|
||||
// A class to hold the arguments required to start the flow
|
||||
public class MyFirstFlowStartArgs {
|
||||
public MemberX500Name otherMember;
|
||||
|
||||
public MemberX500Name getOtherMember() {
|
||||
return otherMember;
|
||||
}
|
||||
|
||||
public MyFirstFlowStartArgs(MemberX500Name otherMember) {
|
||||
this.otherMember = otherMember;
|
||||
}
|
||||
|
||||
// Without the following we get
|
||||
// "Cannot construct instance of `com.r3.developers.csdetemplate.MyFirstFlowStartArgs` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (String)\"{\"otherMember\":\"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB\"}\"; line: 1, column: 2]"
|
||||
public MyFirstFlowStartArgs() {}
|
||||
}
|
94
workflows/build.gradle
Normal file
94
workflows/build.gradle
Normal file
@ -0,0 +1,94 @@
|
||||
plugins {
|
||||
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
|
||||
// These extend existing build environment so that CPB and CPK files can be built.
|
||||
// This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
|
||||
// required by Corda.
|
||||
id 'net.corda.plugins.cordapp-cpb2'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
// Declare dependencies for the modules we will use.
|
||||
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
|
||||
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
|
||||
dependencies {
|
||||
// From other subprojects:
|
||||
cordapp project(':contracts')
|
||||
|
||||
// We need a version of kotlin-stdlib-jdk8 built as an OSGi bundle, this is "kotlin-stdlib-jdk8-osgi".
|
||||
// R3 builds kotlin-stdlib-jdk8-osgi from Kotlin's kotlin-stdlib-jdk8.
|
||||
// NB:
|
||||
// Kotlin's kotlin-osgi-bundle does not provide all of the Kotlin API that is required,
|
||||
// There is no kotlin-stdlib-jdk11, but one is not needed even though we are targetting Java 11.
|
||||
cordaProvided 'net.corda.kotlin:kotlin-stdlib-jdk8-osgi'
|
||||
|
||||
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
|
||||
// Corda API specified.
|
||||
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
|
||||
|
||||
// If using transistive dependencies this will provide most of Corda-API:
|
||||
// cordaProvided 'net.corda:corda-application'
|
||||
|
||||
// Alternatively we can explicitly specify all our Corda-API dependencies:
|
||||
cordaProvided 'net.corda:corda-base'
|
||||
cordaProvided 'net.corda:corda-application'
|
||||
cordaProvided 'net.corda:corda-crypto'
|
||||
cordaProvided 'net.corda:corda-membership'
|
||||
// cordaProvided 'net.corda:corda-persistence'
|
||||
cordaProvided 'net.corda:corda-serialization'
|
||||
cordaProvided 'net.corda:corda-ledger-utxo'
|
||||
cordaProvided 'net.corda:corda-ledger-consensual'
|
||||
|
||||
// CorDapps that use the UTXO ledger must include at least one notary client plugin
|
||||
cordapp "com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-client:$cordaNotaryPluginsVersion"
|
||||
|
||||
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
|
||||
cordaProvided 'org.slf4j:slf4j-api'
|
||||
|
||||
// This are shared so should be here.
|
||||
// Dependencies Required By Test Tooling
|
||||
testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
|
||||
testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
|
||||
|
||||
// 3rd party libraries
|
||||
// Required
|
||||
testImplementation "org.slf4j:slf4j-simple:2.0.0"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
// Optional but used by exmaple tests.
|
||||
testImplementation "org.mockito:mockito-core:$mockitoVersion"
|
||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
|
||||
}
|
||||
|
||||
// The CordApp section.
|
||||
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
|
||||
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the component’s
|
||||
// subproject.
|
||||
// This is required by the corda plugins to build the CorDapp.
|
||||
cordapp {
|
||||
// "targetPlatformVersion" and "minimumPlatformVersion" are intended to specify the preferred
|
||||
// and earliest versions of the Corda platform that the CorDapp will run on respectively.
|
||||
// Enforced versioning has not implemented yet so we need to pass in a dummy value for now.
|
||||
// The platform version will correspond to and be roughly equivalent to the Corda API version.
|
||||
targetPlatformVersion = platformVersion.toInteger()
|
||||
minimumPlatformVersion = platformVersion.toInteger()
|
||||
|
||||
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
|
||||
// Declares the type and metadata of the CPK (this CPB has one CPK).
|
||||
workflow {
|
||||
name "WorkflowsModuleNameHere"
|
||||
versionId 1
|
||||
vendor "VendorNameHere"
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
from components.cordapp
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
package com.r3.developers.csdetemplate;
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
|
||||
import net.corda.v5.base.annotations.CordaSerializable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
|
||||
// A class which will contain a message, It must be marked with @CordaSerializable for Corda
|
||||
// to be able to send from one virtual node to another.
|
||||
// Where a class contains a message, mark it with @CordaSerializable to enable Corda to
|
||||
// send it from one virtual node to another.
|
||||
@CordaSerializable
|
||||
public class Message {
|
||||
// public Message() {}
|
||||
public Message(MemberX500Name sender, String message) {
|
||||
this.sender = sender;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public MemberX500Name getSender() {
|
||||
return sender;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.r3.developers.csdetemplate;
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
|
||||
import net.corda.v5.application.flows.*;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
@ -14,50 +14,50 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
// MyFirstFlow is an initiating flow, it's corresponding responder flow is called MyFirstFlowResponder (defined below)
|
||||
// to link the two sides of the flow together they need to have the same protocol.
|
||||
@InitiatingFlow(protocol = "another-flow")
|
||||
@InitiatingFlow(protocol = "my-first-flow")
|
||||
// MyFirstFlow should inherit from RPCStartableFlow, which tells Corda it can be started via an RPC call
|
||||
public class MyFirstFlow implements RPCStartableFlow {
|
||||
|
||||
// It is useful to be able to log messages from the flows for debugging.
|
||||
// Log messages from the flows for debugging.
|
||||
private final Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
|
||||
|
||||
// Corda has a set of injectable services which are injected into the flow at runtime.
|
||||
// Flows declare them with @CordaInjectable, then the flows have access to their services.
|
||||
|
||||
// JsonMarshallingService provides a Service for manipulating json
|
||||
// JsonMarshallingService provides a service for manipulating JSON.
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
// FlowMessaging provides a service for establishing flow sessions between Virtual Nodes and
|
||||
// sending and receiving payloads between them
|
||||
// FlowMessaging provides a service that establishes flow sessions between virtual nodes
|
||||
// that send and receive payloads between them.
|
||||
@CordaInject
|
||||
public FlowMessaging flowMessaging;
|
||||
|
||||
// MemberLookup provides a service for looking up information about members of the Virtual Network which
|
||||
// this CorDapp is operating in.
|
||||
// MemberLookup provides a service for looking up information about members of the virtual network which
|
||||
// this CorDapp operates in.
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
public MyFirstFlow() {}
|
||||
|
||||
// When a flow is invoked it's call() method is called.
|
||||
// call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||
// for a response from the other flows and services
|
||||
// When a flow is invoked its call() method is called.
|
||||
// Call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||
// for a response from the other flows and services.
|
||||
@NotNull
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(@NotNull RPCRequestData requestBody) {
|
||||
public String call(RPCRequestData requestBody) {
|
||||
|
||||
// Useful logging to follow what's happening in the console or logs
|
||||
// Follow what happens in the console or logs.
|
||||
log.info("MFF: MyFirstFlow.call() called");
|
||||
|
||||
// Show the requestBody in the logs - this can be used to help establish the format for starting a flow on corda
|
||||
// Show the requestBody in the logs - this can be used to help establish the format for starting a flow on Corda.
|
||||
log.info("MFF: requestBody: " + requestBody.getRequestBody());
|
||||
|
||||
// Deserialize the Json requestBody into the MyfirstFlowStartArgs class using the JsonSerialisation Service
|
||||
// Deserialize the Json requestBody into the MyfirstFlowStartArgs class using the JsonSerialisation service.
|
||||
MyFirstFlowStartArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, MyFirstFlowStartArgs.class);
|
||||
|
||||
// Obtain the MemberX500Name of counterparty
|
||||
// Obtain the MemberX500Name of the counterparty.
|
||||
MemberX500Name otherMember = flowArgs.otherMember;
|
||||
|
||||
// Get our identity from the MemberLookup service.
|
||||
@ -69,19 +69,19 @@ public class MyFirstFlow implements RPCStartableFlow {
|
||||
// Log the message to be sent.
|
||||
log.info("MFF: message.message: " + message.message);
|
||||
|
||||
// Start a flow session with the otherMember using the FlowMessaging service
|
||||
// The otherMember's Virtual Node will run the corresponding MyFirstFlowResponder responder flow
|
||||
// Start a flow session with the otherMember using the FlowMessaging service.
|
||||
// The otherMember's virtual node will run the corresponding MyFirstFlowResponder responder flow.
|
||||
FlowSession session = flowMessaging.initiateFlow(otherMember);
|
||||
|
||||
// Send the Payload using the send method on the session to the MyFirstFlowResponder Responder flow
|
||||
// Send the Payload using the send method on the session to the MyFirstFlowResponder responder flow.
|
||||
session.send(message);
|
||||
|
||||
// Receive a response from the Responder flow
|
||||
// Receive a response from the responder flow.
|
||||
Message response = session.receive(Message.class);
|
||||
|
||||
// The return value of a RPCStartableFlow must always be a String, this string will be passed
|
||||
// The return value of a RPCStartableFlow must always be a String. This will be passed
|
||||
// back as the REST RPC response when the status of the flow is queried on Corda, or as the return
|
||||
// value from the flow when testing using the Simulator
|
||||
// value from the flow when testing using the simulator.
|
||||
return response.message;
|
||||
}
|
||||
}
|
||||
@ -91,9 +91,9 @@ public class MyFirstFlow implements RPCStartableFlow {
|
||||
RequestBody for triggering the flow via http-rpc:
|
||||
{
|
||||
"clientRequestId": "r1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.workflows.MyFirstFlow",
|
||||
"requestData": {
|
||||
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
|
||||
}
|
||||
}
|
||||
*/
|
||||
*/
|
@ -1,4 +1,4 @@
|
||||
package com.r3.developers.csdetemplate;
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.InitiatedBy;
|
||||
@ -11,36 +11,36 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
// MyFirstFlowResponder is a responder flow, it's corresponding initiating flow is called MyFirstFlow (defined above)
|
||||
// MyFirstFlowResponder is a responder flow, its corresponding initiating flow is called MyFirstFlow (defined in MyFirstFlow.java)
|
||||
// to link the two sides of the flow together they need to have the same protocol.
|
||||
@InitiatedBy(protocol = "another-flow")
|
||||
@InitiatedBy(protocol = "my-first-flow")
|
||||
// Responder flows must inherit from ResponderFlow
|
||||
public class MyFirstFlowResponder implements ResponderFlow {
|
||||
|
||||
// It is useful to be able to log messages from the flows for debugging.
|
||||
// Log messages from the flows for debugging.
|
||||
private final Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class);
|
||||
|
||||
// MemberLookup provides a service for looking up information about members of the Virtual Network which
|
||||
// this CorDapp is operating in.
|
||||
// MemberLookup looks for information about members of the virtual network which
|
||||
// this CorDapp operates in.
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
public MyFirstFlowResponder() {}
|
||||
|
||||
// Responder flows are invoked when an initiating flow makes a call via a session set up with the Virtual
|
||||
// node hosting the Responder flow. When a responder flow is invoked it's call() method is called.
|
||||
// call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||
// for a response from the other flows and services/
|
||||
// Responder flows are invoked when an initiating flow makes a call via a session set up with the virtual
|
||||
// node hosting the responder flow. When a responder flow is invoked its call() method is called.
|
||||
// Call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||
// for a response from the other flows and services.
|
||||
// The Call method has the flow session passed in as a parameter by Corda so the session is available to
|
||||
// responder flow code, you don't need to inject the FlowMessaging service.
|
||||
@Suspendable
|
||||
@Override
|
||||
public void call(FlowSession session) {
|
||||
|
||||
// Useful logging to follow what's happening in the console or logs
|
||||
// Follow what happens in the console or logs.
|
||||
log.info("MFF: MyFirstResponderFlow.call() called");
|
||||
|
||||
// Receive the payload and deserialize it into a Message class
|
||||
// Receive the payload and deserialize it into a message class.
|
||||
Message receivedMessage = session.receive(Message.class);
|
||||
|
||||
// Log the message as a proxy for performing some useful operation on it.
|
||||
@ -49,7 +49,7 @@ public class MyFirstFlowResponder implements ResponderFlow {
|
||||
// Get our identity from the MemberLookup service.
|
||||
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
||||
|
||||
// Create a response to greet the sender
|
||||
// Create a message to greet the sender.
|
||||
Message response = new Message(ourIdentity,
|
||||
"Hello " + session.getCounterparty().getCommonName() + ", best wishes from " + ourIdentity.getCommonName());
|
||||
|
@ -0,0 +1,15 @@
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
|
||||
// A class to hold the arguments required to start the flow
|
||||
public class MyFirstFlowStartArgs {
|
||||
public MemberX500Name otherMember;
|
||||
|
||||
public MyFirstFlowStartArgs(MemberX500Name otherMember) {
|
||||
this.otherMember = otherMember;
|
||||
}
|
||||
|
||||
// The JSON Marshalling Service, which handles serialisation, needs this constructor.
|
||||
public MyFirstFlowStartArgs() {}
|
||||
}
|
@ -1,50 +1,41 @@
|
||||
package com.r3.developers.csdetemplate;
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
|
||||
|
||||
import net.corda.simulator.HoldingIdentity;
|
||||
import net.corda.simulator.RequestData;
|
||||
import net.corda.simulator.SimulatedVirtualNode;
|
||||
import net.corda.simulator.Simulator;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
class MyFirstFlowTest {
|
||||
|
||||
// Names picked to match the corda network in config/dev-net.json
|
||||
private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
|
||||
private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void test_that_MyFirstFLow_returns_correct_message() {
|
||||
|
||||
// Instantiate an instance of the Simulator
|
||||
// Instantiate an instance of the simulator.
|
||||
Simulator simulator = new Simulator();
|
||||
|
||||
// Create Alice's and Bob HoldingIDs
|
||||
HoldingIdentity aliceHoldingID = HoldingIdentity.Companion.create(aliceX500);
|
||||
HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500);
|
||||
// Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node.
|
||||
// Don't assign Bob's virtual node to a value. You don't need it for this particular test.
|
||||
SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class);
|
||||
simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class);
|
||||
|
||||
// Create Alice and Bob's virtual nodes, including the Class's of the flows which will be registered on each node.
|
||||
// We don't assign Bob's virtual node to a val because we don't need it for this particular test.
|
||||
SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceHoldingID, MyFirstFlow.class);
|
||||
simulator.createVirtualNode(bobHoldingID, MyFirstFlowResponder.class);
|
||||
|
||||
// Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow
|
||||
// Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow.
|
||||
MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
|
||||
|
||||
// Create a requestData object
|
||||
// Create a requestData object.
|
||||
RequestData requestData = RequestData.Companion.create(
|
||||
"request no 1", // A unique reference for the instance of the flow request
|
||||
MyFirstFlow.class, // The name of the flow class which is to be started
|
||||
myFirstFlowStartArgs // The object which contains the start arguments of the flow
|
||||
"request no 1", // A unique reference for the instance of the flow request.
|
||||
MyFirstFlow.class, // The name of the flow class which is to be started.
|
||||
myFirstFlowStartArgs // The object which contains the start arguments of the flow.
|
||||
);
|
||||
|
||||
// Call the Flow on Alice's virtual node and capture the response from the flow
|
||||
// Call the flow on Alice's virtual node and capture the response.
|
||||
String flowResponse = aliceVN.callFlow(requestData);
|
||||
|
||||
// Check that the flow has returned the expected string
|
||||
// Check that the flow has returned the expected string.
|
||||
assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user