mirror of
https://github.com/corda/corda.git
synced 2025-05-02 16:53:22 +00:00
Converted FullNodeConfiguration into a data class and added ability to parse Configs into data classes
This commit is contained in:
parent
868a490bba
commit
c17fe29a62
@ -85,7 +85,7 @@ path to the node's base directory.
|
|||||||
|
|
||||||
:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC.
|
:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC.
|
||||||
|
|
||||||
:webAddress: The host and port on which the bundled webserver will listen if it is started.
|
:webAddress: The host and port on which the webserver will listen if it is started. This is not used by the node itself.
|
||||||
|
|
||||||
.. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one
|
.. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one
|
||||||
of either the machine name, fully qualified machine name, or server IP address to line up with the Subject Alternative
|
of either the machine name, fully qualified machine name, or server IP address to line up with the Subject Alternative
|
||||||
@ -123,7 +123,7 @@ path to the node's base directory.
|
|||||||
:rpcUsers: A list of users who are authorised to access the RPC system. Each user in the list is a config object with the
|
:rpcUsers: A list of users who are authorised to access the RPC system. Each user in the list is a config object with the
|
||||||
following fields:
|
following fields:
|
||||||
|
|
||||||
:user: Username consisting only of word characters (a-z, A-Z, 0-9 and _)
|
:username: Username consisting only of word characters (a-z, A-Z, 0-9 and _)
|
||||||
:password: The password
|
:password: The password
|
||||||
:permissions: A list of permission strings which RPC methods can use to control access
|
:permissions: A list of permission strings which RPC methods can use to control access
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
webPort 10007
|
webPort 10007
|
||||||
cordapps = []
|
cordapps = []
|
||||||
rpcUsers = [
|
rpcUsers = [
|
||||||
['user' : "user",
|
['username' : "user",
|
||||||
'password' : "password",
|
'password' : "password",
|
||||||
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]]
|
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]]
|
||||||
]
|
]
|
||||||
|
@ -18,7 +18,7 @@ networkMapService : {
|
|||||||
}
|
}
|
||||||
useHTTPS : false
|
useHTTPS : false
|
||||||
rpcUsers : [
|
rpcUsers : [
|
||||||
{ user=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
|
{ username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
|
||||||
]
|
]
|
||||||
devMode : true
|
devMode : true
|
||||||
// Certificate signing service will be hosted by R3 in the near future.
|
// Certificate signing service will be hosted by R3 in the near future.
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.docs
|
|||||||
|
|
||||||
import net.corda.node.services.config.ConfigHelper
|
import net.corda.node.services.config.ConfigHelper
|
||||||
import net.corda.node.services.config.FullNodeConfiguration
|
import net.corda.node.services.config.FullNodeConfiguration
|
||||||
|
import net.corda.nodeapi.config.parseAs
|
||||||
import net.corda.verifier.Verifier
|
import net.corda.verifier.Verifier
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -30,13 +31,10 @@ class ExampleConfigTest {
|
|||||||
"example-network-map-node.conf"
|
"example-network-map-node.conf"
|
||||||
) {
|
) {
|
||||||
val baseDirectory = Paths.get("some-example-base-dir")
|
val baseDirectory = Paths.get("some-example-base-dir")
|
||||||
FullNodeConfiguration(
|
ConfigHelper.loadConfig(
|
||||||
baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
ConfigHelper.loadConfig(
|
configFile = it
|
||||||
baseDirectory = baseDirectory,
|
).parseAs<FullNodeConfiguration>()
|
||||||
configFile = it
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ When starting a standalone node using a configuration file we must supply the RP
|
|||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
rpcUsers : [
|
rpcUsers : [
|
||||||
{ user=user, password=password, permissions=[ StartFlow.net.corda.flows.CashFlow ] }
|
{ username=user, password=password, permissions=[ StartFlow.net.corda.flows.CashFlow ] }
|
||||||
]
|
]
|
||||||
|
|
||||||
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar manner:
|
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar manner:
|
||||||
@ -131,7 +131,7 @@ When using the gradle Cordformation plugin to configure and deploy a node you mu
|
|||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
rpcUsers = [
|
rpcUsers = [
|
||||||
['user' : "user",
|
['username' : "user",
|
||||||
'password' : "password",
|
'password' : "password",
|
||||||
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]]
|
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]]
|
||||||
]
|
]
|
||||||
|
@ -36,7 +36,7 @@ class Node {
|
|||||||
/**
|
/**
|
||||||
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
|
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
|
||||||
* The recommended current structure is:
|
* The recommended current structure is:
|
||||||
* [[['user': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]]
|
* [[['username': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]]
|
||||||
* The above is a list to a map of keys to values using Groovy map and list shorthands.
|
* The above is a list to a map of keys to values using Groovy map and list shorthands.
|
||||||
*
|
*
|
||||||
* @note Incorrect configurations will not cause a DSL error.
|
* @note Incorrect configurations will not cause a DSL error.
|
||||||
|
@ -38,4 +38,9 @@ dependencies {
|
|||||||
|
|
||||||
// TypeSafe Config: for simple and human friendly config files.
|
// TypeSafe Config: for simple and human friendly config files.
|
||||||
compile "com.typesafe:config:$typesafe_config_version"
|
compile "com.typesafe:config:$typesafe_config_version"
|
||||||
|
|
||||||
|
// Unit testing helpers.
|
||||||
|
testCompile "junit:junit:$junit_version"
|
||||||
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
|
testCompile project(':test-utils')
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import net.corda.core.flows.FlowException
|
|||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
import net.corda.core.toObservable
|
import net.corda.core.toObservable
|
||||||
|
import net.corda.nodeapi.config.OldConfig
|
||||||
import org.apache.commons.fileupload.MultipartStream
|
import org.apache.commons.fileupload.MultipartStream
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -24,7 +25,11 @@ val rpcLog: Logger by lazy { LoggerFactory.getLogger("net.corda.rpc") }
|
|||||||
/** Used in the RPC wire protocol to wrap an observation with the handle of the observable it's intended for. */
|
/** Used in the RPC wire protocol to wrap an observation with the handle of the observable it's intended for. */
|
||||||
data class MarshalledObservation(val forHandle: Int, val what: Notification<*>)
|
data class MarshalledObservation(val forHandle: Int, val what: Notification<*>)
|
||||||
|
|
||||||
data class User(val username: String, val password: String, val permissions: Set<String>) {
|
data class User(
|
||||||
|
@OldConfig("user")
|
||||||
|
val username: String,
|
||||||
|
val password: String,
|
||||||
|
val permissions: Set<String>) {
|
||||||
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
|
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,73 +2,125 @@ package net.corda.nodeapi.config
|
|||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigUtil
|
||||||
|
import net.corda.core.noneOrSingle
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.net.Proxy
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import kotlin.reflect.jvm.javaType
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
import kotlin.reflect.jvm.jvmErasure
|
||||||
|
|
||||||
private fun <T : Enum<T>> enumBridge(clazz: Class<T>, enumValueString: String): T {
|
@Target(AnnotationTarget.PROPERTY)
|
||||||
return java.lang.Enum.valueOf(clazz, enumValueString)
|
annotation class OldConfig(val value: String)
|
||||||
}
|
|
||||||
private class DummyEnum : Enum<DummyEnum>("", 0)
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
// TODO Move other config parsing to use parseAs and remove this
|
||||||
operator fun <T> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
|
operator fun <T> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
|
||||||
if (metadata.returnType.isMarkedNullable && !hasPath(metadata.name)) {
|
return getValueInternal(metadata.name, metadata.returnType)
|
||||||
return null as T
|
}
|
||||||
}
|
|
||||||
val returnType = metadata.returnType.javaType
|
fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
|
||||||
return when (metadata.returnType.javaType) {
|
require(clazz.isData) { "Only Kotlin data classes can be parsed" }
|
||||||
String::class.java -> getString(metadata.name) as T
|
val constructor = clazz.primaryConstructor!!
|
||||||
Int::class.java -> getInt(metadata.name) as T
|
val args = constructor.parameters
|
||||||
Integer::class.java -> getInt(metadata.name) as T
|
.filterNot { it.isOptional && !hasPath(it.name!!) }
|
||||||
Long::class.java -> getLong(metadata.name) as T
|
.associateBy({ it }) { param ->
|
||||||
Double::class.java -> getDouble(metadata.name) as T
|
// Get the matching property for this parameter
|
||||||
Boolean::class.java -> getBoolean(metadata.name) as T
|
val property = clazz.memberProperties.first { it.name == param.name }
|
||||||
LocalDate::class.java -> LocalDate.parse(getString(metadata.name)) as T
|
val path = defaultToOldPath(property)
|
||||||
Instant::class.java -> Instant.parse(getString(metadata.name)) as T
|
getValueInternal<Any>(path, param.type)
|
||||||
HostAndPort::class.java -> HostAndPort.fromString(getString(metadata.name)) as T
|
|
||||||
Path::class.java -> Paths.get(getString(metadata.name)) as T
|
|
||||||
URL::class.java -> URL(getString(metadata.name)) as T
|
|
||||||
Properties::class.java -> getProperties(metadata.name) as T
|
|
||||||
else -> {
|
|
||||||
if (returnType is Class<*> && Enum::class.java.isAssignableFrom(returnType)) {
|
|
||||||
return enumBridge(returnType as Class<DummyEnum>, getString(metadata.name)) as T
|
|
||||||
}
|
}
|
||||||
throw IllegalArgumentException("Unsupported type ${metadata.returnType}")
|
return constructor.callBy(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> Config.parseAs(): T = parseAs(T::class)
|
||||||
|
|
||||||
|
fun Config.toProperties(): Properties {
|
||||||
|
return entrySet().associateByTo(
|
||||||
|
Properties(),
|
||||||
|
{ ConfigUtil.splitPath(it.key).joinToString(".") },
|
||||||
|
{ it.value.unwrapped().toString() })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun <T> Config.getValueInternal(path: String, type: KType): T {
|
||||||
|
return (if (type.arguments.isEmpty()) getSingleValue(path, type) else getCollectionValue(path, type)) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Config.getSingleValue(path: String, type: KType): Any? {
|
||||||
|
if (type.isMarkedNullable && !hasPath(path)) return null
|
||||||
|
val typeClass = type.jvmErasure
|
||||||
|
return when (typeClass) {
|
||||||
|
String::class -> getString(path)
|
||||||
|
Int::class -> getInt(path)
|
||||||
|
Long::class -> getLong(path)
|
||||||
|
Double::class -> getDouble(path)
|
||||||
|
Boolean::class -> getBoolean(path)
|
||||||
|
LocalDate::class -> LocalDate.parse(getString(path))
|
||||||
|
Instant::class -> Instant.parse(getString(path))
|
||||||
|
HostAndPort::class -> HostAndPort.fromString(getString(path))
|
||||||
|
Path::class -> Paths.get(getString(path))
|
||||||
|
URL::class -> URL(getString(path))
|
||||||
|
Properties::class -> getConfig(path).toProperties()
|
||||||
|
else -> if (typeClass.java.isEnum) {
|
||||||
|
parseEnum(typeClass.java, getString(path))
|
||||||
|
} else {
|
||||||
|
getConfig(path).parseAs(typeClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun Config.getCollectionValue(path: String, type: KType): Collection<Any> {
|
||||||
* Helper class for optional configurations
|
val typeClass = type.jvmErasure
|
||||||
*/
|
require(typeClass == List::class || typeClass == Set::class) { "$typeClass is not supported" }
|
||||||
class OptionalConfig<out T>(val conf: Config, val lambda: () -> T) {
|
val elementClass = type.arguments[0].type?.jvmErasure ?: throw IllegalArgumentException("Cannot work with star projection: $type")
|
||||||
operator fun getValue(receiver: Any, metadata: KProperty<*>): T {
|
if (!hasPath(path)) {
|
||||||
return if (conf.hasPath(metadata.name)) conf.getValue(receiver, metadata) else lambda()
|
return if (typeClass == List::class) emptyList() else emptySet()
|
||||||
}
|
}
|
||||||
|
val values: List<Any> = when (elementClass) {
|
||||||
|
String::class -> getStringList(path)
|
||||||
|
Int::class -> getIntList(path)
|
||||||
|
Long::class -> getLongList(path)
|
||||||
|
Double::class -> getDoubleList(path)
|
||||||
|
Boolean::class -> getBooleanList(path)
|
||||||
|
LocalDate::class -> getStringList(path).map(LocalDate::parse)
|
||||||
|
Instant::class -> getStringList(path).map(Instant::parse)
|
||||||
|
HostAndPort::class -> getStringList(path).map(HostAndPort::fromString)
|
||||||
|
Path::class -> getStringList(path).map { Paths.get(it) }
|
||||||
|
URL::class -> getStringList(path).map(::URL)
|
||||||
|
Properties::class -> getConfigList(path).map(Config::toProperties)
|
||||||
|
else -> if (elementClass.java.isEnum) {
|
||||||
|
getStringList(path).map { parseEnum(elementClass.java, it) }
|
||||||
|
} else {
|
||||||
|
getConfigList(path).map { it.parseAs(elementClass) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (typeClass == Set::class) values.toSet() else values
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Config.getOrElse(lambda: () -> T): OptionalConfig<T> = OptionalConfig(this, lambda)
|
private fun Config.defaultToOldPath(property: KProperty<*>): String {
|
||||||
|
if (!hasPath(property.name)) {
|
||||||
fun Config.getProperties(path: String): Properties {
|
val oldConfig = property.annotations.filterIsInstance<OldConfig>().noneOrSingle()
|
||||||
val obj = this.getObject(path)
|
if (oldConfig != null && hasPath(oldConfig.value)) {
|
||||||
val props = Properties()
|
logger.warn("Config key ${oldConfig.value} has been deprecated and will be removed in a future release. " +
|
||||||
for ((property, objectValue) in obj.entries) {
|
"Use ${property.name} instead")
|
||||||
props.setProperty(property, objectValue.unwrapped().toString())
|
return oldConfig.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return props
|
return property.name
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
inline fun <reified T : Any> Config.getListOrElse(path: String, default: Config.() -> List<T>): List<T> {
|
private fun parseEnum(enumType: Class<*>, name: String): Enum<*> = enumBridge(enumType as Class<Proxy.Type>, name) // Any enum will do
|
||||||
return if (hasPath(path)) {
|
|
||||||
(if (T::class == String::class) getStringList(path) else getConfigList(path)) as List<T>
|
private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T = java.lang.Enum.valueOf(clazz, name)
|
||||||
} else {
|
|
||||||
this.default()
|
private val logger = LoggerFactory.getLogger("net.corda.nodeapi.config")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,238 @@
|
|||||||
|
package net.corda.nodeapi.config
|
||||||
|
|
||||||
|
import com.google.common.net.HostAndPort
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory.empty
|
||||||
|
import com.typesafe.config.ConfigRenderOptions.defaults
|
||||||
|
import com.typesafe.config.ConfigValueFactory
|
||||||
|
import net.corda.core.div
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import java.net.URL
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
|
||||||
|
class ConfigParsingTest {
|
||||||
|
@Test
|
||||||
|
fun `String`() {
|
||||||
|
testPropertyType<StringData, StringListData, String>("hello world!", "bye")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Int`() {
|
||||||
|
testPropertyType<IntData, IntListData, Int>(1, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Long`() {
|
||||||
|
testPropertyType<LongData, LongListData, Long>(Long.MAX_VALUE, Long.MIN_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Double`() {
|
||||||
|
testPropertyType<DoubleData, DoubleListData, Double>(1.2, 3.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Boolean`() {
|
||||||
|
testPropertyType<BooleanData, BooleanListData, Boolean>(true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Enum`() {
|
||||||
|
testPropertyType<EnumData, EnumListData, TestEnum>(TestEnum.Value2, TestEnum.Value1, valuesToString = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `LocalDate`() {
|
||||||
|
testPropertyType<LocalDateData, LocalDateListData, LocalDate>(LocalDate.now(), LocalDate.now().plusDays(1), valuesToString = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Instant`() {
|
||||||
|
testPropertyType<InstantData, InstantListData, Instant>(Instant.now(), Instant.now().plusMillis(100), valuesToString = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `HostAndPort`() {
|
||||||
|
testPropertyType<HostAndPortData, HostAndPortListData, HostAndPort>(
|
||||||
|
HostAndPort.fromParts("localhost", 2223),
|
||||||
|
HostAndPort.fromParts("localhost", 2225),
|
||||||
|
valuesToString = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Path`() {
|
||||||
|
val path = Paths.get("tmp") / "test"
|
||||||
|
testPropertyType<PathData, PathListData, Path>(path, path / "file", valuesToString = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `URL`() {
|
||||||
|
testPropertyType<URLData, URLListData, URL>(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `flat Properties`() {
|
||||||
|
val config = config("value" to mapOf("key" to "prop"))
|
||||||
|
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["key"] = "prop" })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Properties key with dot`() {
|
||||||
|
val config = config("value" to mapOf("key.key2" to "prop"))
|
||||||
|
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["key.key2"] = "prop" })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `nested Properties`() {
|
||||||
|
val config = config("value" to mapOf("first" to mapOf("second" to "prop")))
|
||||||
|
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["first.second"] = "prop" })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `List of Properties`() {
|
||||||
|
val config = config("values" to listOf(emptyMap(), mapOf("key" to "prop")))
|
||||||
|
assertThat(config.parseAs<PropertiesListData>().values).containsExactly(
|
||||||
|
Properties(),
|
||||||
|
Properties().apply { this["key"] = "prop" })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Set`() {
|
||||||
|
val config = config("values" to listOf("a", "a", "b"))
|
||||||
|
assertThat(config.parseAs<StringSetData>().values).containsOnly("a", "b")
|
||||||
|
assertThat(empty().parseAs<StringSetData>().values).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multi property data class`() {
|
||||||
|
val data = config(
|
||||||
|
"b" to true,
|
||||||
|
"i" to 123,
|
||||||
|
"l" to listOf("a", "b"))
|
||||||
|
.parseAs<MultiPropertyData>()
|
||||||
|
assertThat(data.i).isEqualTo(123)
|
||||||
|
assertThat(data.b).isTrue()
|
||||||
|
assertThat(data.l).containsExactly("a", "b")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `nested data classes`() {
|
||||||
|
val config = config(
|
||||||
|
"first" to mapOf(
|
||||||
|
"value" to "nested"))
|
||||||
|
val data = NestedData(StringData("nested"))
|
||||||
|
assertThat(config.parseAs<NestedData>()).isEqualTo(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `List of data classes`() {
|
||||||
|
val config = config(
|
||||||
|
"list" to listOf(
|
||||||
|
mapOf("value" to "1"),
|
||||||
|
mapOf("value" to "2")))
|
||||||
|
val data = DataListData(listOf(StringData("1"), StringData("2")))
|
||||||
|
assertThat(config.parseAs<DataListData>()).isEqualTo(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `default value property`() {
|
||||||
|
assertThat(config("a" to 3).parseAs<DefaultData>()).isEqualTo(DefaultData(3, 2))
|
||||||
|
assertThat(config("a" to 3, "defaultOfTwo" to 3).parseAs<DefaultData>()).isEqualTo(DefaultData(3, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `nullable property`() {
|
||||||
|
assertThat(empty().parseAs<NullableData>().nullable).isNull()
|
||||||
|
assertThat(config("nullable" to null).parseAs<NullableData>().nullable).isNull()
|
||||||
|
assertThat(config("nullable" to "not null").parseAs<NullableData>().nullable).isEqualTo("not null")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `old config property`() {
|
||||||
|
assertThat(config("oldValue" to "old").parseAs<OldData>().newValue).isEqualTo("old")
|
||||||
|
assertThat(config("newValue" to "new").parseAs<OldData>().newValue).isEqualTo("new")
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified S : SingleData<V>, reified L : ListData<V>, V : Any> testPropertyType(
|
||||||
|
value1: V,
|
||||||
|
value2: V,
|
||||||
|
valuesToString: Boolean = false) {
|
||||||
|
testSingleProperty<S, V>(value1, valuesToString)
|
||||||
|
testListProperty<L, V>(value1, value2, valuesToString)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : SingleData<V>, V : Any> testSingleProperty(value: V, valueToString: Boolean) {
|
||||||
|
val constructor = T::class.primaryConstructor!!
|
||||||
|
val config = config("value" to if (valueToString) value.toString() else value)
|
||||||
|
val data = constructor.call(value)
|
||||||
|
assertThat(config.parseAs<T>().value).isEqualTo(data.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : ListData<V>, V : Any> testListProperty(value1: V, value2: V, valuesToString: Boolean) {
|
||||||
|
val rawValues = listOf(value1, value2)
|
||||||
|
val configValues = if (valuesToString) rawValues.map(Any::toString) else rawValues
|
||||||
|
val constructor = T::class.primaryConstructor!!
|
||||||
|
for (n in 0..2) {
|
||||||
|
val config = config("values" to configValues.take(n))
|
||||||
|
val data = constructor.call(rawValues.take(n))
|
||||||
|
assertThat(config.parseAs<T>().values).isEqualTo(data.values)
|
||||||
|
}
|
||||||
|
assertThat(empty().parseAs<T>().values).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun config(vararg values: Pair<String, *>): Config {
|
||||||
|
val config = ConfigValueFactory.fromMap(mapOf(*values))
|
||||||
|
println(config.render(defaults().setOriginComments(false)))
|
||||||
|
return config.toConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface SingleData<out T> {
|
||||||
|
val value: T
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ListData<out T> {
|
||||||
|
val values: List<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StringData(override val value: String) : SingleData<String>
|
||||||
|
data class StringListData(override val values: List<String>) : ListData<String>
|
||||||
|
data class StringSetData(val values: Set<String>)
|
||||||
|
data class IntData(override val value: Int) : SingleData<Int>
|
||||||
|
data class IntListData(override val values: List<Int>) : ListData<Int>
|
||||||
|
data class LongData(override val value: Long) : SingleData<Long>
|
||||||
|
data class LongListData(override val values: List<Long>) : ListData<Long>
|
||||||
|
data class DoubleData(override val value: Double) : SingleData<Double>
|
||||||
|
data class DoubleListData(override val values: List<Double>) : ListData<Double>
|
||||||
|
data class BooleanData(override val value: Boolean) : SingleData<Boolean>
|
||||||
|
data class BooleanListData(override val values: List<Boolean>) : ListData<Boolean>
|
||||||
|
data class EnumData(override val value: TestEnum) : SingleData<TestEnum>
|
||||||
|
data class EnumListData(override val values: List<TestEnum>) : ListData<TestEnum>
|
||||||
|
data class LocalDateData(override val value: LocalDate) : SingleData<LocalDate>
|
||||||
|
data class LocalDateListData(override val values: List<LocalDate>) : ListData<LocalDate>
|
||||||
|
data class InstantData(override val value: Instant) : SingleData<Instant>
|
||||||
|
data class InstantListData(override val values: List<Instant>) : ListData<Instant>
|
||||||
|
data class HostAndPortData(override val value: HostAndPort) : SingleData<HostAndPort>
|
||||||
|
data class HostAndPortListData(override val values: List<HostAndPort>) : ListData<HostAndPort>
|
||||||
|
data class PathData(override val value: Path) : SingleData<Path>
|
||||||
|
data class PathListData(override val values: List<Path>) : ListData<Path>
|
||||||
|
data class URLData(override val value: URL) : SingleData<URL>
|
||||||
|
data class URLListData(override val values: List<URL>) : ListData<URL>
|
||||||
|
data class PropertiesData(override val value: Properties) : SingleData<Properties>
|
||||||
|
data class PropertiesListData(override val values: List<Properties>) : ListData<Properties>
|
||||||
|
data class MultiPropertyData(val i: Int, val b: Boolean, val l: List<String>)
|
||||||
|
data class NestedData(val first: StringData)
|
||||||
|
data class DataListData(val list: List<StringData>)
|
||||||
|
data class DefaultData(val a: Int, val defaultOfTwo: Int = 2)
|
||||||
|
data class NullableData(val nullable: String?)
|
||||||
|
data class OldData(
|
||||||
|
@OldConfig("oldValue")
|
||||||
|
val newValue: String)
|
||||||
|
enum class TestEnum { Value1, Value2 }
|
||||||
|
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
|
||||||
import joptsimple.OptionParser
|
import joptsimple.OptionParser
|
||||||
import joptsimple.util.EnumConverter
|
import joptsimple.util.EnumConverter
|
||||||
import net.corda.core.div
|
import net.corda.core.div
|
||||||
import net.corda.node.services.config.ConfigHelper
|
import net.corda.node.services.config.ConfigHelper
|
||||||
|
import net.corda.node.services.config.FullNodeConfiguration
|
||||||
|
import net.corda.nodeapi.config.parseAs
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -64,7 +65,9 @@ data class CmdLineOptions(val baseDirectory: Path,
|
|||||||
val isVersion: Boolean,
|
val isVersion: Boolean,
|
||||||
val noLocalShell: Boolean,
|
val noLocalShell: Boolean,
|
||||||
val sshdServer: Boolean) {
|
val sshdServer: Boolean) {
|
||||||
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
|
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): FullNodeConfiguration {
|
||||||
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
|
return ConfigHelper
|
||||||
|
.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
|
||||||
|
.parseAs<FullNodeConfiguration>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
import com.jcabi.manifests.Manifests
|
import com.jcabi.manifests.Manifests
|
||||||
import com.typesafe.config.Config
|
|
||||||
import com.typesafe.config.ConfigException
|
import com.typesafe.config.ConfigException
|
||||||
import joptsimple.OptionException
|
import joptsimple.OptionException
|
||||||
import net.corda.core.*
|
import net.corda.core.*
|
||||||
@ -86,9 +85,7 @@ fun main(args: Array<String>) {
|
|||||||
printBasicNodeInfo("Logs can be found in", System.getProperty("log-path"))
|
printBasicNodeInfo("Logs can be found in", System.getProperty("log-path"))
|
||||||
|
|
||||||
val conf = try {
|
val conf = try {
|
||||||
val conf = cmdlineOptions.loadConfig()
|
cmdlineOptions.loadConfig()
|
||||||
checkConfigVersion(conf)
|
|
||||||
FullNodeConfiguration(cmdlineOptions.baseDirectory, conf)
|
|
||||||
} catch (e: ConfigException) {
|
} catch (e: ConfigException) {
|
||||||
println("Unable to load the configuration file: ${e.rootCause.message}")
|
println("Unable to load the configuration file: ${e.rootCause.message}")
|
||||||
exitProcess(2)
|
exitProcess(2)
|
||||||
@ -157,16 +154,6 @@ fun main(args: Array<String>) {
|
|||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkConfigVersion(conf: Config) {
|
|
||||||
// TODO: Remove this check in future milestone.
|
|
||||||
if (conf.hasPath("artemisAddress")) {
|
|
||||||
// artemisAddress has been renamed to p2pAddress in M10.
|
|
||||||
println("artemisAddress has been renamed to p2pAddress in M10, please upgrade your configuration file and start Corda node again.")
|
|
||||||
println("Corda will now exit...")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkJavaVersion() {
|
private fun checkJavaVersion() {
|
||||||
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
|
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
|
||||||
try {
|
try {
|
||||||
|
@ -27,6 +27,7 @@ import net.corda.node.utilities.ServiceIdentityGenerator
|
|||||||
import net.corda.nodeapi.ArtemisMessagingComponent
|
import net.corda.nodeapi.ArtemisMessagingComponent
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.nodeapi.config.SSLConfiguration
|
import net.corda.nodeapi.config.SSLConfiguration
|
||||||
|
import net.corda.nodeapi.config.parseAs
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
@ -115,6 +116,7 @@ data class NodeHandle(
|
|||||||
val nodeInfo: NodeInfo,
|
val nodeInfo: NodeInfo,
|
||||||
val rpc: CordaRPCOps,
|
val rpc: CordaRPCOps,
|
||||||
val configuration: FullNodeConfiguration,
|
val configuration: FullNodeConfiguration,
|
||||||
|
val webAddress: HostAndPort,
|
||||||
val process: Process
|
val process: Process
|
||||||
) {
|
) {
|
||||||
fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.rpcAddress!!)
|
fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.rpcAddress!!)
|
||||||
@ -421,7 +423,7 @@ class DriverDSL(
|
|||||||
"useTestClock" to useTestClock,
|
"useTestClock" to useTestClock,
|
||||||
"rpcUsers" to rpcUsers.map {
|
"rpcUsers" to rpcUsers.map {
|
||||||
mapOf(
|
mapOf(
|
||||||
"user" to it.username,
|
"username" to it.username,
|
||||||
"password" to it.password,
|
"password" to it.password,
|
||||||
"permissions" to it.permissions
|
"permissions" to it.permissions
|
||||||
)
|
)
|
||||||
@ -429,22 +431,19 @@ class DriverDSL(
|
|||||||
"verifierType" to verifierType.name
|
"verifierType" to verifierType.name
|
||||||
) + customOverrides
|
) + customOverrides
|
||||||
|
|
||||||
val configuration = FullNodeConfiguration(
|
val config = ConfigHelper.loadConfig(
|
||||||
baseDirectory,
|
|
||||||
ConfigHelper.loadConfig(
|
|
||||||
baseDirectory = baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
allowMissingConfig = true,
|
allowMissingConfig = true,
|
||||||
configOverrides = configOverrides
|
configOverrides = configOverrides)
|
||||||
)
|
val configuration = config.parseAs<FullNodeConfiguration>()
|
||||||
)
|
|
||||||
|
|
||||||
val processFuture = startNode(executorService, configuration, quasarJarPath, debugPort, systemProperties)
|
val processFuture = startNode(executorService, configuration, config, quasarJarPath, debugPort, systemProperties)
|
||||||
registerProcess(processFuture)
|
registerProcess(processFuture)
|
||||||
return processFuture.flatMap { process ->
|
return processFuture.flatMap { process ->
|
||||||
// We continue to use SSL enabled port for RPC when its for node user.
|
// We continue to use SSL enabled port for RPC when its for node user.
|
||||||
establishRpc(p2pAddress, configuration).flatMap { rpc ->
|
establishRpc(p2pAddress, configuration).flatMap { rpc ->
|
||||||
rpc.waitUntilRegisteredWithNetworkMap().map {
|
rpc.waitUntilRegisteredWithNetworkMap().map {
|
||||||
NodeHandle(rpc.nodeIdentity(), rpc, configuration, process)
|
NodeHandle(rpc.nodeIdentity(), rpc, configuration, webAddress, process)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -482,34 +481,29 @@ class DriverDSL(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryWebserver(configuration: FullNodeConfiguration, process: Process): HostAndPort? {
|
private fun queryWebserver(handle: NodeHandle, process: Process): HostAndPort {
|
||||||
val protocol = if (configuration.useHTTPS) {
|
val protocol = if (handle.configuration.useHTTPS) "https://" else "http://"
|
||||||
"https://"
|
val url = URL("$protocol${handle.webAddress}/api/status")
|
||||||
} else {
|
val client = OkHttpClient.Builder().connectTimeout(5, SECONDS).readTimeout(60, SECONDS).build()
|
||||||
"http://"
|
|
||||||
}
|
|
||||||
val url = URL(protocol + configuration.webAddress.toString() + "/api/status")
|
|
||||||
val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
|
|
||||||
|
|
||||||
while (process.isAlive) try {
|
while (process.isAlive) try {
|
||||||
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
||||||
if (response.isSuccessful && (response.body().string() == "started")) {
|
if (response.isSuccessful && (response.body().string() == "started")) {
|
||||||
return configuration.webAddress
|
return handle.webAddress
|
||||||
}
|
}
|
||||||
} catch(e: ConnectException) {
|
} catch(e: ConnectException) {
|
||||||
log.debug("Retrying webserver info at ${configuration.webAddress}")
|
log.debug("Retrying webserver info at ${handle.webAddress}")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.error("Webserver at ${configuration.webAddress} has died")
|
throw IllegalStateException("Webserver at ${handle.webAddress} has died")
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startWebserver(handle: NodeHandle): ListenableFuture<HostAndPort> {
|
override fun startWebserver(handle: NodeHandle): ListenableFuture<HostAndPort> {
|
||||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||||
val process = DriverDSL.startWebserver(executorService, handle.configuration, debugPort)
|
val process = DriverDSL.startWebserver(executorService, handle, debugPort)
|
||||||
registerProcess(process)
|
registerProcess(process)
|
||||||
return process.map {
|
return process.map {
|
||||||
queryWebserver(handle.configuration, it)!!
|
queryWebserver(handle, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,7 +531,7 @@ class DriverDSL(
|
|||||||
)
|
)
|
||||||
|
|
||||||
log.info("Starting network-map-service")
|
log.info("Starting network-map-service")
|
||||||
val startNode = startNode(executorService, FullNodeConfiguration(baseDirectory, config), quasarJarPath, debugPort, systemProperties)
|
val startNode = startNode(executorService, config.parseAs<FullNodeConfiguration>(), config, quasarJarPath, debugPort, systemProperties)
|
||||||
registerProcess(startNode)
|
registerProcess(startNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,13 +547,14 @@ class DriverDSL(
|
|||||||
private fun startNode(
|
private fun startNode(
|
||||||
executorService: ListeningScheduledExecutorService,
|
executorService: ListeningScheduledExecutorService,
|
||||||
nodeConf: FullNodeConfiguration,
|
nodeConf: FullNodeConfiguration,
|
||||||
|
config: Config,
|
||||||
quasarJarPath: String,
|
quasarJarPath: String,
|
||||||
debugPort: Int?,
|
debugPort: Int?,
|
||||||
overriddenSystemProperties: Map<String, String>
|
overriddenSystemProperties: Map<String, String>
|
||||||
): ListenableFuture<Process> {
|
): ListenableFuture<Process> {
|
||||||
return executorService.submit<Process> {
|
return executorService.submit<Process> {
|
||||||
// Write node.conf
|
// Write node.conf
|
||||||
writeConfig(nodeConf.baseDirectory, "node.conf", nodeConf.config)
|
writeConfig(nodeConf.baseDirectory, "node.conf", config)
|
||||||
|
|
||||||
val systemProperties = mapOf(
|
val systemProperties = mapOf(
|
||||||
"name" to nodeConf.myLegalName,
|
"name" to nodeConf.myLegalName,
|
||||||
@ -586,19 +581,19 @@ class DriverDSL(
|
|||||||
|
|
||||||
private fun startWebserver(
|
private fun startWebserver(
|
||||||
executorService: ListeningScheduledExecutorService,
|
executorService: ListeningScheduledExecutorService,
|
||||||
nodeConf: FullNodeConfiguration,
|
handle: NodeHandle,
|
||||||
debugPort: Int?
|
debugPort: Int?
|
||||||
): ListenableFuture<Process> {
|
): ListenableFuture<Process> {
|
||||||
return executorService.submit<Process> {
|
return executorService.submit<Process> {
|
||||||
val className = "net.corda.webserver.WebServer"
|
val className = "net.corda.webserver.WebServer"
|
||||||
ProcessUtilities.startJavaProcess(
|
ProcessUtilities.startJavaProcess(
|
||||||
className = className, // cannot directly get class for this, so just use string
|
className = className, // cannot directly get class for this, so just use string
|
||||||
arguments = listOf("--base-directory", nodeConf.baseDirectory.toString()),
|
arguments = listOf("--base-directory", handle.configuration.baseDirectory.toString()),
|
||||||
jdwpPort = debugPort,
|
jdwpPort = debugPort,
|
||||||
extraJvmArguments = listOf("-Dname=node-${nodeConf.p2pAddress}-webserver"),
|
extraJvmArguments = listOf("-Dname=node-${handle.configuration.p2pAddress}-webserver"),
|
||||||
errorLogPath = Paths.get("error.$className.log")
|
errorLogPath = Paths.get("error.$className.log")
|
||||||
)
|
)
|
||||||
}.flatMap { process -> addressMustBeBound(executorService, nodeConf.webAddress).map { process } }
|
}.flatMap { process -> addressMustBeBound(executorService, handle.webAddress).map { process } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import net.corda.node.services.transactions.PersistentUniquenessProvider
|
|||||||
import net.corda.node.services.transactions.RaftUniquenessProvider
|
import net.corda.node.services.transactions.RaftUniquenessProvider
|
||||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||||
import net.corda.node.services.transactions.RaftNonValidatingNotaryService
|
import net.corda.node.services.transactions.RaftNonValidatingNotaryService
|
||||||
|
import net.corda.node.services.transactions.*
|
||||||
import net.corda.node.utilities.AddressUtils
|
import net.corda.node.utilities.AddressUtils
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent.NetworkMapAddress
|
import net.corda.nodeapi.ArtemisMessagingComponent.NetworkMapAddress
|
||||||
@ -128,7 +129,7 @@ class Node(override val configuration: FullNodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun makeMessagingService(): MessagingServiceInternal {
|
override fun makeMessagingService(): MessagingServiceInternal {
|
||||||
userService = RPCUserServiceImpl(configuration)
|
userService = RPCUserServiceImpl(configuration.rpcUsers)
|
||||||
val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
|
val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
|
||||||
val myIdentityOrNullIfNetworkMapService = if (networkMapAddress != null) obtainLegalIdentity().owningKey else null
|
val myIdentityOrNullIfNetworkMapService = if (networkMapAddress != null) obtainLegalIdentity().owningKey else null
|
||||||
return NodeMessagingClient(
|
return NodeMessagingClient(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.node.services
|
package net.corda.node.services
|
||||||
|
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,13 +15,8 @@ interface RPCUserService {
|
|||||||
|
|
||||||
// TODO Store passwords as salted hashes
|
// TODO Store passwords as salted hashes
|
||||||
// TODO Or ditch this and consider something like Apache Shiro
|
// TODO Or ditch this and consider something like Apache Shiro
|
||||||
class RPCUserServiceImpl(config: NodeConfiguration) : RPCUserService {
|
class RPCUserServiceImpl(override val users: List<User>) : RPCUserService {
|
||||||
|
override fun getUser(username: String): User? = users.find { it.username == username }
|
||||||
private val _users = config.rpcUsers.associateBy(User::username)
|
|
||||||
|
|
||||||
override fun getUser(username: String): User? = _users[username]
|
|
||||||
|
|
||||||
override val users: List<User> get() = _users.values.toList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startFlowPermission(className: String) = "StartFlow.$className"
|
fun startFlowPermission(className: String) = "StartFlow.$className"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.node.services.config
|
package net.corda.node.services.config
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import com.typesafe.config.Config
|
|
||||||
import net.corda.core.div
|
import net.corda.core.div
|
||||||
import net.corda.core.node.NodeVersionInfo
|
import net.corda.core.node.NodeVersionInfo
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
@ -12,19 +11,13 @@ import net.corda.node.services.messaging.CertificateChainCheckPolicy
|
|||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
import net.corda.node.utilities.TestClock
|
import net.corda.node.utilities.TestClock
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.nodeapi.config.getListOrElse
|
import net.corda.nodeapi.config.OldConfig
|
||||||
import net.corda.nodeapi.config.getOrElse
|
import net.corda.nodeapi.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.config.getValue
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
enum class VerifierType {
|
interface NodeConfiguration : SSLConfiguration {
|
||||||
InMemory,
|
|
||||||
OutOfProcess
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NodeConfiguration : net.corda.nodeapi.config.SSLConfiguration {
|
|
||||||
val baseDirectory: Path
|
val baseDirectory: Path
|
||||||
override val certificatesDirectory: Path get() = baseDirectory / "certificates"
|
override val certificatesDirectory: Path get() = baseDirectory / "certificates"
|
||||||
val myLegalName: String
|
val myLegalName: String
|
||||||
@ -32,83 +25,89 @@ interface NodeConfiguration : net.corda.nodeapi.config.SSLConfiguration {
|
|||||||
val nearestCity: String
|
val nearestCity: String
|
||||||
val emailAddress: String
|
val emailAddress: String
|
||||||
val exportJMXto: String
|
val exportJMXto: String
|
||||||
val dataSourceProperties: Properties get() = Properties()
|
val dataSourceProperties: Properties
|
||||||
val rpcUsers: List<User> get() = emptyList()
|
val rpcUsers: List<User>
|
||||||
val devMode: Boolean
|
val devMode: Boolean
|
||||||
val certificateSigningService: URL
|
val certificateSigningService: URL
|
||||||
val certificateChainCheckPolicies: Map<String, CertificateChainCheckPolicy>
|
val certificateChainCheckPolicies: List<CertChainPolicyConfig>
|
||||||
val verifierType: VerifierType
|
val verifierType: VerifierType
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
data class FullNodeConfiguration(
|
||||||
* [baseDirectory] is not retrieved from the config file but rather from a command line argument.
|
// TODO Remove this subsitution value and use baseDirectory as the subsitution instead
|
||||||
*/
|
@Deprecated(
|
||||||
class FullNodeConfiguration(override val baseDirectory: Path, val config: Config) : NodeConfiguration {
|
"This is a subsitution value which points to the baseDirectory and is manually added into the config before parsing",
|
||||||
override val myLegalName: String by config
|
ReplaceWith("baseDirectory"))
|
||||||
override val nearestCity: String by config
|
val basedir: Path,
|
||||||
override val emailAddress: String by config
|
override val myLegalName: String,
|
||||||
|
override val nearestCity: String,
|
||||||
|
override val emailAddress: String,
|
||||||
|
override val keyStorePassword: String,
|
||||||
|
override val trustStorePassword: String,
|
||||||
|
override val dataSourceProperties: Properties,
|
||||||
|
override val certificateSigningService: URL,
|
||||||
|
override val networkMapService: NetworkMapInfo?,
|
||||||
|
override val rpcUsers: List<User>,
|
||||||
|
override val verifierType: VerifierType,
|
||||||
|
val useHTTPS: Boolean,
|
||||||
|
@OldConfig("artemisAddress")
|
||||||
|
val p2pAddress: HostAndPort,
|
||||||
|
val rpcAddress: HostAndPort?,
|
||||||
|
// TODO This field is slightly redundant as p2pAddress is sufficient to hold the address of the node's MQ broker.
|
||||||
|
// Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one
|
||||||
|
val messagingServerAddress: HostAndPort?,
|
||||||
|
val extraAdvertisedServiceIds: List<String>,
|
||||||
|
val notaryNodeId: Int?,
|
||||||
|
val notaryNodeAddress: HostAndPort?,
|
||||||
|
val notaryClusterAddresses: List<HostAndPort>,
|
||||||
|
override val certificateChainCheckPolicies: List<CertChainPolicyConfig>,
|
||||||
|
override val devMode: Boolean = false,
|
||||||
|
val useTestClock: Boolean = false
|
||||||
|
) : NodeConfiguration {
|
||||||
|
/** This is not retrieved from the config file but rather from a command line argument. */
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
override val baseDirectory: Path get() = basedir
|
||||||
override val exportJMXto: String get() = "http"
|
override val exportJMXto: String get() = "http"
|
||||||
override val keyStorePassword: String by config
|
|
||||||
override val trustStorePassword: String by config
|
|
||||||
override val dataSourceProperties: Properties by config
|
|
||||||
override val devMode: Boolean by config.getOrElse { false }
|
|
||||||
override val certificateSigningService: URL by config
|
|
||||||
override val networkMapService: NetworkMapInfo? = config.getOptionalConfig("networkMapService")?.run {
|
|
||||||
NetworkMapInfo(
|
|
||||||
HostAndPort.fromString(getString("address")),
|
|
||||||
getString("legalName"))
|
|
||||||
}
|
|
||||||
override val rpcUsers: List<User> = config
|
|
||||||
.getListOrElse<Config>("rpcUsers") { emptyList() }
|
|
||||||
.map {
|
|
||||||
val username = it.getString("user")
|
|
||||||
require(username.matches("\\w+".toRegex())) { "Username $username contains invalid characters" }
|
|
||||||
val password = it.getString("password")
|
|
||||||
val permissions = it.getListOrElse<String>("permissions") { emptyList() }.toSet()
|
|
||||||
User(username, password, permissions)
|
|
||||||
}
|
|
||||||
override val certificateChainCheckPolicies = config.getOptionalConfig("certificateChainCheckPolicies")?.run {
|
|
||||||
entrySet().associateByTo(HashMap(), { it.key }, { parseCertificateChainCheckPolicy(getConfig(it.key)) })
|
|
||||||
} ?: emptyMap<String, CertificateChainCheckPolicy>()
|
|
||||||
override val verifierType: VerifierType by config
|
|
||||||
val useHTTPS: Boolean by config
|
|
||||||
val p2pAddress: HostAndPort by config
|
|
||||||
val rpcAddress: HostAndPort? by config
|
|
||||||
val webAddress: HostAndPort by config
|
|
||||||
// TODO This field is slightly redundant as p2pAddress is sufficient to hold the address of the node's MQ broker.
|
|
||||||
// Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one
|
|
||||||
val messagingServerAddress: HostAndPort? by config
|
|
||||||
val extraAdvertisedServiceIds: List<String> = config.getListOrElse<String>("extraAdvertisedServiceIds") { emptyList() }
|
|
||||||
val useTestClock: Boolean by config.getOrElse { false }
|
|
||||||
val notaryNodeId: Int? by config
|
|
||||||
val notaryNodeAddress: HostAndPort? by config
|
|
||||||
val notaryClusterAddresses: List<HostAndPort> = config
|
|
||||||
.getListOrElse<String>("notaryClusterAddresses") { emptyList() }
|
|
||||||
.map { HostAndPort.fromString(it) }
|
|
||||||
|
|
||||||
fun createNode(nodeVersionInfo: NodeVersionInfo): Node {
|
init {
|
||||||
// This is a sanity feature do not remove.
|
// This is a sanity feature do not remove.
|
||||||
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
|
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
|
||||||
|
// TODO Move this to ArtemisMessagingServer
|
||||||
|
rpcUsers.forEach {
|
||||||
|
require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNode(nodeVersionInfo: NodeVersionInfo): Node {
|
||||||
val advertisedServices = extraAdvertisedServiceIds
|
val advertisedServices = extraAdvertisedServiceIds
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.map { ServiceInfo.parse(it) }
|
.map { ServiceInfo.parse(it) }
|
||||||
.toMutableSet()
|
.toMutableSet()
|
||||||
if (networkMapService == null) advertisedServices.add(ServiceInfo(NetworkMapService.type))
|
if (networkMapService == null) advertisedServices += ServiceInfo(NetworkMapService.type)
|
||||||
|
|
||||||
return Node(this, advertisedServices, nodeVersionInfo, if (useTestClock) TestClock() else NodeClock())
|
return Node(this, advertisedServices, nodeVersionInfo, if (useTestClock) TestClock() else NodeClock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseCertificateChainCheckPolicy(config: Config): CertificateChainCheckPolicy {
|
enum class VerifierType {
|
||||||
val policy = config.getString("policy")
|
InMemory,
|
||||||
return when (policy) {
|
OutOfProcess
|
||||||
"Any" -> CertificateChainCheckPolicy.Any
|
|
||||||
"RootMustMatch" -> CertificateChainCheckPolicy.RootMustMatch
|
|
||||||
"LeafMustMatch" -> CertificateChainCheckPolicy.LeafMustMatch
|
|
||||||
"MustContainOneOf" -> CertificateChainCheckPolicy.MustContainOneOf(config.getStringList("trustedAliases").toSet())
|
|
||||||
else -> throw IllegalArgumentException("Invalid certificate chain check policy $policy")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Config.getOptionalConfig(path: String): Config? = if (hasPath(path)) getConfig(path) else null
|
enum class CertChainPolicyType {
|
||||||
|
Any,
|
||||||
|
RootMustMatch,
|
||||||
|
LeafMustMatch,
|
||||||
|
MustContainOneOf
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CertChainPolicyConfig(val role: String, val policy: CertChainPolicyType, val trustedAliases: Set<String>) {
|
||||||
|
val certificateChainCheckPolicy: CertificateChainCheckPolicy get() {
|
||||||
|
return when (policy) {
|
||||||
|
CertChainPolicyType.Any -> CertificateChainCheckPolicy.Any
|
||||||
|
CertChainPolicyType.RootMustMatch -> CertificateChainCheckPolicy.RootMustMatch
|
||||||
|
CertChainPolicyType.LeafMustMatch -> CertificateChainCheckPolicy.LeafMustMatch
|
||||||
|
CertChainPolicyType.MustContainOneOf -> CertificateChainCheckPolicy.MustContainOneOf(trustedAliases)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,16 +4,13 @@ import com.google.common.net.HostAndPort
|
|||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import io.netty.handler.ssl.SslHandler
|
import io.netty.handler.ssl.SslHandler
|
||||||
import net.corda.core.ThreadBox
|
import net.corda.core.*
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
|
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||||
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
|
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
|
||||||
import net.corda.core.div
|
|
||||||
import net.corda.core.minutes
|
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import net.corda.core.seconds
|
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.printBasicNodeInfo
|
import net.corda.node.printBasicNodeInfo
|
||||||
@ -251,8 +248,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
|||||||
)
|
)
|
||||||
val keyStore = X509Utilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword)
|
val keyStore = X509Utilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword)
|
||||||
val trustStore = X509Utilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
val trustStore = X509Utilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||||
val certChecks = defaultCertPolicies.mapValues {
|
val certChecks = defaultCertPolicies.mapValues { (role, defaultPolicy) ->
|
||||||
(config.certificateChainCheckPolicies[it.key] ?: it.value).createCheck(keyStore, trustStore)
|
val configPolicy = config.certificateChainCheckPolicies.noneOrSingle { it.role == role }?.certificateChainCheckPolicy
|
||||||
|
(configPolicy ?: defaultPolicy).createCheck(keyStore, trustStore)
|
||||||
}
|
}
|
||||||
val securityConfig = object : SecurityConfiguration() {
|
val securityConfig = object : SecurityConfiguration() {
|
||||||
// Override to make it work with our login module
|
// Override to make it work with our login module
|
||||||
|
@ -15,9 +15,9 @@ import net.corda.core.messaging.RPCReturnsObservables
|
|||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.node.services.RPCUserService
|
import net.corda.node.services.RPCUserService
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.core.utilities.debug
|
|
||||||
import net.corda.nodeapi.*
|
import net.corda.nodeapi.*
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
|
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
|
||||||
import org.apache.activemq.artemis.api.core.Message
|
import org.apache.activemq.artemis.api.core.Message
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
package net.corda.node.services
|
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
|
||||||
import net.corda.node.services.config.FullNodeConfiguration
|
|
||||||
import net.corda.nodeapi.User
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
|
||||||
import org.junit.Test
|
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
class RPCUserServiceImplTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `missing config`() {
|
|
||||||
val service = loadWithContents("{}")
|
|
||||||
assertThat(service.getUser("user")).isNull()
|
|
||||||
assertThat(service.users).isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `no users`() {
|
|
||||||
val service = loadWithContents("rpcUsers : []")
|
|
||||||
assertThat(service.getUser("user")).isNull()
|
|
||||||
assertThat(service.users).isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `no permissions`() {
|
|
||||||
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein }]")
|
|
||||||
val expectedUser = User("user1", "letmein", permissions = emptySet())
|
|
||||||
assertThat(service.getUser("user1")).isEqualTo(expectedUser)
|
|
||||||
assertThat(service.users).containsOnly(expectedUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `single permission, which is in lower case`() {
|
|
||||||
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein, permissions=[cash] }]")
|
|
||||||
assertThat(service.getUser("user1")?.permissions).containsOnly("cash")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `two permissions, which are upper case`() {
|
|
||||||
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein, permissions=[CASH, ADMIN] }]")
|
|
||||||
assertThat(service.getUser("user1")?.permissions).containsOnly("CASH", "ADMIN")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `two users`() {
|
|
||||||
val service = loadWithContents("""rpcUsers : [
|
|
||||||
{ user=user, password=password, permissions=[ADMIN] }
|
|
||||||
{ user=user2, password=password2, permissions=[] }
|
|
||||||
]""")
|
|
||||||
val user1 = User("user", "password", permissions = setOf("ADMIN"))
|
|
||||||
val user2 = User("user2", "password2", permissions = emptySet())
|
|
||||||
assertThat(service.getUser("user")).isEqualTo(user1)
|
|
||||||
assertThat(service.getUser("user2")).isEqualTo(user2)
|
|
||||||
assertThat(service.users).containsOnly(user1, user2)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `unknown user`() {
|
|
||||||
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein }]")
|
|
||||||
assertThat(service.getUser("test")).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Artemis special characters not permitted in usernames`() {
|
|
||||||
assertThatThrownBy { loadWithContents("rpcUsers : [{ user=user.1, password=letmein }]") }.hasMessageContaining(".")
|
|
||||||
assertThatThrownBy { loadWithContents("rpcUsers : [{ user=user*1, password=letmein }]") }.hasMessageContaining("*")
|
|
||||||
assertThatThrownBy { loadWithContents("""rpcUsers : [{ user="user#1", password=letmein }]""") }.hasMessageContaining("#")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadWithContents(configString: String): RPCUserServiceImpl {
|
|
||||||
return RPCUserServiceImpl(FullNodeConfiguration(Paths.get("."), ConfigFactory.parseString(configString)))
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
package net.corda.node.services.config
|
||||||
|
|
||||||
|
import net.corda.nodeapi.User
|
||||||
|
import net.corda.testing.testConfiguration
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
class FullNodeConfigurationTest {
|
||||||
|
@Test
|
||||||
|
fun `Artemis special characters not permitted in RPC usernames`() {
|
||||||
|
fun configWithRPCUsername(username: String): FullNodeConfiguration {
|
||||||
|
return testConfiguration(Paths.get("."), "NodeA", 0).copy(
|
||||||
|
rpcUsers = listOf(User(username, "pass", emptySet())))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThatThrownBy { configWithRPCUsername("user.1") }.hasMessageContaining(".")
|
||||||
|
assertThatThrownBy { configWithRPCUsername("user*1") }.hasMessageContaining("*")
|
||||||
|
assertThatThrownBy { configWithRPCUsername("user#1") }.hasMessageContaining("#")
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ import com.google.common.net.HostAndPort
|
|||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import com.typesafe.config.ConfigFactory.empty
|
|
||||||
import net.corda.core.crypto.composite
|
import net.corda.core.crypto.composite
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.messaging.Message
|
import net.corda.core.messaging.Message
|
||||||
@ -72,7 +71,7 @@ class ArtemisMessagingTests {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
val baseDirectory = temporaryFolder.root.toPath()
|
val baseDirectory = temporaryFolder.root.toPath()
|
||||||
userService = RPCUserServiceImpl(FullNodeConfiguration(baseDirectory, empty()))
|
userService = RPCUserServiceImpl(emptyList())
|
||||||
config = TestNodeConfiguration(
|
config = TestNodeConfiguration(
|
||||||
baseDirectory = baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
myLegalName = "me",
|
myLegalName = "me",
|
||||||
|
@ -52,7 +52,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||||
ext.rpcUsers = [ ['user' : "demo", 'password' : "demo", 'permissions' : ["StartFlow.net.corda.flows.FinalityFlow"]] ]
|
ext.rpcUsers = [ ['username' : "demo", 'password' : "demo", 'permissions' : ["StartFlow.net.corda.flows.FinalityFlow"]] ]
|
||||||
|
|
||||||
directory "./build/nodes"
|
directory "./build/nodes"
|
||||||
networkMap "Controller"
|
networkMap "Controller"
|
||||||
|
@ -70,7 +70,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
webPort 10007
|
webPort 10007
|
||||||
cordapps = []
|
cordapps = []
|
||||||
rpcUsers = [
|
rpcUsers = [
|
||||||
['user' : "bankUser",
|
['username' : "bankUser",
|
||||||
'password' : "test",
|
'password' : "test",
|
||||||
'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow",
|
'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow",
|
||||||
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"]]
|
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"]]
|
||||||
@ -85,7 +85,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
webPort 10010
|
webPort 10010
|
||||||
cordapps = []
|
cordapps = []
|
||||||
rpcUsers = [
|
rpcUsers = [
|
||||||
['user' : "bigCorpUser",
|
['username' : "bigCorpUser",
|
||||||
'password' : "test",
|
'password' : "test",
|
||||||
'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow"]]
|
'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow"]]
|
||||||
]
|
]
|
||||||
|
@ -83,7 +83,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', 'generateN
|
|||||||
p2pPort 10002
|
p2pPort 10002
|
||||||
rpcPort 10003
|
rpcPort 10003
|
||||||
cordapps = []
|
cordapps = []
|
||||||
rpcUsers = [['user': "demo", 'password': "demo", 'permissions': [
|
rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
|
||||||
'StartFlow.net.corda.notarydemo.flows.DummyIssueAndMove',
|
'StartFlow.net.corda.notarydemo.flows.DummyIssueAndMove',
|
||||||
'StartFlow.net.corda.flows.NotaryFlow$Client'
|
'StartFlow.net.corda.flows.NotaryFlow$Client'
|
||||||
]]]
|
]]]
|
||||||
|
@ -59,7 +59,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||||
ext.rpcUsers = [['user': "demo", 'password': "demo", 'permissions': [
|
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
|
||||||
'StartFlow.net.corda.flows.IssuerFlow$IssuanceRequester',
|
'StartFlow.net.corda.flows.IssuerFlow$IssuanceRequester',
|
||||||
"StartFlow.net.corda.traderdemo.flow.SellerFlow"
|
"StartFlow.net.corda.traderdemo.flow.SellerFlow"
|
||||||
]]]
|
]]]
|
||||||
|
@ -5,8 +5,6 @@ package net.corda.testing
|
|||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.typesafe.config.Config
|
|
||||||
import net.corda.nodeapi.config.SSLConfiguration
|
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
@ -20,12 +18,11 @@ import net.corda.core.utilities.DUMMY_NOTARY
|
|||||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||||
import net.corda.node.internal.AbstractNode
|
import net.corda.node.internal.AbstractNode
|
||||||
import net.corda.node.internal.NetworkMapInfo
|
import net.corda.node.internal.NetworkMapInfo
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.*
|
||||||
import net.corda.node.services.config.configureDevKeyAndTrustStores
|
|
||||||
import net.corda.node.services.config.VerifierType
|
|
||||||
import net.corda.node.services.messaging.CertificateChainCheckPolicy
|
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
import net.corda.node.utilities.AddOrRemove.ADD
|
import net.corda.node.utilities.AddOrRemove.ADD
|
||||||
|
import net.corda.nodeapi.User
|
||||||
|
import net.corda.nodeapi.config.SSLConfiguration
|
||||||
import net.corda.testing.node.MockIdentityService
|
import net.corda.testing.node.MockIdentityService
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
import net.corda.testing.node.makeTestDataSourceProperties
|
import net.corda.testing.node.makeTestDataSourceProperties
|
||||||
@ -162,22 +159,47 @@ inline fun <reified P : FlowLogic<*>> AbstractNode.initiateSingleShotFlow(
|
|||||||
return future
|
return future
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Replace this with testConfiguration
|
||||||
data class TestNodeConfiguration(
|
data class TestNodeConfiguration(
|
||||||
override val baseDirectory: Path,
|
override val baseDirectory: Path,
|
||||||
override val myLegalName: String,
|
override val myLegalName: String,
|
||||||
override val networkMapService: NetworkMapInfo?,
|
override val networkMapService: NetworkMapInfo?,
|
||||||
override val keyStorePassword: String = "cordacadevpass",
|
override val keyStorePassword: String = "cordacadevpass",
|
||||||
override val trustStorePassword: String = "trustpass",
|
override val trustStorePassword: String = "trustpass",
|
||||||
|
override val rpcUsers: List<User> = emptyList(),
|
||||||
override val dataSourceProperties: Properties = makeTestDataSourceProperties(myLegalName),
|
override val dataSourceProperties: Properties = makeTestDataSourceProperties(myLegalName),
|
||||||
override val nearestCity: String = "Null Island",
|
override val nearestCity: String = "Null Island",
|
||||||
override val emailAddress: String = "",
|
override val emailAddress: String = "",
|
||||||
override val exportJMXto: String = "",
|
override val exportJMXto: String = "",
|
||||||
override val devMode: Boolean = true,
|
override val devMode: Boolean = true,
|
||||||
override val certificateSigningService: URL = URL("http://localhost"),
|
override val certificateSigningService: URL = URL("http://localhost"),
|
||||||
override val certificateChainCheckPolicies: Map<String, CertificateChainCheckPolicy> = emptyMap(),
|
override val certificateChainCheckPolicies: List<CertChainPolicyConfig> = emptyList(),
|
||||||
override val verifierType: VerifierType = VerifierType.InMemory) : NodeConfiguration
|
override val verifierType: VerifierType = VerifierType.InMemory) : NodeConfiguration
|
||||||
|
|
||||||
fun Config.getHostAndPort(name: String) = HostAndPort.fromString(getString(name))
|
fun testConfiguration(baseDirectory: Path, legalName: String, basePort: Int): FullNodeConfiguration {
|
||||||
|
return FullNodeConfiguration(
|
||||||
|
basedir = baseDirectory,
|
||||||
|
myLegalName = legalName,
|
||||||
|
networkMapService = null,
|
||||||
|
nearestCity = "Null Island",
|
||||||
|
emailAddress = "",
|
||||||
|
keyStorePassword = "cordacadevpass",
|
||||||
|
trustStorePassword = "trustpass",
|
||||||
|
dataSourceProperties = makeTestDataSourceProperties(legalName),
|
||||||
|
certificateSigningService = URL("http://localhost"),
|
||||||
|
rpcUsers = emptyList(),
|
||||||
|
verifierType = VerifierType.InMemory,
|
||||||
|
useHTTPS = false,
|
||||||
|
p2pAddress = HostAndPort.fromParts("localhost", basePort),
|
||||||
|
rpcAddress = HostAndPort.fromParts("localhost", basePort + 1),
|
||||||
|
messagingServerAddress = null,
|
||||||
|
extraAdvertisedServiceIds = emptyList(),
|
||||||
|
notaryNodeId = null,
|
||||||
|
notaryNodeAddress = null,
|
||||||
|
notaryClusterAddresses = emptyList(),
|
||||||
|
certificateChainCheckPolicies = emptyList(),
|
||||||
|
devMode = true)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun configureTestSSL(legalName: String = "Mega Corp."): SSLConfiguration = object : SSLConfiguration {
|
fun configureTestSSL(legalName: String = "Mega Corp."): SSLConfiguration = object : SSLConfiguration {
|
||||||
|
@ -14,6 +14,7 @@ import net.corda.node.services.config.FullNodeConfiguration
|
|||||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
|
import net.corda.nodeapi.config.parseAs
|
||||||
import net.corda.testing.MOCK_NODE_VERSION_INFO
|
import net.corda.testing.MOCK_NODE_VERSION_INFO
|
||||||
import net.corda.testing.getFreeLocalPorts
|
import net.corda.testing.getFreeLocalPorts
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -126,7 +127,7 @@ abstract class NodeBasedTest {
|
|||||||
"extraAdvertisedServiceIds" to advertisedServices.map { it.toString() },
|
"extraAdvertisedServiceIds" to advertisedServices.map { it.toString() },
|
||||||
"rpcUsers" to rpcUsers.map {
|
"rpcUsers" to rpcUsers.map {
|
||||||
mapOf(
|
mapOf(
|
||||||
"user" to it.username,
|
"username" to it.username,
|
||||||
"password" to it.password,
|
"password" to it.password,
|
||||||
"permissions" to it.permissions
|
"permissions" to it.permissions
|
||||||
)
|
)
|
||||||
@ -134,7 +135,7 @@ abstract class NodeBasedTest {
|
|||||||
) + configOverrides
|
) + configOverrides
|
||||||
)
|
)
|
||||||
|
|
||||||
val node = FullNodeConfiguration(baseDirectory, config).createNode(MOCK_NODE_VERSION_INFO)
|
val node = config.parseAs<FullNodeConfiguration>().createNode(MOCK_NODE_VERSION_INFO)
|
||||||
node.start()
|
node.start()
|
||||||
nodes += node
|
nodes += node
|
||||||
thread(name = legalName) {
|
thread(name = legalName) {
|
||||||
|
@ -30,7 +30,7 @@ class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeL
|
|||||||
|
|
||||||
private val databaseWithCloseable: Pair<Closeable, Database> = configureDatabase(config.dataSourceProperties)
|
private val databaseWithCloseable: Pair<Closeable, Database> = configureDatabase(config.dataSourceProperties)
|
||||||
val database: Database get() = databaseWithCloseable.second
|
val database: Database get() = databaseWithCloseable.second
|
||||||
val userService = RPCUserServiceImpl(config)
|
val userService = RPCUserServiceImpl(config.rpcUsers)
|
||||||
val monitoringService = MonitoringService(MetricRegistry())
|
val monitoringService = MonitoringService(MetricRegistry())
|
||||||
val identity: KeyPair = generateKeyPair()
|
val identity: KeyPair = generateKeyPair()
|
||||||
val executor = ServiceAffinityExecutor(config.myLegalName, 1)
|
val executor = ServiceAffinityExecutor(config.myLegalName, 1)
|
||||||
|
@ -5,14 +5,14 @@ import net.corda.nodeapi.User
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun User.toMap(): Map<String, Any> = mapOf(
|
fun User.toMap(): Map<String, Any> = mapOf(
|
||||||
"user" to username,
|
"username" to username,
|
||||||
"password" to password,
|
"password" to password,
|
||||||
"permissions" to permissions
|
"permissions" to permissions
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun toUser(map: Map<String, Any>) = User(
|
fun toUser(map: Map<String, Any>) = User(
|
||||||
map.getOrElse("user", { "none" }) as String,
|
map.getOrElse("username", { "none" }) as String,
|
||||||
map.getOrElse("password", { "none" }) as String,
|
map.getOrElse("password", { "none" }) as String,
|
||||||
LinkedHashSet<String>(map.getOrElse("permissions", { emptyList<String>() }) as Collection<String>)
|
LinkedHashSet<String>(map.getOrElse("permissions", { emptyList<String>() }) as Collection<String>)
|
||||||
)
|
)
|
||||||
|
@ -7,11 +7,15 @@ import net.corda.core.div
|
|||||||
import net.corda.node.internal.NetworkMapInfo
|
import net.corda.node.internal.NetworkMapInfo
|
||||||
import net.corda.node.services.config.FullNodeConfiguration
|
import net.corda.node.services.config.FullNodeConfiguration
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
|
import net.corda.nodeapi.config.parseAs
|
||||||
import net.corda.webserver.WebServerConfig
|
import net.corda.webserver.WebServerConfig
|
||||||
|
import org.junit.Test
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.test.*
|
import kotlin.test.assertEquals
|
||||||
import org.junit.Test
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class NodeConfigTest {
|
class NodeConfigTest {
|
||||||
|
|
||||||
@ -129,7 +133,7 @@ class NodeConfigTest {
|
|||||||
+ "\"p2pAddress\":\"localhost:10001\","
|
+ "\"p2pAddress\":\"localhost:10001\","
|
||||||
+ "\"rpcAddress\":\"localhost:40002\","
|
+ "\"rpcAddress\":\"localhost:40002\","
|
||||||
+ "\"rpcUsers\":["
|
+ "\"rpcUsers\":["
|
||||||
+ "{\"password\":\"letmein\",\"permissions\":[\"ALL\"],\"user\":\"jenny\"}"
|
+ "{\"password\":\"letmein\",\"permissions\":[\"ALL\"],\"username\":\"jenny\"}"
|
||||||
+ "],"
|
+ "],"
|
||||||
+ "\"useTestClock\":true,"
|
+ "\"useTestClock\":true,"
|
||||||
+ "\"webAddress\":\"localhost:20001\""
|
+ "\"webAddress\":\"localhost:20001\""
|
||||||
@ -159,7 +163,7 @@ class NodeConfigTest {
|
|||||||
+ "\"p2pAddress\":\"localhost:10001\","
|
+ "\"p2pAddress\":\"localhost:10001\","
|
||||||
+ "\"rpcAddress\":\"localhost:40002\","
|
+ "\"rpcAddress\":\"localhost:40002\","
|
||||||
+ "\"rpcUsers\":["
|
+ "\"rpcUsers\":["
|
||||||
+ "{\"password\":\"letmein\",\"permissions\":[\"ALL\"],\"user\":\"jenny\"}"
|
+ "{\"password\":\"letmein\",\"permissions\":[\"ALL\"],\"username\":\"jenny\"}"
|
||||||
+ "],"
|
+ "],"
|
||||||
+ "\"useTestClock\":true,"
|
+ "\"useTestClock\":true,"
|
||||||
+ "\"webAddress\":\"localhost:20001\""
|
+ "\"webAddress\":\"localhost:20001\""
|
||||||
@ -184,11 +188,10 @@ class NodeConfigTest {
|
|||||||
.withValue("basedir", ConfigValueFactory.fromAnyRef(baseDir.toString()))
|
.withValue("basedir", ConfigValueFactory.fromAnyRef(baseDir.toString()))
|
||||||
.withFallback(ConfigFactory.parseResources("reference.conf"))
|
.withFallback(ConfigFactory.parseResources("reference.conf"))
|
||||||
.resolve()
|
.resolve()
|
||||||
val fullConfig = FullNodeConfiguration(baseDir, nodeConfig)
|
val fullConfig = nodeConfig.parseAs<FullNodeConfiguration>()
|
||||||
|
|
||||||
assertEquals("My Name", fullConfig.myLegalName)
|
assertEquals("My Name", fullConfig.myLegalName)
|
||||||
assertEquals("Stockholm", fullConfig.nearestCity)
|
assertEquals("Stockholm", fullConfig.nearestCity)
|
||||||
assertEquals(localPort(20001), fullConfig.webAddress)
|
|
||||||
assertEquals(localPort(40002), fullConfig.rpcAddress)
|
assertEquals(localPort(40002), fullConfig.rpcAddress)
|
||||||
assertEquals(localPort(10001), fullConfig.p2pAddress)
|
assertEquals(localPort(10001), fullConfig.p2pAddress)
|
||||||
assertEquals(listOf("my.service"), fullConfig.extraAdvertisedServiceIds)
|
assertEquals(listOf("my.service"), fullConfig.extraAdvertisedServiceIds)
|
||||||
|
@ -2,7 +2,7 @@ package net.corda.demobench.model
|
|||||||
|
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.*
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class UserTest {
|
class UserTest {
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ class UserTest {
|
|||||||
@Test
|
@Test
|
||||||
fun createFromMap() {
|
fun createFromMap() {
|
||||||
val map = mapOf(
|
val map = mapOf(
|
||||||
"user" to "MyName",
|
"username" to "MyName",
|
||||||
"password" to "MyPassword",
|
"password" to "MyPassword",
|
||||||
"permissions" to listOf("Flow.MyFlow")
|
"permissions" to listOf("Flow.MyFlow")
|
||||||
)
|
)
|
||||||
@ -31,7 +31,7 @@ class UserTest {
|
|||||||
fun userToMap() {
|
fun userToMap() {
|
||||||
val user = User("MyName", "MyPassword", setOf("Flow.MyFlow"))
|
val user = User("MyName", "MyPassword", setOf("Flow.MyFlow"))
|
||||||
val map = user.toMap()
|
val map = user.toMap()
|
||||||
assertEquals("MyName", map["user"])
|
assertEquals("MyName", map["username"])
|
||||||
assertEquals("MyPassword", map["password"])
|
assertEquals("MyPassword", map["password"])
|
||||||
assertEquals(setOf("Flow.MyFlow"), map["permissions"])
|
assertEquals(setOf("Flow.MyFlow"), map["permissions"])
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ package net.corda.webserver
|
|||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import net.corda.core.div
|
import net.corda.core.div
|
||||||
import net.corda.nodeapi.User
|
|
||||||
import net.corda.nodeapi.config.SSLConfiguration
|
import net.corda.nodeapi.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.config.getListOrElse
|
|
||||||
import net.corda.nodeapi.config.getValue
|
import net.corda.nodeapi.config.getValue
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user