[CORDA-1763]: Add node CLI option for validating configuration. (#4121)

This commit is contained in:
Michele Sollecito
2018-10-29 13:33:43 +00:00
committed by GitHub
parent 5086358834
commit 6022cecca5
13 changed files with 169 additions and 82 deletions

View File

@ -2,11 +2,9 @@ package net.corda.node
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions
import net.corda.core.internal.div
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeConfigurationImpl
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import picocli.CommandLine.Option
@ -39,20 +37,9 @@ open class SharedNodeCmdLineOptions {
)
var devMode: Boolean? = null
open fun loadConfig(): NodeConfiguration {
return getRawConfig().parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
}
open fun parseConfiguration(configuration: Config): NodeConfiguration = configuration.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
protected fun getRawConfig(): Config {
val rawConfig = ConfigHelper.loadConfig(
baseDirectory,
configFile
)
if (devMode == true) {
println("Config:\n${rawConfig.root().render(ConfigRenderOptions.defaults())}")
}
return rawConfig
}
open fun rawConfiguration(): Config = ConfigHelper.loadConfig(baseDirectory, configFile)
fun copyFrom(other: SharedNodeCmdLineOptions) {
baseDirectory = other.baseDirectory
@ -63,8 +50,8 @@ open class SharedNodeCmdLineOptions {
}
class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() {
override fun loadConfig(): NodeConfiguration {
return getRawConfig().parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
override fun parseConfiguration(configuration: Config): NodeConfiguration {
return super.parseConfiguration(configuration).also { config ->
require(!config.devMode) { "Registration cannot occur in development mode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
@ -134,15 +121,8 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
)
var networkRootTrustStorePassword: String? = null
override fun loadConfig(): NodeConfiguration {
val rawConfig = ConfigHelper.loadConfig(
baseDirectory,
configFile,
configOverrides = ConfigFactory.parseMap(mapOf("noLocalShell" to this.noLocalShell) +
if (sshdServer) mapOf("sshd" to mapOf("port" to sshdServerPort.toString())) else emptyMap<String, Any>() +
if (devMode != null) mapOf("devMode" to this.devMode) else emptyMap())
)
return rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
override fun parseConfiguration(configuration: Config): NodeConfiguration {
return super.parseConfiguration(configuration).also { config ->
if (isRegistration) {
require(!config.devMode) { "Registration cannot occur in development mode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
@ -151,6 +131,18 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
}
}
}
override fun rawConfiguration(): Config {
val configOverrides = mutableMapOf<String, Any>()
configOverrides += "noLocalShell" to noLocalShell
if (sshdServer) {
configOverrides += "sshd" to mapOf("port" to sshdServerPort.toString())
}
devMode?.let {
configOverrides += "devMode" to it
}
return ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides))
}
}

View File

@ -1,6 +1,5 @@
package net.corda.node.internal
import com.typesafe.config.ConfigException
import io.netty.channel.unix.Errors
import net.corda.cliutils.*
import net.corda.core.crypto.Crypto
@ -15,12 +14,12 @@ import net.corda.node.*
import net.corda.node.internal.Node.Companion.isInvalidJavaVersion
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
import net.corda.node.internal.subcommands.*
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logConfigurationErrors
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon
import net.corda.node.utilities.registration.NodeRegistrationException
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
import net.corda.tools.shell.InteractiveShell
@ -57,15 +56,18 @@ abstract class NodeCliCommand(alias: String, description: String, val startup: N
/** Main corda entry point. */
open class NodeStartupCli : CordaCliWrapper("corda", "Runs a Corda Node") {
open val startup = NodeStartup()
@Mixin
val cmdLineOptions = NodeCmdLineOptions()
private val networkCacheCli by lazy { ClearNetworkCacheCli(startup) }
private val justGenerateNodeInfoCli by lazy { GenerateNodeInfoCli(startup) }
private val justGenerateRpcSslCertsCli by lazy { GenerateRpcSslCertsCli(startup) }
private val initialRegistrationCli by lazy { InitialRegistrationCli(startup) }
private val validateConfigurationCli by lazy { ValidateConfigurationCli() }
override fun initLogging() = this.initLogging(cmdLineOptions.baseDirectory)
override fun additionalSubCommands() = setOf(networkCacheCli, justGenerateNodeInfoCli, justGenerateRpcSslCertsCli, initialRegistrationCli)
override fun additionalSubCommands() = setOf(networkCacheCli, justGenerateNodeInfoCli, justGenerateRpcSslCertsCli, initialRegistrationCli, validateConfigurationCli)
override fun runProgram(): Int {
return when {
@ -104,9 +106,6 @@ open class NodeStartupCli : CordaCliWrapper("corda", "Runs a Corda Node") {
})
}
}
@Mixin
val cmdLineOptions = NodeCmdLineOptions()
}
/** This class provides a common set of functionality for starting a Node from command line arguments. */
@ -140,13 +139,7 @@ open class NodeStartup : NodeStartupLogging {
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
// Step 5. Load and validate node configuration.
val configuration = (attempt { cmdLineOptions.loadConfig() }.doOnException(handleConfigurationLoadingError(cmdLineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value)
?: return ExitCodes.FAILURE
val errors = configuration.validate()
if (errors.isNotEmpty()) {
logger.error("Invalid node configuration. Errors were:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
return ExitCodes.FAILURE
}
val configuration = cmdLineOptions.nodeConfiguration().doOnErrors { errors -> logConfigurationErrors(errors, cmdLineOptions.configFile) }.optional ?: return ExitCodes.FAILURE
// Step 6. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization.
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success
@ -430,23 +423,6 @@ interface NodeStartupLogging {
else -> error.logAsUnexpected("Exception during node startup")
}
}
fun handleConfigurationLoadingError(configFile: Path) = { error: Exception ->
when (error) {
is UnknownConfigurationKeysException -> error.logAsExpected()
is ConfigException.IO -> error.logAsExpected(configFileNotFoundMessage(configFile), ::println)
else -> error.logAsUnexpected("Unexpected error whilst reading node configuration")
}
}
private fun configFileNotFoundMessage(configFile: Path): String {
return """
Unable to load the node config file from '$configFile'.
Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent()
}
}
fun CliWrapperBase.initLogging(baseDirectory: Path) {

View File

@ -5,7 +5,7 @@ import net.corda.node.internal.NodeCliCommand
import net.corda.node.internal.NodeStartup
import net.corda.node.internal.RunAfterNodeInitialisation
class ClearNetworkCacheCli(startup: NodeStartup): NodeCliCommand("clear-network-cache", "Clears local copy of network map, on node startup it will be restored from server or file system.", startup) {
class ClearNetworkCacheCli(startup: NodeStartup): NodeCliCommand("clear-network-cache", "Clear local copy of network map, on node startup it will be restored from server or file system.", startup) {
override fun runProgram(): Int {
return startup.initialiseAndRun(cmdLineOptions, object: RunAfterNodeInitialisation {
override fun run(node: Node) = node.clearNetworkMapCache()

View File

@ -5,7 +5,7 @@ import net.corda.node.internal.NodeCliCommand
import net.corda.node.internal.NodeStartup
import net.corda.node.internal.RunAfterNodeInitialisation
class GenerateNodeInfoCli(startup: NodeStartup): NodeCliCommand("generate-node-info", "Performs the node start-up tasks necessary to generate the nodeInfo file, saves it to disk, then exits.", startup) {
class GenerateNodeInfoCli(startup: NodeStartup): NodeCliCommand("generate-node-info", "Perform the node start-up tasks necessary to generate the nodeInfo file, save it to disk, then exit.", startup) {
override fun runProgram(): Int {
return startup.initialiseAndRun(cmdLineOptions, object : RunAfterNodeInitialisation {
override fun run(node: Node) {

View File

@ -13,7 +13,7 @@ import net.corda.node.utilities.saveToTrustStore
import java.io.Console
import kotlin.system.exitProcess
class GenerateRpcSslCertsCli(startup: NodeStartup): NodeCliCommand("generate-rpc-ssl-settings", "Generates the SSL key and trust stores for a secure RPC connection.", startup) {
class GenerateRpcSslCertsCli(startup: NodeStartup): NodeCliCommand("generate-rpc-ssl-settings", "Generate the SSL key and trust stores for a secure RPC connection.", startup) {
override fun runProgram(): Int {
return startup.initialiseAndRun(cmdLineOptions, GenerateRpcSslCerts())
}

View File

@ -17,7 +17,7 @@ import picocli.CommandLine.Option
import java.io.File
import java.nio.file.Path
class InitialRegistrationCli(val startup: NodeStartup): CliWrapperBase("initial-registration", "Starts initial node registration with Corda network to obtain certificate from the permissioning server.") {
class InitialRegistrationCli(val startup: NodeStartup): CliWrapperBase("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.") {
@Option(names = ["-t", "--network-root-truststore"], description = ["Network root trust store obtained from network operator."])
var networkRootTrustStorePathParameter: Path? = null

View File

@ -0,0 +1,84 @@
package net.corda.node.internal.subcommands
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigRenderOptions
import net.corda.cliutils.CliWrapperBase
import net.corda.cliutils.ExitCodes
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.core.utilities.loggerFor
import net.corda.node.SharedNodeCmdLineOptions
import net.corda.node.internal.initLogging
import net.corda.node.services.config.NodeConfiguration
import picocli.CommandLine.*
import java.nio.file.Path
internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration", "Validate the configuration without starting the node.") {
internal companion object {
private val logger = loggerFor<ValidateConfigurationCli>()
internal fun logConfigurationErrors(errors: Iterable<Exception>, configFile: Path) {
errors.forEach { error ->
when (error) {
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile))
else -> logger.error("Error while parsing node configuration.", error)
}
}
}
private fun configFileNotFoundMessage(configFile: Path): String {
return """
Unable to load the node config file from '$configFile'.
Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent()
}
}
@Mixin
private val cmdLineOptions = SharedNodeCmdLineOptions()
override fun initLogging() = initLogging(cmdLineOptions.baseDirectory)
override fun runProgram(): Int {
val configuration = cmdLineOptions.nodeConfiguration()
if (configuration.isInvalid) {
logConfigurationErrors(configuration.errors, cmdLineOptions.configFile)
return ExitCodes.FAILURE
}
return ExitCodes.SUCCESS
}
}
internal fun SharedNodeCmdLineOptions.nodeConfiguration(): Valid<NodeConfiguration> = NodeConfigurationParser.invoke(this)
private object NodeConfigurationParser : (SharedNodeCmdLineOptions) -> Valid<NodeConfiguration> {
private val logger = loggerFor<ValidateConfigurationCli>()
private val configRenderingOptions = ConfigRenderOptions.defaults().setComments(false).setOriginComments(false).setFormatted(true)
override fun invoke(cmds: SharedNodeCmdLineOptions): Valid<NodeConfiguration> {
return attempt(cmds::rawConfiguration).doIfValid(::log).attemptMap(cmds::parseConfiguration).mapValid(::validate)
}
internal fun log(config: Config) = logger.debug("Actual configuration:\n${config.root().render(configRenderingOptions)}")
private fun validate(configuration: NodeConfiguration): Valid<NodeConfiguration> {
return Validated.withResult(configuration, configuration.validate().asSequence().map { error -> IllegalArgumentException(error) }.toSet())
}
private fun <VALUE, MAPPED> Valid<VALUE>.attemptMap(convert: (VALUE) -> MAPPED): Valid<MAPPED> = mapValid { value -> attempt { convert.invoke(value) } }
private fun <VALUE> attempt(action: () -> VALUE): Valid<VALUE> {
return try {
valid(action.invoke())
} catch (exception: Exception) {
return invalid(exception)
}
}
}
private typealias Valid<TARGET> = Validated<TARGET, Exception>