diff --git a/changelog.md b/changelog.md index 65abb1b250..dde01af98c 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ ### Version 5.0.9 * `cordformation`: Added option to deploy external databases using docker compose task +* `cordformation`: Allow users to specify ports in Dockerform * `cordformation`: Use GNU Screen in `runnodes` on macOS to start nodes in different Screen windows. Activated when the user is already running `screen`. * `jar-filter`: Initial support for byte-code compiled by Kotlin >= 1.4.0 (See [KT-31352](https://youtrack.jetbrains.com/issue/KT-31352)). diff --git a/cordformation/src/main/kotlin/net/corda/plugins/ConfigurationUtils.kt b/cordformation/src/main/kotlin/net/corda/plugins/ConfigurationUtils.kt new file mode 100644 index 0000000000..697d5f39a0 --- /dev/null +++ b/cordformation/src/main/kotlin/net/corda/plugins/ConfigurationUtils.kt @@ -0,0 +1,19 @@ +package net.corda.plugins + +import org.gradle.api.InvalidUserDataException +import java.net.URI +import java.net.URISyntaxException + +class ConfigurationUtils { + + companion object { + + fun parsePort(address: String): Int { + return try { + URI(null, address, null, null, null).port + } catch (ex: URISyntaxException) { + throw InvalidUserDataException("Invalid host and port syntax for RPC address, expected host:port") + } + } + } +} \ No newline at end of file diff --git a/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt b/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt index e0028bd570..30ecedc7e6 100644 --- a/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt +++ b/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt @@ -99,7 +99,7 @@ open class Dockerform @Inject constructor(objects: ObjectFactory) : Baseform(obj "$nodeBuildDir/additional-node-infos:/opt/corda/additional-node-infos", "$nodeBuildDir/drivers:/opt/corda/drivers" ), - "ports" to listOf(it.rpcPort, it.config.getInt("sshd.port")), + "ports" to listOf(it.rpcPort.get(), it.config.getInt("sshd.port")), "image" to (dockerImage ?: "corda/corda-zulu-${it.runtimeVersion().toLowerCase()}") ) diff --git a/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index fb95d7f373..bd90b1f20d 100644 --- a/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -5,6 +5,7 @@ import groovy.lang.Closure import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested @@ -57,9 +58,11 @@ open class Node @Inject constructor(private val project: Project) { private var rpcSettings: RpcSettings = RpcSettings() private var webserverJar: String? = null private var p2pPort = 10002 - internal var rpcPort = 10003 - @Input get - private set + @get:Input + val rpcPort: Provider = project.objects.property(Int::class.javaObjectType).apply { + set(project.provider { rpcSettings.port }) + } + internal var config = ConfigFactory.empty() @Internal get private set @@ -186,7 +189,6 @@ open class Node @Inject constructor(private val project: Project) { @Deprecated("Use {@link CordformNode#rpcSettings(RpcSettings)} instead. Will be removed by Corda V5.0.") fun rpcPort(rpcPort: Int) { rpcAddress(DEFAULT_HOST + ':'.toString() + rpcPort) - this.rpcPort = rpcPort } /** diff --git a/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt b/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt index 0b40364bb2..bb5922e3db 100644 --- a/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt +++ b/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt @@ -3,7 +3,10 @@ package net.corda.plugins import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigValueFactory +import org.gradle.api.InvalidUserDataException import org.gradle.api.tasks.Input +import java.net.URI +import java.net.URISyntaxException class RpcSettings { private var config = ConfigFactory.empty() @@ -19,6 +22,11 @@ class RpcSettings { * RPC address for the node. */ fun address(value: String) { + val parsedValue = ConfigurationUtils.parsePort(value) + port = when (parsedValue) { + -1 -> port + else -> parsedValue + } setValue("address", value) } @@ -34,6 +42,11 @@ class RpcSettings { * RPC admin address for the node (necessary if [useSsl] is false or unset). */ fun adminAddress(value: String) { + val parsedValue = ConfigurationUtils.parsePort(value) + adminPort = when (parsedValue) { + -1 -> adminPort + else -> parsedValue + } setValue("adminAddress", value) } diff --git a/cordformation/src/test/kotlin/net/corda/plugins/BaseformTest.kt b/cordformation/src/test/kotlin/net/corda/plugins/BaseformTest.kt index ed2a9ef072..bdfe1c1797 100644 --- a/cordformation/src/test/kotlin/net/corda/plugins/BaseformTest.kt +++ b/cordformation/src/test/kotlin/net/corda/plugins/BaseformTest.kt @@ -57,6 +57,7 @@ open class BaseformTest { fun getNodeCordappJar(nodeName: String, cordappJarName: String) = Paths.get(testProjectDir.toAbsolutePath().toString(), "build", "nodes", nodeName, "cordapps", "$cordappJarName.jar") fun getNodeCordappConfig(nodeName: String, cordappJarName: String) = Paths.get(testProjectDir.toAbsolutePath().toString(), "build", "nodes", nodeName, "cordapps", "config", "$cordappJarName.conf") fun getNetworkParameterOverrides(nodeName: String) = Paths.get(testProjectDir.toAbsolutePath().toString(), "build", "nodes", nodeName, "network-parameters") + fun getNodeConfig(nodeName: String) = Paths.get(testProjectDir.toAbsolutePath().toString(), "build", "nodes", nodeName, "node.conf") class AMQPParametersSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) { override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() @@ -66,5 +67,4 @@ open class BaseformTest { return magic == amqpMagic && target == SerializationContext.UseCase.P2P } } - } \ No newline at end of file diff --git a/cordformation/src/test/kotlin/net/corda/plugins/ConfigurationUtilsTest.kt b/cordformation/src/test/kotlin/net/corda/plugins/ConfigurationUtilsTest.kt new file mode 100644 index 0000000000..4ef242d352 --- /dev/null +++ b/cordformation/src/test/kotlin/net/corda/plugins/ConfigurationUtilsTest.kt @@ -0,0 +1,23 @@ +package net.corda.plugins + + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class ConfigurationUtilsTest { + + @Test + fun `check correct port value parsing`() { + assertEquals(10000, ConfigurationUtils.parsePort("localhost:10000")) + } + + @Test + fun `missing port value correctly identified in valid address`() { + assertEquals(-1, ConfigurationUtils.parsePort("localhost")) + } + + @Test + fun `missing port value correctly identified in invalid address`() { + assertEquals(-1, ConfigurationUtils.parsePort("localhost!")) + } +} \ No newline at end of file diff --git a/cordformation/src/test/kotlin/net/corda/plugins/DockerformTest.kt b/cordformation/src/test/kotlin/net/corda/plugins/DockerformTest.kt index 94063b2375..8938591560 100644 --- a/cordformation/src/test/kotlin/net/corda/plugins/DockerformTest.kt +++ b/cordformation/src/test/kotlin/net/corda/plugins/DockerformTest.kt @@ -1,8 +1,11 @@ package net.corda.plugins +import com.typesafe.config.ConfigFactory import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue class DockerformTest : BaseformTest() { @@ -93,4 +96,41 @@ class DockerformTest : BaseformTest() { assertThat(getNodeCordappJar(notaryNodeName, cordaFinanceContractsJarName)).isRegularFile() assertThat(getNetworkParameterOverrides(notaryNodeName)).isRegularFile() } + + @Test + fun `deploy two nodes with cordapp dependencies`() { + val runner = getStandardGradleRunnerFor( + "DeployTwoNodeCordappWithDocker.gradle", + "prepareDockerNodes") + + val result = runner.build() + + val bankOfCordaNodeName = "BankOfCorda" + + assertThat(result.task(":prepareDockerNodes")!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + assertThat(getNodeCordappJar(notaryNodeName, cordaFinanceWorkflowsJarName)).isRegularFile() + assertThat(getNodeCordappJar(notaryNodeName, cordaFinanceContractsJarName)).isRegularFile() + assertThat(getNetworkParameterOverrides(notaryNodeName)).isRegularFile() + assertThat(getNodeCordappJar(bankOfCordaNodeName, cordaFinanceWorkflowsJarName)).isRegularFile() + assertThat(getNodeCordappJar(bankOfCordaNodeName, cordaFinanceContractsJarName)).isRegularFile() + assertThat(getNetworkParameterOverrides(bankOfCordaNodeName)).isRegularFile() + + val notaryConfigPath = getNodeConfig(notaryNodeName) + assertThat(notaryConfigPath).isRegularFile() + + val notaryConfig = ConfigFactory.parseFile(notaryConfigPath.toFile()) + assertTrue(notaryConfig.hasPath("rpcSettings.address")) + assertTrue(notaryConfig.hasPath("rpcSettings.adminAddress")) + assertEquals(10003, ConfigurationUtils.parsePort(notaryConfig.getString("rpcSettings.address"))) + assertEquals(10043, ConfigurationUtils.parsePort(notaryConfig.getString("rpcSettings.adminAddress"))) + + val bankOfCordaConfigPath = getNodeConfig(bankOfCordaNodeName) + assertThat(bankOfCordaConfigPath).isRegularFile() + + val bankOfCordaConfig = ConfigFactory.parseFile(bankOfCordaConfigPath.toFile()) + assertTrue(bankOfCordaConfig.hasPath("rpcSettings.address")) + assertTrue(bankOfCordaConfig.hasPath("rpcSettings.adminAddress")) + assertEquals(10006, ConfigurationUtils.parsePort(bankOfCordaConfig.getString("rpcSettings.address"))) + assertEquals(10046, ConfigurationUtils.parsePort(bankOfCordaConfig.getString("rpcSettings.adminAddress"))) + } } \ No newline at end of file diff --git a/cordformation/src/test/resources/net/corda/plugins/DeployTwoNodeCordappWithDocker.gradle b/cordformation/src/test/resources/net/corda/plugins/DeployTwoNodeCordappWithDocker.gradle new file mode 100644 index 0000000000..51716e9745 --- /dev/null +++ b/cordformation/src/test/resources/net/corda/plugins/DeployTwoNodeCordappWithDocker.gradle @@ -0,0 +1,42 @@ +plugins { + id 'net.corda.plugins.cordformation' +} + +apply from: 'repositories.gradle' + +dependencies { + cordaRuntime "$corda_group:corda:$corda_release_version" + cordaRuntime "$corda_group:corda-node-api:$corda_release_version" + cordapp "$corda_group:corda-finance-contracts:$corda_release_version" + cordapp "$corda_group:corda-finance-workflows:$corda_release_version" +} + +task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar']) { + nodeDefaults { + + cordapps = ["$corda_group:corda-finance-contracts:$corda_release_version", + "$corda_group:corda-finance-workflows:$corda_release_version"] + + projectCordapp { + deploy false + } + } + node { + name "O=Notary Service,L=London,C=GB" + notary = [validating : false] + p2pPort 10002 + rpcSettings { + address("localhost:10003") + adminAddress("localhost:10043") + } + } + node { + name "O=BankOfCorda,L=London,C=GB" + p2pPort 10005 + rpcSettings { + address("localhost:10006") + adminAddress("localhost:10046") + } + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] + } +} \ No newline at end of file