mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
[CORDA-1379]: Allow soft trapping of unknown config keys. (#3011)
This commit is contained in:
parent
efa69ce959
commit
235df69efe
@ -7,6 +7,9 @@ release, see :doc:`upgrade-notes`.
|
||||
Unreleased
|
||||
==========
|
||||
|
||||
* Added program line argument ``on-unknown-config-keys`` to allow specifying behaviour on unknown node configuration property keys.
|
||||
Values are: [FAIL, WARN, IGNORE], default to FAIL if unspecified.
|
||||
|
||||
* Fix CORDA-1229. Setter-based serialization was broken with generic types when the property was stored
|
||||
as the raw type, List for example.
|
||||
|
||||
|
@ -31,6 +31,9 @@ e.g.:
|
||||
The property `"dataSourceProperties.dataSourceClassName" = "val"` in ``reference.conf``
|
||||
would be not overwritten by the property `dataSourceProperties.dataSourceClassName = "val2"` in ``node.conf``.
|
||||
|
||||
By default the node will fail to start in presence of unknown property keys. To alter this behaviour, program line argument
|
||||
``on-unknown-config-keys`` can be set to ``WARN`` or ``IGNORE``. Default is ``FAIL`` if unspecified.
|
||||
|
||||
Defaults
|
||||
--------
|
||||
A set of default configuration options are loaded from the built-in resource file ``/node/src/main/resources/reference.conf``.
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.noneOrSingle
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
@ -35,10 +36,10 @@ const val CUSTOM_NODE_PROPERTIES_ROOT = "custom"
|
||||
|
||||
// TODO Move other config parsing to use parseAs and remove this
|
||||
operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
|
||||
return getValueInternal(metadata.name, metadata.returnType)
|
||||
return getValueInternal(metadata.name, metadata.returnType, UnknownConfigKeysPolicy.IGNORE::handle)
|
||||
}
|
||||
|
||||
fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
|
||||
fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): T {
|
||||
require(clazz.isData) { "Only Kotlin data classes can be parsed. Offending: ${clazz.qualifiedName}" }
|
||||
val constructor = clazz.primaryConstructor!!
|
||||
val parameters = constructor.parameters
|
||||
@ -55,14 +56,13 @@ fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
|
||||
.filterNot { it == CUSTOM_NODE_PROPERTIES_ROOT }
|
||||
.filterNot(parameterNames::contains)
|
||||
.toSortedSet()
|
||||
if (unknownConfigurationKeys.isNotEmpty()) {
|
||||
throw UnknownConfigurationKeysException.of(unknownConfigurationKeys)
|
||||
}
|
||||
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)
|
||||
getValueInternal<Any>(path, param.type, onUnknownKeys)
|
||||
}
|
||||
try {
|
||||
return constructor.callBy(args)
|
||||
@ -82,7 +82,7 @@ class UnknownConfigurationKeysException private constructor(val unknownKeys: Set
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> Config.parseAs(): T = parseAs(T::class)
|
||||
inline fun <reified T : Any> Config.parseAs(noinline onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): T = parseAs(T::class, onUnknownKeys)
|
||||
|
||||
fun Config.toProperties(): Properties {
|
||||
return entrySet().associateByTo(
|
||||
@ -91,11 +91,11 @@ fun Config.toProperties(): Properties {
|
||||
{ it.value.unwrapped().toString() })
|
||||
}
|
||||
|
||||
private fun <T : Any> Config.getValueInternal(path: String, type: KType): T {
|
||||
return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type) else getCollectionValue(path, type))
|
||||
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 Config.getSingleValue(path: String, type: KType): Any? {
|
||||
private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit): Any? {
|
||||
if (type.isMarkedNullable && !hasPath(path)) return null
|
||||
val typeClass = type.jvmErasure
|
||||
return when (typeClass) {
|
||||
@ -113,7 +113,7 @@ private fun Config.getSingleValue(path: String, type: KType): Any? {
|
||||
UUID::class -> UUID.fromString(getString(path))
|
||||
CordaX500Name::class -> {
|
||||
when (getValue(path).valueType()) {
|
||||
ConfigValueType.OBJECT -> getConfig(path).parseAs()
|
||||
ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys)
|
||||
else -> CordaX500Name.parse(getString(path))
|
||||
}
|
||||
}
|
||||
@ -122,12 +122,12 @@ private fun Config.getSingleValue(path: String, type: KType): Any? {
|
||||
else -> if (typeClass.java.isEnum) {
|
||||
parseEnum(typeClass.java, getString(path))
|
||||
} else {
|
||||
getConfig(path).parseAs(typeClass)
|
||||
getConfig(path).parseAs(typeClass, onUnknownKeys)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Config.getCollectionValue(path: String, type: KType): Collection<Any> {
|
||||
private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit): 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")
|
||||
@ -151,7 +151,7 @@ private fun Config.getCollectionValue(path: String, type: KType): Collection<Any
|
||||
else -> if (elementClass.java.isEnum) {
|
||||
getStringList(path).map { parseEnum(elementClass.java, it) }
|
||||
} else {
|
||||
getConfigList(path).map { it.parseAs(elementClass) }
|
||||
getConfigList(path).map { it.parseAs(elementClass, onUnknownKeys) }
|
||||
}
|
||||
}
|
||||
return if (typeClass == Set::class) values.toSet() else values
|
||||
@ -242,3 +242,17 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
|
||||
}
|
||||
|
||||
private val logger = LoggerFactory.getLogger("net.corda.nodeapi.internal.config")
|
||||
|
||||
enum class UnknownConfigKeysPolicy(private val handle: (Set<String>, logger: Logger) -> Unit) {
|
||||
|
||||
FAIL({ unknownKeys, _ -> throw UnknownConfigurationKeysException.of(unknownKeys) }),
|
||||
WARN({ unknownKeys, logger -> logger.warn("Unknown configuration keys found: ${unknownKeys.joinToString(", ", "[", "]")}.") }),
|
||||
IGNORE({ _, _ -> });
|
||||
|
||||
fun handle(unknownKeys: Set<String>, logger: Logger) {
|
||||
|
||||
if (unknownKeys.isNotEmpty()) {
|
||||
handle.invoke(unknownKeys, logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import net.corda.core.internal.exists
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
|
||||
import org.slf4j.event.Level
|
||||
import java.io.PrintStream
|
||||
import java.nio.file.Path
|
||||
@ -42,6 +43,11 @@ class ArgsParser {
|
||||
.defaultsTo((Paths.get("certificates") / "network-root-truststore.jks"))
|
||||
private val networkRootTrustStorePasswordArg = optionParser.accepts("network-root-truststore-password", "Network root trust store password obtained from network operator.")
|
||||
.withRequiredArg()
|
||||
private val unknownConfigKeysPolicy = optionParser.accepts("on-unknown-config-keys", "How to behave on unknown node configuration property keys: [WARN, FAIL, IGNORE].")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(object : EnumConverter<UnknownConfigKeysPolicy>(UnknownConfigKeysPolicy::class.java) {})
|
||||
.defaultsTo(UnknownConfigKeysPolicy.FAIL)
|
||||
|
||||
private val isVersionArg = optionParser.accepts("version", "Print the version and exit")
|
||||
private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info",
|
||||
"Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then quit")
|
||||
@ -66,6 +72,7 @@ class ArgsParser {
|
||||
val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg)
|
||||
val networkRootTrustStorePath = optionSet.valueOf(networkRootTrustStorePathArg)
|
||||
val networkRootTrustStorePassword = optionSet.valueOf(networkRootTrustStorePasswordArg)
|
||||
val unknownConfigKeysPolicy = optionSet.valueOf(unknownConfigKeysPolicy)
|
||||
|
||||
val registrationConfig = if (isRegistration) {
|
||||
requireNotNull(networkRootTrustStorePassword) { "Network root trust store password must be provided in registration mode using --network-root-truststore-password." }
|
||||
@ -85,7 +92,8 @@ class ArgsParser {
|
||||
noLocalShell,
|
||||
sshdServer,
|
||||
justGenerateNodeInfo,
|
||||
bootstrapRaftCluster)
|
||||
bootstrapRaftCluster,
|
||||
unknownConfigKeysPolicy)
|
||||
}
|
||||
|
||||
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
||||
@ -103,13 +111,14 @@ data class CmdLineOptions(val baseDirectory: Path,
|
||||
val noLocalShell: Boolean,
|
||||
val sshdServer: Boolean,
|
||||
val justGenerateNodeInfo: Boolean,
|
||||
val bootstrapRaftCluster: Boolean) {
|
||||
val bootstrapRaftCluster: Boolean,
|
||||
val unknownConfigKeysPolicy: UnknownConfigKeysPolicy) {
|
||||
fun loadConfig(): NodeConfiguration {
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory,
|
||||
configFile,
|
||||
configOverrides = ConfigFactory.parseMap(mapOf("noLocalShell" to this.noLocalShell))
|
||||
).parseAsNodeConfiguration()
|
||||
).parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
|
||||
if (nodeRegistrationOption != null) {
|
||||
require(!config.devMode) { "registration cannot occur in devMode" }
|
||||
requireNotNull(config.compatibilityZoneURL) {
|
||||
|
@ -9,12 +9,10 @@ import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.internal.artemis.CertificateChainCheckPolicy
|
||||
import net.corda.node.services.config.rpc.NodeRpcOptions
|
||||
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.config.parseAs
|
||||
import net.corda.nodeapi.internal.config.*
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.tools.shell.SSHDConfiguration
|
||||
import org.slf4j.Logger
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
@ -120,7 +118,7 @@ data class P2PMessagingRetryConfiguration(
|
||||
val backoffBase: Double
|
||||
)
|
||||
|
||||
fun Config.parseAsNodeConfiguration(): NodeConfiguration = parseAs<NodeConfigurationImpl>()
|
||||
fun Config.parseAsNodeConfiguration(onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): NodeConfiguration = parseAs<NodeConfigurationImpl>(onUnknownKeys)
|
||||
|
||||
data class NodeConfigurationImpl(
|
||||
/** This is not retrieved from the config file but rather from a command line argument. */
|
||||
|
@ -3,6 +3,7 @@ package net.corda.node
|
||||
import joptsimple.OptionException
|
||||
import net.corda.core.internal.delete
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
@ -42,7 +43,8 @@ class ArgsParserTest {
|
||||
noLocalShell = false,
|
||||
sshdServer = false,
|
||||
justGenerateNodeInfo = false,
|
||||
bootstrapRaftCluster = false))
|
||||
bootstrapRaftCluster = false,
|
||||
unknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -161,4 +163,13 @@ class ArgsParserTest {
|
||||
val cmdLineOptions = parser.parse("--bootstrap-raft-cluster")
|
||||
assertThat(cmdLineOptions.bootstrapRaftCluster).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on-unknown-config-keys options`() {
|
||||
|
||||
UnknownConfigKeysPolicy.values().forEach { onUnknownConfigKeyPolicy ->
|
||||
val cmdLineOptions = parser.parse("--on-unknown-config-keys", onUnknownConfigKeyPolicy.name)
|
||||
assertThat(cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(onUnknownConfigKeyPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user