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(
|
cordaPipeline(
|
||||||
nexusAppId: 'com.corda.CSDE-Java.5.0',
|
nexusAppId: 'com.corda.CSDE-Java.5.0',
|
||||||
publishRepoPrefix: '',
|
publishRepoPrefix: '',
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# CSDE-cordapp-template-java
|
# 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).
|
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:
|
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 {
|
plugins {
|
||||||
id 'org.jetbrains.kotlin.jvm'
|
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 'net.corda.cordapp.cordapp-configuration'
|
||||||
|
|
||||||
id 'org.jetbrains.kotlin.plugin.jpa'
|
id 'org.jetbrains.kotlin.plugin.jpa'
|
||||||
@ -18,110 +13,39 @@ plugins {
|
|||||||
id 'csde'
|
id 'csde'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'com.r3.hellocorda'
|
allprojects {
|
||||||
version '1.0-SNAPSHOT'
|
group 'com.r3.hellocorda'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
def javaVersion = VERSION_11
|
def javaVersion = VERSION_11
|
||||||
|
|
||||||
// The CordApp section.
|
// Declare the set of Java compiler options we need to build a CorDapp.
|
||||||
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
|
tasks.withType(JavaCompile) {
|
||||||
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the component’s
|
// -parameters - Needed for reflection and serialization to work correctly.
|
||||||
// subproject.
|
options.compilerArgs += [
|
||||||
// This is required by the corda plugins to build the CorDapp.
|
"-parameters"
|
||||||
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 Kotlin compiler options we need to build a CorDapp.
|
repositories {
|
||||||
tasks.withType(JavaCompile) {
|
// All dependencies are held in Maven Central
|
||||||
|
mavenCentral()
|
||||||
// -parameters - Needed for reflection and serialization to work correctly.
|
|
||||||
options.compilerArgs += [
|
|
||||||
"-parameters"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
// All dependencies are held in Maven Central
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
url = "$artifactoryContextUrl/"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Declare dependencies for the modules we will use.
|
tasks.withType(Test).configureEach {
|
||||||
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
|
useJUnitPlatform()
|
||||||
// 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'
|
|
||||||
|
|
||||||
// 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 {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
maven(MavenPublication) {
|
maven(MavenPublication) {
|
||||||
artifactId "corda-CSDE-java-sample"
|
artifactId "corda-CSDE-java-sample"
|
||||||
groupId project.group
|
groupId project.group
|
||||||
artifact jar
|
artifact jar
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,9 +6,6 @@ plugins {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
|
||||||
url = "$artifactoryContextUrl/"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
jacksonVersion = 2.13.3
|
jacksonVersion = 2.13.4
|
||||||
unirestVersion=3.13.10
|
unirestVersion=3.13.10
|
||||||
cordaApiVersion=5.0.0.505-Beta1.0-HC00
|
|
||||||
|
|
||||||
# R3 internal repository
|
cordaApiVersion=5.0.0.523-Fox1.0
|
||||||
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00
|
|
||||||
|
@ -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 {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
@ -18,21 +38,37 @@ configurations {
|
|||||||
canBeResolved = true
|
canBeResolved = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notaryServerCPB {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependencies for supporting tools
|
// Dependencies for supporting tools
|
||||||
dependencies {
|
dependencies {
|
||||||
combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion"
|
combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion"
|
||||||
myPostgresJDBC 'org.postgresql:postgresql:42.4.1'
|
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"
|
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.getenv("CSDE_CORDA_BIN") ?: System.getProperty('user.home') + "/.corda/corda5"
|
||||||
def cordaBinDir= System.getProperty('user.home') + "/.corda/corda5"
|
def cordaCliBinDir = System.getenv("CSDE_CORDA_CLI") ?:System.getProperty('user.home') + "/.corda/cli"
|
||||||
def cordaCliBinDir = System.getProperty('user.home') + "/.corda/cli"
|
|
||||||
def cordaJDBCDir = cordaBinDir + "/jdbcDrivers"
|
def cordaJDBCDir = cordaBinDir + "/jdbcDrivers"
|
||||||
|
def cordaNotaryServerDir = cordaBinDir + "/notaryserver"
|
||||||
def signingCertAlias="gradle-plugin-default-key"
|
def signingCertAlias="gradle-plugin-default-key"
|
||||||
// Get error if this is not a autotyped object
|
// Get error if this is not a autotyped object
|
||||||
// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem"
|
// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem"
|
||||||
@ -41,25 +77,75 @@ def keystoreAlias = "my-signing-key"
|
|||||||
def keystoreFName = devEnvWorkspace + "/signingkeys.pfx"
|
def keystoreFName = devEnvWorkspace + "/signingkeys.pfx"
|
||||||
def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem"
|
def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem"
|
||||||
def combiWorkerPidCacheFile = devEnvWorkspace + "/CordaPID.dat"
|
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
|
// todo: Need to read things from cordapp plugin - the cordapp names will be changed by the user
|
||||||
def cpiName = 'cpi name'
|
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(),
|
cordaClusterURL.toString(),
|
||||||
cordaRpcUser,
|
cordaRpcUser,
|
||||||
cordaRpcPasswd,
|
cordaRpcPasswd,
|
||||||
devEnvWorkspace,
|
devEnvWorkspace,
|
||||||
|
// todo: why is this not obtained in the groovy def's abouve - its inconsistent.
|
||||||
new String("${System.getProperty("java.home")}/bin"),
|
new String("${System.getProperty("java.home")}/bin"),
|
||||||
dbContainerName,
|
dbContainerName,
|
||||||
cordaJDBCDir,
|
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") {
|
tasks.register("getPostgresJDBC") {
|
||||||
group = pluginImplGroupName
|
group = supportingGroup
|
||||||
doLast {
|
doLast {
|
||||||
copy {
|
copy {
|
||||||
from configurations.myPostgresJDBC
|
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) {
|
tasks.register("getDevCordaLite", Copy) {
|
||||||
group = pluginImplGroupName
|
group = supportingGroup
|
||||||
from configurations.combinedWorker
|
from configurations.combinedWorker
|
||||||
into cordaBinDir
|
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') {
|
tasks.register('listVNodes') {
|
||||||
group = pluginGroupName
|
group = queriesGroup
|
||||||
doLast {
|
doLast {
|
||||||
csdeHelper.listVNodes()
|
cordaStatusQueries.listVNodes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('listCPIs') {
|
tasks.register('listCPIs') {
|
||||||
group = pluginImplGroupName
|
group = queriesGroup
|
||||||
doLast {
|
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.
|
// Empty task, just acts as the Task user entry point task.
|
||||||
tasks.register('deployCordapp') {
|
tasks.register('quickDeployCordapp') {
|
||||||
group = pluginGroupName
|
group = cordappGroup
|
||||||
dependsOn("createAndRegVNodes")
|
dependsOn("5-createAndRegVNodes")
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register("startCorda") {
|
|
||||||
group = pluginGroupName
|
|
||||||
dependsOn('getDevCordaLite', 'getPostgresJDBC')
|
|
||||||
doLast {
|
|
||||||
mkdir devEnvWorkspace
|
|
||||||
csdeHelper.startCorda()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register("stopCorda") {
|
|
||||||
group = pluginGroupName
|
|
||||||
doLast {
|
|
||||||
csdeHelper.stopCorda()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
package com.r3.csde;
|
||||||
|
|
||||||
public class CsdeException extends Exception {
|
public class CsdeException extends Exception {
|
||||||
|
public CsdeException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
public CsdeException(String message){
|
public CsdeException(String message){
|
||||||
super(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" : [
|
"identities" : [
|
||||||
"CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
|
"CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
"CN=Bob, 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=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.
|
# 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.
|
# 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.523-Fox1.0
|
||||||
cordaApiVersion=5.0.0.505-Beta1.0-HC00
|
|
||||||
|
# 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
|
# 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.
|
# For the time being this just needs to be set to a dummy value.
|
||||||
platformVersion = 999
|
platformVersion = 999
|
||||||
@ -24,15 +31,9 @@ mockitoKotlinVersion=4.0.0
|
|||||||
mockitoVersion=4.6.1
|
mockitoVersion=4.6.1
|
||||||
hamcrestVersion=2.2
|
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
|
cordaClusterURL=https://localhost:8888
|
||||||
cordaRpcUser=admin
|
cordaRpcUser=admin
|
||||||
cordaRpcPasswd=admin
|
cordaRpcPasswd=admin
|
||||||
devEnvWorkspace=workspace
|
devEnvWorkspace=workspace
|
||||||
dbContainerName=CSDEpostgresql
|
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.
|
// Declare the repositories where plugins are stored.
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
maven {
|
mavenCentral()
|
||||||
url = "$artifactoryContextUrl/"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
|
// 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.
|
// 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'
|
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.annotations.CordaSerializable;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
|
||||||
// A class which will contain a message, It must be marked with @CordaSerializable for Corda
|
// Where a class contains a message, mark it with @CordaSerializable to enable Corda to
|
||||||
// to be able to send from one virtual node to another.
|
// send it from one virtual node to another.
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
public class Message {
|
public class Message {
|
||||||
// public Message() {}
|
|
||||||
public Message(MemberX500Name sender, String message) {
|
public Message(MemberX500Name sender, String message) {
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemberX500Name getSender() {
|
public MemberX500Name getSender() {
|
||||||
return sender;
|
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.flows.*;
|
||||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
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)
|
// 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.
|
// 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
|
// MyFirstFlow should inherit from RPCStartableFlow, which tells Corda it can be started via an RPC call
|
||||||
public class MyFirstFlow implements RPCStartableFlow {
|
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);
|
private final Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
|
||||||
|
|
||||||
// Corda has a set of injectable services which are injected into the flow at runtime.
|
// 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.
|
// 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
|
@CordaInject
|
||||||
public JsonMarshallingService jsonMarshallingService;
|
public JsonMarshallingService jsonMarshallingService;
|
||||||
|
|
||||||
// FlowMessaging provides a service for establishing flow sessions between Virtual Nodes and
|
// FlowMessaging provides a service that establishes flow sessions between virtual nodes
|
||||||
// sending and receiving payloads between them
|
// that send and receive payloads between them.
|
||||||
@CordaInject
|
@CordaInject
|
||||||
public FlowMessaging flowMessaging;
|
public FlowMessaging flowMessaging;
|
||||||
|
|
||||||
// MemberLookup provides a service for looking up information about members of the Virtual Network which
|
// MemberLookup provides a service for looking up information about members of the virtual network which
|
||||||
// this CorDapp is operating in.
|
// this CorDapp operates in.
|
||||||
@CordaInject
|
@CordaInject
|
||||||
public MemberLookup memberLookup;
|
public MemberLookup memberLookup;
|
||||||
|
|
||||||
public MyFirstFlow() {}
|
public MyFirstFlow() {}
|
||||||
|
|
||||||
// When a flow is invoked it's call() method is called.
|
// 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
|
// 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
|
// for a response from the other flows and services.
|
||||||
@NotNull
|
@NotNull
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@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");
|
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());
|
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);
|
MyFirstFlowStartArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, MyFirstFlowStartArgs.class);
|
||||||
|
|
||||||
// Obtain the MemberX500Name of counterparty
|
// Obtain the MemberX500Name of the counterparty.
|
||||||
MemberX500Name otherMember = flowArgs.otherMember;
|
MemberX500Name otherMember = flowArgs.otherMember;
|
||||||
|
|
||||||
// Get our identity from the MemberLookup service.
|
// Get our identity from the MemberLookup service.
|
||||||
@ -69,19 +69,19 @@ public class MyFirstFlow implements RPCStartableFlow {
|
|||||||
// Log the message to be sent.
|
// Log the message to be sent.
|
||||||
log.info("MFF: message.message: " + message.message);
|
log.info("MFF: message.message: " + message.message);
|
||||||
|
|
||||||
// Start a flow session with the otherMember using the FlowMessaging service
|
// Start a flow session with the otherMember using the FlowMessaging service.
|
||||||
// The otherMember's Virtual Node will run the corresponding MyFirstFlowResponder responder flow
|
// The otherMember's virtual node will run the corresponding MyFirstFlowResponder responder flow.
|
||||||
FlowSession session = flowMessaging.initiateFlow(otherMember);
|
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);
|
session.send(message);
|
||||||
|
|
||||||
// Receive a response from the Responder flow
|
// Receive a response from the responder flow.
|
||||||
Message response = session.receive(Message.class);
|
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
|
// 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;
|
return response.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,9 +91,9 @@ public class MyFirstFlow implements RPCStartableFlow {
|
|||||||
RequestBody for triggering the flow via http-rpc:
|
RequestBody for triggering the flow via http-rpc:
|
||||||
{
|
{
|
||||||
"clientRequestId": "r1",
|
"clientRequestId": "r1",
|
||||||
"flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow",
|
"flowClassName": "com.r3.developers.csdetemplate.workflows.MyFirstFlow",
|
||||||
"requestData": {
|
"requestData": {
|
||||||
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
|
"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.CordaInject;
|
||||||
import net.corda.v5.application.flows.InitiatedBy;
|
import net.corda.v5.application.flows.InitiatedBy;
|
||||||
@ -11,36 +11,36 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
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.
|
// 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
|
// Responder flows must inherit from ResponderFlow
|
||||||
public class MyFirstFlowResponder implements 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);
|
private final Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class);
|
||||||
|
|
||||||
// MemberLookup provides a service for looking up information about members of the Virtual Network which
|
// MemberLookup looks for information about members of the virtual network which
|
||||||
// this CorDapp is operating in.
|
// this CorDapp operates in.
|
||||||
@CordaInject
|
@CordaInject
|
||||||
public MemberLookup memberLookup;
|
public MemberLookup memberLookup;
|
||||||
|
|
||||||
public MyFirstFlowResponder() {}
|
public MyFirstFlowResponder() {}
|
||||||
|
|
||||||
// Responder flows are invoked when an initiating flow makes a call via a session set up with the Virtual
|
// 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.
|
// 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
|
// 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/
|
// 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
|
// 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.
|
// responder flow code, you don't need to inject the FlowMessaging service.
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@Override
|
||||||
public void call(FlowSession session) {
|
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");
|
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);
|
Message receivedMessage = session.receive(Message.class);
|
||||||
|
|
||||||
// Log the message as a proxy for performing some useful operation on it.
|
// 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.
|
// Get our identity from the MemberLookup service.
|
||||||
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
||||||
|
|
||||||
// Create a response to greet the sender
|
// Create a message to greet the sender.
|
||||||
Message response = new Message(ourIdentity,
|
Message response = new Message(ourIdentity,
|
||||||
"Hello " + session.getCounterparty().getCommonName() + ", best wishes from " + ourIdentity.getCommonName());
|
"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.RequestData;
|
||||||
import net.corda.simulator.SimulatedVirtualNode;
|
import net.corda.simulator.SimulatedVirtualNode;
|
||||||
import net.corda.simulator.Simulator;
|
import net.corda.simulator.Simulator;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
|
||||||
class MyFirstFlowTest {
|
class MyFirstFlowTest {
|
||||||
|
|
||||||
// Names picked to match the corda network in config/dev-net.json
|
// 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 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");
|
private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void test_that_MyFirstFLow_returns_correct_message() {
|
public void test_that_MyFirstFLow_returns_correct_message() {
|
||||||
|
// Instantiate an instance of the simulator.
|
||||||
// Instantiate an instance of the Simulator
|
|
||||||
Simulator simulator = new Simulator();
|
Simulator simulator = new Simulator();
|
||||||
|
|
||||||
// Create Alice's and Bob HoldingIDs
|
// Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node.
|
||||||
HoldingIdentity aliceHoldingID = HoldingIdentity.Companion.create(aliceX500);
|
// Don't assign Bob's virtual node to a value. You don't need it for this particular test.
|
||||||
HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500);
|
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.
|
// Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow.
|
||||||
// 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
|
|
||||||
MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
|
MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
|
||||||
|
|
||||||
// Create a requestData object
|
// Create a requestData object.
|
||||||
RequestData requestData = RequestData.Companion.create(
|
RequestData requestData = RequestData.Companion.create(
|
||||||
"request no 1", // A unique reference for the instance of the flow request
|
"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
|
MyFirstFlow.class, // The name of the flow class which is to be started.
|
||||||
myFirstFlowStartArgs // The object which contains the start arguments of the flow
|
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);
|
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"));
|
assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user