Razvan/merge 4.3 into 4.4 (#5494)

* NOTICK: Corda 4.3-RC01

Created first release candidate of Corda 4.3 - RC01.

* CORDA-3141: Add GracefulReconnect callbacks which allow logic to be performed when RPC disconnects unexpectedly (#5430)

Also removed potential for growing stack trace on reconnects.

* CORDA-2050 Upgrade Corda to Java 11 (compatibility mode) (#5356)

Upgrade Corda to run with Java 11 (compatibility mode) - see https://github.com/corda/corda/pull/5356

* ENT-4198 Adding legal text

Signed-off-by: Ed Prosser <edward.prosser@r3.com>

* TM-29 new baseline for 4.3 since new debt has been added with the last few commits (#5487)

* TM-23 compileAll task to compile all code (#5490)

* Add simple compileAll task to be used by warning check

* lazy configure compileAll

* TM-32 Merge OS 4.3 into 4.4

* TM-32 fixed detekt issue

* Downgrade Dokka back to 0.9.17 due to failing docs_builder.

* add ability to group test types together (#5459)

* add ability to group test types together

* add ability to specify podCount for use in parallel testing

* remove compiler xml

* add Jenkinsfile to enable scanning

* trigger build

* add ability to specify what docker tag to use from outside of the build

* fix docker work dir

* fix pipeline syntax issues

* use environment rather than `def`

* move agent restrictor outside of stages block

* use steps block

* more pipeline syntax fixes

* even more pipeline syntax fixes

* even more pipeline syntax fixes

* add kubenetize as property to image build

* move clear of docker image to end of build rather than start to prevent colocated builds

* escape dollar on docker image remove command

* attempt to kill all existing jobs

* fix compile issue due to killall_jobs

* fix compile issue due to killall_jobs pt2

* fix spelling

* make all variables environment variables

* add logic to delete images locally after pushing

* wrap testing phase with try / finally so that junit reports are always evaluated

* change the behaviour around post build actions

* break implicit link between testing phase and image building phase, allowing testing to occur without a rebuild and push of image

* prepend registry name to provided tag

* allow tasks to specify whether they wish to stream output from containers

* add timestamps directive to Jenkinsfile to have timing info on output

* make KubesTest resilient against transient pod failures in k8s

* increase CPU request

* add logic to allow specifying container resource requests

* attempt to run unit and integration tests in parallel

* change unit tests to use 3 cores to allow co-location on 8c machines

* join grouped tests together to give pod meaningful name

* add step to renew token with GKE

* change renew step to use pods instead of nodes

* fix bug where memory request is not correctly passed to pod

* disable unit tests for now

* [CORDA-2368] Added exception handling for missing files that displays appropriate messages rather than defaulting to file names. (#5472)

* NOTIK Minor adjustments to Detekt rules to reflect current working practises  (#5498)

* Minor adjustments to rules to reflect current working practises (including IntelliJ code style alignment)

* Adjust another rule in line with existing code style.

* rebaseline with changed detekt ruleset

* rebaseline with NodeStartup changes
This commit is contained in:
Schife 2019-09-20 15:10:15 +01:00 committed by Stefano Franz
parent 74e8b6e468
commit 28852ce47d
129 changed files with 5664 additions and 6214 deletions

View File

@ -27,6 +27,7 @@
</editorconfig> </editorconfig>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="RIGHT_MARGIN" value="140" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" /> <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" /> <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
@ -38,6 +39,188 @@
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" /> <option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
<option name="EXTENDS_LIST_WRAP" value="0" /> <option name="EXTENDS_LIST_WRAP" value="0" />
<option name="ASSIGNMENT_WRAP" value="0" /> <option name="ASSIGNMENT_WRAP" value="0" />
<option name="WRAP_ON_TYPING" value="0" />
<option name="arrangementSettings" />
<option name="forceArrangeMenuAvailable" value="false" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="RIGHT_MARGIN" value="140" />
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="true" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="true" />
<option name="LINE_COMMENT_ADD_SPACE" value="false" />
<option name="KEEP_LINE_BREAKS" value="true" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="true" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="true" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BETWEEN_PACKAGE_DECLARATION_AND_HEADER" value="2" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="BLANK_LINES_BEFORE_PACKAGE" value="0" />
<option name="BLANK_LINES_AFTER_PACKAGE" value="1" />
<option name="BLANK_LINES_BEFORE_IMPORTS" value="1" />
<option name="BLANK_LINES_AFTER_IMPORTS" value="1" />
<option name="BLANK_LINES_AROUND_CLASS" value="1" />
<option name="BLANK_LINES_AROUND_FIELD" value="0" />
<option name="BLANK_LINES_AROUND_METHOD" value="1" />
<option name="BLANK_LINES_BEFORE_METHOD_BODY" value="0" />
<option name="BLANK_LINES_AROUND_FIELD_IN_INTERFACE" value="0" />
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="1" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="0" />
<option name="BLANK_LINES_AFTER_ANONYMOUS_CLASS_HEADER" value="0" />
<option name="BLANK_LINES_BEFORE_CLASS_END" value="0" />
<option name="BRACE_STYLE" value="1" />
<option name="CLASS_BRACE_STYLE" value="1" />
<option name="METHOD_BRACE_STYLE" value="1" />
<option name="LAMBDA_BRACE_STYLE" value="1" />
<option name="USE_FLYING_GEESE_BRACES" value="false" />
<option name="DO_NOT_INDENT_TOP_LEVEL_CLASS_MEMBERS" value="false" />
<option name="ELSE_ON_NEW_LINE" value="false" />
<option name="WHILE_ON_NEW_LINE" value="false" />
<option name="CATCH_ON_NEW_LINE" value="false" />
<option name="FINALLY_ON_NEW_LINE" value="false" />
<option name="INDENT_CASE_FROM_SWITCH" value="true" />
<option name="CASE_STATEMENT_ON_NEW_LINE" value="true" />
<option name="INDENT_BREAK_FROM_CASE" value="true" />
<option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="false" />
<option name="ALIGN_MULTILINE_RESOURCES" value="true" />
<option name="ALIGN_MULTILINE_FOR" value="true" />
<option name="INDENT_WHEN_CASES" value="true" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="false" />
<option name="ALIGN_MULTILINE_ASSIGNMENT" value="false" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="false" />
<option name="ALIGN_MULTILINE_THROWS_LIST" value="false" />
<option name="ALIGN_THROWS_KEYWORD" value="false" />
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="false" />
<option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="false" />
<option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="false" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="false" />
<option name="ALIGN_GROUP_FIELD_DECLARATIONS" value="false" />
<option name="ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS" value="false" />
<option name="ALIGN_CONSECUTIVE_ASSIGNMENTS" value="false" />
<option name="ALIGN_SUBSEQUENT_SIMPLE_METHODS" value="false" />
<option name="SPACE_AROUND_ASSIGNMENT_OPERATORS" value="true" />
<option name="SPACE_AROUND_LOGICAL_OPERATORS" value="true" />
<option name="SPACE_AROUND_EQUALITY_OPERATORS" value="true" />
<option name="SPACE_AROUND_RELATIONAL_OPERATORS" value="true" />
<option name="SPACE_AROUND_BITWISE_OPERATORS" value="true" />
<option name="SPACE_AROUND_ADDITIVE_OPERATORS" value="true" />
<option name="SPACE_AROUND_MULTIPLICATIVE_OPERATORS" value="true" />
<option name="SPACE_AROUND_SHIFT_OPERATORS" value="true" />
<option name="SPACE_AROUND_UNARY_OPERATOR" value="false" />
<option name="SPACE_AROUND_LAMBDA_ARROW" value="true" />
<option name="SPACE_AROUND_METHOD_REF_DBL_COLON" value="false" />
<option name="SPACE_AFTER_COMMA" value="true" />
<option name="SPACE_AFTER_COMMA_IN_TYPE_ARGUMENTS" value="true" />
<option name="SPACE_BEFORE_COMMA" value="false" />
<option name="SPACE_AFTER_SEMICOLON" value="true" />
<option name="SPACE_BEFORE_SEMICOLON" value="false" />
<option name="SPACE_WITHIN_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_METHOD_CALL_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_EMPTY_METHOD_CALL_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_METHOD_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_EMPTY_METHOD_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_IF_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_WHILE_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_FOR_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_TRY_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_CATCH_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_SWITCH_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_SYNCHRONIZED_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_CAST_PARENTHESES" value="false" />
<option name="SPACE_WITHIN_BRACKETS" value="false" />
<option name="SPACE_WITHIN_BRACES" value="false" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="false" />
<option name="SPACE_WITHIN_EMPTY_ARRAY_INITIALIZER_BRACES" value="false" />
<option name="SPACE_AFTER_TYPE_CAST" value="true" />
<option name="SPACE_BEFORE_METHOD_CALL_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_METHOD_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_IF_PARENTHESES" value="true" />
<option name="SPACE_BEFORE_WHILE_PARENTHESES" value="true" />
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="true" />
<option name="SPACE_BEFORE_TRY_PARENTHESES" value="true" />
<option name="SPACE_BEFORE_CATCH_PARENTHESES" value="true" />
<option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="true" />
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="true" />
<option name="SPACE_BEFORE_CLASS_LBRACE" value="true" />
<option name="SPACE_BEFORE_METHOD_LBRACE" value="true" />
<option name="SPACE_BEFORE_IF_LBRACE" value="true" />
<option name="SPACE_BEFORE_ELSE_LBRACE" value="true" />
<option name="SPACE_BEFORE_WHILE_LBRACE" value="true" />
<option name="SPACE_BEFORE_FOR_LBRACE" value="true" />
<option name="SPACE_BEFORE_DO_LBRACE" value="true" />
<option name="SPACE_BEFORE_SWITCH_LBRACE" value="true" />
<option name="SPACE_BEFORE_TRY_LBRACE" value="true" />
<option name="SPACE_BEFORE_CATCH_LBRACE" value="true" />
<option name="SPACE_BEFORE_FINALLY_LBRACE" value="true" />
<option name="SPACE_BEFORE_SYNCHRONIZED_LBRACE" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="false" />
<option name="SPACE_BEFORE_ANNOTATION_ARRAY_INITIALIZER_LBRACE" value="false" />
<option name="SPACE_BEFORE_ELSE_KEYWORD" value="true" />
<option name="SPACE_BEFORE_WHILE_KEYWORD" value="true" />
<option name="SPACE_BEFORE_CATCH_KEYWORD" value="true" />
<option name="SPACE_BEFORE_FINALLY_KEYWORD" value="true" />
<option name="SPACE_BEFORE_QUEST" value="true" />
<option name="SPACE_AFTER_QUEST" value="true" />
<option name="SPACE_BEFORE_COLON" value="true" />
<option name="SPACE_AFTER_COLON" value="true" />
<option name="SPACE_BEFORE_TYPE_PARAMETER_LIST" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="0" />
<option name="PREFER_PARAMETERS_WRAP" value="false" />
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
<option name="METHOD_PARAMETERS_WRAP" value="0" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
<option name="RESOURCE_LIST_WRAP" value="0" />
<option name="RESOURCE_LIST_LPAREN_ON_NEXT_LINE" value="false" />
<option name="RESOURCE_LIST_RPAREN_ON_NEXT_LINE" value="false" />
<option name="EXTENDS_LIST_WRAP" value="0" />
<option name="THROWS_LIST_WRAP" value="0" />
<option name="EXTENDS_KEYWORD_WRAP" value="0" />
<option name="THROWS_KEYWORD_WRAP" value="0" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="WRAP_FIRST_METHOD_IN_CALL_CHAIN" value="false" />
<option name="PARENTHESES_EXPRESSION_LPAREN_WRAP" value="false" />
<option name="PARENTHESES_EXPRESSION_RPAREN_WRAP" value="false" />
<option name="BINARY_OPERATION_WRAP" value="0" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="false" />
<option name="TERNARY_OPERATION_WRAP" value="0" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="false" />
<option name="MODIFIER_LIST_WRAP" value="false" />
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="false" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="false" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="false" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="false" />
<option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="false" />
<option name="FOR_STATEMENT_WRAP" value="0" />
<option name="FOR_STATEMENT_LPAREN_ON_NEXT_LINE" value="false" />
<option name="FOR_STATEMENT_RPAREN_ON_NEXT_LINE" value="false" />
<option name="ARRAY_INITIALIZER_WRAP" value="0" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="false" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="false" />
<option name="ASSIGNMENT_WRAP" value="0" />
<option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="false" />
<option name="LABELED_STATEMENT_WRAP" value="2" />
<option name="WRAP_COMMENTS" value="false" />
<option name="ASSERT_STATEMENT_WRAP" value="0" />
<option name="ASSERT_STATEMENT_COLON_ON_NEXT_LINE" value="false" />
<option name="IF_BRACE_FORCE" value="0" />
<option name="DOWHILE_BRACE_FORCE" value="0" />
<option name="WHILE_BRACE_FORCE" value="0" />
<option name="FOR_BRACE_FORCE" value="0" />
<option name="WRAP_LONG_LINES" value="false" />
<option name="METHOD_ANNOTATION_WRAP" value="2" />
<option name="CLASS_ANNOTATION_WRAP" value="2" />
<option name="FIELD_ANNOTATION_WRAP" value="2" />
<option name="PARAMETER_ANNOTATION_WRAP" value="0" />
<option name="VARIABLE_ANNOTATION_WRAP" value="0" />
<option name="SPACE_BEFORE_ANOTATION_PARAMETER_LIST" value="false" />
<option name="SPACE_WITHIN_ANNOTATION_PARENTHESES" value="false" />
<option name="ENUM_CONSTANTS_WRAP" value="0" />
<option name="FORCE_REARRANGE_MODE" value="0" />
<option name="WRAP_ON_TYPING" value="0" />
</codeStyleSettings> </codeStyleSettings>
</code_scheme> </code_scheme>
</component> </component>

View File

@ -1,6 +1,5 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<state> <state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" /> <option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state> </state>
</component> </component>

52
Jenkinsfile vendored
View File

@ -2,6 +2,8 @@ killall_jobs()
pipeline { pipeline {
agent { label 'k8s' } agent { label 'k8s' }
options { timestamps() }
environment { environment {
DOCKER_TAG_TO_USE = "${UUID.randomUUID().toString().toLowerCase().subSequence(0, 12)}" DOCKER_TAG_TO_USE = "${UUID.randomUUID().toString().toLowerCase().subSequence(0, 12)}"
EXECUTOR_NUMBER = "${env.EXECUTOR_NUMBER}" EXECUTOR_NUMBER = "${env.EXECUTOR_NUMBER}"
@ -9,7 +11,7 @@ pipeline {
} }
stages { stages {
stage('Corda Pull Request Integration Tests - Generate Build Image') { stage('Corda Pull Request - Generate Build Image') {
steps { steps {
withCredentials([string(credentialsId: 'container_reg_passwd', variable: 'DOCKER_PUSH_PWD')]) { withCredentials([string(credentialsId: 'container_reg_passwd', variable: 'DOCKER_PUSH_PWD')]) {
sh "./gradlew " + sh "./gradlew " +
@ -19,26 +21,42 @@ pipeline {
"-Ddocker.provided.tag=\"\${DOCKER_TAG_TO_USE}\"" + "-Ddocker.provided.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" clean pushBuildImage" " clean pushBuildImage"
} }
} sh "kubectl auth can-i get pods"
}
stage('Corda Pull Request Integration Tests - Run Integration Tests') {
steps {
withCredentials([string(credentialsId: 'container_reg_passwd', variable: 'DOCKER_PUSH_PWD')]) {
sh "./gradlew " +
"-DbuildId=\"\${BUILD_ID}\" " +
"-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " +
"-Dkubenetize=true " +
"-Ddocker.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" allParallelIntegrationTest"
}
junit '**/build/test-results-xml/**/*.xml'
} }
} }
stage('Clear testing images') { stage('Corda Pull Request - Run Tests') {
steps { parallel {
sh """docker rmi -f \$(docker images | grep \${DOCKER_TAG_TO_USE} | awk '{print \$3}') || echo \"there were no images to delete\"""" stage('Integration Tests') {
steps {
sh "./gradlew " +
"-DbuildId=\"\${BUILD_ID}\" " +
"-Dkubenetize=true " +
"-Ddocker.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" allParallelIntegrationTest"
}
post {
always {
junit '**/build/test-results-xml/**/*.xml'
}
}
}
// stage('Unit Tests') {
// steps {
// sh "./gradlew " +
// "-DbuildId=\"\${BUILD_ID}\" " +
// "-Dkubenetize=true " +
// "-Ddocker.tag=\"\${DOCKER_TAG_TO_USE}\"" +
// " allParallelUnitTest"
// }
// post {
// always {
// junit '**/build/test-results-xml/**/*.xml'
// }
// }
// }
} }
} }
} }
} }

View File

@ -1,4 +1,8 @@
import net.corda.testing.DistributedTesting import net.corda.testing.DistributedTesting
import net.corda.testing.ParallelTestGroup
import static org.gradle.api.JavaVersion.VERSION_1_8
import static org.gradle.api.JavaVersion.VERSION_11
buildscript { buildscript {
// For sharing constants between builds // For sharing constants between builds
@ -17,7 +21,15 @@ buildscript {
ext.warnings_as_errors = project.hasProperty("compilation.warningsAsErrors") ? project.property("compilation.warningsAsErrors").toBoolean() : false ext.warnings_as_errors = project.hasProperty("compilation.warningsAsErrors") ? project.property("compilation.warningsAsErrors").toBoolean() : false
ext.quasar_group = 'co.paralleluniverse' ext.quasar_group = 'co.paralleluniverse'
ext.quasar_version = constants.getProperty("quasarVersion") // Set version of Quasar according to version of Java used:
if (JavaVersion.current() == JavaVersion.VERSION_11) {
ext.quasar_version = constants.getProperty("quasarVersion11")
ext.quasar_classifier = constants.getProperty("quasarClassifier11")
}
else {
ext.quasar_version = constants.getProperty("quasarVersion")
ext.quasar_classifier = constants.getProperty("quasarClassifier")
}
ext.quasar_exclusions = [ ext.quasar_exclusions = [
'co.paralleluniverse**', 'co.paralleluniverse**',
'groovy**', 'groovy**',
@ -96,7 +108,6 @@ buildscript {
ext.docker_compose_rule_version = '0.35.0' ext.docker_compose_rule_version = '0.35.0'
ext.selenium_version = '3.141.59' ext.selenium_version = '3.141.59'
ext.ghostdriver_version = '2.1.0' ext.ghostdriver_version = '2.1.0'
ext.eaagentloader_version = '1.0.3'
ext.proguard_version = constants.getProperty('proguardVersion') ext.proguard_version = constants.getProperty('proguardVersion')
ext.jsch_version = '0.1.55' ext.jsch_version = '0.1.55'
ext.protonj_version = '0.33.0' // Overide Artemis version ext.protonj_version = '0.33.0' // Overide Artemis version
@ -106,8 +117,8 @@ buildscript {
ext.picocli_version = '3.9.6' ext.picocli_version = '3.9.6'
ext.commons_io_version = '2.6' ext.commons_io_version = '2.6'
ext.controlsfx_version = '8.40.15' ext.controlsfx_version = '8.40.15'
ext.fontawesomefx_commons_version = '8.15' ext.fontawesomefx_commons_version = '11.0'
ext.fontawesomefx_fontawesome_version = '4.7.0-5' ext.fontawesomefx_fontawesome_version = '4.7.0-11'
// Name of the IntelliJ SDK created for the deterministic Java rt.jar. // Name of the IntelliJ SDK created for the deterministic Java rt.jar.
// ext.deterministic_idea_sdk = '1.8 (Deterministic)' // ext.deterministic_idea_sdk = '1.8 (Deterministic)'
@ -179,8 +190,17 @@ apply plugin: 'com.jfrog.artifactory'
// with the run configurations. It also doesn't realise that the project is a Java 8 project and misconfigures // with the run configurations. It also doesn't realise that the project is a Java 8 project and misconfigures
// the resulting import. This fixes it. // the resulting import. This fixes it.
apply plugin: 'java' apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8 println "Java version: " + JavaVersion.current()
sourceCompatibility = VERSION_1_8
if (JavaVersion.current() == JavaVersion.VERSION_1_8)
targetCompatibility = VERSION_1_8
else
targetCompatibility = VERSION_11
println "Java source compatibility: " + sourceCompatibility
println "Java target compatibility: " + targetCompatibility
println "Quasar version: " + quasar_version
println "Quasar classifier: " + quasar_classifier
allprojects { allprojects {
apply plugin: 'kotlin' apply plugin: 'kotlin'
@ -210,8 +230,16 @@ allprojects {
nugetconfEnabled = false nugetconfEnabled = false
} }
} }
sourceCompatibility = 1.8 sourceCompatibility = VERSION_1_8
targetCompatibility = 1.8 if (JavaVersion.current() == JavaVersion.VERSION_1_8)
targetCompatibility = VERSION_1_8
else
targetCompatibility = VERSION_11
jacoco {
// JDK11 official support (https://github.com/jacoco/jacoco/releases/tag/v0.8.3)
toolVersion = "0.8.3"
}
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Xlint:-options" << "-parameters" options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Xlint:-options" << "-parameters"
@ -235,6 +263,10 @@ allprojects {
} }
} }
tasks.register('compileAll') { task ->
task.dependsOn tasks.withType(AbstractCompile)
}
tasks.withType(Jar) { task -> tasks.withType(Jar) { task ->
// Includes War and Ear // Includes War and Ear
manifest { manifest {
@ -248,6 +280,7 @@ allprojects {
tasks.withType(Test) { tasks.withType(Test) {
forkEvery = 10 forkEvery = 10
ignoreFailures = project.hasProperty('tests.ignoreFailures') ? project.property('tests.ignoreFailures').toBoolean() : false
failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast').toBoolean() : false failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast').toBoolean() : false
// Prevent the project from creating temporary files outside of the build directory. // Prevent the project from creating temporary files outside of the build directory.
@ -333,6 +366,10 @@ allprojects {
if (!JavaVersion.current().java8Compatible) if (!JavaVersion.current().java8Compatible)
throw new GradleException("Corda requires Java 8, please upgrade to at least 1.8.0_$java8_minUpdateVersion") throw new GradleException("Corda requires Java 8, please upgrade to at least 1.8.0_$java8_minUpdateVersion")
configurations {
detekt
}
// Required for building out the fat JAR. // Required for building out the fat JAR.
dependencies { dependencies {
compile project(':node') compile project(':node')
@ -352,6 +389,7 @@ dependencies {
runtime project(':finance:contracts') runtime project(':finance:contracts')
runtime project(':webserver') runtime project(':webserver')
testCompile project(':test-utils') testCompile project(':test-utils')
detekt 'io.gitlab.arturbosch.detekt:detekt-cli:1.0.1'
} }
jar { jar {
@ -380,6 +418,26 @@ task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
} }
} }
task detekt(type: JavaExec) {
main = "io.gitlab.arturbosch.detekt.cli.Main"
classpath = configurations.detekt
def input = "$projectDir"
def config = "$projectDir/detekt-config.yml"
def baseline = "$projectDir/detekt-baseline.xml"
def params = ['-i', input, '-c', config, '-b', baseline]
args(params)
}
task detektBaseline(type: JavaExec) {
main = "io.gitlab.arturbosch.detekt.cli.Main"
classpath = configurations.detekt
def input = "$projectDir"
def config = "$projectDir/detekt-config.yml"
def baseline = "$projectDir/detekt-baseline.xml"
def params = ['-i', input, '-c', config, '-b', baseline, '--create-baseline']
args(params)
}
tasks.withType(Test) { tasks.withType(Test) {
reports.html.destination = file("${reporting.baseDir}/${name}") reports.html.destination = file("${reporting.baseDir}/${name}")
} }
@ -521,32 +579,27 @@ buildScan {
termsOfServiceAgree = 'yes' termsOfServiceAgree = 'yes'
} }
task allParallelIntegrationTest(type: ParallelTestGroup) {
testGroups "integrationTest"
numberOfShards 15
streamOutput false
coresPerFork 6
memoryInGbPerFork 10
}
task allParallelUnitTest(type: ParallelTestGroup) {
testGroups "test"
numberOfShards 15
streamOutput false
coresPerFork 3
memoryInGbPerFork 6
}
task allParallelUnitAndIntegrationTest(type: ParallelTestGroup) {
testGroups "test", "integrationTest"
numberOfShards 20
streamOutput false
coresPerFork 6
memoryInGbPerFork 10
}
apply plugin: DistributedTesting apply plugin: DistributedTesting
configurations {
detekt
}
dependencies {
detekt 'io.gitlab.arturbosch.detekt:detekt-cli:1.0.1'
}
task detekt(type: JavaExec) {
main = "io.gitlab.arturbosch.detekt.cli.Main"
classpath = configurations.detekt
def input = "$projectDir"
def config = "$projectDir/detekt-config.yml"
def baseline = "$projectDir/detekt-baseline.xml"
def params = ['-i', input, '-c', config, '-b', baseline]
args(params)
}
task detektBaseline(type: JavaExec) {
main = "io.gitlab.arturbosch.detekt.cli.Main"
classpath = configurations.detekt
def input = "$projectDir"
def config = "$projectDir/detekt-config.yml"
def baseline = "$projectDir/detekt-baseline.xml"
def params = ['-i', input, '-c', config, '-b', baseline, '--create-baseline']
args(params)
}

View File

@ -35,6 +35,7 @@ dependencies {
compile gradleApi() compile gradleApi()
compile "io.fabric8:kubernetes-client:4.4.1" compile "io.fabric8:kubernetes-client:4.4.1"
compile 'org.apache.commons:commons-compress:1.19' compile 'org.apache.commons:commons-compress:1.19'
compile 'org.apache.commons:commons-lang3:3.9'
compile 'commons-codec:commons-codec:1.13' compile 'commons-codec:commons-codec:1.13'
compile "io.github.classgraph:classgraph:$class_graph_version" compile "io.github.classgraph:classgraph:$class_graph_version"
compile "com.bmuschko:gradle-docker-plugin:5.0.0" compile "com.bmuschko:gradle-docker-plugin:5.0.0"

View File

@ -1,10 +1,8 @@
package net.corda.testing package net.corda.testing
import com.bmuschko.gradle.docker.tasks.image.DockerPushImage import com.bmuschko.gradle.docker.tasks.image.DockerPushImage
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.testing.Test import org.gradle.api.tasks.testing.Test
/** /**
@ -22,6 +20,7 @@ class DistributedTesting implements Plugin<Project> {
ensureImagePluginIsApplied(project) ensureImagePluginIsApplied(project)
ImageBuilding imagePlugin = project.plugins.getPlugin(ImageBuilding) ImageBuilding imagePlugin = project.plugins.getPlugin(ImageBuilding)
DockerPushImage imageBuildingTask = imagePlugin.pushTask DockerPushImage imageBuildingTask = imagePlugin.pushTask
String providedTag = System.getProperty("docker.tag")
//in each subproject //in each subproject
//1. add the task to determine all tests within the module //1. add the task to determine all tests within the module
@ -31,7 +30,7 @@ class DistributedTesting implements Plugin<Project> {
subProject.tasks.withType(Test) { Test task -> subProject.tasks.withType(Test) { Test task ->
ListTests testListerTask = createTestListingTasks(task, subProject) ListTests testListerTask = createTestListingTasks(task, subProject)
Test modifiedTestTask = modifyTestTaskForParallelExecution(subProject, task, testListerTask) Test modifiedTestTask = modifyTestTaskForParallelExecution(subProject, task, testListerTask)
KubesTest parallelTestTask = generateParallelTestingTask(subProject, task, imageBuildingTask) KubesTest parallelTestTask = generateParallelTestingTask(subProject, task, imageBuildingTask, providedTag)
} }
} }
@ -45,55 +44,57 @@ class DistributedTesting implements Plugin<Project> {
//first step is to create a single task which will invoke all the submodule tasks for each grouping //first step is to create a single task which will invoke all the submodule tasks for each grouping
//ie allParallelTest will invoke [node:test, core:test, client:rpc:test ... etc] //ie allParallelTest will invoke [node:test, core:test, client:rpc:test ... etc]
//ie allIntegrationTest will invoke [node:integrationTest, core:integrationTest, client:rpc:integrationTest ... etc] //ie allIntegrationTest will invoke [node:integrationTest, core:integrationTest, client:rpc:integrationTest ... etc]
createGroupedParallelTestTasks(allKubesTestingTasksGroupedByType, project, imageBuildingTask) Set<ParallelTestGroup> userGroups = new HashSet<>(project.tasks.withType(ParallelTestGroup))
Collection<ParallelTestGroup> userDefinedGroups = userGroups.forEach { testGrouping ->
List<KubesTest> groups = ((ParallelTestGroup) testGrouping).groups.collect {
allKubesTestingTasksGroupedByType.get(it)
}.flatten()
String superListOfTasks = groups.collect { it.fullTaskToExecutePath }.join(" ")
def userDefinedParallelTask = project.rootProject.tasks.create("userDefined" + testGrouping.name.capitalize(), KubesTest) {
if (!providedTag) {
dependsOn imageBuildingTask
}
numberOfPods = testGrouping.getShardCount()
printOutput = testGrouping.printToStdOut
fullTaskToExecutePath = superListOfTasks
taskToExecuteName = testGrouping.groups.join("And")
memoryGbPerFork = testGrouping.gbOfMemory
numberOfCoresPerFork = testGrouping.coresToUse
doFirst {
dockerTag = dockerTag = providedTag ? ImageBuilding.registryName + ":" + providedTag : (imageBuildingTask.imageName.get() + ":" + imageBuildingTask.tag.get())
}
}
def reportOnAllTask = project.rootProject.tasks.create("userDefinedReports${testGrouping.name.capitalize()}", KubesReporting) {
dependsOn userDefinedParallelTask
destinationDir new File(project.rootProject.getBuildDir(), "userDefinedReports${testGrouping.name.capitalize()}")
doFirst {
destinationDir.deleteDir()
shouldPrintOutput = !testGrouping.printToStdOut
podResults = userDefinedParallelTask.containerResults
reportOn(userDefinedParallelTask.testOutput)
}
}
userDefinedParallelTask.finalizedBy(reportOnAllTask)
testGrouping.dependsOn(userDefinedParallelTask)
}
} }
} }
private List<Task> createGroupedParallelTestTasks(Map<String, List<KubesTest>> allKubesTestingTasksGroupedByType, Project project, DockerPushImage imageBuildingTask) { private KubesTest generateParallelTestingTask(Project projectContainingTask, Test task, DockerPushImage imageBuildingTask, String providedTag) {
allKubesTestingTasksGroupedByType.entrySet().collect { entry ->
def taskType = entry.key
def allTasksOfType = entry.value
def allParallelTask = project.rootProject.tasks.create("allParallel" + taskType.capitalize(), KubesTest) {
dependsOn imageBuildingTask
printOutput = true
fullTaskToExecutePath = allTasksOfType.collect { task -> task.fullTaskToExecutePath }.join(" ")
taskToExecuteName = taskType
doFirst {
dockerTag = imageBuildingTask.imageName.get() + ":" + imageBuildingTask.tag.get()
}
}
//second step is to create a task to use the reports output by the parallel test task
def reportOnAllTask = project.rootProject.tasks.create("reportAllParallel${taskType.capitalize()}", KubesReporting) {
dependsOn allParallelTask
destinationDir new File(project.rootProject.getBuildDir(), "allResults${taskType.capitalize()}")
doFirst {
destinationDir.deleteDir()
podResults = allParallelTask.containerResults
reportOn(allParallelTask.testOutput)
}
}
//invoke this report task after parallel testing
allParallelTask.finalizedBy(reportOnAllTask)
project.logger.info "Created task: ${allParallelTask.getPath()} to enable testing on kubenetes for tasks: ${allParallelTask.fullTaskToExecutePath}"
project.logger.info "Created task: ${reportOnAllTask.getPath()} to generate test html output for task ${allParallelTask.getPath()}"
return allParallelTask
}
}
private KubesTest generateParallelTestingTask(Project projectContainingTask, Test task, DockerPushImage imageBuildingTask) {
def taskName = task.getName() def taskName = task.getName()
def capitalizedTaskName = task.getName().capitalize() def capitalizedTaskName = task.getName().capitalize()
KubesTest createdParallelTestTask = projectContainingTask.tasks.create("parallel" + capitalizedTaskName, KubesTest) { KubesTest createdParallelTestTask = projectContainingTask.tasks.create("parallel" + capitalizedTaskName, KubesTest) {
dependsOn imageBuildingTask if (!providedTag) {
dependsOn imageBuildingTask
}
printOutput = true printOutput = true
fullTaskToExecutePath = task.getPath() fullTaskToExecutePath = task.getPath()
taskToExecuteName = taskName taskToExecuteName = taskName
doFirst { doFirst {
dockerTag = imageBuildingTask.imageName.get() + ":" + imageBuildingTask.tag.get() dockerTag = providedTag ? ImageBuilding.registryName + ":" + providedTag : (imageBuildingTask.imageName.get() + ":" + imageBuildingTask.tag.get())
} }
} }
projectContainingTask.logger.info "Created task: ${createdParallelTestTask.getPath()} to enable testing on kubenetes for task: ${task.getPath()}" projectContainingTask.logger.info "Created task: ${createdParallelTestTask.getPath()} to enable testing on kubenetes for task: ${task.getPath()}"

View File

@ -1,10 +1,7 @@
package net.corda.testing package net.corda.testing
import com.bmuschko.gradle.docker.DockerRegistryCredentials import com.bmuschko.gradle.docker.DockerRegistryCredentials
import com.bmuschko.gradle.docker.tasks.container.DockerCreateContainer import com.bmuschko.gradle.docker.tasks.container.*
import com.bmuschko.gradle.docker.tasks.container.DockerLogsContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStartContainer
import com.bmuschko.gradle.docker.tasks.container.DockerWaitContainer
import com.bmuschko.gradle.docker.tasks.image.* import com.bmuschko.gradle.docker.tasks.image.*
import org.gradle.api.GradleException import org.gradle.api.GradleException
import org.gradle.api.Plugin import org.gradle.api.Plugin
@ -16,6 +13,7 @@ import org.gradle.api.Project
*/ */
class ImageBuilding implements Plugin<Project> { class ImageBuilding implements Plugin<Project> {
public static final String registryName = "stefanotestingcr.azurecr.io/testing"
DockerPushImage pushTask DockerPushImage pushTask
@Override @Override
@ -25,7 +23,7 @@ class ImageBuilding implements Plugin<Project> {
registryCredentialsForPush.username.set("stefanotestingcr") registryCredentialsForPush.username.set("stefanotestingcr")
registryCredentialsForPush.password.set(System.getProperty("docker.push.password") ? System.getProperty("docker.push.password") : "") registryCredentialsForPush.password.set(System.getProperty("docker.push.password") ? System.getProperty("docker.push.password") : "")
DockerPullImage pullTask = project.tasks.create("pullBaseImage", DockerPullImage){ DockerPullImage pullTask = project.tasks.create("pullBaseImage", DockerPullImage) {
repository = "stefanotestingcr.azurecr.io/buildbase" repository = "stefanotestingcr.azurecr.io/buildbase"
tag = "latest" tag = "latest"
doFirst { doFirst {
@ -83,33 +81,41 @@ class ImageBuilding implements Plugin<Project> {
targetContainerId createBuildContainer.getContainerId() targetContainerId createBuildContainer.getContainerId()
} }
DockerTagImage tagBuildImageResult = project.tasks.create('tagBuildImageResult', DockerTagImage) { DockerTagImage tagBuildImageResult = project.tasks.create('tagBuildImageResult', DockerTagImage) {
dependsOn commitBuildImageResult dependsOn commitBuildImageResult
imageId = commitBuildImageResult.getImageId() imageId = commitBuildImageResult.getImageId()
tag = System.getProperty("docker.provided.tag") ? System.getProperty("docker.provided.tag") : "${UUID.randomUUID().toString().toLowerCase().subSequence(0, 12)}" tag = System.getProperty("docker.provided.tag") ? System.getProperty("docker.provided.tag") : "${UUID.randomUUID().toString().toLowerCase().subSequence(0, 12)}"
repository = "stefanotestingcr.azurecr.io/testing" repository = registryName
} }
if (System.getProperty("docker.tag")) { DockerPushImage pushBuildImage = project.tasks.create('pushBuildImage', DockerPushImage) {
DockerPushImage pushBuildImage = project.tasks.create('pushBuildImage', DockerPushImage) { dependsOn tagBuildImageResult
doFirst { doFirst {
registryCredentials = registryCredentialsForPush registryCredentials = registryCredentialsForPush
}
imageName = "stefanotestingcr.azurecr.io/testing"
tag = System.getProperty("docker.tag")
} }
this.pushTask = pushBuildImage imageName = registryName
} else { tag = tagBuildImageResult.tag
DockerPushImage pushBuildImage = project.tasks.create('pushBuildImage', DockerPushImage) {
dependsOn tagBuildImageResult
doFirst {
registryCredentials = registryCredentialsForPush
}
imageName = "stefanotestingcr.azurecr.io/testing"
tag = tagBuildImageResult.tag
}
this.pushTask = pushBuildImage
} }
this.pushTask = pushBuildImage
DockerRemoveContainer deleteContainer = project.tasks.create('deleteBuildContainer', DockerRemoveContainer) {
dependsOn pushBuildImage
targetContainerId createBuildContainer.getContainerId()
}
DockerRemoveImage deleteTaggedImage = project.tasks.create('deleteTaggedImage', DockerRemoveImage) {
dependsOn pushBuildImage
force = true
targetImageId commitBuildImageResult.getImageId()
}
DockerRemoveImage deleteBuildImage = project.tasks.create('deleteBuildImage', DockerRemoveImage) {
dependsOn deleteContainer, deleteTaggedImage
force = true
targetImageId buildDockerImageForSource.getImageId()
}
if (System.getProperty("docker.keep.image") == null) {
pushBuildImage.finalizedBy(deleteContainer, deleteBuildImage, deleteTaggedImage)
}
} }
} }

View File

@ -16,7 +16,6 @@ import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import java.util.stream.Collectors import java.util.stream.Collectors
import java.util.stream.IntStream import java.util.stream.IntStream
@ -29,6 +28,8 @@ class KubesTest extends DefaultTask {
String fullTaskToExecutePath String fullTaskToExecutePath
String taskToExecuteName String taskToExecuteName
Boolean printOutput = false Boolean printOutput = false
Integer numberOfCoresPerFork = 4
Integer memoryGbPerFork = 6
public volatile List<File> testOutput = Collections.emptyList() public volatile List<File> testOutput = Collections.emptyList()
public volatile List<KubePodResult> containerResults = Collections.emptyList() public volatile List<KubePodResult> containerResults = Collections.emptyList()
@ -38,7 +39,6 @@ class KubesTest extends DefaultTask {
int numberOfPods = 20 int numberOfPods = 20
int timeoutInMinutesForPodToStart = 60 int timeoutInMinutesForPodToStart = 60
@TaskAction @TaskAction
void runTestsOnKubes() { void runTestsOnKubes() {
@ -53,7 +53,7 @@ class KubesTest extends DefaultTask {
def currentUser = System.getProperty("user.name") ? System.getProperty("user.name") : "UNKNOWN_USER" def currentUser = System.getProperty("user.name") ? System.getProperty("user.name") : "UNKNOWN_USER"
String stableRunId = new BigInteger(64, new Random(buildId.hashCode() + currentUser.hashCode())).toString(36).toLowerCase() String stableRunId = new BigInteger(64, new Random(buildId.hashCode() + currentUser.hashCode() + taskToExecuteName.hashCode())).toString(36).toLowerCase()
String suffix = new BigInteger(64, new Random()).toString(36).toLowerCase() String suffix = new BigInteger(64, new Random()).toString(36).toLowerCase()
io.fabric8.kubernetes.client.Config config = new io.fabric8.kubernetes.client.ConfigBuilder() io.fabric8.kubernetes.client.Config config = new io.fabric8.kubernetes.client.ConfigBuilder()
@ -77,164 +77,179 @@ class KubesTest extends DefaultTask {
//it's possible that a pod is being deleted by the original build, this can lead to racey conditions //it's possible that a pod is being deleted by the original build, this can lead to racey conditions
} }
List<CompletableFuture<KubePodResult>> futures = IntStream.range(0, numberOfPods).mapToObj({ i ->
List<CompletableFuture<KubePodResult>> podCreationFutures = IntStream.range(0, numberOfPods).mapToObj({ i -> String podName = (taskToExecuteName + "-" + stableRunId + suffix + i).toLowerCase()
CompletableFuture.supplyAsync({ runBuild(client, namespace, numberOfPods, i, podName, printOutput, 3)
File outputFile = Files.createTempFile("container", ".log").toFile()
String podName = (taskToExecuteName + "-" + stableRunId + suffix + i).toLowerCase()
Pod podRequest = buildPod(podName)
project.logger.lifecycle("created pod: " + podName)
Pod createdPod = client.pods().inNamespace(namespace).create(podRequest)
Runtime.getRuntime().addShutdownHook({
println "Deleting pod: " + podName
client.pods().delete(createdPod)
})
CompletableFuture<Void> waiter = new CompletableFuture<Void>()
KubePodResult result = new KubePodResult(createdPod, waiter, outputFile)
startBuildAndLogging(client, namespace, numberOfPods, i, podName, printOutput, waiter, { int resultCode ->
println podName + " has completed with resultCode=$resultCode"
result.setResultCode(resultCode)
}, outputFile)
return result
}, executorService)
}).collect(Collectors.toList()) }).collect(Collectors.toList())
this.testOutput = Collections.synchronizedList(futures.collect { it -> it.get().binaryResults }.flatten())
def binaryFileFutures = podCreationFutures.collect { creationFuture -> this.containerResults = futures.collect { it -> it.get() }
return creationFuture.thenComposeAsync({ podResult ->
return podResult.waiter.thenApply {
project.logger.lifecycle("Successfully terminated log streaming for " + podResult.createdPod.getMetadata().getName())
println "Gathering test results from ${podResult.createdPod.metadata.name}"
def binaryResults = downloadTestXmlFromPod(client, namespace, podResult.createdPod)
project.logger.lifecycle("deleting: " + podResult.createdPod.getMetadata().getName())
client.resource(podResult.createdPod).delete()
return binaryResults
}
}, singleThreadedExecutor)
}
def allFilesDownloadedFuture = CompletableFuture.allOf(*binaryFileFutures.toArray(new CompletableFuture[0])).thenApply {
def allBinaryFiles = binaryFileFutures.collect { future ->
Collection<File> binaryFiles = future.get()
return binaryFiles
}.flatten()
this.testOutput = Collections.synchronizedList(allBinaryFiles)
return allBinaryFiles
}
allFilesDownloadedFuture.get()
this.containerResults = podCreationFutures.collect { it -> it.get() }
} }
void startBuildAndLogging(KubernetesClient client, CompletableFuture<KubePodResult> runBuild(KubernetesClient client,
String namespace, String namespace,
int numberOfPods, int numberOfPods,
int podIdx, int podIdx,
String podName, String podName,
boolean printOutput, boolean printOutput,
CompletableFuture<Void> waiter, int numberOfRetries) {
Consumer<Integer> resultSetter,
File outputFileForContainer) {
try {
project.logger.lifecycle("Waiting for pod " + podName + " to start before executing build")
client.pods().inNamespace(namespace).withName(podName).waitUntilReady(timeoutInMinutesForPodToStart, TimeUnit.MINUTES)
project.logger.lifecycle("pod " + podName + " has started, executing build")
Watch eventWatch = client.pods().inNamespace(namespace).withName(podName).watch(new Watcher<Pod>() {
@Override
void eventReceived(Watcher.Action action, Pod resource) {
project.logger.lifecycle("[StatusChange] pod " + resource.getMetadata().getName() + " " + action.name())
}
@Override CompletableFuture<KubePodResult> toReturn = new CompletableFuture<KubePodResult>()
void onClose(KubernetesClientException cause) {
}
})
def stdOutOs = new PipedOutputStream() executorService.submit({
def stdOutIs = new PipedInputStream(4096) int tryCount = 0
ByteArrayOutputStream errChannelStream = new ByteArrayOutputStream(); Pod createdPod = null
while (tryCount < numberOfRetries) {
try {
Pod podRequest = buildPod(podName)
project.logger.lifecycle("requesting pod: " + podName)
createdPod = client.pods().inNamespace(namespace).create(podRequest)
project.logger.lifecycle("scheduled pod: " + podName)
File outputFile = Files.createTempFile("container", ".log").toFile()
attachStatusListenerToPod(client, namespace, podName)
schedulePodForDeleteOnShutdown(podName, client, createdPod)
waitForPodToStart(podName, client, namespace)
def stdOutOs = new PipedOutputStream()
def stdOutIs = new PipedInputStream(4096)
ByteArrayOutputStream errChannelStream = new ByteArrayOutputStream();
KubePodResult result = new KubePodResult(createdPod, null, outputFile)
CompletableFuture<KubePodResult> waiter = new CompletableFuture<>()
ExecListener execListener = buildExecListenerForPod(podName, errChannelStream, waiter, result)
stdOutIs.connect(stdOutOs)
ExecWatch execWatch = client.pods().inNamespace(namespace).withName(podName)
.writingOutput(stdOutOs)
.writingErrorChannel(errChannelStream)
.usingListener(execListener).exec(getBuildCommand(numberOfPods, podIdx))
def terminatingListener = new ExecListener() { startLogPumping(outputFile, stdOutIs, podIdx, printOutput)
KubePodResult execResult = waiter.join()
@Override project.logger.lifecycle("build has ended on on pod ${podName} (${podIdx}/${numberOfPods})")
void onOpen(Response response) { project.logger.lifecycle "Gathering test results from ${execResult.createdPod.metadata.name}"
project.logger.lifecycle("Build started on pod " + podName) def binaryResults = downloadTestXmlFromPod(client, namespace, execResult.createdPod)
} project.logger.lifecycle("deleting: " + execResult.createdPod.getMetadata().getName())
client.resource(execResult.createdPod).delete()
@Override result.binaryResults = binaryResults
void onFailure(Throwable t, Response response) { toReturn.complete(result)
project.logger.lifecycle("Received error from rom pod " + podName) break
waiter.completeExceptionally(t) } catch (Exception e) {
} logger.error("Encountered error during testing cycle on pod ${podName} (${podIdx}/${numberOfPods})", e)
@Override
void onClose(int code, String reason) {
project.logger.lifecycle("Received onClose() from pod " + podName + " with returnCode=" + code)
try { try {
def errChannelContents = errChannelStream.toString() if (createdPod) {
println errChannelContents client.pods().delete(createdPod)
Status status = Serialization.unmarshal(errChannelContents, Status.class); while (client.pods().inNamespace(namespace).list().getItems().find { p -> p.metadata.name == podName }) {
resultSetter.accept(status.details?.causes?.first()?.message?.toInteger() ? status.details?.causes?.first()?.message?.toInteger() : 0) logger.warn("pod ${podName} has not been deleted, waiting 1s")
waiter.complete() Thread.sleep(1000)
} catch (Exception e) { }
waiter.completeExceptionally(e) }
} catch (Exception ignored) {
} }
tryCount++
logger.lifecycle("will retry ${podName} another ${numberOfRetries - tryCount} times")
} }
} }
if (tryCount >= numberOfRetries) {
toReturn.completeExceptionally(new RuntimeException("Failed to build in pod ${podName} (${podIdx}/${numberOfPods}) within retry limit"))
}
})
return toReturn
}
stdOutIs.connect(stdOutOs) void startLogPumping(File outputFile, stdOutIs, podIdx, boolean printOutput) {
Thread loggingThread = new Thread({ ->
ExecWatch execWatch = client.pods().inNamespace(namespace).withName(podName) BufferedWriter out = null
.writingOutput(stdOutOs) BufferedReader br = null
.writingErrorChannel(errChannelStream) try {
.usingListener(terminatingListener).exec(getBuildCommand(numberOfPods, podIdx)) out = new BufferedWriter(new FileWriter(outputFile))
br = new BufferedReader(new InputStreamReader(stdOutIs))
project.logger.lifecycle("Pod: " + podName + " has started ") String line
while ((line = br.readLine()) != null) {
Thread loggingThread = new Thread({ -> def toWrite = ("${taskToExecuteName}/Container" + podIdx + ": " + line).trim()
BufferedWriter out = null if (printOutput) {
BufferedReader br = null project.logger.lifecycle(toWrite)
try {
out = new BufferedWriter(new FileWriter(outputFileForContainer))
br = new BufferedReader(new InputStreamReader(stdOutIs))
String line
while ((line = br.readLine()) != null) {
def toWrite = ("${taskToExecuteName}/Container" + podIdx + ": " + line).trim()
if (printOutput) {
project.logger.lifecycle(toWrite)
}
out.println(toWrite)
} }
} catch (IOException ignored) { out.println(toWrite)
} }
finally { } catch (IOException ignored) {
out?.close() }
br?.close() finally {
} out?.close()
}) br?.close()
}
})
loggingThread.setDaemon(true) loggingThread.setDaemon(true)
loggingThread.start() loggingThread.start()
} catch (InterruptedException ignored) { }
throw new GradleException("Could not get slot on cluster within timeout")
ExecListener buildExecListenerForPod(podName, errChannelStream, CompletableFuture<KubePodResult> waitingFuture, KubePodResult result) {
new ExecListener() {
@Override
void onOpen(Response response) {
project.logger.lifecycle("Build started on pod " + podName)
}
@Override
void onFailure(Throwable t, Response response) {
project.logger.lifecycle("Received error from rom pod " + podName)
waitingFuture.completeExceptionally(t)
}
@Override
void onClose(int code, String reason) {
project.logger.lifecycle("Received onClose() from pod " + podName + " with returnCode=" + code)
try {
def errChannelContents = errChannelStream.toString()
Status status = Serialization.unmarshal(errChannelContents, Status.class);
result.resultCode = status.details?.causes?.first()?.message?.toInteger() ? status.details?.causes?.first()?.message?.toInteger() : 0
waitingFuture.complete(result)
} catch (Exception e) {
waitingFuture.completeExceptionally(e)
}
}
} }
} }
void schedulePodForDeleteOnShutdown(String podName, client, Pod createdPod) {
project.logger.info("attaching shutdown hook for pod ${podName}")
Runtime.getRuntime().addShutdownHook({
println "Deleting pod: " + podName
client.pods().delete(createdPod)
})
}
Watch attachStatusListenerToPod(KubernetesClient client, String namespace, String podName) {
client.pods().inNamespace(namespace).withName(podName).watch(new Watcher<Pod>() {
@Override
void eventReceived(Watcher.Action action, Pod resource) {
project.logger.lifecycle("[StatusChange] pod ${resource.getMetadata().getName()} ${action.name()} (${resource.status.phase})")
}
@Override
void onClose(KubernetesClientException cause) {
}
})
}
void waitForPodToStart(String podName, KubernetesClient client, String namespace) {
project.logger.lifecycle("Waiting for pod " + podName + " to start before executing build")
client.pods().inNamespace(namespace).withName(podName).waitUntilReady(timeoutInMinutesForPodToStart, TimeUnit.MINUTES)
project.logger.lifecycle("pod " + podName + " has started, executing build")
}
Pod buildPod(String podName) { Pod buildPod(String podName) {
return new PodBuilder().withNewMetadata().withName(podName).endMetadata() return new PodBuilder().withNewMetadata().withName(podName).endMetadata()
.withNewSpec() .withNewSpec()
.addNewVolume() .addNewVolume()
.withName("gradlecache") .withName("gradlecache")
.withNewHostPath() .withNewHostPath()
.withPath("/gradle") .withPath("/tmp/gradle")
.withType("DirectoryOrCreate") .withType("DirectoryOrCreate")
.endHostPath() .endHostPath()
.endVolume() .endVolume()
.addNewContainer() .addNewContainer()
.withImage(dockerTag) .withImage(dockerTag)
.withCommand("bash") .withCommand("bash")
//max container life time is 30min .withArgs("-c", "sleep 3600")
.withArgs("-c", "sleep 1800")
.addNewEnv() .addNewEnv()
.withName("DRIVER_NODE_MEMORY") .withName("DRIVER_NODE_MEMORY")
.withValue("1024m") .withValue("1024m")
@ -243,8 +258,8 @@ class KubesTest extends DefaultTask {
.endEnv() .endEnv()
.withName(podName) .withName(podName)
.withNewResources() .withNewResources()
.addToRequests("cpu", new Quantity("2")) .addToRequests("cpu", new Quantity("${numberOfCoresPerFork}"))
.addToRequests("memory", new Quantity("6Gi")) .addToRequests("memory", new Quantity("${memoryGbPerFork}Gi"))
.endResources() .endResources()
.addNewVolumeMount() .addNewVolumeMount()
.withName("gradlecache") .withName("gradlecache")
@ -276,7 +291,7 @@ class KubesTest extends DefaultTask {
tempDir.toFile().mkdirs() tempDir.toFile().mkdirs()
} }
project.logger.lifecycle("saving to " + podName + " results to: " + tempDir.toAbsolutePath().toFile().getAbsolutePath()) project.logger.lifecycle("Saving " + podName + " results to: " + tempDir.toAbsolutePath().toFile().getAbsolutePath())
client.pods() client.pods()
.inNamespace(namespace) .inNamespace(namespace)
.withName(podName) .withName(podName)

View File

@ -0,0 +1,41 @@
package net.corda.testing
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class ParallelTestGroup extends DefaultTask {
List<String> groups = new ArrayList<>()
int shardCount = 20
int coresToUse = 4
int gbOfMemory = 4
boolean printToStdOut = true
void numberOfShards(int shards){
this.shardCount = shards
}
void coresPerFork(int cores){
this.coresToUse = cores
}
void memoryInGbPerFork(int gb){
this.gbOfMemory = gb
}
//when this is false, only containers will "failed" exit codes will be printed to stdout
void streamOutput(boolean print){
this.printToStdOut = print
}
void testGroups(String... group) {
testGroups(group.toList())
}
void testGroups(List<String> group) {
group.forEach {
groups.add(it)
}
}
}

View File

@ -3,6 +3,8 @@ package net.corda.testing;
import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Pod;
import java.io.File; import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class KubePodResult { public class KubePodResult {
@ -11,6 +13,7 @@ public class KubePodResult {
private final CompletableFuture<Void> waiter; private final CompletableFuture<Void> waiter;
private volatile Integer resultCode = 255; private volatile Integer resultCode = 255;
private final File output; private final File output;
private volatile Collection<File> binaryResults = Collections.emptyList();
KubePodResult(Pod createdPod, CompletableFuture<Void> waiter, File output) { KubePodResult(Pod createdPod, CompletableFuture<Void> waiter, File output) {
this.createdPod = createdPod; this.createdPod = createdPod;

View File

@ -16,6 +16,7 @@
package net.corda.testing; package net.corda.testing;
import org.apache.commons.compress.utils.IOUtils;
import org.gradle.api.DefaultTask; import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException; import org.gradle.api.GradleException;
import org.gradle.api.Transformer; import org.gradle.api.Transformer;
@ -33,6 +34,8 @@ import org.gradle.internal.operations.BuildOperationExecutor;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -49,6 +52,7 @@ public class KubesReporting extends DefaultTask {
private File destinationDir = new File(getProject().getBuildDir(), "test-reporting"); private File destinationDir = new File(getProject().getBuildDir(), "test-reporting");
private List<Object> results = new ArrayList<Object>(); private List<Object> results = new ArrayList<Object>();
List<KubePodResult> podResults = new ArrayList<>(); List<KubePodResult> podResults = new ArrayList<>();
boolean shouldPrintOutput = true;
public KubesReporting() { public KubesReporting() {
//force this task to always run, as it's responsible for parsing exit codes //force this task to always run, as it's responsible for parsing exit codes
@ -147,12 +151,17 @@ public class KubesReporting extends DefaultTask {
if (!containersWithNonZeroReturnCodes.isEmpty()) { if (!containersWithNonZeroReturnCodes.isEmpty()) {
String reportUrl = new ConsoleRenderer().asClickableFileUrl(new File(destinationDir, "index.html")); String reportUrl = new ConsoleRenderer().asClickableFileUrl(new File(destinationDir, "index.html"));
if (shouldPrintOutput){
String containerOutputs = containersWithNonZeroReturnCodes.stream().map(KubePodResult::getOutput).map(file -> new ConsoleRenderer().asClickableFileUrl(file)).reduce("", containersWithNonZeroReturnCodes.forEach(container -> {
(s, s2) -> s + "\n" + s2 try {
); System.out.println("\n##### CONTAINER OUTPUT START #####");
IOUtils.copy(new FileInputStream(container.getOutput()), System.out);
String message = "remote build failed, check test report at " + reportUrl + "\n and container outputs at " + containerOutputs; System.out.println("##### CONTAINER OUTPUT END #####\n");
} catch (IOException ignored) {
}
});
}
String message = "remote build failed, check test report at " + reportUrl;
throw new GradleException(message); throw new GradleException(message);
} }
} else { } else {

View File

@ -1,3 +1,17 @@
// JDK 11 JavaFX
plugins {
id 'org.openjfx.javafxplugin' version '0.0.7' apply false
}
if (JavaVersion.current().isJava9Compatible()) {
apply plugin: 'org.openjfx.javafxplugin'
javafx {
version = "11.0.2"
modules = ['javafx.controls',
'javafx.fxml'
]
}
}
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'

View File

@ -0,0 +1,19 @@
package net.corda.client.jfx.utils;
import javafx.collections.ObservableList;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
import org.jetbrains.annotations.NotNull;
// Java 9 introduces a new abstract method that we need to override (without using the explicit Kotlin `override` keyword to be backwards compatible
// https://docs.oracle.com/javase/9/docs/api/javafx/collections/transformation/TransformationList.html#getViewIndex-int-
public class AggregatedList<A,E,K> extends AbstractAggregatedList<A,E,K> {
@SuppressWarnings("unchecked")
public AggregatedList(@NotNull ObservableList<? extends E> list, @NotNull Function1<E,K> toKey, @NotNull Function2<K,ObservableList<E>,A> assemble) {
super(list, toKey, assemble);
}
public int getViewIndex(int i) {
return 0;
}
}

View File

@ -0,0 +1,16 @@
package net.corda.client.jfx.utils;
import javafx.collections.ObservableList;
import org.jetbrains.annotations.NotNull;
// Java 9 introduces a new abstract method that we need to override (without using the explicit Kotlin `override` keyword to be backwards compatible
// https://docs.oracle.com/javase/9/docs/api/javafx/collections/transformation/TransformationList.html#getViewIndex-int-
public class ConcatenatedList<A> extends AbstractConcatenatedList<A> {
public ConcatenatedList(@NotNull ObservableList<ObservableList<A>> sourceList) {
super(sourceList);
}
public int getViewIndex(int i) {
return 0;
}
}

View File

@ -0,0 +1,17 @@
package net.corda.client.jfx.utils;
import javafx.collections.ObservableList;
import org.jetbrains.annotations.NotNull;
// Java 9 introduces a new abstract method that we need to override (without using the explicit Kotlin `override` keyword to be backwards compatible
// https://docs.oracle.com/javase/9/docs/api/javafx/collections/transformation/TransformationList.html#getViewIndex-int-
public class FlattenedList extends AbstractFlattenedList {
@SuppressWarnings("unchecked")
public FlattenedList(@NotNull ObservableList sourceList) {
super(sourceList);
}
public int getViewIndex(int i) {
return 0;
}
}

View File

@ -0,0 +1,17 @@
package net.corda.client.jfx.utils;
import javafx.collections.ObservableList;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
// Java 9 introduces a new abstract method that we need to override (without using the explicit Kotlin `override` keyword to be backwards compatible
// https://docs.oracle.com/javase/9/docs/api/javafx/collections/transformation/TransformationList.html#getViewIndex-int-
public class MappedList<A,B> extends AbstractMappedList<A,B> {
public MappedList(@NotNull ObservableList<A> list, @NotNull Function1<A,B> function) {
super(list, function);
}
public int getViewIndex(int i) {
return 0;
}
}

View File

@ -0,0 +1,16 @@
package net.corda.client.jfx.utils;
import javafx.collections.ObservableList;
import org.jetbrains.annotations.NotNull;
// Java 9 introduces a new abstract method that we need to override (without using the explicit Kotlin `override` keyword to be backwards compatible
// https://docs.oracle.com/javase/9/docs/api/javafx/collections/transformation/TransformationList.html#getViewIndex-int-
public class ReplayedList<A> extends AbstractReplayedList<A> {
public ReplayedList(@NotNull ObservableList<A> sourceList) {
super(sourceList);
}
public int getViewIndex(int i) {
return 0;
}
}

View File

@ -63,7 +63,7 @@ object Models {
*/ */
private val dependencyGraph = HashMap<KClass<*>, MutableSet<KClass<*>>>() private val dependencyGraph = HashMap<KClass<*>, MutableSet<KClass<*>>>()
fun <M : Any> initModel(klass: KClass<M>) = modelStore.getOrPut(klass) { klass.java.newInstance() } fun <M : Any> initModel(klass: KClass<M>) = modelStore.getOrPut(klass) { klass.java.getDeclaredConstructor().newInstance() }
fun <M : Any> get(klass: KClass<M>, origin: KClass<*>): M { fun <M : Any> get(klass: KClass<M>, origin: KClass<*>): M {
dependencyGraph.getOrPut(origin) { mutableSetOf() }.add(klass) dependencyGraph.getOrPut(origin) { mutableSetOf() }.add(klass)
val model = initModel(klass) val model = initModel(klass)

View File

@ -1,6 +1,7 @@
package net.corda.client.jfx.model package net.corda.client.jfx.model
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps import net.corda.client.rpc.internal.ReconnectingCordaRPCOps
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
@ -71,7 +72,7 @@ class NodeMonitorModel : AutoCloseable {
* TODO provide an unsubscribe mechanism * TODO provide an unsubscribe mechanism
*/ */
fun register(nodeHostAndPort: NetworkHostAndPort, username: String, password: String) { fun register(nodeHostAndPort: NetworkHostAndPort, username: String, password: String) {
rpc = ReconnectingCordaRPCOps(nodeHostAndPort, username, password) rpc = ReconnectingCordaRPCOps(nodeHostAndPort, username, password, CordaRPCClientConfiguration.DEFAULT)
proxyObservable.value = rpc proxyObservable.value = rpc

View File

@ -6,7 +6,7 @@ import javafx.collections.ObservableList
import javafx.collections.transformation.TransformationList import javafx.collections.transformation.TransformationList
/** /**
* Given an [ObservableList]<[E]> and a grouping key [K], [AggregatedList] groups the elements by the key into a fresh * Given an [ObservableList]<[E]> and a grouping key [K], [AbstractAggregatedList] groups the elements by the key into a fresh
* [ObservableList]<[E]> for each group and exposes the groups as an observable list of [A]s by calling [assemble] on each. * [ObservableList]<[E]> for each group and exposes the groups as an observable list of [A]s by calling [assemble] on each.
* *
* Changes done to elements of the input list are reflected in the observable list of the respective group, whereas * Changes done to elements of the input list are reflected in the observable list of the respective group, whereas
@ -36,8 +36,8 @@ import javafx.collections.transformation.TransformationList
* @param toKey Function to extract the key from an element. * @param toKey Function to extract the key from an element.
* @param assemble Function to assemble the aggregation into the exposed [A]. * @param assemble Function to assemble the aggregation into the exposed [A].
*/ */
class AggregatedList<A, E : Any, K : Any>( abstract class AbstractAggregatedList<A, E : Any, K : Any>(
list: ObservableList<out E>, val list: ObservableList<out E>,
val toKey: (E) -> K, val toKey: (E) -> K,
val assemble: (K, ObservableList<E>) -> A val assemble: (K, ObservableList<E>) -> A
) : TransformationList<A, E>(list) { ) : TransformationList<A, E>(list) {

View File

@ -7,13 +7,14 @@ import javafx.collections.transformation.TransformationList
import java.util.* import java.util.*
/** /**
* [ConcatenatedList] takes a list of lists and concatenates them. Any change to the underlying lists or the outer list * [AbstractConcatenatedList] takes a list of lists and concatenates them. Any change to the underlying lists or the outer list
* is propagated as expected. * is propagated as expected.
*/ */
class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : TransformationList<A, ObservableList<A>>(sourceList) { abstract class AbstractConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : TransformationList<A, ObservableList<A>>(sourceList) {
// A wrapper for input lists so we hash differently even if a list is reused in the input. // A wrapper for input lists so we hash differently even if a list is reused in the input.
@VisibleForTesting @VisibleForTesting
internal class WrappedObservableList<A>( class WrappedObservableList<A>(
val observableList: ObservableList<A> val observableList: ObservableList<A>
) )
// First let's clarify some concepts as it's easy to confuse which list we're handling where. // First let's clarify some concepts as it's easy to confuse which list we're handling where.
@ -37,9 +38,9 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
// Note that similar to 'nestedIndexOffsets', 'startingOffsetOf' also isn't a one-to-one mapping because of // Note that similar to 'nestedIndexOffsets', 'startingOffsetOf' also isn't a one-to-one mapping because of
// potentially several empty nested lists. // potentially several empty nested lists.
@VisibleForTesting @VisibleForTesting
internal val indexMap = HashMap<WrappedObservableList<out A>, Pair<Int, ListChangeListener<A>>>() val indexMap = HashMap<WrappedObservableList<out A>, Pair<Int, ListChangeListener<A>>>()
@VisibleForTesting @VisibleForTesting
internal val nestedIndexOffsets = ArrayList<Int>(sourceList.size) val nestedIndexOffsets = ArrayList<Int>(sourceList.size)
init { init {
var offset = 0 var offset = 0

View File

@ -8,11 +8,10 @@ import javafx.collections.transformation.TransformationList
import java.util.* import java.util.*
/** /**
* [FlattenedList] flattens the passed in list of [ObservableValue]s so that changes in individual updates to the values * [AbstractFlattenedList] flattens the passed in list of [ObservableValue]s so that changes in individual updates to the values
* are reflected in the exposed list as expected. * are reflected in the exposed list as expected.
*/ */
class FlattenedList<A>(val sourceList: ObservableList<out ObservableValue<out A>>) : TransformationList<A, ObservableValue<out A>>(sourceList) { abstract class AbstractFlattenedList<A>(val sourceList: ObservableList<out ObservableValue<out A>>) : TransformationList<A, ObservableValue<out A>>(sourceList) {
/** /**
* We maintain an ObservableValue->index map. This is needed because we need the ObservableValue's index in order to * We maintain an ObservableValue->index map. This is needed because we need the ObservableValue's index in order to
* propagate a change and if the listener closure captures the index at the time of the call to * propagate a change and if the listener closure captures the index at the time of the call to

View File

@ -10,7 +10,8 @@ import java.util.*
* when an element is inserted or updated. * when an element is inserted or updated.
* Use this instead of [EasyBind.map] to trade off memory vs CPU, or if (god forbid) the mapped function is side-effecting. * Use this instead of [EasyBind.map] to trade off memory vs CPU, or if (god forbid) the mapped function is side-effecting.
*/ */
class MappedList<A, B>(list: ObservableList<A>, val function: (A) -> B) : TransformationList<B, A>(list) { abstract class AbstractMappedList<A, B>(list: ObservableList<A>, val function: (A) -> B) : TransformationList<B, A>(list) {
private val backingList = ArrayList<B>(list.size) private val backingList = ArrayList<B>(list.size)
init { init {

View File

@ -9,7 +9,7 @@ import java.util.*
* This list type just replays changes propagated from the underlying source list. Used for testing changes and backing a * This list type just replays changes propagated from the underlying source list. Used for testing changes and backing a
* non-backed observable * non-backed observable
*/ */
class ReplayedList<A>(sourceList: ObservableList<A>) : TransformationList<A, A>(sourceList) { abstract class AbstractReplayedList<A>(sourceList: ObservableList<A>) : TransformationList<A, A>(sourceList) {
val replayedList = ArrayList<A>(sourceList) val replayedList = ArrayList<A>(sourceList)

View File

@ -143,14 +143,16 @@ fun <A, B> ObservableList<out A>.foldObservable(initial: B, folderFunction: (B,
* val people: ObservableList<Person> = (..) * val people: ObservableList<Person> = (..)
* val heights: ObservableList<Long> = people.map(Person::height).flatten() * val heights: ObservableList<Long> = people.map(Person::height).flatten()
*/ */
fun <A> ObservableList<out ObservableValue<out A>>.flatten(): ObservableList<A> = FlattenedList(this) @Suppress("UNCHECKED_CAST")
fun <A> ObservableList<out ObservableValue<out A>>.flatten(): ObservableList<A> = FlattenedList(this) as ObservableList<A>
/** /**
* data class Person(val height: ObservableValue<Long>) * data class Person(val height: ObservableValue<Long>)
* val people: List<Person> = listOf(alice, bob) * val people: List<Person> = listOf(alice, bob)
* val heights: ObservableList<Long> = people.map(Person::height).sequence() * val heights: ObservableList<Long> = people.map(Person::height).sequence()
*/ */
fun <A> Collection<ObservableValue<out A>>.sequence(): ObservableList<A> = FlattenedList(FXCollections.observableArrayList(this)) @Suppress("UNCHECKED_CAST")
fun <A> Collection<ObservableValue<out A>>.sequence(): ObservableList<A> = FlattenedList(FXCollections.observableArrayList(this)) as ObservableList<A>
/** /**
* data class Person(val height: Long) * data class Person(val height: Long)
@ -173,9 +175,12 @@ fun <K, A> ObservableList<out A>.associateBy(toKey: (A) -> K): ObservableMap<K,
* val people: ObservableList<Person> = (..) * val people: ObservableList<Person> = (..)
* val heightToNames: ObservableMap<Long, ObservableList<String>> = people.associateByAggregation(Person::height) { name, person -> person.name } * val heightToNames: ObservableMap<Long, ObservableList<String>> = people.associateByAggregation(Person::height) { name, person -> person.name }
*/ */
@Suppress("UNCHECKED_CAST")
fun <K : Any, A : Any, B> ObservableList<out A>.associateByAggregation(toKey: (A) -> K, assemble: (K, A) -> B): ObservableMap<K, ObservableList<B>> { fun <K : Any, A : Any, B> ObservableList<out A>.associateByAggregation(toKey: (A) -> K, assemble: (K, A) -> B): ObservableMap<K, ObservableList<B>> {
return AssociatedList(AggregatedList(this, toKey) { key, members -> Pair(key, members) }, { it.first }) { key, pair -> @Suppress("UNCHECKED_CAST")
pair.second.map { assemble(key, it) } val sourceList = AggregatedList(this, toKey) { key, members -> Pair(key, members) } as AggregatedList<Pair<K, ObservableList<A>>, A, K>
return AssociatedList(sourceList, { (it as Pair<K,ObservableList<A>>).first }) { key, pair ->
(pair as Pair<K,ObservableList<A>>).second.map { assemble(key, it) }
} }
} }

View File

@ -16,7 +16,8 @@ class FlattenedListTest {
@Before @Before
fun setup() { fun setup() {
sourceList = FXCollections.observableArrayList(SimpleObjectProperty(1234)) sourceList = FXCollections.observableArrayList(SimpleObjectProperty(1234))
flattenedList = FlattenedList(sourceList) @Suppress("UNCHECKED_CAST")
flattenedList = FlattenedList(sourceList) as ObservableList<Int>
replayedList = ReplayedList(flattenedList) replayedList = ReplayedList(flattenedList)
} }

View File

@ -98,6 +98,9 @@ dependencies {
smokeTestImplementation "junit:junit:$junit_version" smokeTestImplementation "junit:junit:$junit_version"
smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}" smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}" smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
// JDK11: required by Quasar at run-time
smokeTestRuntimeOnly "com.esotericsoftware:kryo:4.0.2"
} }
task integrationTest(type: Test) { task integrationTest(type: Test) {

View File

@ -3,6 +3,7 @@ package net.corda.client.rpcreconnect
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.CordaRPCClientTest import net.corda.client.rpc.CordaRPCClientTest
import net.corda.client.rpc.GracefulReconnect
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps import net.corda.client.rpc.internal.ReconnectingCordaRPCOps
import net.corda.core.messaging.startTrackedFlow import net.corda.core.messaging.startTrackedFlow
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
@ -30,6 +31,8 @@ class CordaRPCClientReconnectionTest {
private val portAllocator = incrementalPortAllocation() private val portAllocator = incrementalPortAllocation()
private val gracefulReconnect = GracefulReconnect()
companion object { companion object {
val rpcUser = User("user1", "test", permissions = setOf(Permissions.all())) val rpcUser = User("user1", "test", permissions = setOf(Permissions.all()))
} }
@ -53,7 +56,7 @@ class CordaRPCClientReconnectionTest {
maxReconnectAttempts = 5 maxReconnectAttempts = 5
)) ))
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = true).proxy as ReconnectingCordaRPCOps).use { (client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect).proxy as ReconnectingCordaRPCOps).use {
val rpcOps = it val rpcOps = it
val networkParameters = rpcOps.networkParameters val networkParameters = rpcOps.networkParameters
val cashStatesFeed = rpcOps.vaultTrack(Cash.State::class.java) val cashStatesFeed = rpcOps.vaultTrack(Cash.State::class.java)
@ -68,7 +71,7 @@ class CordaRPCClientReconnectionTest {
val networkParametersAfterCrash = rpcOps.networkParameters val networkParametersAfterCrash = rpcOps.networkParameters
assertThat(networkParameters).isEqualTo(networkParametersAfterCrash) assertThat(networkParameters).isEqualTo(networkParametersAfterCrash)
assertTrue { assertTrue {
latch.await(2, TimeUnit.SECONDS) latch.await(20, TimeUnit.SECONDS)
} }
} }
} }
@ -93,7 +96,7 @@ class CordaRPCClientReconnectionTest {
maxReconnectAttempts = 5 maxReconnectAttempts = 5
)) ))
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = true).proxy as ReconnectingCordaRPCOps).use { (client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect).proxy as ReconnectingCordaRPCOps).use {
val rpcOps = it val rpcOps = it
val cashStatesFeed = rpcOps.vaultTrack(Cash.State::class.java) val cashStatesFeed = rpcOps.vaultTrack(Cash.State::class.java)
val subscription = cashStatesFeed.updates.subscribe { latch.countDown() } val subscription = cashStatesFeed.updates.subscribe { latch.countDown() }
@ -133,7 +136,7 @@ class CordaRPCClientReconnectionTest {
maxReconnectAttempts = 5 maxReconnectAttempts = 5
)) ))
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = true).proxy as ReconnectingCordaRPCOps).use { (client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect).proxy as ReconnectingCordaRPCOps).use {
val rpcOps = it val rpcOps = it
val networkParameters = rpcOps.networkParameters val networkParameters = rpcOps.networkParameters
val cashStatesFeed = rpcOps.vaultTrack(Cash.State::class.java) val cashStatesFeed = rpcOps.vaultTrack(Cash.State::class.java)

View File

@ -41,8 +41,20 @@ class CordaRPCConnection private constructor(
companion object { companion object {
@CordaInternal @CordaInternal
internal fun createWithGracefulReconnection(username: String, password: String, addresses: List<NetworkHostAndPort>): CordaRPCConnection { internal fun createWithGracefulReconnection(
return CordaRPCConnection(null, ReconnectingCordaRPCOps(addresses, username, password)) username: String,
password: String,
addresses: List<NetworkHostAndPort>,
rpcConfiguration: CordaRPCClientConfiguration,
gracefulReconnect: GracefulReconnect
): CordaRPCConnection {
return CordaRPCConnection(null, ReconnectingCordaRPCOps(
addresses,
username,
password,
rpcConfiguration,
gracefulReconnect
))
} }
} }
@ -241,6 +253,20 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
} }
/**
* GracefulReconnect provides the opportunity to perform certain logic when the RPC encounters a connection disconnect
* during communication with the node.
*
* NOTE: The callbacks provided may be executed on a separate thread to that which called the RPC command.
*
* @param onDisconnect implement this callback to perform logic when the RPC disconnects on connection disconnect
* @param onReconnect implement this callback to perform logic when the RPC has reconnected after connection disconnect
*/
class GracefulReconnect(val onDisconnect: () -> Unit = {}, val onReconnect: () -> Unit = {}) {
constructor(onDisconnect: Runnable, onReconnect: Runnable ) :
this(onDisconnect = { onDisconnect.run() }, onReconnect = { onReconnect.run() })
}
/** /**
* An RPC client connects to the specified server and allows you to make calls to the server that perform various * An RPC client connects to the specified server and allows you to make calls to the server that perform various
* useful tasks. Please see the Client RPC section of docs.corda.net to learn more about how this API works. A brief * useful tasks. Please see the Client RPC section of docs.corda.net to learn more about how this API works. A brief
@ -371,11 +397,11 @@ class CordaRPCClient private constructor(
* *
* @param username The username to authenticate with. * @param username The username to authenticate with.
* @param password The password to authenticate with. * @param password The password to authenticate with.
* @param gracefulReconnect whether the connection will reconnect gracefully. * @param gracefulReconnect a [GracefulReconnect] class containing callback logic when the RPC is dis/reconnected unexpectedly
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout. * @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
*/ */
@JvmOverloads @JvmOverloads
fun start(username: String, password: String, gracefulReconnect: Boolean = false): CordaRPCConnection { fun start(username: String, password: String, gracefulReconnect: GracefulReconnect? = null): CordaRPCConnection {
return start(username, password, null, null, gracefulReconnect) return start(username, password, null, null, gracefulReconnect)
} }
@ -388,11 +414,11 @@ class CordaRPCClient private constructor(
* @param username The username to authenticate with. * @param username The username to authenticate with.
* @param password The password to authenticate with. * @param password The password to authenticate with.
* @param targetLegalIdentity in case of multi-identity RPC endpoint specific legal identity to which the calls must be addressed. * @param targetLegalIdentity in case of multi-identity RPC endpoint specific legal identity to which the calls must be addressed.
* @param gracefulReconnect whether the connection will reconnect gracefully. * @param gracefulReconnect a [GracefulReconnect] class containing callback logic when the RPC is dis/reconnected unexpectedly
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout. * @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
*/ */
@JvmOverloads @JvmOverloads
fun start(username: String, password: String, targetLegalIdentity: CordaX500Name, gracefulReconnect: Boolean = false): CordaRPCConnection { fun start(username: String, password: String, targetLegalIdentity: CordaX500Name, gracefulReconnect: GracefulReconnect? = null): CordaRPCConnection {
return start(username, password, null, null, targetLegalIdentity, gracefulReconnect) return start(username, password, null, null, targetLegalIdentity, gracefulReconnect)
} }
@ -406,11 +432,11 @@ class CordaRPCClient private constructor(
* @param password The password to authenticate with. * @param password The password to authenticate with.
* @param externalTrace external [Trace] for correlation. * @param externalTrace external [Trace] for correlation.
* @param impersonatedActor the actor on behalf of which all the invocations will be made. * @param impersonatedActor the actor on behalf of which all the invocations will be made.
* @param gracefulReconnect whether the connection will reconnect gracefully. * @param gracefulReconnect a [GracefulReconnect] class containing callback logic when the RPC is dis/reconnected unexpectedly
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout. * @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
*/ */
@JvmOverloads @JvmOverloads
fun start(username: String, password: String, externalTrace: Trace?, impersonatedActor: Actor?, gracefulReconnect: Boolean = false): CordaRPCConnection { fun start(username: String, password: String, externalTrace: Trace?, impersonatedActor: Actor?, gracefulReconnect: GracefulReconnect? = null): CordaRPCConnection {
return start(username, password, externalTrace, impersonatedActor, null, gracefulReconnect) return start(username, password, externalTrace, impersonatedActor, null, gracefulReconnect)
} }
@ -425,19 +451,21 @@ class CordaRPCClient private constructor(
* @param externalTrace external [Trace] for correlation. * @param externalTrace external [Trace] for correlation.
* @param impersonatedActor the actor on behalf of which all the invocations will be made. * @param impersonatedActor the actor on behalf of which all the invocations will be made.
* @param targetLegalIdentity in case of multi-identity RPC endpoint specific legal identity to which the calls must be addressed. * @param targetLegalIdentity in case of multi-identity RPC endpoint specific legal identity to which the calls must be addressed.
* @param gracefulReconnect whether the connection will reconnect gracefully. * @param gracefulReconnect a [GracefulReconnect] class containing callback logic when the RPC is dis/reconnected unexpectedly.
* Note that when using graceful reconnect the values for [CordaRPCClientConfiguration.connectionMaxRetryInterval] and
* [CordaRPCClientConfiguration.maxReconnectAttempts] will be overridden in order to mangage the reconnects.
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout. * @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
*/ */
@JvmOverloads @JvmOverloads
fun start(username: String, password: String, externalTrace: Trace?, impersonatedActor: Actor?, targetLegalIdentity: CordaX500Name?, gracefulReconnect: Boolean = false): CordaRPCConnection { fun start(username: String, password: String, externalTrace: Trace?, impersonatedActor: Actor?, targetLegalIdentity: CordaX500Name?, gracefulReconnect: GracefulReconnect? = null): CordaRPCConnection {
val addresses = if (haAddressPool.isEmpty()) { val addresses = if (haAddressPool.isEmpty()) {
listOf(hostAndPort!!) listOf(hostAndPort!!)
} else { } else {
haAddressPool haAddressPool
} }
return if (gracefulReconnect) { return if (gracefulReconnect != null) {
CordaRPCConnection.createWithGracefulReconnection(username, password, addresses) CordaRPCConnection.createWithGracefulReconnection(username, password, addresses, configuration, gracefulReconnect)
} else { } else {
CordaRPCConnection(getRpcClient().start(InternalCordaRPCOps::class.java, username, password, externalTrace, impersonatedActor, targetLegalIdentity)) CordaRPCConnection(getRpcClient().start(InternalCordaRPCOps::class.java, username, password, externalTrace, impersonatedActor, targetLegalIdentity))
} }

View File

@ -1,6 +1,7 @@
package net.corda.client.rpc.internal package net.corda.client.rpc.internal
import net.corda.client.rpc.* import net.corda.client.rpc.*
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps.ReconnectingRPCConnection.CurrentState.*
import net.corda.client.rpc.reconnect.CouldNotStartFlowException import net.corda.client.rpc.reconnect.CouldNotStartFlowException
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.div import net.corda.core.internal.div
@ -11,12 +12,14 @@ import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
import net.corda.core.utilities.* import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.seconds
import net.corda.nodeapi.exceptions.RejectedCommandException import net.corda.nodeapi.exceptions.RejectedCommandException
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.apache.activemq.artemis.api.core.ActiveMQUnBlockedException import org.apache.activemq.artemis.api.core.ActiveMQUnBlockedException
import rx.Observable
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method import java.lang.reflect.Method
@ -52,22 +55,25 @@ class ReconnectingCordaRPCOps private constructor(
nodeHostAndPort: NetworkHostAndPort, nodeHostAndPort: NetworkHostAndPort,
username: String, username: String,
password: String, password: String,
rpcConfiguration: CordaRPCClientConfiguration,
sslConfiguration: ClientRpcSslOptions? = null, sslConfiguration: ClientRpcSslOptions? = null,
classLoader: ClassLoader? = null, classLoader: ClassLoader? = null,
observersPool: ExecutorService? = null observersPool: ExecutorService? = null
) : this( ) : this(
ReconnectingRPCConnection(listOf(nodeHostAndPort), username, password, sslConfiguration, classLoader), ReconnectingRPCConnection(listOf(nodeHostAndPort), username, password, rpcConfiguration, sslConfiguration, classLoader),
observersPool ?: Executors.newCachedThreadPool(), observersPool ?: Executors.newCachedThreadPool(),
observersPool != null) observersPool != null)
constructor( constructor(
nodeHostAndPorts: List<NetworkHostAndPort>, nodeHostAndPorts: List<NetworkHostAndPort>,
username: String, username: String,
password: String, password: String,
rpcConfiguration: CordaRPCClientConfiguration,
gracefulReconnect: GracefulReconnect? = null,
sslConfiguration: ClientRpcSslOptions? = null, sslConfiguration: ClientRpcSslOptions? = null,
classLoader: ClassLoader? = null, classLoader: ClassLoader? = null,
observersPool: ExecutorService? = null observersPool: ExecutorService? = null
) : this( ) : this(
ReconnectingRPCConnection(nodeHostAndPorts, username, password, sslConfiguration, classLoader), ReconnectingRPCConnection(nodeHostAndPorts, username, password, rpcConfiguration, sslConfiguration, classLoader, gracefulReconnect),
observersPool ?: Executors.newCachedThreadPool(), observersPool ?: Executors.newCachedThreadPool(),
observersPool != null) observersPool != null)
private companion object { private companion object {
@ -116,43 +122,59 @@ class ReconnectingCordaRPCOps private constructor(
val nodeHostAndPorts: List<NetworkHostAndPort>, val nodeHostAndPorts: List<NetworkHostAndPort>,
val username: String, val username: String,
val password: String, val password: String,
val rpcConfiguration: CordaRPCClientConfiguration,
val sslConfiguration: ClientRpcSslOptions? = null, val sslConfiguration: ClientRpcSslOptions? = null,
val classLoader: ClassLoader? val classLoader: ClassLoader?,
val gracefulReconnect: GracefulReconnect? = null
) : RPCConnection<CordaRPCOps> { ) : RPCConnection<CordaRPCOps> {
private var currentRPCConnection: CordaRPCConnection? = null private var currentRPCConnection: CordaRPCConnection? = null
enum class CurrentState { enum class CurrentState {
UNCONNECTED, CONNECTED, CONNECTING, CLOSED, DIED UNCONNECTED, CONNECTED, CONNECTING, CLOSED, DIED
} }
private var currentState = CurrentState.UNCONNECTED
private var currentState = UNCONNECTED
init { init {
current current
} }
private val current: CordaRPCConnection private val current: CordaRPCConnection
@Synchronized get() = when (currentState) { @Synchronized get() = when (currentState) {
CurrentState.UNCONNECTED -> connect() UNCONNECTED -> connect()
CurrentState.CONNECTED -> currentRPCConnection!! CONNECTED -> currentRPCConnection!!
CurrentState.CLOSED -> throw IllegalArgumentException("The ReconnectingRPCConnection has been closed.") CLOSED -> throw IllegalArgumentException("The ReconnectingRPCConnection has been closed.")
CurrentState.CONNECTING, CurrentState.DIED -> throw IllegalArgumentException("Illegal state: $currentState ") CONNECTING, DIED -> throw IllegalArgumentException("Illegal state: $currentState ")
} }
/**
* Called on external error.
* Will block until the connection is established again.
*/
@Synchronized @Synchronized
fun reconnectOnError(e: Throwable) { private fun doReconnect(e: Throwable, previousConnection: CordaRPCConnection?) {
val previousConnection = currentRPCConnection if (previousConnection != currentRPCConnection) {
currentState = CurrentState.DIED // We've already done this, skip
return
}
// First one to get here gets to do all the reconnect logic, including calling onDisconnect and onReconnect. This makes sure
// that they're only called once per reconnect.
currentState = DIED
gracefulReconnect?.onDisconnect?.invoke()
//TODO - handle error cases //TODO - handle error cases
log.error("Reconnecting to ${this.nodeHostAndPorts} due to error: ${e.message}") log.error("Reconnecting to ${this.nodeHostAndPorts} due to error: ${e.message}")
log.debug("", e) log.debug("", e)
connect() connect()
previousConnection?.forceClose() previousConnection?.forceClose()
gracefulReconnect?.onReconnect?.invoke()
}
/**
* Called on external error.
* Will block until the connection is established again.
*/
fun reconnectOnError(e: Throwable) {
val previousConnection = currentRPCConnection
doReconnect(e, previousConnection)
} }
@Synchronized @Synchronized
private fun connect(): CordaRPCConnection { private fun connect(): CordaRPCConnection {
currentState = CurrentState.CONNECTING currentState = CONNECTING
currentRPCConnection = establishConnectionWithRetry() currentRPCConnection = establishConnectionWithRetry()
currentState = CurrentState.CONNECTED currentState = CONNECTED
return currentRPCConnection!! return currentRPCConnection!!
} }
@ -161,7 +183,7 @@ class ReconnectingCordaRPCOps private constructor(
log.info("Connecting to: $attemptedAddress") log.info("Connecting to: $attemptedAddress")
try { try {
return CordaRPCClient( return CordaRPCClient(
attemptedAddress, CordaRPCClientConfiguration(connectionMaxRetryInterval = retryInterval, maxReconnectAttempts = 1), sslConfiguration, classLoader attemptedAddress, rpcConfiguration.copy(connectionMaxRetryInterval = retryInterval, maxReconnectAttempts = 1), sslConfiguration, classLoader
).start(username, password).also { ).start(username, password).also {
// Check connection is truly operational before returning it. // Check connection is truly operational before returning it.
require(it.proxy.nodeInfo().legalIdentitiesAndCerts.isNotEmpty()) { require(it.proxy.nodeInfo().legalIdentitiesAndCerts.isNotEmpty()) {
@ -204,63 +226,70 @@ class ReconnectingCordaRPCOps private constructor(
get() = current.serverProtocolVersion get() = current.serverProtocolVersion
@Synchronized @Synchronized
override fun notifyServerAndClose() { override fun notifyServerAndClose() {
currentState = CurrentState.CLOSED currentState = CLOSED
currentRPCConnection?.notifyServerAndClose() currentRPCConnection?.notifyServerAndClose()
} }
@Synchronized @Synchronized
override fun forceClose() { override fun forceClose() {
currentState = CurrentState.CLOSED currentState = CLOSED
currentRPCConnection?.forceClose() currentRPCConnection?.forceClose()
} }
@Synchronized @Synchronized
override fun close() { override fun close() {
currentState = CurrentState.CLOSED currentState = CLOSED
currentRPCConnection?.close() currentRPCConnection?.close()
} }
} }
private class ErrorInterceptingHandler(val reconnectingRPCConnection: ReconnectingRPCConnection, val observersPool: ExecutorService) : InvocationHandler { private class ErrorInterceptingHandler(val reconnectingRPCConnection: ReconnectingRPCConnection, val observersPool: ExecutorService) : InvocationHandler {
private fun Method.isStartFlow() = name.startsWith("startFlow") || name.startsWith("startTrackedFlow") private fun Method.isStartFlow() = name.startsWith("startFlow") || name.startsWith("startTrackedFlow")
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
val result: Any? = try { private fun checkIfIsStartFlow(method: Method, e: InvocationTargetException) {
log.debug { "Invoking RPC $method..." } if (method.isStartFlow()) {
method.invoke(reconnectingRPCConnection.proxy, *(args ?: emptyArray())).also { // Don't retry flows
log.debug { "RPC $method invoked successfully." } throw CouldNotStartFlowException(e.targetException)
} }
} catch (e: InvocationTargetException) { }
fun retry() = if (method.isStartFlow()) {
// Don't retry flows private fun doInvoke(method: Method, args: Array<out Any>?): Any? {
throw CouldNotStartFlowException(e.targetException) // will stop looping when [method.invoke] succeeds
} else { while (true) {
this.invoke(proxy, method, args) try {
} log.debug { "Invoking RPC $method..." }
when (e.targetException) { return method.invoke(reconnectingRPCConnection.proxy, *(args ?: emptyArray())).also {
is RejectedCommandException -> { log.debug { "RPC $method invoked successfully." }
log.error("Node is being shutdown. Operation ${method.name} rejected. Retrying when node is up...", e)
reconnectingRPCConnection.reconnectOnError(e)
this.invoke(proxy, method, args)
} }
is ConnectionFailureException -> { } catch (e: InvocationTargetException) {
log.error("Failed to perform operation ${method.name}. Connection dropped. Retrying....", e) when (e.targetException) {
reconnectingRPCConnection.reconnectOnError(e) is RejectedCommandException -> {
retry() log.error("Node is being shutdown. Operation ${method.name} rejected. Retrying when node is up...", e)
} reconnectingRPCConnection.reconnectOnError(e)
is RPCException -> { }
log.error("Failed to perform operation ${method.name}. RPCException. Retrying....", e) is ConnectionFailureException -> {
reconnectingRPCConnection.reconnectOnError(e) log.error("Failed to perform operation ${method.name}. Connection dropped. Retrying....", e)
Thread.sleep(1000) // TODO - explain why this sleep is necessary reconnectingRPCConnection.reconnectOnError(e)
retry() checkIfIsStartFlow(method, e)
} }
else -> { is RPCException -> {
log.error("Failed to perform operation ${method.name}. Unknown error. Retrying....", e) log.error("Failed to perform operation ${method.name}. RPCException. Retrying....", e)
reconnectingRPCConnection.reconnectOnError(e) reconnectingRPCConnection.reconnectOnError(e)
retry() Thread.sleep(1000) // TODO - explain why this sleep is necessary
checkIfIsStartFlow(method, e)
}
else -> {
log.error("Failed to perform operation ${method.name}. Unknown error. Retrying....", e)
reconnectingRPCConnection.reconnectOnError(e)
checkIfIsStartFlow(method, e)
}
} }
} }
} }
}
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
return when (method.returnType) { return when (method.returnType) {
DataFeed::class.java -> { DataFeed::class.java -> {
// Intercept the data feed methods and returned a ReconnectingObservable instance // Intercept the data feed methods and return a ReconnectingObservable instance
val initialFeed: DataFeed<Any, Any?> = uncheckedCast(result) val initialFeed: DataFeed<Any, Any?> = uncheckedCast(doInvoke(method, args))
val observable = ReconnectingObservable(reconnectingRPCConnection, observersPool, initialFeed) { val observable = ReconnectingObservable(reconnectingRPCConnection, observersPool, initialFeed) {
// This handles reconnecting and creates new feeds. // This handles reconnecting and creates new feeds.
uncheckedCast(this.invoke(reconnectingRPCConnection.proxy, method, args)) uncheckedCast(this.invoke(reconnectingRPCConnection.proxy, method, args))
@ -268,10 +297,11 @@ class ReconnectingCordaRPCOps private constructor(
initialFeed.copy(updates = observable) initialFeed.copy(updates = observable)
} }
// TODO - add handlers for Observable return types. // TODO - add handlers for Observable return types.
else -> result else -> doInvoke(method, args)
} }
} }
} }
override fun close() { override fun close() {
if (!userPool) observersPool.shutdown() if (!userPool) observersPool.shutdown()
retryFlowsPool.shutdown() retryFlowsPool.shutdown()

View File

@ -5,7 +5,8 @@ package net.corda.common.logging
/** /**
* constants in this file are generated by gradle * constants in this file are generated by gradle
* to change this file, edit src/main/template/kotlin/net/corda/common/logging/Constants.kt * to change this file, edit src/main/template/kotlin/net/corda/common/logging/Constants.kt
* the generated file does not need to be committed to source control (originally added to source control for ease of use) * the generated file does not need to be committed to source control
* (originally added to source control for ease of use)
*/ */
internal const val CURRENT_MAJOR_RELEASE = "4.4-SNAPSHOT" internal const val CURRENT_MAJOR_RELEASE = "4.4-SNAPSHOT"

View File

@ -5,7 +5,8 @@ package net.corda.common.logging
/** /**
* constants in this file are generated by gradle * constants in this file are generated by gradle
* to change this file, edit src/main/template/kotlin/net/corda/common/logging/Constants.kt * to change this file, edit src/main/template/kotlin/net/corda/common/logging/Constants.kt
* the generated file does not need to be committed to source control (originally added to source control for ease of use) * the generated file does not need to be committed to source control
* (originally added to source control for ease of use)
*/ */
internal const val CURRENT_MAJOR_RELEASE = "@corda_release_version@" internal const val CURRENT_MAJOR_RELEASE = "@corda_release_version@"

View File

@ -12,7 +12,11 @@ java8MinUpdateVersion=171
# ***************************************************************# # ***************************************************************#
platformVersion=5 platformVersion=5
guavaVersion=28.0-jre guavaVersion=28.0-jre
# Quasar version to use with Java 8:
quasarVersion=0.7.10 quasarVersion=0.7.10
quasarClassifier=jdk8
# Quasar version to use with Java 11:
quasarVersion11=0.8.0
proguardVersion=6.1.1 proguardVersion=6.1.1
bouncycastleVersion=1.60 bouncycastleVersion=1.60
classgraphVersion=4.8.41 classgraphVersion=4.8.41

View File

@ -8,6 +8,9 @@ apply plugin: 'idea'
evaluationDependsOn(":core") evaluationDependsOn(":core")
// required by DJVM and Avian JVM (for running inside the SGX enclave) which only supports Java 8.
targetCompatibility = VERSION_1_8
def javaHome = System.getProperty('java.home') def javaHome = System.getProperty('java.home')
def jarBaseName = "corda-${project.name}".toString() def jarBaseName = "corda-${project.name}".toString()
@ -69,8 +72,12 @@ task predeterminise(type: ProGuardTask) {
injars patchCore injars patchCore
outjars file("$buildDir/proguard/pre-deterministic-${project.version}.jar") outjars file("$buildDir/proguard/pre-deterministic-${project.version}.jar")
libraryjars file("$javaHome/lib/rt.jar") if (JavaVersion.current().isJava9Compatible()) {
libraryjars file("$javaHome/lib/jce.jar") libraryjars "$javaHome/jmods"
} else {
libraryjars "$javaHome/lib/rt.jar"
libraryjars "$javaHome/lib/jce.jar"
}
configurations.compileClasspath.forEach { configurations.compileClasspath.forEach {
if (originalJar != it) { if (originalJar != it) {
libraryjars it, filter: '!META-INF/versions/**' libraryjars it, filter: '!META-INF/versions/**'
@ -118,8 +125,12 @@ task determinise(type: ProGuardTask) {
injars jarFilter injars jarFilter
outjars file("$buildDir/proguard/$jarBaseName-${project.version}.jar") outjars file("$buildDir/proguard/$jarBaseName-${project.version}.jar")
libraryjars file("$javaHome/lib/rt.jar") if (JavaVersion.current().isJava9Compatible()) {
libraryjars file("$javaHome/lib/jce.jar") libraryjars "$javaHome/jmods"
} else {
libraryjars "$javaHome/lib/rt.jar"
libraryjars "$javaHome/lib/jce.jar"
}
configurations.deterministicLibraries.forEach { configurations.deterministicLibraries.forEach {
libraryjars it, filter: '!META-INF/versions/**' libraryjars it, filter: '!META-INF/versions/**'
} }
@ -145,6 +156,8 @@ task determinise(type: ProGuardTask) {
} }
import net.corda.gradle.jarfilter.MetaFixerTask import net.corda.gradle.jarfilter.MetaFixerTask
import static org.gradle.api.JavaVersion.VERSION_1_8
task metafix(type: MetaFixerTask) { task metafix(type: MetaFixerTask) {
outputDir file("$buildDir/libs") outputDir file("$buildDir/libs")
jars determinise jars determinise

View File

@ -0,0 +1,61 @@
package net.corda.deterministic;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
/**
* Temporarily restore Sun's [SecureRandom] provider.
* This is ONLY for allowing us to generate test data, e.g. signatures.
*
* JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via module directives (not available to Kotlin compiler):
* sun.security.provider.SecureRandom()
*/
public class CheatingSecurityProvider extends Provider implements AutoCloseable {
private static AtomicInteger counter = new AtomicInteger();
@SuppressWarnings("deprecation") // JDK11: should replace with Provider(String name, double version, String info) (since 9)
public CheatingSecurityProvider() {
super("Cheat-" + counter.getAndIncrement(), 1.8, "Cheat security provider");
putService(new CheatingSecureRandomService(this));
assertEquals(1, Security.insertProviderAt(this, 1));
}
public void close() {
Security.removeProvider(getName());
}
private class SunSecureRandom extends SecureRandom {
public SunSecureRandom() {
// JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via open module directive
super(new sun.security.provider.SecureRandom(), null);
}
}
private class CheatingSecureRandomService extends Provider.Service {
public CheatingSecureRandomService(Provider provider) {
super(provider, "SecureRandom", "CheatingPRNG", CheatingSecureRandomSpi.class.getName(), null, null);
}
private SecureRandomSpi instance = new CheatingSecureRandomSpi();
public Object newInstance(Object constructorParameter){
return instance;
}
}
private class CheatingSecureRandomSpi extends SecureRandomSpi {
private SecureRandom secureRandom = new SunSecureRandom();
public void engineSetSeed(byte[] seed) { secureRandom.setSeed(seed); }
public void engineNextBytes(byte[] bytes) { secureRandom.nextBytes(bytes); }
public byte[] engineGenerateSeed(int numBytes) { return secureRandom.generateSeed(numBytes); }
}
}

View File

@ -1,44 +0,0 @@
package net.corda.deterministic
import org.junit.Assert.*
import java.security.Provider
import java.security.SecureRandom
import java.security.SecureRandomSpi
import java.security.Security
import java.util.concurrent.atomic.AtomicInteger
/**
* Temporarily restore Sun's [SecureRandom] provider.
* This is ONLY for allowing us to generate test data, e.g. signatures.
*/
class CheatingSecurityProvider : Provider("Cheat-${counter.getAndIncrement()}", 1.8, "Cheat security provider"), AutoCloseable {
private companion object {
private val counter = AtomicInteger()
}
init {
putService(CheatingSecureRandomService(this))
assertEquals(1, Security.insertProviderAt(this, 1))
}
override fun close() {
Security.removeProvider(name)
}
private class SunSecureRandom : SecureRandom(sun.security.provider.SecureRandom(), null)
private class CheatingSecureRandomService(provider: Provider)
: Provider.Service(provider, "SecureRandom", "CheatingPRNG", CheatingSecureRandomSpi::javaClass.name, null, null) {
private val instance: SecureRandomSpi = CheatingSecureRandomSpi()
override fun newInstance(constructorParameter: Any?) = instance
}
private class CheatingSecureRandomSpi : SecureRandomSpi() {
private val secureRandom: SecureRandom = SunSecureRandom()
override fun engineSetSeed(seed: ByteArray) = secureRandom.setSeed(seed)
override fun engineNextBytes(bytes: ByteArray) = secureRandom.nextBytes(bytes)
override fun engineGenerateSeed(numBytes: Int): ByteArray = secureRandom.generateSeed(numBytes)
}
}

View File

@ -57,7 +57,7 @@ class AttachmentsClassLoaderSerializationTests {
SecureHash.zeroHash, SecureHash.zeroHash,
{ attachmentTrustCalculator.calculate(it) }) { classLoader -> { attachmentTrustCalculator.calculate(it) }) { classLoader ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classLoader) val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classLoader)
val contract = contractClass.newInstance() as Contract val contract = contractClass.getDeclaredConstructor().newInstance() as Contract
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value) assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
val txt = IOUtils.toString(classLoader.getResourceAsStream("file1.txt"), Charsets.UTF_8.name()) val txt = IOUtils.toString(classLoader.getResourceAsStream("file1.txt"), Charsets.UTF_8.name())

View File

@ -90,7 +90,7 @@ class AttachmentsClassLoaderTests {
val classloader = createClassloader(isolatedId) val classloader = createClassloader(isolatedId)
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classloader) val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classloader)
val contract = contractClass.newInstance() as Contract val contract = contractClass.getDeclaredConstructor().newInstance() as Contract
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value) assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
} }

View File

@ -1,3 +1,5 @@
import static org.gradle.api.JavaVersion.VERSION_1_8
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'kotlin-jpa' apply plugin: 'kotlin-jpa'
apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.quasar-utils'
@ -7,6 +9,11 @@ apply plugin: 'com.jfrog.artifactory'
description 'Corda core' description 'Corda core'
evaluationDependsOn(':node:capsule')
// required by DJVM and Avian JVM (for running inside the SGX enclave) which only supports Java 8.
targetCompatibility = VERSION_1_8
configurations { configurations {
integrationTestCompile.extendsFrom testCompile integrationTestCompile.extendsFrom testCompile
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
@ -71,6 +78,9 @@ dependencies {
compile group: "io.github.classgraph", name: "classgraph", version: class_graph_version compile group: "io.github.classgraph", name: "classgraph", version: class_graph_version
// JDK11: required by Quasar at run-time
testRuntimeOnly "com.esotericsoftware:kryo:4.0.2"
testCompile "com.nhaarman:mockito-kotlin:$mockito_kotlin_version" testCompile "com.nhaarman:mockito-kotlin:$mockito_kotlin_version"
testCompile "org.mockito:mockito-core:$mockito_version" testCompile "org.mockito:mockito-core:$mockito_version"
testCompile "org.assertj:assertj-core:$assertj_version" testCompile "org.assertj:assertj-core:$assertj_version"

View File

@ -9,6 +9,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.Provider import java.security.Provider
@KeepForDJVM @KeepForDJVM
@Suppress("DEPRECATION") // JDK11: should replace with Provider(String name, double version, String info) (since 9)
class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") { class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") {
companion object { companion object {
const val PROVIDER_NAME = "Corda" const val PROVIDER_NAME = "Corda"

View File

@ -35,7 +35,7 @@ object ContractUpgradeFlow {
// DOCEND 1 // DOCEND 1
@Suspendable @Suspendable
override fun call(): Void? { override fun call(): Void? {
val upgrade = upgradedContractClass.newInstance() val upgrade = upgradedContractClass.getDeclaredConstructor().newInstance()
if (upgrade.legacyContract != stateAndRef.state.contract) { if (upgrade.legacyContract != stateAndRef.state.contract) {
throw FlowException("The contract state cannot be upgraded using provided UpgradedContract.") throw FlowException("The contract state cannot be upgraded using provided UpgradedContract.")
} }

View File

@ -41,6 +41,7 @@ fun checkMinimumPlatformVersion(minimumPlatformVersion: Int, requiredMinPlatform
} }
} }
// JDK11: revisit (JDK 9+ uses different numbering scheme: see https://docs.oracle.com/javase/9/docs/api/java/lang/Runtime.Version.html)
@Throws(NumberFormatException::class) @Throws(NumberFormatException::class)
fun getJavaUpdateVersion(javaVersion: String): Long = javaVersion.substringAfter("_").substringBefore("-").toLong() fun getJavaUpdateVersion(javaVersion: String): Long = javaVersion.substringAfter("_").substringBefore("-").toLong()

View File

@ -352,6 +352,7 @@ class DeclaredField<T>(clazz: Class<*>, name: String, private val receiver: Any?
val name: String = javaField.name val name: String = javaField.name
private fun <RESULT> Field.accessible(action: Field.() -> RESULT): RESULT { private fun <RESULT> Field.accessible(action: Field.() -> RESULT): RESULT {
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9)
val accessible = isAccessible val accessible = isAccessible
isAccessible = true isAccessible = true
try { try {
@ -393,16 +394,17 @@ fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ i
val Class<*>.location: URL get() = protectionDomain.codeSource.location val Class<*>.location: URL get() = protectionDomain.codeSource.location
/** Convenience method to get the package name of a class literal. */ /** Convenience method to get the package name of a class literal. */
val KClass<*>.packageName: String get() = java.packageName val KClass<*>.packageName: String get() = java.packageName_
val Class<*>.packageName: String get() = requireNotNull(this.packageNameOrNull) { "$this not defined inside a package" } // re-defined to prevent clash with Java 9 Class.packageName: https://docs.oracle.com/javase/9/docs/api/java/lang/Class.html#getPackageName--
val Class<*>.packageName_: String get() = requireNotNull(this.packageNameOrNull) { "$this not defined inside a package" }
val Class<*>.packageNameOrNull: String? // This intentionally does not go via `package` as that code path is slow and contended and just ends up doing this. val Class<*>.packageNameOrNull: String? // This intentionally does not go via `package` as that code path is slow and contended and just ends up doing this.
get() { get() {
val name = this.getName() val name = this.name
val i = name.lastIndexOf('.') val i = name.lastIndexOf('.')
if (i != -1) { return if (i != -1) {
return name.substring(0, i) name.substring(0, i)
} else { } else {
return null null
} }
} }

View File

@ -366,7 +366,7 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
val contractInstances: List<Contract> = contractClasses.map { (contractClassName, contractClass) -> val contractInstances: List<Contract> = contractClasses.map { (contractClassName, contractClass) ->
try { try {
contractClass.newInstance() contractClass.getDeclaredConstructor().newInstance()
} catch (e: Exception) { } catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e) throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e)
} }

View File

@ -133,7 +133,7 @@ data class ContractUpgradeWireTransaction(
private fun upgradedContract(className: ContractClassName, classLoader: ClassLoader): UpgradedContract<ContractState, ContractState> = try { private fun upgradedContract(className: ContractClassName, classLoader: ClassLoader): UpgradedContract<ContractState, ContractState> = try {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
classLoader.loadClass(className).asSubclass(UpgradedContract::class.java).newInstance() as UpgradedContract<ContractState, ContractState> classLoader.loadClass(className).asSubclass(UpgradedContract::class.java).getDeclaredConstructor().newInstance() as UpgradedContract<ContractState, ContractState>
} catch (e: Exception) { } catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(id, className, e) throw TransactionVerificationException.ContractCreationError(id, className, e)
} }

View File

@ -0,0 +1,133 @@
package net.corda.core.internal;
import net.corda.core.crypto.Crypto;
import net.i2p.crypto.eddsa.EdDSAEngine;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import org.junit.Test;
import sun.security.util.BitArray;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X509Key;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.SignatureException;
import java.util.Random;
import static org.junit.Assert.assertTrue;
/**
* JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via module directives (not available to Kotlin compiler):
* import sun.security.util.BitArray;
* import sun.security.util.ObjectIdentifier;
* import sun.security.x509.AlgorithmId;
* import sun.security.x509.X509Key;
*/
public class X509EdDSAEngineTest {
private static long SEED = 20170920L;
private static int TEST_DATA_SIZE = 2000;
// offset into an EdDSA header indicating where the key header and actual key start
// in the underlying byte array
private static int keyHeaderStart = 9;
private static int keyStart = 12;
private X509Key toX509Key(EdDSAPublicKey publicKey) throws IOException, InvalidKeyException {
byte[] internals = publicKey.getEncoded();
// key size in the header includes the count unused bits at the end of the key
// [keyHeaderStart + 2] but NOT the key header ID [keyHeaderStart] so the
// actual length of the key blob is size - 1
int keySize = (internals[keyHeaderStart + 1]) - 1;
byte[] key = new byte[keySize];
System.arraycopy(internals, keyStart, key, 0, keySize);
// 1.3.101.102 is the EdDSA OID
return new TestX509Key(new AlgorithmId(new ObjectIdentifier("1.3.101.112")), new BitArray(keySize * 8, key));
}
class TestX509Key extends X509Key {
TestX509Key(AlgorithmId algorithmId, BitArray key) throws InvalidKeyException {
this.algid = algorithmId;
this.setKey(key);
this.encode();
}
}
/**
* Put the X509EdDSA engine through basic tests to verify that the functions are hooked up correctly.
*/
@Test
public void SignAndVerify() throws InvalidKeyException, SignatureException {
X509EdDSAEngine engine = new X509EdDSAEngine();
KeyPair keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED));
EdDSAPublicKey publicKey = (EdDSAPublicKey) keyPair.getPublic();
byte[] randomBytes = new byte[TEST_DATA_SIZE];
new Random(SEED).nextBytes(randomBytes);
engine.initSign(keyPair.getPrivate());
engine.update(randomBytes[0]);
engine.update(randomBytes, 1, randomBytes.length - 1);
// Now verify the signature
byte[] signature = engine.sign();
engine.initVerify(publicKey);
engine.update(randomBytes);
assertTrue(engine.verify(signature));
}
/**
* Verify that signing with an X509Key wrapped EdDSA key works.
*/
@Test
public void SignAndVerifyWithX509Key() throws InvalidKeyException, SignatureException, IOException {
X509EdDSAEngine engine = new X509EdDSAEngine();
KeyPair keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED + 1));
X509Key publicKey = toX509Key((EdDSAPublicKey) keyPair.getPublic());
byte[] randomBytes = new byte[TEST_DATA_SIZE];
new Random(SEED + 1).nextBytes(randomBytes);
engine.initSign(keyPair.getPrivate());
engine.update(randomBytes[0]);
engine.update(randomBytes, 1, randomBytes.length - 1);
// Now verify the signature
byte[] signature = engine.sign();
engine.initVerify(publicKey);
engine.update(randomBytes);
assertTrue(engine.verify(signature));
}
/**
* Verify that signing with an X509Key wrapped EdDSA key succeeds when using the underlying EdDSAEngine.
*/
@Test
public void SignAndVerifyWithX509KeyAndOldEngineFails() throws InvalidKeyException, SignatureException, IOException {
X509EdDSAEngine engine = new X509EdDSAEngine();
KeyPair keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED + 1));
X509Key publicKey = toX509Key((EdDSAPublicKey) keyPair.getPublic());
byte[] randomBytes = new byte[TEST_DATA_SIZE];
new Random(SEED + 1).nextBytes(randomBytes);
engine.initSign(keyPair.getPrivate());
engine.update(randomBytes[0]);
engine.update(randomBytes, 1, randomBytes.length - 1);
// Now verify the signature
byte[] signature = engine.sign();
engine.initVerify(publicKey);
engine.update(randomBytes);
engine.verify(signature);
}
/** Verify will fail if the input public key cannot be converted to EdDSA public key. */
@Test(expected = InvalidKeyException.class)
public void verifyWithNonSupportedKeyTypeFails() throws InvalidKeyException {
EdDSAEngine engine = new EdDSAEngine();
KeyPair keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger.valueOf(SEED));
engine.initVerify(keyPair.getPublic());
}
}

View File

@ -1,125 +0,0 @@
package net.corda.core.internal
import net.corda.core.crypto.Crypto
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.junit.Test
import sun.security.util.BitArray
import sun.security.util.ObjectIdentifier
import sun.security.x509.AlgorithmId
import sun.security.x509.X509Key
import java.math.BigInteger
import java.security.InvalidKeyException
import java.util.*
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class TestX509Key(algorithmId: AlgorithmId, key: BitArray) : X509Key() {
init {
this.algid = algorithmId
this.setKey(key)
this.encode()
}
}
class X509EdDSAEngineTest {
companion object {
private const val SEED = 20170920L
private const val TEST_DATA_SIZE = 2000
// offset into an EdDSA header indicating where the key header and actual key start
// in the underlying byte array
private const val keyHeaderStart = 9
private const val keyStart = 12
private fun toX509Key(publicKey: EdDSAPublicKey): X509Key {
val internals = publicKey.encoded
// key size in the header includes the count unused bits at the end of the key
// [keyHeaderStart + 2] but NOT the key header ID [keyHeaderStart] so the
// actual length of the key blob is size - 1
val keySize = (internals[keyHeaderStart + 1].toInt()) - 1
val key = ByteArray(keySize)
System.arraycopy(internals, keyStart, key, 0, keySize)
// 1.3.101.102 is the EdDSA OID
return TestX509Key(AlgorithmId(ObjectIdentifier("1.3.101.112")), BitArray(keySize * 8, key))
}
}
/**
* Put the X509EdDSA engine through basic tests to verify that the functions are hooked up correctly.
*/
@Test
fun `sign and verify`() {
val engine = X509EdDSAEngine()
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED))
val publicKey = keyPair.public as EdDSAPublicKey
val randomBytes = ByteArray(TEST_DATA_SIZE)
Random(SEED).nextBytes(randomBytes)
engine.initSign(keyPair.private)
engine.update(randomBytes[0])
engine.update(randomBytes, 1, randomBytes.size - 1)
// Now verify the signature
val signature = engine.sign()
engine.initVerify(publicKey)
engine.update(randomBytes)
assertTrue { engine.verify(signature) }
}
/**
* Verify that signing with an X509Key wrapped EdDSA key works.
*/
@Test
fun `sign and verify with X509Key`() {
val engine = X509EdDSAEngine()
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED + 1))
val publicKey = toX509Key(keyPair.public as EdDSAPublicKey)
val randomBytes = ByteArray(TEST_DATA_SIZE)
Random(SEED + 1).nextBytes(randomBytes)
engine.initSign(keyPair.private)
engine.update(randomBytes[0])
engine.update(randomBytes, 1, randomBytes.size - 1)
// Now verify the signature
val signature = engine.sign()
engine.initVerify(publicKey)
engine.update(randomBytes)
assertTrue { engine.verify(signature) }
}
/**
* Verify that signing with an X509Key wrapped EdDSA key fails when using the underlying EdDSAEngine.
*/
@Test
fun `sign and verify with X509Key and old engine fails`() {
val engine = EdDSAEngine()
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED + 1))
val publicKey = toX509Key(keyPair.public as EdDSAPublicKey)
val randomBytes = ByteArray(TEST_DATA_SIZE)
Random(SEED + 1).nextBytes(randomBytes)
engine.initSign(keyPair.private)
engine.update(randomBytes[0])
engine.update(randomBytes, 1, randomBytes.size - 1)
// Now verify the signature
val signature = engine.sign()
assertFailsWith<InvalidKeyException> {
engine.initVerify(publicKey)
engine.update(randomBytes)
engine.verify(signature)
}
}
/** Verify will fail if the input public key cannot be converted to EdDSA public key. */
@Test
fun `verify with non-supported key type fails`() {
val engine = EdDSAEngine()
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger.valueOf(SEED))
assertFailsWith<InvalidKeyException> { engine.initVerify(keyPair.public) }
}
}

File diff suppressed because one or more lines are too long

View File

@ -84,7 +84,7 @@ complexity:
ComplexMethod: ComplexMethod:
active: true active: true
threshold: 10 threshold: 10
ignoreSingleWhenExpression: false ignoreSingleWhenExpression: true
LabeledExpression: LabeledExpression:
active: false active: false
LargeClass: LargeClass:
@ -92,7 +92,7 @@ complexity:
threshold: 150 threshold: 150
LongMethod: LongMethod:
active: true active: true
threshold: 20 threshold: 120
LongParameterList: LongParameterList:
active: true active: true
threshold: 6 threshold: 6
@ -135,7 +135,7 @@ empty-blocks:
EmptyForBlock: EmptyForBlock:
active: true active: true
EmptyFunctionBlock: EmptyFunctionBlock:
active: true active: false
EmptyIfBlock: EmptyIfBlock:
active: true active: true
EmptyInitBlock: EmptyInitBlock:
@ -240,7 +240,7 @@ naming:
VariableNaming: VariableNaming:
active: true active: true
variablePattern: '[a-z][A-Za-z0-9]*' variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' privateVariablePattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
excludeClassPattern: '$^' excludeClassPattern: '$^'
performance: performance:
@ -321,7 +321,7 @@ style:
ignoreEnums: false ignoreEnums: false
MaxLineLength: MaxLineLength:
active: true active: true
maxLineLength: 120 maxLineLength: 140
excludePackageStatements: false excludePackageStatements: false
excludeImportStatements: false excludeImportStatements: false
MayBeConst: MayBeConst:
@ -331,7 +331,7 @@ style:
NestedClassesVisibility: NestedClassesVisibility:
active: false active: false
NewLineAtEndOfFile: NewLineAtEndOfFile:
active: true active: false
NoTabs: NoTabs:
active: false active: false
OptionalAbstractKeyword: OptionalAbstractKeyword:

View File

@ -59,6 +59,7 @@ task buildDockerFolder(dependsOn: [":node:capsule:buildCordaJAR", shadowJar]) {
from "src/bash/generate-config.sh" from "src/bash/generate-config.sh"
from "src/docker/DockerfileAL" from "src/docker/DockerfileAL"
from "src/docker/Dockerfile" from "src/docker/Dockerfile"
from "src/docker/Dockerfile11"
rename(cordaJar.name, "corda.jar") rename(cordaJar.name, "corda.jar")
rename(shadowJar.archivePath.name, "config-exporter.jar") rename(shadowJar.archivePath.name, "config-exporter.jar")
} }
@ -67,8 +68,8 @@ task buildDockerFolder(dependsOn: [":node:capsule:buildCordaJAR", shadowJar]) {
final String runTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) final String runTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
final String suffix = project.version.toString().toLowerCase().contains("snapshot") ? runTime : "RELEASE" final String suffix = project.version.toString().toLowerCase().contains("snapshot") ? runTime : "RELEASE"
final zuluBuildTags = ["corda/corda-zulu-${project.version.toString().toLowerCase()}:${suffix}", "corda/corda-zulu-${project.version.toString().toLowerCase()}:latest"] final zuluBuildTags = ["corda/corda-zulu-java${JavaVersion.current()}-${project.version.toString().toLowerCase()}:${suffix}", "corda/corda-zulu-java${JavaVersion.current()}-${project.version.toString().toLowerCase()}:latest"]
final correttoBuildTags = ["corda/corda-corretto-${project.version.toString().toLowerCase()}:${suffix}", "corda/corda-corretto-${project.version.toString().toLowerCase()}:latest"] final correttoBuildTags = ["corda/corda-corretto-java${JavaVersion.current()}-${project.version.toString().toLowerCase()}:${suffix}", "corda/corda-corretto-java${JavaVersion.current()}-${project.version.toString().toLowerCase()}:latest"]
task buildOfficialZuluDockerImage(type: DockerBuildImage, dependsOn: [buildDockerFolder]) { task buildOfficialZuluDockerImage(type: DockerBuildImage, dependsOn: [buildDockerFolder]) {
inputDir = new File(project.buildDir, "docker-temp") inputDir = new File(project.buildDir, "docker-temp")
@ -76,6 +77,12 @@ task buildOfficialZuluDockerImage(type: DockerBuildImage, dependsOn: [buildDocke
dockerFile = new File(new File(project.buildDir, "docker-temp"), "Dockerfile") dockerFile = new File(new File(project.buildDir, "docker-temp"), "Dockerfile")
} }
task buildOfficialZuluJDK11DockerImage(type: DockerBuildImage, dependsOn: [buildDockerFolder]) {
inputDir = new File(project.buildDir, "docker-temp")
tags = zuluBuildTags
dockerFile = new File(new File(project.buildDir, "docker-temp"), "Dockerfile11")
}
task buildOfficialCorrettoDockerImage(type: DockerBuildImage, dependsOn: [buildDockerFolder]) { task buildOfficialCorrettoDockerImage(type: DockerBuildImage, dependsOn: [buildDockerFolder]) {
inputDir = new File(project.buildDir, "docker-temp") inputDir = new File(project.buildDir, "docker-temp")
tags = correttoBuildTags tags = correttoBuildTags
@ -90,6 +97,14 @@ task pushZuluLatestTag('type': DockerPushImage, dependsOn: [buildOfficialZuluDoc
imageName = zuluBuildTags[1] imageName = zuluBuildTags[1]
} }
task pushZulu11TimeStampedTag('type': DockerPushImage, dependsOn: [buildOfficialZuluJDK11DockerImage]){
imageName = zuluBuildTags[0]
}
task pushZulu11LatestTag('type': DockerPushImage, dependsOn: [buildOfficialZuluJDK11DockerImage]){
imageName = zuluBuildTags[1]
}
task pushCorrettoTimeStampedTag('type': DockerPushImage, dependsOn: [buildOfficialCorrettoDockerImage]){ task pushCorrettoTimeStampedTag('type': DockerPushImage, dependsOn: [buildOfficialCorrettoDockerImage]){
imageName = correttoBuildTags[0] imageName = correttoBuildTags[0]
} }

View File

@ -0,0 +1,28 @@
# Build and publish an Azul Zulu patched JDK 11 to the R3 Azure docker registry as follows:
# colljos@ci-agent-101l:~$ cd /home/colljos/azul/case17645
# $docker build . -f Dockerfile.zulu-sa-jdk-11-patch --no-cache -t azul/zulu-sa-jdk:11.0.3_7_LTS
# $docker tag azul/zulu-sa-jdk:11.0.3_7_LTS corda.azurecr.io/jdk/azul/zulu-sa-jdk:11.0.3_7_LTS
# $docker login -u corda corda.azurecr.io
# docker push corda.azurecr.io/jdk/azul/zulu-sa-jdk:11.0.3_7_LTS
# Remember to set the DOCKER env variables accordingly to access the R3 Azure docker registry:
# export DOCKER_URL=https://corda.azurecr.io
# export DOCKER_USERNAME=<username>
# export DOCKER_PASSWORD=<password>
RUN addgroup corda && adduser --ingroup corda --disabled-password -gecos "" --shell /bin/bash corda
COPY zulu11.31.16-sa-jdk11.0.3-linux_x64.tar /opt
RUN tar xvf /opt/zulu11.31.16-sa-jdk11.0.3-linux_x64.tar -C /opt && ln -s /opt/zulu11.31.16-sa-jdk11.0.3-linux_x64 /opt/jdk
RUN rm /opt/zulu11.31.16-sa-jdk11.0.3-linux_x64.tar && \
chown -R corda /opt/zulu11.31.16-sa-jdk11.0.3-linux_x64 && \
chgrp -R corda /opt/zulu11.31.16-sa-jdk11.0.3-linux_x64
# Set environment
ENV JAVA_HOME /opt/jdk
ENV PATH ${PATH}:${JAVA_HOME}/bin
CMD ["java", "-version"]

View File

@ -0,0 +1,78 @@
# Using Azul Zulu patched JDK 11 (local built and published docker image)
# colljos@ci-agent-101l:~$ jdk11azul
# openjdk version "11.0.3" 2019-04-16 LTS
# OpenJDK Runtime Environment Zulu11.31+16-SA (build 11.0.3+7-LTS)
# OpenJDK 64-Bit Server VM Zulu11.31+16-SA (build 11.0.3+7-LTS, mixed mode)
# Remember to set the DOCKER env variables accordingly to access the R3 Azure docker registry:
# export DOCKER_URL=https://corda.azurecr.io
# export DOCKER_USERNAME=<username>
# export DOCKER_PASSWORD=<password>
FROM corda.azurecr.io/jdk/azul/zulu-sa-jdk:11.0.3_7_LTS
## Add packages, clean cache, create dirs, create corda user and change ownership
RUN apt-get update && \
apt-get -y upgrade && \
apt-get -y install bash curl unzip && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /opt/corda/cordapps && \
mkdir -p /opt/corda/persistence && \
mkdir -p /opt/corda/certificates && \
mkdir -p /opt/corda/drivers && \
mkdir -p /opt/corda/logs && \
mkdir -p /opt/corda/bin && \
mkdir -p /opt/corda/additional-node-infos && \
mkdir -p /etc/corda && \
chown -R corda /opt/corda && \
chgrp -R corda /opt/corda && \
chown -R corda /etc/corda && \
chgrp -R corda /etc/corda && \
chown -R corda /opt/corda && \
chgrp -R corda /opt/corda && \
chown -R corda /etc/corda && \
chgrp -R corda /etc/corda
ENV CORDAPPS_FOLDER="/opt/corda/cordapps" \
PERSISTENCE_FOLDER="/opt/corda/persistence" \
CERTIFICATES_FOLDER="/opt/corda/certificates" \
DRIVERS_FOLDER="/opt/corda/drivers" \
CONFIG_FOLDER="/etc/corda" \
MY_P2P_PORT=10200 \
MY_RPC_PORT=10201 \
MY_RPC_ADMIN_PORT=10202 \
PATH=$PATH:/opt/corda/bin \
JVM_ARGS="-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions " \
CORDA_ARGS=""
##CORDAPPS FOLDER
VOLUME ["/opt/corda/cordapps"]
##PERSISTENCE FOLDER
VOLUME ["/opt/corda/persistence"]
##CERTS FOLDER
VOLUME ["/opt/corda/certificates"]
##OPTIONAL JDBC DRIVERS FOLDER
VOLUME ["/opt/corda/drivers"]
##LOG FOLDER
VOLUME ["/opt/corda/logs"]
##ADDITIONAL NODE INFOS FOLDER
VOLUME ["/opt/corda/additional-node-infos"]
##CONFIG LOCATION
VOLUME ["/etc/corda"]
##CORDA JAR
COPY --chown=corda:corda corda.jar /opt/corda/bin/corda.jar
##CONFIG MANIPULATOR JAR
COPY --chown=corda:corda config-exporter.jar /opt/corda/config-exporter.jar
##CONFIG GENERATOR SHELL SCRIPT
COPY --chown=corda:corda generate-config.sh /opt/corda/bin/config-generator
##CORDA RUN SCRIPT
COPY --chown=corda:corda run-corda.sh /opt/corda/bin/run-corda
##BASE CONFIG FOR GENERATOR
COPY --chown=corda:corda starting-node.conf /opt/corda/starting-node.conf
USER "corda"
EXPOSE ${MY_P2P_PORT} ${MY_RPC_PORT} ${MY_RPC_ADMIN_PORT}
WORKDIR /opt/corda
CMD ["run-corda"]

View File

@ -6,6 +6,7 @@ release, see :doc:`app-upgrade-notes`.
Unreleased Unreleased
---------- ----------
* Support for Java 11 (compatibility mode). Please read https://github.com/corda/corda/pull/5356.
* Updating FinalityFlow with functionality to indicate the appropriate StatesToRecord. This allows the initiating party to record states * Updating FinalityFlow with functionality to indicate the appropriate StatesToRecord. This allows the initiating party to record states
from transactions which they are proposing, but are not necessarily participants of. from transactions which they are proposing, but are not necessarily participants of.
@ -47,8 +48,9 @@ Unreleased
* Added ``nodeDiagnosticInfo`` to the RPC API. The new RPC is also available as the ``run nodeDiagnosticInfo`` command executable from * Added ``nodeDiagnosticInfo`` to the RPC API. The new RPC is also available as the ``run nodeDiagnosticInfo`` command executable from
the Corda shell. It retrieves version information about the Corda platform and the CorDapps installed on the node. the Corda shell. It retrieves version information about the Corda platform and the CorDapps installed on the node.
* ``CordaRPCClient.start`` has a new ``gracefulReconnect`` parameter. When ``true`` (the default is ``false``) it will cause the RPC client * ``CordaRPCClient.start`` has a new ``gracefulReconnect`` parameter. The class ``GracefulReconnect`` takes two lambdas - one for callbacks
to try to automatically reconnect to the node on disconnect. Further any ``Observable`` s previously created will continue to vend new on disconnect, and one for callbacks on reconnection. When provided (ie. the ``gracefulReconnect`` parameter is not null) the RPC client
will to try to automatically reconnect to the node on disconnect. Further any ``Observable`` s previously created will continue to vend new
events on reconnect. events on reconnect.
.. note:: This is only best-effort and there are no guarantees of reliability. .. note:: This is only best-effort and there are no guarantees of reliability.

View File

@ -373,10 +373,29 @@ More specifically, the behaviour in the second case is a bit more subtle:
You can enable this graceful form of reconnection by using the ``gracefulReconnect`` parameter in the following way: You can enable this graceful form of reconnection by using the ``gracefulReconnect`` parameter in the following way:
.. sourcecode:: kotlin .. container:: codeset
val cordaClient = CordaRPCClient(nodeRpcAddress) .. sourcecode:: kotlin
val cordaRpcOps = cordaClient.start(rpcUserName, rpcUserPassword, gracefulReconnect = true).proxy
val gracefulReconnect = GracefulReconnect(onDisconnect={/*insert disconnect handling*/}, onReconnect{/*insert reconnect handling*/})
val cordaClient = CordaRPCClient(nodeRpcAddress)
val cordaRpcOps = cordaClient.start(rpcUserName, rpcUserPassword, gracefulReconnect = gracefulReconnect).proxy
.. sourcecode:: java
private void onDisconnect() {
// Insert implementation
}
private void onReconnect() {
// Insert implementation
}
void method() {
GracefulReconnect gracefulReconnect = new GracefulReconnect(this::onDisconnect, this::onReconnect);
CordaRPCClient cordaClient = new CordaRPCClient(nodeRpcAddress);
CordaRPCConnection cordaRpcOps = cordaClient.start(rpcUserName, rpcUserPassword, gracefulReconnect);
}
Retrying flow invocations Retrying flow invocations
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -26,10 +26,21 @@ sourceSets {
compileTestJava.dependsOn tasks.getByPath(':node:capsule:buildCordaJAR') compileTestJava.dependsOn tasks.getByPath(':node:capsule:buildCordaJAR')
dependencies { dependencies {
// Cordformation needs a SLF4J implementation when executing the Network
// Bootstrapper, but Log4J doesn't shutdown completely from within Gradle.
// Use a much simpler SLF4J implementation here instead.
cordaRuntime "org.slf4j:slf4j-simple:$slf4j_version"
compile project(':core') compile project(':core')
compile project(':client:jfx') compile project(':client:jfx')
compile project(':node-driver') compile (project(':node-driver')) {
compile project(':webserver') // We already have a SLF4J implementation on our runtime classpath,
// and we don't need another one.
exclude group: 'org.apache.logging.log4j'
}
compile (project(':webserver')) {
exclude group: "org.apache.logging.log4j"
}
testCompile project(':test-utils') testCompile project(':test-utils')
compile "org.graphstream:gs-core:1.3" compile "org.graphstream:gs-core:1.3"

View File

@ -4,7 +4,7 @@ import net.corda.core.contracts.LinearState
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.packageName import net.corda.core.internal.packageName_
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
@ -34,7 +34,7 @@ class WorkflowTransactionBuildTutorialTest {
@Before @Before
fun setup() { fun setup() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf(javaClass.packageName)) mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf(javaClass.packageName_))
aliceNode = mockNet.createPartyNode(ALICE_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME) bobNode = mockNet.createPartyNode(BOB_NAME)
alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME) alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)

View File

@ -4,6 +4,7 @@ import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.packageName import net.corda.core.internal.packageName
import net.corda.core.internal.packageName_
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.*
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
@ -31,7 +32,7 @@ class CustomVaultQueryTest {
@Before @Before
fun setup() { fun setup() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance", IOUFlow::class.packageName, javaClass.packageName, "com.template")) mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance", IOUFlow::class.packageName, javaClass.packageName_, "com.template"))
nodeA = mockNet.createPartyNode() nodeA = mockNet.createPartyNode()
nodeB = mockNet.createPartyNode() nodeB = mockNet.createPartyNode()
notary = mockNet.defaultNotaryIdentity notary = mockNet.defaultNotaryIdentity

View File

@ -36,7 +36,7 @@ Welcome to Corda !
.. _`繁體中文 (Traditional Chinese)`: _static/corda-introductory-whitepaper-zht.pdf .. _`繁體中文 (Traditional Chinese)`: _static/corda-introductory-whitepaper-zht.pdf
.. _`日本語 (Japanese)`: _static/corda-introductory-whitepaper-jp.pdf .. _`日本語 (Japanese)`: _static/corda-introductory-whitepaper-jp.pdf
.. _`download the PDF`: _static/corda-developer-site.pdf .. _`download the PDF`: _static/corda-developer-site.pdf
.. only:: latex .. only:: latex
Welcome to Corda, a platform for building decentralized applications. This guidebook covers everything you need to know to create Welcome to Corda, a platform for building decentralized applications. This guidebook covers everything you need to know to create
@ -147,3 +147,4 @@ Welcome to Corda !
deterministic-modules.rst deterministic-modules.rst
design/design-docs-index.rst design/design-docs-index.rst
changelog changelog
legal-info

3813
docs/source/legal-info.rst Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -38,6 +38,9 @@ dependencies {
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version" compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
runtime 'com.mattbertolini:liquibase-slf4j:2.0.0' runtime 'com.mattbertolini:liquibase-slf4j:2.0.0'
// JDK11: required by Quasar at run-time
runtime "com.esotericsoftware:kryo:4.0.2"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}" testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"

View File

@ -107,7 +107,7 @@ class AttachmentsClassLoaderStaticContractTests {
@Test @Test
fun `verify that contract DummyContract is in classPath`() { fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName(ATTACHMENT_PROGRAM_ID) val contractClass = Class.forName(ATTACHMENT_PROGRAM_ID)
assertThat(contractClass.newInstance()).isInstanceOf(Contract::class.java) assertThat(contractClass.getDeclaredConstructor().newInstance()).isInstanceOf(Contract::class.java)
} }
private fun cordappLoaderForPackages(packages: Collection<String>): CordappLoader { private fun cordappLoaderForPackages(packages: Collection<String>): CordappLoader {

View File

@ -70,7 +70,9 @@ class TlsDiffProtocolsTest(private val serverAlgo: String, private val clientAlg
CIPHER_SUITES_ALL(arrayOf( CIPHER_SUITES_ALL(arrayOf(
// 1.3 only // 1.3 only
"TLS_AES_128_GCM_SHA256", "TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256", // Unsupported CipherSuite: TLS_CHACHA20_POLY1305_SHA256 (java version "11.0.2" 2019-01-15 LTS)
// Works with: openjdk version "12.0.1" 2019-04-16 (OpenJDK Runtime Environment (build 12.0.1+12))
// "TLS_CHACHA20_POLY1305_SHA256",
// 1.2 only // 1.2 only
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"

View File

@ -40,7 +40,6 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import sun.security.rsa.RSAPrivateCrtKeyImpl
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.io.IOException import java.io.IOException
@ -86,7 +85,6 @@ class X509UtilitiesTest {
Triple(ECDSA_SECP256K1_SHA256,java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java), Triple(ECDSA_SECP256K1_SHA256,java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java),
Triple(EDDSA_ED25519_SHA512, EdDSAPrivateKey::class.java, EdDSAPrivateKey::class.java), Triple(EDDSA_ED25519_SHA512, EdDSAPrivateKey::class.java, EdDSAPrivateKey::class.java),
// By default, JKS returns SUN RSA key. // By default, JKS returns SUN RSA key.
Triple(RSA_SHA256, RSAPrivateCrtKeyImpl::class.java, BCRSAPrivateCrtKey::class.java),
Triple(SPHINCS256_SHA256, BCSphincs256PrivateKey::class.java, BCSphincs256PrivateKey::class.java) Triple(SPHINCS256_SHA256, BCSphincs256PrivateKey::class.java, BCSphincs256PrivateKey::class.java)
) )
} }

View File

@ -69,10 +69,10 @@ sourceSets {
} }
jib.container { jib.container {
mainClass = "net.corda.node.Corda" mainClass = "net.corda.node.Corda"
args = ['--log-to-console', '--no-local-shell', '--config-file=/config/node.conf'] args = ['--log-to-console', '--no-local-shell', '--config-file=/config/node.conf']
// The Groovy string needs to be converted to a `java.lang.String` below. // The Groovy string needs to be converted to a `java.lang.String` below.
jvmFlags = ['-Xmx1g', "-javaagent:/app/libs/quasar-core-${quasar_version}-jdk8.jar".toString()] jvmFlags = ['-Xmx1g', "-javaagent:/app/libs/quasar-core-${quasar_version}.jar".toString()]
} }
// Use manual resource copying of log4j2.xml rather than source sets. // Use manual resource copying of log4j2.xml rather than source sets.
@ -194,10 +194,7 @@ dependencies {
// Integration test helpers // Integration test helpers
integrationTestCompile "junit:junit:$junit_version" integrationTestCompile "junit:junit:$junit_version"
integrationTestCompile "org.assertj:assertj-core:${assertj_version}" integrationTestCompile "org.assertj:assertj-core:${assertj_version}"
// AgentLoader: dynamic loading of JVM agents
compile group: 'com.ea.agentloader', name: 'ea-agent-loader', version: "${eaagentloader_version}"
// BFT-Smart dependencies // BFT-Smart dependencies
compile 'com.github.bft-smart:library:master-v1.1-beta-g6215ec8-87' compile 'com.github.bft-smart:library:master-v1.1-beta-g6215ec8-87'
@ -221,7 +218,10 @@ dependencies {
compile "org.jolokia:jolokia-jvm:${jolokia_version}:agent" compile "org.jolokia:jolokia-jvm:${jolokia_version}:agent"
// Optional New Relic JVM reporter, used to push metrics to the configured account associated with a newrelic.yml configuration. See https://mvnrepository.com/artifact/com.palominolabs.metrics/metrics-new-relic // Optional New Relic JVM reporter, used to push metrics to the configured account associated with a newrelic.yml configuration. See https://mvnrepository.com/artifact/com.palominolabs.metrics/metrics-new-relic
compile "com.palominolabs.metrics:metrics-new-relic:${metrics_new_relic_version}" compile "com.palominolabs.metrics:metrics-new-relic:${metrics_new_relic_version}"
// Required by JVMAgentUtil (x-compatible java 8 & 11 agent lookup mechanism)
compile files("${System.properties['java.home']}/../lib/tools.jar")
testCompile(project(':test-cli')) testCompile(project(':test-cli'))
testCompile(project(':test-utils')) testCompile(project(':test-utils'))
@ -238,6 +238,11 @@ tasks.withType(JavaCompile) {
options.compilerArgs << '-proc:none' options.compilerArgs << '-proc:none'
} }
tasks.withType(Test) {
if (JavaVersion.current() == JavaVersion.VERSION_11)
jvmArgs '-Djdk.attach.allowAttachSelf=true'
}
task integrationTest(type: Test) { task integrationTest(type: Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath

View File

@ -52,10 +52,12 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
applicationId = "net.corda.node.Corda" applicationId = "net.corda.node.Corda"
// See experimental/quasar-hook/README.md for how to generate. // See experimental/quasar-hook/README.md for how to generate.
def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)" def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)"
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"] javaAgents = quasar_classifier == null ? ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}"] : ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}"]
systemProperties['visualvm.display.name'] = 'Corda' systemProperties['visualvm.display.name'] = 'Corda'
minJavaVersion = '1.8.0' if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
minUpdateVersion['1.8'] = java8_minUpdateVersion minJavaVersion = '1.8.0'
minUpdateVersion['1.8'] = java8_minUpdateVersion
}
caplets = ['CordaCaplet'] caplets = ['CordaCaplet']
// JVM configuration: // JVM configuration:
@ -65,6 +67,8 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
// //
// If you change these flags, please also update Driver.kt // If you change these flags, please also update Driver.kt
jvmArgs = ['-Xmx512m', '-XX:+UseG1GC'] jvmArgs = ['-Xmx512m', '-XX:+UseG1GC']
if (JavaVersion.current() == JavaVersion.VERSION_11)
jvmArgs += ['-Djdk.attach.allowAttachSelf=true']
} }
} }

View File

@ -9,6 +9,7 @@ import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.*;
import java.util.stream.Stream;
public class CordaCaplet extends Capsule { public class CordaCaplet extends Capsule {
@ -185,8 +186,8 @@ public class CordaCaplet extends Capsule {
private static void checkJavaVersion() { private static void checkJavaVersion() {
String version = System.getProperty("java.version"); String version = System.getProperty("java.version");
if (version == null || !version.startsWith("1.8")) { if (version == null || Stream.of("1.8", "11").noneMatch(version::startsWith)) {
System.err.printf("Error: Unsupported Java version %s; currently only version 1.8 is supported.\n", version); System.err.printf("Error: Unsupported Java version %s; currently only version 1.8 or 11 is supported.\n", version);
System.exit(1); System.exit(1);
} }
} }

View File

@ -1,7 +1,7 @@
package net.corda.node.services.rpc package net.corda.node.services.rpc
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.client.rpc.GracefulReconnect
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps import net.corda.client.rpc.internal.ReconnectingCordaRPCOps
import net.corda.client.rpc.notUsed import net.corda.client.rpc.notUsed
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
@ -12,7 +12,10 @@ import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.builder import net.corda.core.node.services.vault.builder
import net.corda.core.utilities.* import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1
@ -36,8 +39,10 @@ import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
import kotlin.test.currentStackTrace
/** /**
* This is a stress test for the rpc reconnection logic, which triggers failures in a probabilistic way. * This is a stress test for the rpc reconnection logic, which triggers failures in a probabilistic way.
@ -114,10 +119,21 @@ class RpcReconnectTests {
val baseAmount = Amount.parseCurrency("0 USD") val baseAmount = Amount.parseCurrency("0 USD")
val issuerRef = OpaqueBytes.of(0x01) val issuerRef = OpaqueBytes.of(0x01)
var numDisconnects = 0
var numReconnects = 0
val maxStackOccurrences = AtomicInteger()
val addressesForRpc = addresses.map { it.proxyAddress } val addressesForRpc = addresses.map { it.proxyAddress }
// DOCSTART rpcReconnectingRPC // DOCSTART rpcReconnectingRPC
val onReconnect = {
numReconnects++
// We only expect to see a single reconnectOnError in the stack trace. Otherwise we're in danger of stack overflow recursion
maxStackOccurrences.set(max(maxStackOccurrences.get(), currentStackTrace().count { it.methodName == "reconnectOnError" }))
Unit
}
val reconnect = GracefulReconnect(onDisconnect = { numDisconnects++ }, onReconnect = onReconnect)
val client = CordaRPCClient(addressesForRpc) val client = CordaRPCClient(addressesForRpc)
val bankAReconnectingRpc = client.start(demoUser.username, demoUser.password, gracefulReconnect = true).proxy as ReconnectingCordaRPCOps val bankAReconnectingRpc = client.start(demoUser.username, demoUser.password, gracefulReconnect = reconnect).proxy as ReconnectingCordaRPCOps
// DOCEND rpcReconnectingRPC // DOCEND rpcReconnectingRPC
// Observe the vault and collect the observations. // Observe the vault and collect the observations.
@ -266,6 +282,11 @@ class RpcReconnectTests {
val nrFailures = nrRestarts.get() val nrFailures = nrRestarts.get()
log.info("Checking results after $nrFailures restarts.") log.info("Checking results after $nrFailures restarts.")
// We should get one disconnect and one reconnect for each failure
assertThat(numDisconnects).isEqualTo(numReconnects)
assertThat(numReconnects).isLessThanOrEqualTo(nrFailures)
assertThat(maxStackOccurrences.get()).isLessThan(2)
// Query the vault and check that states were created for all flows. // Query the vault and check that states were created for all flows.
fun readCashStates() = bankAReconnectingRpc fun readCashStates() = bankAReconnectingRpc
.vaultQueryByWithPagingSpec(Cash.State::class.java, QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.CONSUMED), PageSpecification(1, 10000)) .vaultQueryByWithPagingSpec(Cash.State::class.java, QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.CONSUMED), PageSpecification(1, 10000))

View File

@ -88,13 +88,14 @@ import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import net.corda.nodeapi.internal.persistence.* import net.corda.nodeapi.internal.persistence.*
import net.corda.tools.shell.InteractiveShell import net.corda.tools.shell.InteractiveShell
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.jolokia.jvmagent.JolokiaServer
import org.jolokia.jvmagent.JolokiaServerConfig
import org.slf4j.Logger import org.slf4j.Logger
import rx.Observable import rx.Observable
import rx.Scheduler import rx.Scheduler
import java.io.IOException import java.io.IOException
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStoreException import java.security.KeyStoreException
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
@ -331,7 +332,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
log.info("Node starting up ...") log.info("Node starting up ...")
val trustRoot = initKeyStores() val trustRoot = initKeyStores()
initialiseJVMAgents() initialiseJolokia()
schemaService.mappedSchemasWarnings().forEach { schemaService.mappedSchemasWarnings().forEach {
val warning = it.toWarning() val warning = it.toWarning()
@ -992,19 +993,15 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return NodeVaultService(platformClock, keyManagementService, services, database, schemaService, cordappLoader.appClassLoader) return NodeVaultService(platformClock, keyManagementService, services, database, schemaService, cordappLoader.appClassLoader)
} }
/** Load configured JVM agents */ // JDK 11: switch to directly instantiating jolokia server (rather than indirectly via dynamically self attaching Java Agents,
private fun initialiseJVMAgents() { // which is no longer supported from JDK 9 onwards (https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8180425).
// No longer need to use https://github.com/electronicarts/ea-agent-loader either (which is also deprecated)
private fun initialiseJolokia() {
configuration.jmxMonitoringHttpPort?.let { port -> configuration.jmxMonitoringHttpPort?.let { port ->
requireNotNull(NodeBuildProperties.JOLOKIA_AGENT_VERSION) { val config = JolokiaServerConfig(mapOf("port" to port.toString()))
"'jolokiaAgentVersion' missing from build properties" val server = JolokiaServer(config, false)
} log.info("Starting Jolokia server on HTTP port: $port")
log.info("Starting Jolokia agent on HTTP port: $port") server.start()
val libDir = Paths.get(configuration.baseDirectory.toString(), "drivers")
val jarFilePath = JVMAgentRegistry.resolveAgentJar(
"jolokia-jvm-${NodeBuildProperties.JOLOKIA_AGENT_VERSION}-agent.jar", libDir)
?: throw Error("Unable to locate agent jar file")
log.info("Agent jar file: $jarFilePath")
JVMAgentRegistry.attach("jolokia", "port=$port", jarFilePath)
} }
} }

View File

@ -43,7 +43,7 @@ object DataSourceFactory {
dataSource dataSource
} else { } else {
// Basic init for the one test that wants to go via this API but without starting a HikariPool: // Basic init for the one test that wants to go via this API but without starting a HikariPool:
(Class.forName(hikariProperties.getProperty("dataSourceClassName")).newInstance() as DataSource).also { (Class.forName(hikariProperties.getProperty("dataSourceClassName")).getDeclaredConstructor().newInstance() as DataSource).also {
PropertyElf.setTargetFromProperties(it, config.dataSourceProperties) PropertyElf.setTargetFromProperties(it, config.dataSourceProperties)
} }
} }

View File

@ -159,10 +159,14 @@ open class Node(configuration: NodeConfiguration,
} }
private fun hasMinimumJavaVersion(): Boolean { private fun hasMinimumJavaVersion(): Boolean {
// when the ext.java8_minUpdateVersion gradle constant changes, so must this check // JDK 11: review naming convention and checking of 'minUpdateVersion' and 'distributionType` (OpenJDK, Oracle, Zulu, AdoptOpenJDK, Cornetto)
return try { return try {
val update = getJavaUpdateVersion(SystemUtils.JAVA_VERSION) // To filter out cases like 1.8.0_202-ea if (SystemUtils.IS_JAVA_11)
SystemUtils.IS_JAVA_1_8 && update >= 171 return true
else {
val update = getJavaUpdateVersion(SystemUtils.JAVA_VERSION) // To filter out cases like 1.8.0_202-ea
(SystemUtils.IS_JAVA_1_8 && update >= 171)
}
} catch (e: NumberFormatException) { // custom JDKs may not have the update version (e.g. 1.8.0-adoptopenjdk) } catch (e: NumberFormatException) { // custom JDKs may not have the update version (e.g. 1.8.0-adoptopenjdk)
false false
} }

View File

@ -24,6 +24,7 @@ import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.lo
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.shouldStartLocalShell import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon import net.corda.node.services.config.shouldStartSSHDaemon
import net.corda.node.utilities.JVMAgentUtil.getJvmAgentProperties
import net.corda.node.utilities.registration.NodeRegistrationException import net.corda.node.utilities.registration.NodeRegistrationException
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
@ -32,7 +33,6 @@ import net.corda.tools.shell.InteractiveShell
import org.fusesource.jansi.Ansi import org.fusesource.jansi.Ansi
import org.slf4j.bridge.SLF4JBridgeHandler import org.slf4j.bridge.SLF4JBridgeHandler
import picocli.CommandLine.Mixin import picocli.CommandLine.Mixin
import sun.misc.VMSupport
import java.io.IOException import java.io.IOException
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
@ -245,12 +245,13 @@ open class NodeStartup : NodeStartupLogging {
logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
logger.info("Main class: ${NodeConfiguration::class.java.location.toURI().path}") logger.info("Main class: ${NodeConfiguration::class.java.location.toURI().path}")
logger.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}") logger.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
logger.info("bootclasspath: ${info.bootClassPath}") // JDK 11 (bootclasspath no longer supported from JDK 9)
if (info.isBootClassPathSupported) logger.info("bootclasspath: ${info.bootClassPath}")
logger.info("classpath: ${info.classPath}") logger.info("classpath: ${info.classPath}")
logger.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}") logger.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
logger.info("Machine: ${lookupMachineNameAndMaybeWarn()}") logger.info("Machine: ${lookupMachineNameAndMaybeWarn()}")
logger.info("Working Directory: ${cmdLineOptions.baseDirectory}") logger.info("Working Directory: ${cmdLineOptions.baseDirectory}")
val agentProperties = VMSupport.getAgentProperties() val agentProperties = getJvmAgentProperties(logger)
if (agentProperties.containsKey("sun.jdwp.listenerAddress")) { if (agentProperties.containsKey("sun.jdwp.listenerAddress")) {
logger.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}") logger.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}")
} }
@ -467,6 +468,8 @@ interface NodeStartupLogging {
error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.") error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.")
error is Errors.NativeIoException && error.message?.contains("Can't assign requested address") == true -> error.logAsExpected("Exception during node startup. Check that addresses in node config resolve correctly.") error is Errors.NativeIoException && error.message?.contains("Can't assign requested address") == true -> error.logAsExpected("Exception during node startup. Check that addresses in node config resolve correctly.")
error is UnresolvedAddressException -> error.logAsExpected("Exception during node startup. Check that addresses in node config resolve correctly.") error is UnresolvedAddressException -> error.logAsExpected("Exception during node startup. Check that addresses in node config resolve correctly.")
error is java.nio.file.AccessDeniedException -> error.logAsExpected("Exception during node startup. Corda started with insufficient privileges to access ${error.file}")
error is java.nio.file.NoSuchFileException -> error.logAsExpected("Exception during node startup. Corda cannot find file ${error.file}")
error.isOpenJdkKnownIssue() -> error.logAsExpected("Exception during node startup - ${error.message}. This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.") error.isOpenJdkKnownIssue() -> error.logAsExpected("Exception during node startup - ${error.message}. This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
else -> error.logAsUnexpected("Exception during node startup") else -> error.logAsUnexpected("Exception during node startup")
} }

View File

@ -2,7 +2,6 @@ package net.corda.node.internal.artemis
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.internal.security.Password import net.corda.node.internal.security.Password
import net.corda.node.internal.security.RPCSecurityManager import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.services.rpc.LoginListener import net.corda.node.services.rpc.LoginListener
@ -23,7 +22,6 @@ import javax.security.auth.callback.UnsupportedCallbackException
import javax.security.auth.login.FailedLoginException import javax.security.auth.login.FailedLoginException
import javax.security.auth.login.LoginException import javax.security.auth.login.LoginException
import javax.security.auth.spi.LoginModule import javax.security.auth.spi.LoginModule
import javax.security.cert.X509Certificate
/** /**
* *
@ -120,8 +118,9 @@ class BrokerJaasLoginModule : BaseBrokerJaasLoginModule() {
// The Main authentication logic, responsible for running all the configured checks for each user type // The Main authentication logic, responsible for running all the configured checks for each user type
// and return the actual User and principals // and return the actual User and principals
private fun authenticateAndAuthorise(username: String, certificates: Array<X509Certificate>?, password: String): Pair<String, List<RolePrincipal>> { @Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
fun requireTls(certificates: Array<X509Certificate>?) = requireNotNull(certificates) { "No client certificates presented." } private fun authenticateAndAuthorise(username: String, certificates: Array<javax.security.cert.X509Certificate>?, password: String): Pair<String, List<RolePrincipal>> {
fun requireTls(certificates: Array<javax.security.cert.X509Certificate>?) = requireNotNull(certificates) { "No client certificates presented." }
return when (username) { return when (username) {
ArtemisMessagingComponent.NODE_P2P_USER -> { ArtemisMessagingComponent.NODE_P2P_USER -> {
@ -174,7 +173,8 @@ abstract class BaseBrokerJaasLoginModule : LoginModule {
protected lateinit var callbackHandler: CallbackHandler protected lateinit var callbackHandler: CallbackHandler
protected val principals = ArrayList<Principal>() protected val principals = ArrayList<Principal>()
protected fun getUsernamePasswordAndCerts(): Triple<String, String, Array<X509Certificate>?> { @Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
protected fun getUsernamePasswordAndCerts(): Triple<String, String, Array<javax.security.cert.X509Certificate>?> {
val nameCallback = NameCallback("Username: ") val nameCallback = NameCallback("Username: ")
val passwordCallback = PasswordCallback("Password: ", false) val passwordCallback = PasswordCallback("Password: ", false)
val certificateCallback = CertificateCallback() val certificateCallback = CertificateCallback()

View File

@ -3,13 +3,13 @@ package net.corda.node.internal.artemis
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import java.security.KeyStore import java.security.KeyStore
import javax.security.cert.CertificateException import java.security.cert.CertificateException
import javax.security.cert.X509Certificate
sealed class CertificateChainCheckPolicy { sealed class CertificateChainCheckPolicy {
@FunctionalInterface @FunctionalInterface
interface Check { interface Check {
fun checkCertificateChain(theirChain: Array<X509Certificate>) @Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>)
} }
abstract fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check abstract fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check
@ -17,7 +17,8 @@ sealed class CertificateChainCheckPolicy {
object Any : CertificateChainCheckPolicy() { object Any : CertificateChainCheckPolicy() {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check { override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
return object : Check { return object : Check {
override fun checkCertificateChain(theirChain: Array<X509Certificate>) { @Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
// nothing to do here // nothing to do here
} }
} }
@ -28,7 +29,8 @@ sealed class CertificateChainCheckPolicy {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check { override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
val rootPublicKey = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA).publicKey val rootPublicKey = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA).publicKey
return object : Check { return object : Check {
override fun checkCertificateChain(theirChain: Array<X509Certificate>) { @Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
val theirRoot = theirChain.last().publicKey val theirRoot = theirChain.last().publicKey
if (rootPublicKey != theirRoot) { if (rootPublicKey != theirRoot) {
throw CertificateException("Root certificate mismatch, their root = $theirRoot") throw CertificateException("Root certificate mismatch, their root = $theirRoot")
@ -42,7 +44,8 @@ sealed class CertificateChainCheckPolicy {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check { override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
val ourPublicKey = keyStore.getCertificate(X509Utilities.CORDA_CLIENT_TLS).publicKey val ourPublicKey = keyStore.getCertificate(X509Utilities.CORDA_CLIENT_TLS).publicKey
return object : Check { return object : Check {
override fun checkCertificateChain(theirChain: Array<X509Certificate>) { @Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
val theirLeaf = theirChain.first().publicKey val theirLeaf = theirChain.first().publicKey
if (ourPublicKey != theirLeaf) { if (ourPublicKey != theirLeaf) {
throw CertificateException("Leaf certificate mismatch, their leaf = $theirLeaf") throw CertificateException("Leaf certificate mismatch, their leaf = $theirLeaf")
@ -56,7 +59,8 @@ sealed class CertificateChainCheckPolicy {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check { override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
val trustedPublicKeys = trustedAliases.map { trustStore.getCertificate(it).publicKey }.toSet() val trustedPublicKeys = trustedAliases.map { trustStore.getCertificate(it).publicKey }.toSet()
return object : Check { return object : Check {
override fun checkCertificateChain(theirChain: Array<X509Certificate>) { @Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
if (!theirChain.any { it.publicKey in trustedPublicKeys }) { if (!theirChain.any { it.publicKey in trustedPublicKeys }) {
throw CertificateException("Their certificate chain contained none of the trusted ones") throw CertificateException("Their certificate chain contained none of the trusted ones")
} }
@ -73,7 +77,8 @@ sealed class CertificateChainCheckPolicy {
class UsernameMustMatchCommonNameCheck : Check { class UsernameMustMatchCommonNameCheck : Check {
lateinit var username: String lateinit var username: String
override fun checkCertificateChain(theirChain: Array<X509Certificate>) { @Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
if (!theirChain.any { certificate -> CordaX500Name.parse(certificate.subjectDN.name).commonName == username }) { if (!theirChain.any { certificate -> CordaX500Name.parse(certificate.subjectDN.name).commonName == username }) {
throw CertificateException("Client certificate does not match login username.") throw CertificateException("Client certificate does not match login username.")
} }

View File

@ -26,7 +26,6 @@ import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.SignedNetworkParameters import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.SchemaMigration import net.corda.nodeapi.internal.persistence.SchemaMigration
import sun.reflect.generics.reflectiveObjects.NotImplementedException
import java.nio.file.Paths import java.nio.file.Paths
import java.time.Clock import java.time.Clock
import java.time.Duration import java.time.Duration
@ -49,11 +48,11 @@ class MigrationServicesForResolution(
val cordappLoader = SchemaMigration.loader.get() val cordappLoader = SchemaMigration.loader.get()
override fun getAppContext(): CordappContext { override fun getAppContext(): CordappContext {
throw NotImplementedException() TODO("not implemented")
} }
override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? { override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? {
throw NotImplementedException() TODO("not implemented")
} }
} }
private val cordappLoader = SchemaMigration.loader.get() private val cordappLoader = SchemaMigration.loader.get()

View File

@ -42,13 +42,12 @@ import org.objenesis.instantiator.ObjectInstantiator
import org.objenesis.strategy.InstantiatorStrategy import org.objenesis.strategy.InstantiatorStrategy
import org.objenesis.strategy.StdInstantiatorStrategy import org.objenesis.strategy.StdInstantiatorStrategy
import org.slf4j.Logger import org.slf4j.Logger
import sun.security.ec.ECPublicKeyImpl
import sun.security.provider.certpath.X509CertPath
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.FileInputStream import java.io.FileInputStream
import java.io.InputStream import java.io.InputStream
import java.lang.reflect.Modifier.isPublic import java.lang.reflect.Modifier.isPublic
import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
@ -97,7 +96,8 @@ object DefaultKryoCustomizer {
register(BufferedInputStream::class.java, InputStreamSerializer) register(BufferedInputStream::class.java, InputStreamSerializer)
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer) register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
noReferencesWithin<WireTransaction>() noReferencesWithin<WireTransaction>()
register(ECPublicKeyImpl::class.java, publicKeySerializer) register(PublicKey::class.java, publicKeySerializer)
register(PrivateKey::class.java, PrivateKeySerializer)
register(EdDSAPublicKey::class.java, publicKeySerializer) register(EdDSAPublicKey::class.java, publicKeySerializer)
register(EdDSAPrivateKey::class.java, PrivateKeySerializer) register(EdDSAPrivateKey::class.java, PrivateKeySerializer)
register(CompositeKey::class.java, publicKeySerializer) // Using a custom serializer for compactness register(CompositeKey::class.java, publicKeySerializer) // Using a custom serializer for compactness
@ -109,7 +109,6 @@ object DefaultKryoCustomizer {
register(Class::class.java, ClassSerializer) register(Class::class.java, ClassSerializer)
register(FileInputStream::class.java, InputStreamSerializer) register(FileInputStream::class.java, InputStreamSerializer)
register(CertPath::class.java, CertPathSerializer) register(CertPath::class.java, CertPathSerializer)
register(X509CertPath::class.java, CertPathSerializer)
register(BCECPrivateKey::class.java, PrivateKeySerializer) register(BCECPrivateKey::class.java, PrivateKeySerializer)
register(BCECPublicKey::class.java, publicKeySerializer) register(BCECPublicKey::class.java, publicKeySerializer)
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer) register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)

View File

@ -41,10 +41,10 @@ import net.corda.core.utilities.contextLogger
import net.corda.node.internal.NodeStartup import net.corda.node.internal.NodeStartup
import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.CheckpointStorage
import net.corda.node.services.statemachine.* import net.corda.node.services.statemachine.*
import net.corda.node.utilities.JVMAgentUtil.getJvmAgentProperties
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.serialization.internal.CheckpointSerializeAsTokenContextImpl import net.corda.serialization.internal.CheckpointSerializeAsTokenContextImpl
import net.corda.serialization.internal.withTokenContext import net.corda.serialization.internal.withTokenContext
import sun.misc.VMSupport
import java.nio.file.Path import java.nio.file.Path
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
@ -150,9 +150,9 @@ class CheckpointDumper(private val checkpointStorage: CheckpointStorage, private
} }
private fun checkpointAgentRunning(): Boolean { private fun checkpointAgentRunning(): Boolean {
val agentProperties = VMSupport.getAgentProperties() val agentProperties = getJvmAgentProperties(log)
return agentProperties.values.any { value -> return agentProperties.values.any { value ->
(value is String && value.contains("checkpoint-agent.jar")) value is String && value.contains("checkpoint-agent.jar")
} }
} }

View File

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

View File

@ -0,0 +1,25 @@
package net.corda.node.utilities
import com.sun.tools.attach.VirtualMachine
import org.slf4j.Logger
import java.lang.management.ManagementFactory
import java.util.*
object JVMAgentUtil {
/**
* Utility to attach to own VM at run-time and obtain agent details.
* In Java 9 this requires setting the following run-time jvm flag: -Djdk.attach.allowAttachSelf=true
* This mechanism supersedes the usage of VMSupport which is not available from Java 9 onwards.
*/
fun getJvmAgentProperties(log: Logger): Properties {
val jvmPid = ManagementFactory.getRuntimeMXBean().name.substringBefore('@')
return try {
val vm = VirtualMachine.attach(jvmPid)
return vm.agentProperties
} catch (e: Exception) {
log.warn("Unable to determine whether checkpoint agent is running: ${e.message}.\n" +
"You may need to pass in -Djdk.attach.allowAttachSelf=true if running on a Java 9 or later VM")
Properties()
}
}
}

View File

@ -23,7 +23,9 @@ import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.createNodeInfoAndSigned import net.corda.testing.internal.createNodeInfoAndSigned
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.apache.commons.lang3.SystemUtils
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
@ -32,6 +34,7 @@ import java.time.Duration
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNull import kotlin.test.assertNull
import kotlin.test.assertTrue
class NodeTest { class NodeTest {
@Rule @Rule
@ -143,6 +146,14 @@ class NodeTest {
} }
} }
// JDK 11 check
@Test
fun `test getJavaRuntimeVersion`() {
assertTrue(SystemUtils.IS_JAVA_1_8 || SystemUtils.IS_JAVA_11)
}
// JDK11: revisit (JDK 9+ uses different numbering scheme: see https://docs.oracle.com/javase/9/docs/api/java/lang/Runtime.Version.html)
@Ignore
@Test @Test
fun `test getJavaUpdateVersion`() { fun `test getJavaUpdateVersion`() {
assertThat(getJavaUpdateVersion("1.8.0_202-ea")).isEqualTo(202) assertThat(getJavaUpdateVersion("1.8.0_202-ea")).isEqualTo(202)

View File

@ -2,13 +2,13 @@ package net.corda.node.internal.cordapp
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.internal.packageName
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.testing.node.internal.cordappWithPackages import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Paths import java.nio.file.Paths
import net.corda.core.internal.packageName_
@InitiatingFlow @InitiatingFlow
class DummyFlow : FlowLogic<Unit>() { class DummyFlow : FlowLogic<Unit>() {
@ -79,7 +79,7 @@ class JarScanningCordappLoaderTest {
@Test @Test
fun `flows are loaded by loader`() { fun `flows are loaded by loader`() {
val jarFile = cordappWithPackages(javaClass.packageName).jarFile val jarFile = cordappWithPackages(javaClass.packageName_).jarFile
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jarFile.toUri().toURL())) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jarFile.toUri().toURL()))
// One cordapp from this source tree. In gradle it will also pick up the node jar. // One cordapp from this source tree. In gradle it will also pick up the node jar.

View File

@ -24,6 +24,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import org.apache.commons.lang3.SystemUtils
import org.assertj.core.api.Assertions.* import org.assertj.core.api.Assertions.*
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -353,8 +354,11 @@ class KryoTests(private val compression: CordaSerializationEncoding?) {
val obj = Holder(ByteArray(20000)) val obj = Holder(ByteArray(20000))
val uncompressedSize = obj.checkpointSerialize(context.withEncoding(null)).size val uncompressedSize = obj.checkpointSerialize(context.withEncoding(null)).size
val compressedSize = obj.checkpointSerialize(context.withEncoding(CordaSerializationEncoding.SNAPPY)).size val compressedSize = obj.checkpointSerialize(context.withEncoding(CordaSerializationEncoding.SNAPPY)).size
// If these need fixing, sounds like Kryo wire format changed and checkpoints might not surive an upgrade. // If these need fixing, sounds like Kryo wire format changed and checkpoints might not survive an upgrade.
assertEquals(20222, uncompressedSize) if (SystemUtils.IS_JAVA_11)
assertEquals(20172, uncompressedSize)
else
assertEquals(20222, uncompressedSize)
assertEquals(1111, compressedSize) assertEquals(1111, compressedSize)
} }
} }

View File

@ -4,7 +4,6 @@ import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times import com.nhaarman.mockito_kotlin.times
import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.verify
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.SignedDataWithCert import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
@ -26,8 +25,7 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.io.PrintStream import kotlin.test.assertNull
import kotlin.streams.toList
class DBNetworkParametersStorageTest { class DBNetworkParametersStorageTest {
@Rule @Rule
@ -98,24 +96,11 @@ class DBNetworkParametersStorageTest {
@Test @Test
fun `try save parameters with incorrect signature`() { fun `try save parameters with incorrect signature`() {
database.transaction { database.transaction {
val consoleOutput = interceptConsoleOutput { // logs a warning (java.security.cert.CertPathValidatorException: Cert path failed to validate)
networkParametersService.lookup(hash3) assertNull(networkParametersService.lookup(hash3))
}
assertThat(consoleOutput).anySatisfy {
it.contains("Caused by: java.security.cert.CertPathValidatorException: subject/issuer name chaining check failed")
}
} }
} }
private fun interceptConsoleOutput(block: () -> Unit): List<String> {
val oldOut = System.out
val out = ByteOutputStream()
System.setOut(PrintStream(out))
block()
System.setOut(oldOut)
return out.bytes.inputStream().bufferedReader().lines().toList()
}
private fun createMockNetworkMapClient(): NetworkMapClient { private fun createMockNetworkMapClient(): NetworkMapClient {
return mock { return mock {
on { getNetworkParameters(any()) }.then { on { getNetworkParameters(any()) }.then {

View File

@ -5,7 +5,7 @@ import com.esotericsoftware.kryo.KryoException
import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.packageName import net.corda.core.internal.packageName_
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
@ -45,7 +45,7 @@ class ExposeJpaToFlowsTests {
fun setUp() { fun setUp() {
mockNet = MockNetwork(MockNetworkParameters(cordappsForAllNodes = listOf(enclosedCordapp()))) mockNet = MockNetwork(MockNetworkParameters(cordappsForAllNodes = listOf(enclosedCordapp())))
val (db, mockServices) = MockServices.makeTestDatabaseAndMockServices( val (db, mockServices) = MockServices.makeTestDatabaseAndMockServices(
cordappPackages = listOf(javaClass.packageName), cordappPackages = listOf(javaClass.packageName_),
identityService = makeTestIdentityService(myself.identity), identityService = makeTestIdentityService(myself.identity),
initialIdentity = myself, initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4) networkParameters = testNetworkParameters(minimumPlatformVersion = 4)

View File

@ -12,14 +12,23 @@ dependencies {
cordapp project(':finance:contracts') cordapp project(':finance:contracts')
cordapp project(':finance:workflows') cordapp project(':finance:workflows')
// Cordformation needs a SLF4J implementation when executing the Network
// Bootstrapper, but Log4J doesn't shutdown completely from within Gradle.
// Use a much simpler SLF4J implementation here instead.
cordaRuntime "org.slf4j:slf4j-simple:$slf4j_version"
// Corda integration dependencies // Corda integration dependencies
cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts') cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaRuntime project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') cordaRuntime project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
cordaCompile project(':core') cordaCompile project(':core')
cordaCompile project(':client:jfx') cordaCompile project(':client:jfx')
cordaCompile project(':client:rpc') cordaCompile project(':client:rpc')
cordaCompile project(':webserver') cordaCompile (project(':webserver')) {
cordaCompile project(':node-driver') exclude group: "org.apache.logging.log4j"
}
cordaCompile (project(':node-driver')) {
exclude group: "org.apache.logging.log4j"
}
// Javax is required for webapis // Javax is required for webapis
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"

View File

@ -1,6 +1,7 @@
package net.corda.bank.api package net.corda.bank.api
import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps import net.corda.client.rpc.internal.ReconnectingCordaRPCOps
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
@ -43,7 +44,7 @@ object BankOfCordaClientApi {
*/ */
fun requestRPCIssueHA(availableRpcServers: List<NetworkHostAndPort>, params: IssueRequestParams): SignedTransaction { fun requestRPCIssueHA(availableRpcServers: List<NetworkHostAndPort>, params: IssueRequestParams): SignedTransaction {
// TODO: privileged security controls required // TODO: privileged security controls required
ReconnectingCordaRPCOps(availableRpcServers, BOC_RPC_USER, BOC_RPC_PWD).use { rpc-> ReconnectingCordaRPCOps(availableRpcServers, BOC_RPC_USER, BOC_RPC_PWD, CordaRPCClientConfiguration.DEFAULT).use { rpc->
rpc.waitUntilNetworkReady().getOrThrow() rpc.waitUntilNetworkReady().getOrThrow()
// Resolve parties via RPC // Resolve parties via RPC

View File

@ -4,7 +4,10 @@ apply plugin: 'net.corda.plugins.cordformation'
dependencies { dependencies {
runtimeOnly project(':node-api') runtimeOnly project(':node-api')
runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" // Cordformation needs a SLF4J implementation when executing the Network
// Bootstrapper, but Log4J doesn't shutdown completely from within Gradle.
// Use a much simpler SLF4J implementation here instead.
cordaRuntime "org.slf4j:slf4j-simple:$slf4j_version"
// Corda integration dependencies // Corda integration dependencies
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')

View File

@ -34,11 +34,18 @@ dependencies {
cordapp project(path: ':samples:simm-valuation-demo:contracts-states', configuration: 'shrinkArtifacts') cordapp project(path: ':samples:simm-valuation-demo:contracts-states', configuration: 'shrinkArtifacts')
cordapp project(':samples:simm-valuation-demo:flows') cordapp project(':samples:simm-valuation-demo:flows')
// Cordformation needs a SLF4J implementation when executing the Network
// Bootstrapper, but Log4J doesn't shutdown completely from within Gradle.
// Use a much simpler SLF4J implementation here instead.
cordaRuntime "org.slf4j:slf4j-simple:$slf4j_version"
// Corda integration dependencies // Corda integration dependencies
cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts') cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaRuntime project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') cordaRuntime project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
cordaCompile project(':core') cordaCompile project(':core')
cordaCompile project(':webserver') cordaCompile (project(':webserver')) {
exclude group: "org.apache.logging.log4j"
}
// Javax is required for webapis // Javax is required for webapis
compile "org.glassfish.jersey.core:jersey-server:$jersey_version" compile "org.glassfish.jersey.core:jersey-server:$jersey_version"

View File

@ -51,8 +51,12 @@ task shrink(type: ProGuardTask) {
injars jar injars jar
outjars shrinkJar outjars shrinkJar
libraryjars "$javaHome/lib/rt.jar" if (JavaVersion.current().isJava9Compatible()) {
libraryjars "$javaHome/lib/jce.jar" libraryjars "$javaHome/jmods"
} else {
libraryjars "$javaHome/lib/rt.jar"
libraryjars "$javaHome/lib/jce.jar"
}
configurations.runtimeClasspath.forEach { configurations.runtimeClasspath.forEach {
libraryjars it.path, filter: '!META-INF/versions/**' libraryjars it.path, filter: '!META-INF/versions/**'
} }

View File

@ -1,3 +1,5 @@
import static org.gradle.api.JavaVersion.VERSION_1_8
description 'Corda serialization (deterministic)' description 'Corda serialization (deterministic)'
apply from: '../deterministic.gradle' apply from: '../deterministic.gradle'
@ -8,6 +10,9 @@ apply plugin: 'idea'
evaluationDependsOn(":serialization") evaluationDependsOn(":serialization")
// required by DJVM and Avian JVM (for running inside the SGX enclave) which only supports Java 8.
targetCompatibility = VERSION_1_8
def javaHome = System.getProperty('java.home') def javaHome = System.getProperty('java.home')
def jarBaseName = "corda-${project.name}".toString() def jarBaseName = "corda-${project.name}".toString()
@ -68,9 +73,13 @@ task predeterminise(type: ProGuardTask, dependsOn: project(':core-deterministic'
injars patchSerialization injars patchSerialization
outjars file("$buildDir/proguard/pre-deterministic-${project.version}.jar") outjars file("$buildDir/proguard/pre-deterministic-${project.version}.jar")
libraryjars file("$javaHome/lib/rt.jar") if (JavaVersion.current().isJava9Compatible()) {
libraryjars file("$javaHome/lib/jce.jar") libraryjars "$javaHome/jmods"
libraryjars file("$javaHome/lib/ext/sunec.jar") } else {
libraryjars file("$javaHome/lib/rt.jar")
libraryjars file("$javaHome/lib/jce.jar")
libraryjars file("$javaHome/lib/ext/sunec.jar")
}
configurations.compileClasspath.forEach { configurations.compileClasspath.forEach {
if (originalJar != it) { if (originalJar != it) {
libraryjars it, filter: '!META-INF/versions/**' libraryjars it, filter: '!META-INF/versions/**'
@ -113,8 +122,12 @@ task determinise(type: ProGuardTask) {
injars jarFilter injars jarFilter
outjars file("$buildDir/proguard/$jarBaseName-${project.version}.jar") outjars file("$buildDir/proguard/$jarBaseName-${project.version}.jar")
libraryjars file("$javaHome/lib/rt.jar") if (JavaVersion.current().isJava9Compatible()) {
libraryjars file("$javaHome/lib/jce.jar") libraryjars "$javaHome/jmods"
} else {
libraryjars file("$javaHome/lib/rt.jar")
libraryjars file("$javaHome/lib/jce.jar")
}
configurations.deterministicLibraries.forEach { configurations.deterministicLibraries.forEach {
libraryjars it, filter: '!META-INF/versions/**' libraryjars it, filter: '!META-INF/versions/**'
} }

View File

@ -242,14 +242,14 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
} }
val des = DeserializationInput(freshDeserializationFactory) val des = DeserializationInput(freshDeserializationFactory)
val desObj = des.deserialize(bytes, testSerializationContext.withEncodingWhitelist(encodingWhitelist)) val desObj = des.deserialize(bytes, testSerializationContext.withEncodingWhitelist(encodingWhitelist))
assertTrue(Objects.deepEquals(obj, desObj) == expectedEqual) assertTrue(deepEquals(obj, desObj) == expectedEqual)
// Now repeat with a re-used factory // Now repeat with a re-used factory
val ser2 = SerializationOutput(factory) val ser2 = SerializationOutput(factory)
val des2 = DeserializationInput(factory) val des2 = DeserializationInput(factory)
val desObj2 = des2.deserialize(ser2.serialize(obj, compression), testSerializationContext.withEncodingWhitelist(encodingWhitelist)) val desObj2 = des2.deserialize(ser2.serialize(obj, compression), testSerializationContext.withEncodingWhitelist(encodingWhitelist))
assertTrue(Objects.deepEquals(obj, desObj2) == expectedEqual) assertTrue(deepEquals(obj, desObj2) == expectedEqual)
assertTrue(Objects.deepEquals(desObj, desObj2) == expectDeserializedEqual) assertTrue(deepEquals(desObj, desObj2) == expectDeserializedEqual)
// TODO: add some schema assertions to check correctly formed. // TODO: add some schema assertions to check correctly formed.
return desObj return desObj
@ -580,7 +580,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
assertTrue(desThrowable is CordaRuntimeException) // Since we don't handle the other case(s) yet assertTrue(desThrowable is CordaRuntimeException) // Since we don't handle the other case(s) yet
if (desThrowable is CordaRuntimeException) { if (desThrowable is CordaRuntimeException) {
assertEquals("${t.javaClass.name}: ${t.message}", desThrowable.message) assertEquals("${t.javaClass.name}: ${t.message}", desThrowable.message)
assertTrue(Objects.deepEquals(t.stackTrace, desThrowable.stackTrace)) assertTrue(Objects.deepEquals(t.stackTrace.toStackTraceBasic, desThrowable.stackTrace.toStackTraceBasic))
assertEquals(t.suppressed.size, desThrowable.suppressed.size) assertEquals(t.suppressed.size, desThrowable.suppressed.size)
t.suppressed.zip(desThrowable.suppressed).forEach { (before, after) -> assertSerializedThrowableEquivalent(before, after) } t.suppressed.zip(desThrowable.suppressed).forEach { (before, after) -> assertSerializedThrowableEquivalent(before, after) }
} }
@ -1521,5 +1521,36 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
assertEquals(20059, uncompressedSize) assertEquals(20059, uncompressedSize)
assertEquals(1018, compressedSize) assertEquals(1018, compressedSize)
} }
}
// JDK11: backwards compatibility function to deal with StacktraceElement comparison pre-JPMS
private fun deepEquals(a: Any?, b: Any?): Boolean {
return if (a === b)
true
else if (a == null || b == null)
false
else {
if (a is Exception && b is Exception)
(a.cause == b.cause && a.localizedMessage == b.localizedMessage && a.message == b.message) &&
Objects.deepEquals(a.stackTrace.toStackTraceBasic, b.stackTrace.toStackTraceBasic)
else
Objects.deepEquals(a, b)
}
}
private val <T> Array<T>.toStackTraceBasic: Unit
get() {
this.map { StackTraceElementBasic(it as StackTraceElement) }
}
// JPMS adds additional fields that are not equal according to classloader/module hierarchy
data class StackTraceElementBasic(val ste: StackTraceElement) {
override fun equals(other: Any?): Boolean {
return if (other is StackTraceElementBasic)
(ste.className == other.ste.className) &&
(ste.methodName == other.ste.methodName) &&
(ste.fileName == other.ste.fileName) &&
(ste.lineNumber == other.ste.lineNumber)
else false
}
}
}

View File

@ -1,9 +1,14 @@
import static org.gradle.api.JavaVersion.VERSION_1_8
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.artifactory'
description 'Corda serialization' description 'Corda serialization'
// required by DJVM and Avian JVM (for running inside the SGX enclave) which only supports Java 8.
targetCompatibility = VERSION_1_8
dependencies { dependencies {
compile project(":core") compile project(":core")

View File

@ -2,8 +2,6 @@ package net.corda.serialization.internal
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import sun.misc.Unsafe
import sun.security.util.Password
import java.io.* import java.io.*
import java.lang.invoke.* import java.lang.invoke.*
import java.lang.reflect.AccessibleObject import java.lang.reflect.AccessibleObject
@ -50,7 +48,6 @@ object AllButBlacklisted : ClassWhitelist {
ClassLoader::class.java.name, ClassLoader::class.java.name,
Handler::class.java.name, // MemoryHandler, StreamHandler Handler::class.java.name, // MemoryHandler, StreamHandler
Runtime::class.java.name, Runtime::class.java.name,
Unsafe::class.java.name,
ZipFile::class.java.name, ZipFile::class.java.name,
Provider::class.java.name, Provider::class.java.name,
SecurityManager::class.java.name, SecurityManager::class.java.name,
@ -62,7 +59,6 @@ object AllButBlacklisted : ClassWhitelist {
// java.security. // java.security.
KeyStore::class.java.name, KeyStore::class.java.name,
Password::class.java.name,
AccessController::class.java.name, AccessController::class.java.name,
Permission::class.java.name, Permission::class.java.name,

View File

@ -5,7 +5,6 @@ import net.corda.core.utilities.NetworkHostAndPort
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import rx.Notification import rx.Notification
import rx.exceptions.OnErrorNotImplementedException import rx.exceptions.OnErrorNotImplementedException
import sun.security.x509.X509CertImpl
import java.security.cert.CRLReason import java.security.cert.CRLReason
import java.util.* import java.util.*
@ -62,7 +61,6 @@ object DefaultWhitelist : SerializationWhitelist {
StackTraceElement::class.java, StackTraceElement::class.java,
// Implementation of X509Certificate. // Implementation of X509Certificate.
X509CertImpl::class.java,
CRLReason::class.java CRLReason::class.java
) )
} }

View File

@ -88,9 +88,7 @@ open class ArraySerializer(override val type: Type, factory: LocalSerializerFact
context: SerializationContext context: SerializationContext
): Any { ): Any {
if (obj is List<*>) { if (obj is List<*>) {
return obj.map { return obj.map { input.readObjectOrNull(it, schemas, elementType, context) }.toArrayOfType(elementType)
input.readObjectOrNull(redescribe(it, elementType), schemas, elementType, context)
}.toArrayOfType(elementType)
} else throw AMQPNotSerializableException(type, "Expected a List but found $obj") } else throw AMQPNotSerializableException(type, "Expected a List but found $obj")
} }

View File

@ -26,10 +26,12 @@ private class ConstructorCaller(private val javaConstructor: Constructor<Any>):
try { try {
javaConstructor.newInstance(*parameters) javaConstructor.newInstance(*parameters)
} catch (e: InvocationTargetException) { } catch (e: InvocationTargetException) {
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9)
throw NotSerializableException( throw NotSerializableException(
"Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " + "Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " +
"failed when called with parameters ${parameters.toList()}: ${e.cause!!.message}") "failed when called with parameters ${parameters.toList()}: ${e.cause!!.message}")
} catch (e: IllegalAccessException) { } catch (e: IllegalAccessException) {
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9)
throw NotSerializableException( throw NotSerializableException(
"Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " + "Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " +
"not accessible: ${e.message}") "not accessible: ${e.message}")
@ -44,10 +46,12 @@ private class SetterCaller(val setter: Method): (Any, Any?) -> Unit {
try { try {
setter.invoke(target, value) setter.invoke(target, value)
} catch (e: InvocationTargetException) { } catch (e: InvocationTargetException) {
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9)
throw NotSerializableException( throw NotSerializableException(
"Setter ${setter.declaringClass}.${setter.name} (isAccessible=${setter.isAccessible} " + "Setter ${setter.declaringClass}.${setter.name} (isAccessible=${setter.isAccessible} " +
"failed when called with parameter $value: ${e.cause!!.message}") "failed when called with parameter $value: ${e.cause!!.message}")
} catch (e: IllegalAccessException) { } catch (e: IllegalAccessException) {
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9)
throw NotSerializableException( throw NotSerializableException(
"Setter ${setter.declaringClass}.${setter.name} (isAccessible=${setter.isAccessible} " + "Setter ${setter.declaringClass}.${setter.name} (isAccessible=${setter.isAccessible} " +
"not accessible: ${e.message}") "not accessible: ${e.message}")

View File

@ -346,7 +346,7 @@ class ClassCarpenterImpl @JvmOverloads constructor (override val whitelist: Clas
visitCode() visitCode()
visitLdcInsn(Type.getType("L${schema.jvmName};")) visitLdcInsn(Type.getType("L${schema.jvmName};"))
visitVarInsn(ALOAD, 0) visitVarInsn(ALOAD, 0)
visitMethodInsn(INVOKESTATIC, jlEnum, "valueOf", "(L$jlClass;L$jlString;)L$jlEnum;", true) visitMethodInsn(INVOKESTATIC, jlEnum, "valueOf", "(L$jlClass;L$jlString;)L$jlEnum;", false)
visitTypeInsn(CHECKCAST, schema.jvmName) visitTypeInsn(CHECKCAST, schema.jvmName)
visitInsn(ARETURN) visitInsn(ARETURN)
visitMaxs(0, 0) visitMaxs(0, 0)

View File

@ -3,6 +3,7 @@ package net.corda.serialization.internal.amqp
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.serialization.internal.amqp.testutils.* import net.corda.serialization.internal.amqp.testutils.*
import org.junit.Test import org.junit.Test
import java.lang.Character.valueOf
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.fail import kotlin.test.fail
@ -49,9 +50,9 @@ class DeserializeSimpleTypesTests {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@Test @Test
fun testCharacter() { fun testCharacter() {
data class C(val c: Character) data class C(val c: Char)
val c = C(Character('c')) val c = C(valueOf('c'))
val serialisedC = SerializationOutput(sf1).serialize(c) val serialisedC = SerializationOutput(sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC) val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
@ -90,9 +91,9 @@ class DeserializeSimpleTypesTests {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@Test @Test
fun testArrayOfInteger() { fun testArrayOfInteger() {
class IA(val ia: Array<Integer>) class IA(val ia: Array<Int>)
val ia = IA(arrayOf(Integer(1), Integer(2), Integer(3))) val ia = IA(arrayOf(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)))
assertEquals("class [Ljava.lang.Integer;", ia.ia::class.java.toString()) assertEquals("class [Ljava.lang.Integer;", ia.ia::class.java.toString())
assertEquals(AMQPTypeIdentifiers.nameForType(ia.ia::class.java), "int[]") assertEquals(AMQPTypeIdentifiers.nameForType(ia.ia::class.java), "int[]")

Some files were not shown because too many files have changed in this diff Show More