[CORDA-1528]: Node configuration not containing property "rpcSettings.address" fails with error "No configuration setting found for key 'address'" (fix). (#3229)

This commit is contained in:
Michele Sollecito 2018-05-24 13:20:04 +01:00 committed by GitHub
parent 3136e973a7
commit 61fedb5fd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 62 deletions

View File

@ -2,7 +2,12 @@
package net.corda.nodeapi.internal.config
import com.typesafe.config.*
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigUtil
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.ConfigValueType
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.uncheckedCast
@ -39,7 +44,7 @@ operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T
return getValueInternal(metadata.name, metadata.returnType, UnknownConfigKeysPolicy.IGNORE::handle)
}
fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): T {
fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle, nestedPath: String? = null): T {
require(clazz.isData) { "Only Kotlin data classes can be parsed. Offending: ${clazz.qualifiedName}" }
val constructor = clazz.primaryConstructor!!
val parameters = constructor.parameters
@ -59,11 +64,11 @@ fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, log
onUnknownKeys.invoke(unknownConfigurationKeys, logger)
val args = parameters.filterNot { it.isOptional && !hasPath(it.name!!) }.associateBy({ it }) { param ->
// Get the matching property for this parameter
val property = clazz.memberProperties.first { it.name == param.name }
val path = defaultToOldPath(property)
getValueInternal<Any>(path, param.type, onUnknownKeys)
}
// Get the matching property for this parameter
val property = clazz.memberProperties.first { it.name == param.name }
val path = defaultToOldPath(property)
getValueInternal<Any>(path, param.type, onUnknownKeys, nestedPath)
}
try {
return constructor.callBy(args)
} catch (e: InvocationTargetException) {
@ -91,68 +96,83 @@ fun Config.toProperties(): Properties {
{ it.value.unwrapped().toString() })
}
private fun <T : Any> Config.getValueInternal(path: String, type: KType, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit)): T {
return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type, onUnknownKeys) else getCollectionValue(path, type, onUnknownKeys))
private fun <T : Any> Config.getValueInternal(path: String, type: KType, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit), nestedPath: String? = null): T {
return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type, onUnknownKeys, nestedPath) else getCollectionValue(path, type, onUnknownKeys, nestedPath))
}
private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit): Any? {
private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit, nestedPath: String? = null): 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))
Duration::class -> getDuration(path)
Instant::class -> Instant.parse(getString(path))
NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path))
Path::class -> Paths.get(getString(path))
URL::class -> URL(getString(path))
UUID::class -> UUID.fromString(getString(path))
CordaX500Name::class -> {
when (getValue(path).valueType()) {
ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys)
else -> CordaX500Name.parse(getString(path))
return try {
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))
Duration::class -> getDuration(path)
Instant::class -> Instant.parse(getString(path))
NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path))
Path::class -> Paths.get(getString(path))
URL::class -> URL(getString(path))
UUID::class -> UUID.fromString(getString(path))
CordaX500Name::class -> {
when (getValue(path).valueType()) {
ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys)
else -> CordaX500Name.parse(getString(path))
}
}
Properties::class -> getConfig(path).toProperties()
Config::class -> getConfig(path)
else -> if (typeClass.java.isEnum) {
parseEnum(typeClass.java, getString(path))
} else {
getConfig(path).parseAs(typeClass, onUnknownKeys, nestedPath?.let { "$it.$path" } ?: path)
}
}
Properties::class -> getConfig(path).toProperties()
Config::class -> getConfig(path)
else -> if (typeClass.java.isEnum) {
parseEnum(typeClass.java, getString(path))
} else {
getConfig(path).parseAs(typeClass, onUnknownKeys)
}
} catch (e: ConfigException.Missing) {
throw e.relative(path, nestedPath)
}
}
private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit): Collection<Any> {
private fun ConfigException.Missing.relative(path: String, nestedPath: String?): ConfigException.Missing {
return when {
nestedPath != null -> throw ConfigException.Missing("$nestedPath.$path")
else -> this
}
}
private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit, nestedPath: String? = null): Collection<Any> {
val typeClass = type.jvmErasure
require(typeClass == List::class || typeClass == Set::class) { "$typeClass is not supported" }
val elementClass = type.arguments[0].type?.jvmErasure ?: throw IllegalArgumentException("Cannot work with star projection: $type")
if (!hasPath(path)) {
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)
NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse)
Path::class -> getStringList(path).map { Paths.get(it) }
URL::class -> getStringList(path).map(::URL)
UUID::class -> getStringList(path).map { UUID.fromString(it) }
CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse)
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, onUnknownKeys) }
val values: List<Any> = try {
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)
NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse)
Path::class -> getStringList(path).map { Paths.get(it) }
URL::class -> getStringList(path).map(::URL)
UUID::class -> getStringList(path).map { UUID.fromString(it) }
CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse)
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, onUnknownKeys) }
}
}
} catch (e: ConfigException.Missing) {
throw e.relative(path, nestedPath)
}
return if (typeClass == Set::class) values.toSet() else values
}
@ -219,7 +239,7 @@ private fun Any.toConfigMap(): Map<String, Any> {
private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
return when (elementType) {
// For the types already supported by Config we can use the Iterable as is
// For the types already supported by Config we can use the Iterable as is
String::class.java -> this
Integer::class.java -> this
java.lang.Long::class.java -> this

View File

@ -1,6 +1,7 @@
package net.corda.node.services.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import net.corda.core.context.AuthServiceId
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
@ -184,7 +185,10 @@ data class NodeConfigurationImpl(
logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.")
settings.copy(address = explicitAddress)
}
else -> settings
else -> {
settings.address ?: throw ConfigException.Missing("rpcSettings.address")
settings
}
}.asOptions(fallbackSslOptions)
}
@ -256,16 +260,16 @@ data class NodeConfigurationImpl(
}
data class NodeRpcSettings(
val address: NetworkHostAndPort,
val adminAddress: NetworkHostAndPort,
val address: NetworkHostAndPort?,
val adminAddress: NetworkHostAndPort?,
val standAloneBroker: Boolean = false,
val useSsl: Boolean = false,
val ssl: BrokerRpcSslOptions?
) {
fun asOptions(fallbackSslOptions: BrokerRpcSslOptions): NodeRpcOptions {
return object : NodeRpcOptions {
override val address = this@NodeRpcSettings.address
override val adminAddress = this@NodeRpcSettings.adminAddress
override val address = this@NodeRpcSettings.address!!
override val adminAddress = this@NodeRpcSettings.adminAddress!!
override val standAloneBroker = this@NodeRpcSettings.standAloneBroker
override val useSsl = this@NodeRpcSettings.useSsl
override val sslConfig = this@NodeRpcSettings.ssl ?: fallbackSslOptions

View File

@ -1,22 +1,23 @@
package net.corda.node.services.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import net.corda.core.internal.div
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigValueFactory
import net.corda.core.internal.toPath
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.tools.shell.SSHDConfiguration
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.net.URI
import java.net.URL
import java.nio.file.Paths
import java.util.*
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@ -133,6 +134,27 @@ class NodeConfigurationImplTest {
assertThat(errors).hasOnlyOneElementSatisfying { error -> error.contains("compatibilityZoneURL") && error.contains("devMode") }
}
@Test
fun `errors for nested config keys contain path`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
val missingPropertyPath = "rpcSettings.address"
rawConfig = rawConfig.withoutPath(missingPropertyPath)
assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.isInstanceOfSatisfying(ConfigException.Missing::class.java) { exception ->
assertThat(exception.message).isNotNull()
assertThat(exception.message).contains(missingPropertyPath)
}
}
@Test
fun `rpcAddress and rpcSettings_address are equivalent`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
rawConfig = rawConfig.withoutPath("rpcSettings.address")
rawConfig = rawConfig.withValue("rpcAddress", ConfigValueFactory.fromAnyRef("localhost:4444"))
assertThatCode { rawConfig.parseAsNodeConfiguration() }.doesNotThrowAnyException()
}
private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfiguration {
return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions)
}

View File

@ -0,0 +1,31 @@
myLegalName = "O=Alice Corp, L=Madrid, C=ES"
emailAddress = "admin@company.com"
keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
crlCheckSoftFail = true
baseDirectory = "/opt/corda"
dataSourceProperties = {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
dataSource.url = "jdbc:h2:file:blah"
dataSource.user = "sa"
dataSource.password = ""
}
database = {
transactionIsolationLevel = "REPEATABLE_READ"
exportHibernateJMXStatistics = "false"
}
p2pAddress = "localhost:2233"
h2port = 0
useTestClock = false
verifierType = InMemory
rpcSettings = {
address = "locahost:3418"
adminAddress = "localhost:3419"
useSsl = false
standAloneBroker = false
}
p2pMessagingRetry {
messageRedeliveryDelay = 30 seconds
maxRetryCount = 3
backoffBase = 2.0
}