mirror of
https://github.com/corda/corda.git
synced 2025-06-12 20:28:18 +00:00
[CORDA-1993]: Replace reflection-based NodeConfiguration parsing with versioned property-based parsing mechanism. (#4132)
This commit is contained in:
committed by
GitHub
parent
9277042db8
commit
6c749889d0
@ -1,10 +1,17 @@
|
||||
package net.corda.node
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
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.internal.div
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.Valid
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
|
||||
import picocli.CommandLine.Option
|
||||
@ -12,6 +19,9 @@ import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
open class SharedNodeCmdLineOptions {
|
||||
private companion object {
|
||||
private val logger by lazy { loggerFor<SharedNodeCmdLineOptions>() }
|
||||
}
|
||||
@Option(
|
||||
names = ["-b", "--base-directory"],
|
||||
description = ["The node working directory where all the files are kept."]
|
||||
@ -37,9 +47,19 @@ open class SharedNodeCmdLineOptions {
|
||||
)
|
||||
var devMode: Boolean? = null
|
||||
|
||||
open fun parseConfiguration(configuration: Config): NodeConfiguration = configuration.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
|
||||
open fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
|
||||
|
||||
open fun rawConfiguration(): Config = ConfigHelper.loadConfig(baseDirectory, configFile)
|
||||
val option = Configuration.Validation.Options(strict = unknownConfigKeysPolicy == UnknownConfigKeysPolicy.FAIL)
|
||||
return configuration.parseAsNodeConfiguration(option)
|
||||
}
|
||||
|
||||
open fun rawConfiguration(): Validated<Config, ConfigException> {
|
||||
return try {
|
||||
valid(ConfigHelper.loadConfig(baseDirectory, configFile))
|
||||
} catch (e: ConfigException) {
|
||||
return invalid(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun copyFrom(other: SharedNodeCmdLineOptions) {
|
||||
baseDirectory = other.baseDirectory
|
||||
@ -47,11 +67,32 @@ open class SharedNodeCmdLineOptions {
|
||||
unknownConfigKeysPolicy= other.unknownConfigKeysPolicy
|
||||
devMode = other.devMode
|
||||
}
|
||||
|
||||
fun logRawConfigurationErrors(errors: Set<ConfigException>) {
|
||||
if (errors.isNotEmpty()) {
|
||||
logger.error("There were error(s) while attempting to load the node configuration:")
|
||||
}
|
||||
errors.forEach { error ->
|
||||
when (error) {
|
||||
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile))
|
||||
else -> logger.error(error.message, 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()
|
||||
}
|
||||
}
|
||||
|
||||
class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
override fun parseConfiguration(configuration: Config): NodeConfiguration {
|
||||
return super.parseConfiguration(configuration).also { config ->
|
||||
override fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
|
||||
return super.parseConfiguration(configuration).doIfValid { 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."
|
||||
@ -121,8 +162,8 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
)
|
||||
var networkRootTrustStorePassword: String? = null
|
||||
|
||||
override fun parseConfiguration(configuration: Config): NodeConfiguration {
|
||||
return super.parseConfiguration(configuration).also { config ->
|
||||
override fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
|
||||
return super.parseConfiguration(configuration).doIfValid { config ->
|
||||
if (isRegistration) {
|
||||
require(!config.devMode) { "Registration cannot occur in development mode" }
|
||||
require(config.compatibilityZoneURL != null || config.networkServices != null) {
|
||||
@ -132,7 +173,7 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun rawConfiguration(): Config {
|
||||
override fun rawConfiguration(): Validated<Config, ConfigException> {
|
||||
val configOverrides = mutableMapOf<String, Any>()
|
||||
configOverrides += "noLocalShell" to noLocalShell
|
||||
if (sshdServer) {
|
||||
@ -141,7 +182,11 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
devMode?.let {
|
||||
configOverrides += "devMode" to it
|
||||
}
|
||||
return ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides))
|
||||
return try {
|
||||
valid(ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides)))
|
||||
} catch (e: ConfigException) {
|
||||
return invalid(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ 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.internal.subcommands.ValidateConfigurationCli.Companion.logRawConfig
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.shouldStartLocalShell
|
||||
import net.corda.node.services.config.shouldStartSSHDaemon
|
||||
@ -34,7 +35,6 @@ import java.net.InetAddress
|
||||
import java.nio.file.Path
|
||||
import java.time.DayOfWeek
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
|
||||
/** An interface that can be implemented to tell the node what to do once it's intitiated. */
|
||||
interface RunAfterNodeInitialisation {
|
||||
@ -139,7 +139,8 @@ 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 = cmdLineOptions.nodeConfiguration().doOnErrors { errors -> logConfigurationErrors(errors, cmdLineOptions.configFile) }.optional ?: return ExitCodes.FAILURE
|
||||
val rawConfig = cmdLineOptions.rawConfiguration().doOnErrors(cmdLineOptions::logRawConfigurationErrors).optional ?: return ExitCodes.FAILURE
|
||||
val configuration = cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).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
|
||||
|
@ -1,41 +1,32 @@
|
||||
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.common.configuration.parsing.internal.Configuration
|
||||
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
|
||||
import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec
|
||||
import net.corda.nodeapi.internal.config.toConfigValue
|
||||
import picocli.CommandLine.Mixin
|
||||
|
||||
internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration", "Validate the configuration without starting the node.") {
|
||||
internal companion object {
|
||||
private val logger by lazy { 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 val configRenderingOptions = ConfigRenderOptions.defaults().setFormatted(true).setComments(false).setOriginComments(false)
|
||||
|
||||
internal fun logConfigurationErrors(errors: Iterable<Configuration.Validation.Error>) {
|
||||
logger.error(errors.joinToString(System.lineSeparator(), "Error(s) while parsing node configuration:${System.lineSeparator()}") { error -> "\t- ${error.description()}" })
|
||||
}
|
||||
|
||||
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()
|
||||
private fun Configuration.Validation.Error.description(): String {
|
||||
return "for path: \"$pathAsString\": $message"
|
||||
}
|
||||
|
||||
internal fun logRawConfig(config: Config) = logger.debug("Actual configuration:\n${V1NodeConfigurationSpec.describe(config, Any::toConfigValue).render(configRenderingOptions)}")
|
||||
}
|
||||
|
||||
@Mixin
|
||||
@ -44,41 +35,7 @@ internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration
|
||||
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
|
||||
val rawConfig = cmdLineOptions.rawConfiguration().doOnErrors(cmdLineOptions::logRawConfigurationErrors).optional ?: return ExitCodes.FAILURE
|
||||
return cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional?.let { ExitCodes.SUCCESS } ?: ExitCodes.FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
internal fun SharedNodeCmdLineOptions.nodeConfiguration(): Valid<NodeConfiguration> = NodeConfigurationParser.invoke(this)
|
||||
|
||||
private object NodeConfigurationParser : (SharedNodeCmdLineOptions) -> Valid<NodeConfiguration> {
|
||||
private val logger by lazy { 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>
|
||||
}
|
@ -1,24 +1,22 @@
|
||||
package net.corda.node.services.config
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.common.validation.internal.Validated
|
||||
import net.corda.core.context.AuthServiceId
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.TimedFlow
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.config.rpc.NodeRpcOptions
|
||||
import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec
|
||||
import net.corda.node.services.keys.cryptoservice.BCCryptoService
|
||||
import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
|
||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
||||
import net.corda.nodeapi.internal.config.*
|
||||
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
|
||||
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||
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
|
||||
@ -27,10 +25,6 @@ import javax.security.auth.x500.X500Principal
|
||||
|
||||
val Int.MB: Long get() = this * 1024L * 1024L
|
||||
|
||||
private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1)
|
||||
private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1)
|
||||
private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps"
|
||||
|
||||
interface NodeConfiguration {
|
||||
val myLegalName: CordaX500Name
|
||||
val emailAddress: String
|
||||
@ -91,23 +85,24 @@ interface NodeConfiguration {
|
||||
val cryptoServiceName: SupportedCryptoServices?
|
||||
val cryptoServiceConf: String? // Location for the cryptoService conf file.
|
||||
|
||||
fun validate(): List<String>
|
||||
|
||||
companion object {
|
||||
// default to at least 8MB and a bit extra for larger heap sizes
|
||||
val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory()
|
||||
internal val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory()
|
||||
|
||||
internal val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1)
|
||||
internal val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1)
|
||||
|
||||
// add 5% of any heapsize over 300MB to the default transaction cache size
|
||||
private fun getAdditionalCacheMemory(): Long {
|
||||
return Math.max((Runtime.getRuntime().maxMemory() - 300.MB) / 20, 0)
|
||||
}
|
||||
|
||||
val defaultAttachmentContentCacheSize: Long = 10.MB
|
||||
const val defaultAttachmentCacheBound = 1024L
|
||||
internal val defaultAttachmentContentCacheSize: Long = 10.MB
|
||||
internal const val defaultAttachmentCacheBound = 1024L
|
||||
|
||||
const val cordappDirectoriesKey = "cordappDirectories"
|
||||
|
||||
val defaultJmxReporterType = JmxReporterType.JOLOKIA
|
||||
internal val defaultJmxReporterType = JmxReporterType.JOLOKIA
|
||||
}
|
||||
|
||||
fun makeCryptoService(): CryptoService {
|
||||
@ -128,7 +123,14 @@ enum class JmxReporterType {
|
||||
JOLOKIA, NEW_RELIC
|
||||
}
|
||||
|
||||
data class DevModeOptions(val disableCheckpointChecker: Boolean = false, val allowCompatibilityZone: Boolean = false)
|
||||
data class DevModeOptions(val disableCheckpointChecker: Boolean = Defaults.disableCheckpointChecker, val allowCompatibilityZone: Boolean = Defaults.disableCheckpointChecker) {
|
||||
|
||||
internal object Defaults {
|
||||
|
||||
val disableCheckpointChecker = false
|
||||
val allowCompatibilityZone = false
|
||||
}
|
||||
}
|
||||
|
||||
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
|
||||
return this.devMode && this.devModeOptions?.disableCheckpointChecker != true
|
||||
@ -182,245 +184,9 @@ data class FlowTimeoutConfiguration(
|
||||
val backoffBase: Double
|
||||
)
|
||||
|
||||
fun Config.parseAsNodeConfiguration(onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): NodeConfiguration = parseAs<NodeConfigurationImpl>(onUnknownKeys)
|
||||
internal typealias Valid<TARGET> = Validated<TARGET, Configuration.Validation.Error>
|
||||
|
||||
data class NodeConfigurationImpl(
|
||||
/** This is not retrieved from the config file but rather from a command line argument. */
|
||||
override val baseDirectory: Path,
|
||||
override val myLegalName: CordaX500Name,
|
||||
override val jmxMonitoringHttpPort: Int? = null,
|
||||
override val emailAddress: String,
|
||||
private val keyStorePassword: String,
|
||||
private val trustStorePassword: String,
|
||||
override val crlCheckSoftFail: Boolean,
|
||||
override val dataSourceProperties: Properties,
|
||||
override val compatibilityZoneURL: URL? = null,
|
||||
override var networkServices: NetworkServicesConfig? = null,
|
||||
override val tlsCertCrlDistPoint: URL? = null,
|
||||
override val tlsCertCrlIssuer: X500Principal? = null,
|
||||
override val rpcUsers: List<User>,
|
||||
override val security: SecurityConfiguration? = null,
|
||||
override val verifierType: VerifierType,
|
||||
override val flowTimeout: FlowTimeoutConfiguration,
|
||||
override val p2pAddress: NetworkHostAndPort,
|
||||
override val additionalP2PAddresses: List<NetworkHostAndPort> = emptyList(),
|
||||
private val rpcAddress: NetworkHostAndPort? = null,
|
||||
private val rpcSettings: NodeRpcSettings,
|
||||
override val messagingServerAddress: NetworkHostAndPort?,
|
||||
override val messagingServerExternal: Boolean = (messagingServerAddress != null),
|
||||
override val notary: NotaryConfig?,
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Do not configure")
|
||||
override val certificateChainCheckPolicies: List<CertChainPolicyConfig> = emptyList(),
|
||||
override val devMode: Boolean = false,
|
||||
override val noLocalShell: Boolean = false,
|
||||
override val devModeOptions: DevModeOptions? = null,
|
||||
override val useTestClock: Boolean = false,
|
||||
override val lazyBridgeStart: Boolean = true,
|
||||
override val detectPublicIp: Boolean = true,
|
||||
// TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
|
||||
override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(),
|
||||
override val sshd: SSHDConfiguration? = null,
|
||||
override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode),
|
||||
private val transactionCacheSizeMegaBytes: Int? = null,
|
||||
private val attachmentContentCacheSizeMegaBytes: Int? = null,
|
||||
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
|
||||
override val extraNetworkMapKeys: List<UUID> = emptyList(),
|
||||
// do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing)
|
||||
private val h2port: Int? = null,
|
||||
private val h2Settings: NodeH2Settings? = null,
|
||||
// do not use or remove (used by Capsule)
|
||||
private val jarDirs: List<String> = emptyList(),
|
||||
override val flowMonitorPeriodMillis: Duration = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS,
|
||||
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS,
|
||||
override val cordappDirectories: List<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT),
|
||||
override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA,
|
||||
override val flowOverrides: FlowOverrideConfig?,
|
||||
override val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() },
|
||||
override val cryptoServiceName: SupportedCryptoServices? = null,
|
||||
override val cryptoServiceConf: String? = null
|
||||
) : NodeConfiguration {
|
||||
companion object {
|
||||
private val logger = loggerFor<NodeConfigurationImpl>()
|
||||
// private val supportedCryptoServiceNames = setOf("BC", "UTIMACO", "GEMALTO-LUNA", "AZURE-KEY-VAULT")
|
||||
}
|
||||
|
||||
private val actualRpcSettings: NodeRpcSettings
|
||||
|
||||
init {
|
||||
actualRpcSettings = when {
|
||||
rpcAddress != null -> {
|
||||
require(rpcSettings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." }
|
||||
logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.")
|
||||
|
||||
rpcSettings.copy(address = rpcAddress)
|
||||
}
|
||||
else -> {
|
||||
rpcSettings.address ?: throw ConfigException.Missing("rpcSettings.address")
|
||||
rpcSettings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val certificatesDirectory = baseDirectory / "certificates"
|
||||
|
||||
private val signingCertificateStorePath = certificatesDirectory / "nodekeystore.jks"
|
||||
private val p2pKeystorePath: Path get() = certificatesDirectory / "sslkeystore.jks"
|
||||
|
||||
// TODO: There are two implications here:
|
||||
// 1. "signingCertificateStore" and "p2pKeyStore" have the same passwords. In the future we should re-visit this "rule" and see of they can be made different;
|
||||
// 2. The passwords for store and for keys in this store are the same, this is due to limitations of Artemis.
|
||||
override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword, keyStorePassword)
|
||||
private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword, keyStorePassword)
|
||||
|
||||
private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks"
|
||||
private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword, trustStorePassword)
|
||||
override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore)
|
||||
|
||||
override val rpcOptions: NodeRpcOptions
|
||||
get() {
|
||||
return actualRpcSettings.asOptions()
|
||||
}
|
||||
|
||||
private fun validateTlsCertCrlConfig(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (tlsCertCrlIssuer != null) {
|
||||
if (tlsCertCrlDistPoint == null) {
|
||||
errors += "tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL"
|
||||
}
|
||||
}
|
||||
if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) {
|
||||
errors += "tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE"
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateCryptoService(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (cryptoServiceName == null && cryptoServiceConf != null) {
|
||||
errors += "cryptoServiceName is null, but cryptoServiceConf is set to $cryptoServiceConf"
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
override fun validate(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
errors += validateDevModeOptions()
|
||||
val rpcSettingsErrors = validateRpcSettings(rpcSettings)
|
||||
errors += rpcSettingsErrors
|
||||
if (rpcSettingsErrors.isEmpty()) {
|
||||
// Forces lazy property to initialise in order to throw exceptions
|
||||
rpcOptions
|
||||
}
|
||||
errors += validateTlsCertCrlConfig()
|
||||
errors += validateNetworkServices()
|
||||
errors += validateH2Settings()
|
||||
errors += validateCryptoService()
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateH2Settings(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (h2port != null && h2Settings != null) {
|
||||
errors += "Cannot specify both 'h2port' and 'h2Settings' in configuration"
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateRpcSettings(options: NodeRpcSettings): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (options.adminAddress == null) {
|
||||
errors += "'rpcSettings.adminAddress': missing"
|
||||
}
|
||||
if (options.useSsl && options.ssl == null) {
|
||||
errors += "'rpcSettings.ssl': missing (rpcSettings.useSsl was set to true)."
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateDevModeOptions(): List<String> {
|
||||
if (devMode) {
|
||||
compatibilityZoneURL?.let {
|
||||
if (devModeOptions?.allowCompatibilityZone != true) {
|
||||
return listOf("'compatibilityZoneURL': present. Property cannot be set when 'devMode' is true unless devModeOptions.allowCompatibilityZone is also true")
|
||||
}
|
||||
}
|
||||
|
||||
// if compatibiliZoneURL is set then it will be copied into the networkServices field and thus skipping
|
||||
// this check by returning above is fine.
|
||||
networkServices?.let {
|
||||
if (devModeOptions?.allowCompatibilityZone != true) {
|
||||
return listOf("'networkServices': present. Property cannot be set when 'devMode' is true unless devModeOptions.allowCompatibilityZone is also true")
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun validateNetworkServices(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
|
||||
if (compatibilityZoneURL != null && networkServices != null && !(networkServices!!.inferred)) {
|
||||
errors += "Cannot configure both compatibilityZoneUrl and networkServices simultaneously"
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
override val transactionCacheSizeBytes: Long
|
||||
get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes
|
||||
override val attachmentContentCacheSizeBytes: Long
|
||||
get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes
|
||||
|
||||
override val effectiveH2Settings: NodeH2Settings?
|
||||
get() = when {
|
||||
h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port))
|
||||
else -> h2Settings
|
||||
}
|
||||
|
||||
init {
|
||||
// This is a sanity feature do not remove.
|
||||
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
|
||||
require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
|
||||
require(security == null || rpcUsers.isEmpty()) {
|
||||
"Cannot specify both 'rpcUsers' and 'security' in configuration"
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
if (certificateChainCheckPolicies.isNotEmpty()) {
|
||||
logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version.
|
||||
|Please contact the R3 team on the public slack to discuss your use case.
|
||||
""".trimMargin())
|
||||
}
|
||||
|
||||
// Support the deprecated method of configuring network services with a single compatibilityZoneURL option
|
||||
if (compatibilityZoneURL != null && networkServices == null) {
|
||||
networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true)
|
||||
}
|
||||
require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" }
|
||||
}
|
||||
}
|
||||
|
||||
data class NodeRpcSettings(
|
||||
val address: NetworkHostAndPort?,
|
||||
val adminAddress: NetworkHostAndPort?,
|
||||
val standAloneBroker: Boolean = false,
|
||||
val useSsl: Boolean = false,
|
||||
val ssl: BrokerRpcSslOptions?
|
||||
) {
|
||||
fun asOptions(): NodeRpcOptions {
|
||||
return object : NodeRpcOptions {
|
||||
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
|
||||
|
||||
override fun toString(): String {
|
||||
return "address: $address, adminAddress: $adminAddress, standAloneBroker: $standAloneBroker, useSsl: $useSsl, sslConfig: $sslConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun Config.parseAsNodeConfiguration(options: Configuration.Validation.Options = Configuration.Validation.Options(strict = true)): Valid<NodeConfiguration> = V1NodeConfigurationSpec.parse(this, options)
|
||||
|
||||
data class NodeH2Settings(
|
||||
val address: NetworkHostAndPort?
|
||||
@ -494,7 +260,7 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ
|
||||
|
||||
// Provider of users credentials and permissions data
|
||||
data class DataSource(val type: AuthDataSourceType,
|
||||
val passwordEncryption: PasswordEncryption = PasswordEncryption.NONE,
|
||||
val passwordEncryption: PasswordEncryption = Defaults.passwordEncryption,
|
||||
val connection: Properties? = null,
|
||||
val users: List<User>? = null) {
|
||||
init {
|
||||
@ -503,6 +269,10 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ
|
||||
AuthDataSourceType.DB -> require(users == null && connection != null)
|
||||
}
|
||||
}
|
||||
|
||||
internal object Defaults {
|
||||
val passwordEncryption = PasswordEncryption.NONE
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -0,0 +1,303 @@
|
||||
package net.corda.node.services.config
|
||||
|
||||
import com.typesafe.config.ConfigException
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.config.rpc.NodeRpcOptions
|
||||
import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
|
||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
||||
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
|
||||
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
||||
import net.corda.nodeapi.internal.config.SslConfiguration
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.tools.shell.SSHDConfiguration
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
data class NodeConfigurationImpl(
|
||||
/** This is not retrieved from the config file but rather from a command line argument. */
|
||||
override val baseDirectory: Path,
|
||||
override val myLegalName: CordaX500Name,
|
||||
override val jmxMonitoringHttpPort: Int? = Defaults.jmxMonitoringHttpPort,
|
||||
override val emailAddress: String,
|
||||
private val keyStorePassword: String,
|
||||
private val trustStorePassword: String,
|
||||
override val crlCheckSoftFail: Boolean,
|
||||
override val dataSourceProperties: Properties,
|
||||
override val compatibilityZoneURL: URL? = Defaults.compatibilityZoneURL,
|
||||
override var networkServices: NetworkServicesConfig? = Defaults.networkServices,
|
||||
override val tlsCertCrlDistPoint: URL? = Defaults.tlsCertCrlDistPoint,
|
||||
override val tlsCertCrlIssuer: X500Principal? = Defaults.tlsCertCrlIssuer,
|
||||
override val rpcUsers: List<User>,
|
||||
override val security: SecurityConfiguration? = Defaults.security,
|
||||
override val verifierType: VerifierType,
|
||||
override val flowTimeout: FlowTimeoutConfiguration,
|
||||
override val p2pAddress: NetworkHostAndPort,
|
||||
override val additionalP2PAddresses: List<NetworkHostAndPort> = Defaults.additionalP2PAddresses,
|
||||
private val rpcAddress: NetworkHostAndPort? = Defaults.rpcAddress,
|
||||
private val rpcSettings: NodeRpcSettings,
|
||||
override val messagingServerAddress: NetworkHostAndPort?,
|
||||
override val messagingServerExternal: Boolean = Defaults.messagingServerExternal(messagingServerAddress),
|
||||
override val notary: NotaryConfig?,
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Do not configure")
|
||||
override val certificateChainCheckPolicies: List<CertChainPolicyConfig> = Defaults.certificateChainCheckPolicies,
|
||||
override val devMode: Boolean = Defaults.devMode,
|
||||
override val noLocalShell: Boolean = Defaults.noLocalShell,
|
||||
override val devModeOptions: DevModeOptions? = Defaults.devModeOptions,
|
||||
override val useTestClock: Boolean = Defaults.useTestClock,
|
||||
override val lazyBridgeStart: Boolean = Defaults.lazyBridgeStart,
|
||||
override val detectPublicIp: Boolean = Defaults.detectPublicIp,
|
||||
// TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
|
||||
override val additionalNodeInfoPollingFrequencyMsec: Long = Defaults.additionalNodeInfoPollingFrequencyMsec,
|
||||
override val sshd: SSHDConfiguration? = Defaults.sshd,
|
||||
override val database: DatabaseConfig = Defaults.database(devMode),
|
||||
private val transactionCacheSizeMegaBytes: Int? = Defaults.transactionCacheSizeMegaBytes,
|
||||
private val attachmentContentCacheSizeMegaBytes: Int? = Defaults.attachmentContentCacheSizeMegaBytes,
|
||||
override val attachmentCacheBound: Long = Defaults.attachmentCacheBound,
|
||||
override val extraNetworkMapKeys: List<UUID> = Defaults.extraNetworkMapKeys,
|
||||
// do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing)
|
||||
private val h2port: Int? = Defaults.h2port,
|
||||
private val h2Settings: NodeH2Settings? = Defaults.h2Settings,
|
||||
// do not use or remove (used by Capsule)
|
||||
private val jarDirs: List<String> = Defaults.jarDirs,
|
||||
override val flowMonitorPeriodMillis: Duration = Defaults.flowMonitorPeriodMillis,
|
||||
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = Defaults.flowMonitorSuspensionLoggingThresholdMillis,
|
||||
override val cordappDirectories: List<Path> = Defaults.cordappsDirectories(baseDirectory),
|
||||
override val jmxReporterType: JmxReporterType? = Defaults.jmxReporterType,
|
||||
override val flowOverrides: FlowOverrideConfig?,
|
||||
override val cordappSignerKeyFingerprintBlacklist: List<String> = Defaults.cordappSignerKeyFingerprintBlacklist,
|
||||
override val cryptoServiceName: SupportedCryptoServices? = null,
|
||||
override val cryptoServiceConf: String? = null
|
||||
) : NodeConfiguration {
|
||||
internal object Defaults {
|
||||
val jmxMonitoringHttpPort: Int? = null
|
||||
val compatibilityZoneURL: URL? = null
|
||||
val networkServices: NetworkServicesConfig? = null
|
||||
val tlsCertCrlDistPoint: URL? = null
|
||||
val tlsCertCrlIssuer: X500Principal? = null
|
||||
val security: SecurityConfiguration? = null
|
||||
val additionalP2PAddresses: List<NetworkHostAndPort> = emptyList()
|
||||
val rpcAddress: NetworkHostAndPort? = null
|
||||
@Suppress("DEPRECATION")
|
||||
val certificateChainCheckPolicies: List<CertChainPolicyConfig> = emptyList()
|
||||
const val devMode: Boolean = false
|
||||
const val noLocalShell: Boolean = false
|
||||
val devModeOptions: DevModeOptions? = null
|
||||
const val useTestClock: Boolean = false
|
||||
const val lazyBridgeStart: Boolean = true
|
||||
const val detectPublicIp: Boolean = true
|
||||
val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis()
|
||||
val sshd: SSHDConfiguration? = null
|
||||
val transactionCacheSizeMegaBytes: Int? = null
|
||||
val attachmentContentCacheSizeMegaBytes: Int? = null
|
||||
const val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound
|
||||
val extraNetworkMapKeys: List<UUID> = emptyList()
|
||||
val h2port: Int? = null
|
||||
val h2Settings: NodeH2Settings? = null
|
||||
val jarDirs: List<String> = emptyList()
|
||||
val flowMonitorPeriodMillis: Duration = NodeConfiguration.DEFAULT_FLOW_MONITOR_PERIOD_MILLIS
|
||||
val flowMonitorSuspensionLoggingThresholdMillis: Duration = NodeConfiguration.DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
|
||||
val jmxReporterType: JmxReporterType = NodeConfiguration.defaultJmxReporterType
|
||||
val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() }
|
||||
|
||||
fun cordappsDirectories(baseDirectory: Path) = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT)
|
||||
|
||||
fun messagingServerExternal(messagingServerAddress: NetworkHostAndPort?) = messagingServerAddress != null
|
||||
|
||||
fun database(devMode: Boolean) = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps"
|
||||
|
||||
private val logger = loggerFor<NodeConfigurationImpl>()
|
||||
}
|
||||
|
||||
private val actualRpcSettings: NodeRpcSettings
|
||||
|
||||
init {
|
||||
actualRpcSettings = when {
|
||||
rpcAddress != null -> {
|
||||
require(rpcSettings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." }
|
||||
logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.")
|
||||
|
||||
rpcSettings.copy(address = rpcAddress)
|
||||
}
|
||||
else -> {
|
||||
rpcSettings.address ?: throw ConfigException.Missing("rpcSettings.address")
|
||||
rpcSettings
|
||||
}
|
||||
}
|
||||
|
||||
// This is a sanity feature do not remove.
|
||||
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
|
||||
require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
|
||||
require(security == null || rpcUsers.isEmpty()) {
|
||||
"Cannot specify both 'rpcUsers' and 'security' in configuration"
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
if (certificateChainCheckPolicies.isNotEmpty()) {
|
||||
logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version.
|
||||
|Please contact the R3 team on the public Slack to discuss your use case.
|
||||
""".trimMargin())
|
||||
}
|
||||
|
||||
// Support the deprecated method of configuring network services with a single compatibilityZoneURL option
|
||||
if (compatibilityZoneURL != null && networkServices == null) {
|
||||
networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true)
|
||||
}
|
||||
require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" }
|
||||
}
|
||||
|
||||
override val certificatesDirectory = baseDirectory / "certificates"
|
||||
|
||||
private val signingCertificateStorePath = certificatesDirectory / "nodekeystore.jks"
|
||||
private val p2pKeystorePath: Path get() = certificatesDirectory / "sslkeystore.jks"
|
||||
|
||||
// TODO: There are two implications here:
|
||||
// 1. "signingCertificateStore" and "p2pKeyStore" have the same passwords. In the future we should re-visit this "rule" and see of they can be made different;
|
||||
// 2. The passwords for store and for keys in this store are the same, this is due to limitations of Artemis.
|
||||
override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword, keyStorePassword)
|
||||
private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword, keyStorePassword)
|
||||
|
||||
private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks"
|
||||
private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword, trustStorePassword)
|
||||
override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore)
|
||||
|
||||
override val rpcOptions: NodeRpcOptions
|
||||
get() {
|
||||
return actualRpcSettings.asOptions()
|
||||
}
|
||||
|
||||
override val transactionCacheSizeBytes: Long
|
||||
get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes
|
||||
override val attachmentContentCacheSizeBytes: Long
|
||||
get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes
|
||||
|
||||
override val effectiveH2Settings: NodeH2Settings?
|
||||
get() = when {
|
||||
h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port))
|
||||
else -> h2Settings
|
||||
}
|
||||
|
||||
fun validate(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
errors += validateDevModeOptions()
|
||||
val rpcSettingsErrors = validateRpcSettings(rpcSettings)
|
||||
errors += rpcSettingsErrors
|
||||
if (rpcSettingsErrors.isEmpty()) {
|
||||
// Forces lazy property to initialise in order to throw exceptions
|
||||
rpcOptions
|
||||
}
|
||||
errors += validateTlsCertCrlConfig()
|
||||
errors += validateNetworkServices()
|
||||
errors += validateH2Settings()
|
||||
errors += validateCryptoService()
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateTlsCertCrlConfig(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (tlsCertCrlIssuer != null) {
|
||||
if (tlsCertCrlDistPoint == null) {
|
||||
errors += "'tlsCertCrlDistPoint' is mandatory when 'tlsCertCrlIssuer' is specified"
|
||||
}
|
||||
}
|
||||
if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) {
|
||||
errors += "'tlsCertCrlDistPoint' is mandatory when 'crlCheckSoftFail' is false"
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateH2Settings(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (h2port != null && h2Settings != null) {
|
||||
errors += "cannot specify both 'h2port' and 'h2Settings'"
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateCryptoService(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (cryptoServiceName == null && cryptoServiceConf != null) {
|
||||
errors += "'cryptoServiceName' is mandatory when 'cryptoServiceConf' is specified"
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateRpcSettings(options: NodeRpcSettings): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (options.adminAddress == null) {
|
||||
errors += "'rpcSettings.adminAddress' is mandatory"
|
||||
}
|
||||
if (options.useSsl && options.ssl == null) {
|
||||
errors += "'rpcSettings.ssl' is mandatory when 'rpcSettings.useSsl' is specified"
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateDevModeOptions(): List<String> {
|
||||
if (devMode) {
|
||||
compatibilityZoneURL?.let {
|
||||
if (devModeOptions?.allowCompatibilityZone != true) {
|
||||
return listOf("cannot specify 'compatibilityZoneURL' when 'devMode' is true, unless 'devModeOptions.allowCompatibilityZone' is also true")
|
||||
}
|
||||
}
|
||||
|
||||
// if compatibilityZoneURL is set then it will be copied into the networkServices field and thus skipping
|
||||
// this check by returning above is fine.
|
||||
networkServices?.let {
|
||||
if (devModeOptions?.allowCompatibilityZone != true) {
|
||||
return listOf("cannot specify 'networkServices' when 'devMode' is true, unless 'devModeOptions.allowCompatibilityZone' is also true")
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun validateNetworkServices(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
|
||||
if (compatibilityZoneURL != null && networkServices != null && !(networkServices!!.inferred)) {
|
||||
errors += "cannot specify both 'compatibilityZoneUrl' and 'networkServices'"
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
}
|
||||
|
||||
data class NodeRpcSettings(
|
||||
val address: NetworkHostAndPort?,
|
||||
val adminAddress: NetworkHostAndPort?,
|
||||
val standAloneBroker: Boolean = Defaults.standAloneBroker,
|
||||
val useSsl: Boolean = Defaults.useSsl,
|
||||
val ssl: BrokerRpcSslOptions?
|
||||
) {
|
||||
internal object Defaults {
|
||||
val standAloneBroker = false
|
||||
val useSsl = false
|
||||
}
|
||||
|
||||
fun asOptions(): NodeRpcOptions {
|
||||
return object : NodeRpcOptions {
|
||||
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
|
||||
|
||||
override fun toString(): String {
|
||||
return "address: $address, adminAddress: $adminAddress, standAloneBroker: $standAloneBroker, useSsl: $useSsl, sslConfig: $sslConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package net.corda.node.services.config.schema.parsers
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigObject
|
||||
import com.typesafe.config.ConfigUtil
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
import net.corda.common.validation.internal.Validated.Companion.valid
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.Valid
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
internal fun toProperties(rawValue: ConfigObject): Properties = rawValue.toConfig().toProperties()
|
||||
|
||||
private fun Config.toProperties() = entrySet().associateByTo(Properties(), { ConfigUtil.splitPath(it.key).joinToString(".") }, { it.value.unwrapped().toString() })
|
||||
|
||||
internal fun toCordaX500Name(rawValue: String) = attempt<CordaX500Name, IllegalArgumentException> { CordaX500Name.parse(rawValue) }
|
||||
|
||||
internal fun toURL(rawValue: String) = attempt<URL, MalformedURLException> { URL(rawValue) }
|
||||
|
||||
internal fun toUUID(rawValue: String) = attempt<UUID, IllegalArgumentException> { UUID.fromString(rawValue) }
|
||||
|
||||
internal fun toNetworkHostAndPort(rawValue: String) = attempt<NetworkHostAndPort, IllegalArgumentException> { NetworkHostAndPort.parse(rawValue) }
|
||||
|
||||
internal fun toPrincipal(rawValue: String) = attempt<X500Principal, IllegalArgumentException> { X500Principal(rawValue) }
|
||||
|
||||
internal fun toPath(rawValue: String) = attempt<Path, InvalidPathException> { Paths.get(rawValue) }
|
||||
|
||||
private inline fun <RESULT, reified ERROR : Exception> attempt(action: () -> RESULT, message: (ERROR) -> String): Valid<RESULT> {
|
||||
return try {
|
||||
valid(action.invoke())
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
is ERROR -> badValue(message.invoke(e))
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified RESULT, reified ERROR : Exception> attempt(action: () -> RESULT) = attempt(action, { e: ERROR -> "value does not comply with ${RESULT::class.java.simpleName} specification (${e.message})" })
|
||||
|
||||
internal fun <RESULT> validValue(result: RESULT) = valid<RESULT, Configuration.Validation.Error>(result)
|
||||
|
||||
internal fun <RESULT> badValue(message: String) = invalid<RESULT, Configuration.Validation.Error>(Configuration.Validation.Error.BadValue.of(message))
|
@ -0,0 +1,236 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package net.corda.node.services.config.schema.v1
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigObject
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.common.configuration.parsing.internal.get
|
||||
import net.corda.common.configuration.parsing.internal.listOrEmpty
|
||||
import net.corda.common.configuration.parsing.internal.map
|
||||
import net.corda.common.configuration.parsing.internal.mapValid
|
||||
import net.corda.common.configuration.parsing.internal.nested
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
import net.corda.common.validation.internal.Validated.Companion.valid
|
||||
import net.corda.core.context.AuthServiceId
|
||||
import net.corda.node.services.config.AuthDataSourceType
|
||||
import net.corda.node.services.config.CertChainPolicyConfig
|
||||
import net.corda.node.services.config.CertChainPolicyType
|
||||
import net.corda.node.services.config.DevModeOptions
|
||||
import net.corda.node.services.config.FlowOverride
|
||||
import net.corda.node.services.config.FlowOverrideConfig
|
||||
import net.corda.node.services.config.FlowTimeoutConfiguration
|
||||
import net.corda.node.services.config.NetworkServicesConfig
|
||||
import net.corda.node.services.config.NodeH2Settings
|
||||
import net.corda.node.services.config.NodeRpcSettings
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.config.PasswordEncryption
|
||||
import net.corda.node.services.config.SecurityConfiguration
|
||||
import net.corda.node.services.config.SecurityConfiguration.AuthService.Companion.defaultAuthServiceId
|
||||
import net.corda.node.services.config.Valid
|
||||
import net.corda.node.services.config.schema.parsers.attempt
|
||||
import net.corda.node.services.config.schema.parsers.badValue
|
||||
import net.corda.node.services.config.schema.parsers.toCordaX500Name
|
||||
import net.corda.node.services.config.schema.parsers.toNetworkHostAndPort
|
||||
import net.corda.node.services.config.schema.parsers.toPath
|
||||
import net.corda.node.services.config.schema.parsers.toProperties
|
||||
import net.corda.node.services.config.schema.parsers.toURL
|
||||
import net.corda.node.services.config.schema.parsers.toUUID
|
||||
import net.corda.node.services.config.schema.parsers.validValue
|
||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
|
||||
import net.corda.tools.shell.SSHDConfiguration
|
||||
|
||||
internal object UserSpec : Configuration.Specification<User>("User") {
|
||||
private val username by string().optional()
|
||||
private val user by string().optional()
|
||||
private val password by string(sensitive = true)
|
||||
private val permissions by string().listOrEmpty()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<User> {
|
||||
val username = configuration[username] ?: configuration[user]
|
||||
return when (username) {
|
||||
null -> invalid(Configuration.Validation.Error.MissingValue.forKey("username"))
|
||||
else -> valid(User(username, configuration[password], configuration[permissions].toSet()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object SecurityConfigurationSpec : Configuration.Specification<SecurityConfiguration>("SecurityConfiguration") {
|
||||
private object AuthServiceSpec : Configuration.Specification<SecurityConfiguration.AuthService>("AuthService") {
|
||||
private object DataSourceSpec : Configuration.Specification<SecurityConfiguration.AuthService.DataSource>("DataSource") {
|
||||
private val type by enum(AuthDataSourceType::class)
|
||||
private val passwordEncryption by enum(PasswordEncryption::class).optional().withDefaultValue(SecurityConfiguration.AuthService.DataSource.Defaults.passwordEncryption)
|
||||
private val connection by nestedObject(sensitive = true).map(::toProperties).optional()
|
||||
private val users by nested(UserSpec).list().optional()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService.DataSource> {
|
||||
val type = configuration[type]
|
||||
val passwordEncryption = configuration[passwordEncryption]
|
||||
val connection = configuration[connection]
|
||||
val users = configuration[users]
|
||||
|
||||
return when {
|
||||
type == AuthDataSourceType.INMEMORY && (users == null || connection != null) -> badValue("\"INMEMORY\" data source type requires \"users\" and cannot specify \"connection\"")
|
||||
type == AuthDataSourceType.DB && (users != null || connection == null) -> badValue("\"DB\" data source type requires \"connection\" and cannot specify \"users\"")
|
||||
else -> valid(SecurityConfiguration.AuthService.DataSource(type, passwordEncryption, connection, users))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object OptionsSpec : Configuration.Specification<SecurityConfiguration.AuthService.Options>("Options") {
|
||||
private object CacheSpec : Configuration.Specification<SecurityConfiguration.AuthService.Options.Cache>("Cache") {
|
||||
private val expireAfterSecs by long().mapValid { value -> if (value >= 0) validValue(value) else badValue("cannot be less than 0'") }
|
||||
private val maxEntries by long().mapValid { value -> if (value >= 0) validValue(value) else badValue("cannot be less than 0'") }
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService.Options.Cache> {
|
||||
return valid(SecurityConfiguration.AuthService.Options.Cache(configuration[expireAfterSecs], configuration[maxEntries]))
|
||||
}
|
||||
}
|
||||
|
||||
private val cache by nested(CacheSpec).optional()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService.Options> {
|
||||
return valid(SecurityConfiguration.AuthService.Options(configuration[cache]))
|
||||
}
|
||||
}
|
||||
|
||||
private val dataSource by nested(DataSourceSpec)
|
||||
private val id by string().map(::AuthServiceId).optional()
|
||||
val options by nested(OptionsSpec).optional()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService> {
|
||||
val dataSource = configuration[dataSource]
|
||||
val id = configuration[id] ?: defaultAuthServiceId(dataSource.type)
|
||||
val options = configuration[options]
|
||||
return when {
|
||||
dataSource.type == AuthDataSourceType.INMEMORY && options?.cache != null -> badValue("no cache supported for \"INMEMORY\" data provider")
|
||||
else -> valid(SecurityConfiguration.AuthService(dataSource, id, options))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val authService by nested(AuthServiceSpec)
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<SecurityConfiguration> {
|
||||
return valid(SecurityConfiguration(configuration[authService]))
|
||||
}
|
||||
}
|
||||
|
||||
internal object DevModeOptionsSpec : Configuration.Specification<DevModeOptions>("DevModeOptions") {
|
||||
private val disableCheckpointChecker by boolean().optional().withDefaultValue(DevModeOptions.Defaults.disableCheckpointChecker)
|
||||
private val allowCompatibilityZone by boolean().optional().withDefaultValue(DevModeOptions.Defaults.allowCompatibilityZone)
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<DevModeOptions> {
|
||||
return valid(DevModeOptions(configuration[disableCheckpointChecker], configuration[allowCompatibilityZone]))
|
||||
}
|
||||
}
|
||||
|
||||
internal object NetworkServicesConfigSpec : Configuration.Specification<NetworkServicesConfig>("NetworkServicesConfig") {
|
||||
private val doormanURL by string().mapValid(::toURL)
|
||||
private val networkMapURL by string().mapValid(::toURL)
|
||||
private val pnm by string().mapValid(::toUUID).optional()
|
||||
private val inferred by boolean().optional().withDefaultValue(false)
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<NetworkServicesConfig> {
|
||||
return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred]))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
internal object CertChainPolicyConfigSpec : Configuration.Specification<CertChainPolicyConfig>("CertChainPolicyConfig") {
|
||||
private val role by string()
|
||||
private val policy by enum(CertChainPolicyType::class)
|
||||
private val trustedAliases by string().listOrEmpty()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<CertChainPolicyConfig> {
|
||||
return valid(CertChainPolicyConfig(configuration[role], configuration[policy], configuration[trustedAliases].toSet()))
|
||||
}
|
||||
}
|
||||
|
||||
internal object FlowTimeoutConfigurationSpec : Configuration.Specification<FlowTimeoutConfiguration>("FlowTimeoutConfiguration") {
|
||||
private val timeout by duration()
|
||||
private val maxRestartCount by int()
|
||||
private val backoffBase by double()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<FlowTimeoutConfiguration> {
|
||||
return valid(FlowTimeoutConfiguration(configuration[timeout], configuration[maxRestartCount], configuration[backoffBase]))
|
||||
}
|
||||
}
|
||||
|
||||
internal object NotaryConfigSpec : Configuration.Specification<NotaryConfig>("NotaryConfig") {
|
||||
private val validating by boolean()
|
||||
private val serviceLegalName by string().mapValid(::toCordaX500Name).optional()
|
||||
private val className by string().optional().withDefaultValue("net.corda.node.services.transactions.SimpleNotaryService")
|
||||
private val extraConfig by nestedObject().map(ConfigObject::toConfig).optional()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<NotaryConfig> {
|
||||
return valid(NotaryConfig(configuration[validating], configuration[serviceLegalName], configuration[className], configuration[extraConfig]))
|
||||
}
|
||||
}
|
||||
|
||||
internal object NodeRpcSettingsSpec : Configuration.Specification<NodeRpcSettings>("NodeRpcSettings") {
|
||||
internal object BrokerRpcSslOptionsSpec : Configuration.Specification<BrokerRpcSslOptions>("BrokerRpcSslOptions") {
|
||||
private val keyStorePath by string().mapValid(::toPath)
|
||||
private val keyStorePassword by string(sensitive = true)
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<BrokerRpcSslOptions> {
|
||||
return valid(BrokerRpcSslOptions(configuration[keyStorePath], configuration[keyStorePassword]))
|
||||
}
|
||||
}
|
||||
|
||||
private val address by string().mapValid(::toNetworkHostAndPort).optional()
|
||||
private val adminAddress by string().mapValid(::toNetworkHostAndPort).optional()
|
||||
private val standAloneBroker by boolean().optional().withDefaultValue(NodeRpcSettings.Defaults.standAloneBroker)
|
||||
private val useSsl by boolean().optional().withDefaultValue(NodeRpcSettings.Defaults.useSsl)
|
||||
private val ssl by nested(BrokerRpcSslOptionsSpec).optional()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<NodeRpcSettings> {
|
||||
return valid(NodeRpcSettings(configuration[address], configuration[adminAddress], configuration[standAloneBroker], configuration[useSsl], configuration[ssl]))
|
||||
}
|
||||
}
|
||||
|
||||
internal object SSHDConfigurationSpec : Configuration.Specification<SSHDConfiguration>("SSHDConfiguration") {
|
||||
private val port by int()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<SSHDConfiguration> = attempt<SSHDConfiguration, IllegalArgumentException> { SSHDConfiguration(configuration[port]) }
|
||||
}
|
||||
|
||||
|
||||
internal object DatabaseConfigSpec : Configuration.Specification<DatabaseConfig>("DatabaseConfig") {
|
||||
private val initialiseSchema by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.initialiseSchema)
|
||||
private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel)
|
||||
private val exportHibernateJMXStatistics by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.exportHibernateJMXStatistics)
|
||||
private val mappedSchemaCacheSize by long().optional().withDefaultValue(DatabaseConfig.Defaults.mappedSchemaCacheSize)
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<DatabaseConfig> {
|
||||
return valid(DatabaseConfig(configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize]))
|
||||
}
|
||||
}
|
||||
|
||||
internal object NodeH2SettingsSpec : Configuration.Specification<NodeH2Settings>("NodeH2Settings") {
|
||||
private val address by string().mapValid(::toNetworkHostAndPort).optional()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<NodeH2Settings> {
|
||||
return valid(NodeH2Settings(configuration[address]))
|
||||
}
|
||||
}
|
||||
|
||||
internal object FlowOverridesConfigSpec : Configuration.Specification<FlowOverrideConfig>("FlowOverrideConfig") {
|
||||
internal object SingleSpec : Configuration.Specification<FlowOverride>("FlowOverride") {
|
||||
private val initiator by string()
|
||||
private val responder by string()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<FlowOverride> {
|
||||
return valid(FlowOverride(configuration[initiator], configuration[responder]))
|
||||
}
|
||||
}
|
||||
|
||||
private val overrides by nested(FlowOverridesConfigSpec.SingleSpec).listOrEmpty()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<FlowOverrideConfig> {
|
||||
return valid(FlowOverrideConfig(configuration[overrides]))
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package net.corda.node.services.config.schema.v1
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.common.configuration.parsing.internal.get
|
||||
import net.corda.common.configuration.parsing.internal.listOrEmpty
|
||||
import net.corda.common.configuration.parsing.internal.map
|
||||
import net.corda.common.configuration.parsing.internal.mapValid
|
||||
import net.corda.common.configuration.parsing.internal.nested
|
||||
import net.corda.common.configuration.parsing.internal.toValidationError
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
import net.corda.common.validation.internal.Validated.Companion.valid
|
||||
import net.corda.node.services.config.JmxReporterType
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.NodeConfigurationImpl
|
||||
import net.corda.node.services.config.NodeConfigurationImpl.Defaults
|
||||
import net.corda.node.services.config.Valid
|
||||
import net.corda.node.services.config.VerifierType
|
||||
import net.corda.node.services.config.schema.parsers.badValue
|
||||
import net.corda.node.services.config.schema.parsers.toCordaX500Name
|
||||
import net.corda.node.services.config.schema.parsers.toNetworkHostAndPort
|
||||
import net.corda.node.services.config.schema.parsers.toPath
|
||||
import net.corda.node.services.config.schema.parsers.toPrincipal
|
||||
import net.corda.node.services.config.schema.parsers.toProperties
|
||||
import net.corda.node.services.config.schema.parsers.toURL
|
||||
import net.corda.node.services.config.schema.parsers.toUUID
|
||||
import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
|
||||
|
||||
internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfiguration>("NodeConfiguration") {
|
||||
private val myLegalName by string().mapValid(::toCordaX500Name)
|
||||
private val emailAddress by string()
|
||||
private val jmxMonitoringHttpPort by int().optional()
|
||||
private val dataSourceProperties by nestedObject(sensitive = true).map(::toProperties)
|
||||
private val rpcUsers by nested(UserSpec).listOrEmpty()
|
||||
private val security by nested(SecurityConfigurationSpec).optional()
|
||||
private val devMode by boolean().optional().withDefaultValue(Defaults.devMode)
|
||||
private val devModeOptions by nested(DevModeOptionsSpec).optional()
|
||||
private val compatibilityZoneURL by string().mapValid(::toURL).optional()
|
||||
private val networkServices by nested(NetworkServicesConfigSpec).optional()
|
||||
private val certificateChainCheckPolicies by nested(CertChainPolicyConfigSpec).list().optional().withDefaultValue(Defaults.certificateChainCheckPolicies)
|
||||
private val verifierType by enum(VerifierType::class)
|
||||
private val flowTimeout by nested(FlowTimeoutConfigurationSpec)
|
||||
private val notary by nested(NotaryConfigSpec).optional()
|
||||
private val additionalNodeInfoPollingFrequencyMsec by long().optional().withDefaultValue(Defaults.additionalNodeInfoPollingFrequencyMsec)
|
||||
private val p2pAddress by string().mapValid(::toNetworkHostAndPort)
|
||||
private val additionalP2PAddresses by string().mapValid(::toNetworkHostAndPort).list().optional().withDefaultValue(Defaults.additionalP2PAddresses)
|
||||
private val rpcSettings by nested(NodeRpcSettingsSpec)
|
||||
private val messagingServerAddress by string().mapValid(::toNetworkHostAndPort).optional()
|
||||
private val messagingServerExternal by boolean().optional()
|
||||
private val useTestClock by boolean().optional().withDefaultValue(Defaults.useTestClock)
|
||||
private val lazyBridgeStart by boolean().optional().withDefaultValue(Defaults.lazyBridgeStart)
|
||||
private val detectPublicIp by boolean().optional().withDefaultValue(Defaults.detectPublicIp)
|
||||
private val sshd by nested(SSHDConfigurationSpec).optional()
|
||||
private val database by nested(DatabaseConfigSpec).optional()
|
||||
private val noLocalShell by boolean().optional().withDefaultValue(Defaults.noLocalShell)
|
||||
private val attachmentCacheBound by long().optional().withDefaultValue(Defaults.attachmentCacheBound)
|
||||
private val extraNetworkMapKeys by string().mapValid(::toUUID).list().optional().withDefaultValue(Defaults.extraNetworkMapKeys)
|
||||
private val tlsCertCrlDistPoint by string().mapValid(::toURL).optional()
|
||||
private val tlsCertCrlIssuer by string().mapValid(::toPrincipal).optional()
|
||||
private val h2Settings by nested(NodeH2SettingsSpec).optional()
|
||||
private val flowMonitorPeriodMillis by duration().optional().withDefaultValue(Defaults.flowMonitorPeriodMillis)
|
||||
private val flowMonitorSuspensionLoggingThresholdMillis by duration().optional().withDefaultValue(Defaults.flowMonitorSuspensionLoggingThresholdMillis)
|
||||
private val crlCheckSoftFail by boolean()
|
||||
private val jmxReporterType by enum(JmxReporterType::class).optional().withDefaultValue(Defaults.jmxReporterType)
|
||||
private val baseDirectory by string().mapValid(::toPath)
|
||||
private val flowOverrides by nested(FlowOverridesConfigSpec).optional()
|
||||
private val keyStorePassword by string(sensitive = true)
|
||||
private val trustStorePassword by string(sensitive = true)
|
||||
private val rpcAddress by string().mapValid(::toNetworkHostAndPort).optional()
|
||||
private val transactionCacheSizeMegaBytes by int().optional()
|
||||
private val attachmentContentCacheSizeMegaBytes by int().optional()
|
||||
private val h2port by int().optional()
|
||||
private val jarDirs by string().list().optional().withDefaultValue(Defaults.jarDirs)
|
||||
private val cordappDirectories by string().mapValid(::toPath).list().optional()
|
||||
private val cordappSignerKeyFingerprintBlacklist by string().list().optional().withDefaultValue(Defaults.cordappSignerKeyFingerprintBlacklist)
|
||||
private val cryptoServiceName by enum(SupportedCryptoServices::class).optional()
|
||||
private val cryptoServiceConf by string().optional()
|
||||
@Suppress("unused")
|
||||
private val custom by nestedObject().optional()
|
||||
|
||||
override fun parseValid(configuration: Config): Valid<NodeConfiguration> {
|
||||
|
||||
val messagingServerExternal = configuration[messagingServerExternal] ?: Defaults.messagingServerExternal(configuration[messagingServerAddress])
|
||||
val database = configuration[database] ?: Defaults.database(configuration[devMode])
|
||||
val cordappDirectories = configuration[cordappDirectories] ?: Defaults.cordappsDirectories(configuration[baseDirectory])
|
||||
val result = try {
|
||||
valid<NodeConfigurationImpl, Configuration.Validation.Error>(NodeConfigurationImpl(
|
||||
baseDirectory = configuration[baseDirectory],
|
||||
myLegalName = configuration[myLegalName],
|
||||
emailAddress = configuration[emailAddress],
|
||||
p2pAddress = configuration[p2pAddress],
|
||||
keyStorePassword = configuration[keyStorePassword],
|
||||
trustStorePassword = configuration[trustStorePassword],
|
||||
crlCheckSoftFail = configuration[crlCheckSoftFail],
|
||||
dataSourceProperties = configuration[dataSourceProperties],
|
||||
rpcUsers = configuration[rpcUsers],
|
||||
verifierType = configuration[verifierType],
|
||||
flowTimeout = configuration[flowTimeout],
|
||||
rpcSettings = configuration[rpcSettings],
|
||||
messagingServerAddress = configuration[messagingServerAddress],
|
||||
notary = configuration[notary],
|
||||
flowOverrides = configuration[flowOverrides],
|
||||
additionalP2PAddresses = configuration[additionalP2PAddresses],
|
||||
additionalNodeInfoPollingFrequencyMsec = configuration[additionalNodeInfoPollingFrequencyMsec],
|
||||
jmxMonitoringHttpPort = configuration[jmxMonitoringHttpPort],
|
||||
security = configuration[security],
|
||||
devMode = configuration[devMode],
|
||||
devModeOptions = configuration[devModeOptions],
|
||||
compatibilityZoneURL = configuration[compatibilityZoneURL],
|
||||
networkServices = configuration[networkServices],
|
||||
certificateChainCheckPolicies = configuration[certificateChainCheckPolicies],
|
||||
messagingServerExternal = messagingServerExternal,
|
||||
useTestClock = configuration[useTestClock],
|
||||
lazyBridgeStart = configuration[lazyBridgeStart],
|
||||
detectPublicIp = configuration[detectPublicIp],
|
||||
sshd = configuration[sshd],
|
||||
database = database,
|
||||
noLocalShell = configuration[noLocalShell],
|
||||
attachmentCacheBound = configuration[attachmentCacheBound],
|
||||
extraNetworkMapKeys = configuration[extraNetworkMapKeys],
|
||||
tlsCertCrlDistPoint = configuration[tlsCertCrlDistPoint],
|
||||
tlsCertCrlIssuer = configuration[tlsCertCrlIssuer],
|
||||
h2Settings = configuration[h2Settings],
|
||||
flowMonitorPeriodMillis = configuration[flowMonitorPeriodMillis],
|
||||
flowMonitorSuspensionLoggingThresholdMillis = configuration[flowMonitorSuspensionLoggingThresholdMillis],
|
||||
jmxReporterType = configuration[jmxReporterType],
|
||||
rpcAddress = configuration[rpcAddress],
|
||||
transactionCacheSizeMegaBytes = configuration[transactionCacheSizeMegaBytes],
|
||||
attachmentContentCacheSizeMegaBytes = configuration[attachmentContentCacheSizeMegaBytes],
|
||||
h2port = configuration[h2port],
|
||||
jarDirs = configuration[jarDirs],
|
||||
cordappDirectories = cordappDirectories,
|
||||
cordappSignerKeyFingerprintBlacklist = configuration[cordappSignerKeyFingerprintBlacklist],
|
||||
cryptoServiceName = configuration[cryptoServiceName],
|
||||
cryptoServiceConf = configuration[cryptoServiceConf]
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
return when (e) {
|
||||
is ConfigException -> invalid(e.toValidationError(typeName = "NodeConfiguration"))
|
||||
is IllegalArgumentException -> badValue(e.message!!)
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
return result.mapValid { conf -> Valid.withResult(conf as NodeConfiguration, conf.validate().map(::toError).toSet()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun toError(validationErrorMessage: String): Configuration.Validation.Error = Configuration.Validation.Error.BadValue.of(validationErrorMessage)
|
@ -72,7 +72,6 @@
|
||||
multiParam: false
|
||||
acceptableValues:
|
||||
- "FAIL"
|
||||
- "WARN"
|
||||
- "IGNORE"
|
||||
- parameterName: "--sshd"
|
||||
parameterType: "boolean"
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.node.services.config
|
||||
|
||||
import com.typesafe.config.*
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.core.internal.toPath
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.seconds
|
||||
@ -33,14 +34,16 @@ class NodeConfigurationImplTest {
|
||||
fun `can't have tlsCertCrlDistPoint null when tlsCertCrlIssuer is given`() {
|
||||
val configValidationResult = configTlsCertCrlOptions(null, "C=US, L=New York, OU=Corda, O=R3 HoldCo LLC, CN=Corda Root CA").validate()
|
||||
assertTrue { configValidationResult.isNotEmpty() }
|
||||
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL")
|
||||
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint")
|
||||
assertThat(configValidationResult.first()).contains("tlsCertCrlIssuer")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() {
|
||||
val configValidationResult = configTlsCertCrlOptions(null, null, false).validate()
|
||||
assertTrue { configValidationResult.isNotEmpty() }
|
||||
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE")
|
||||
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint")
|
||||
assertThat(configValidationResult.first()).contains("crlCheckSoftFail")
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -143,9 +146,9 @@ class NodeConfigurationImplTest {
|
||||
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)
|
||||
assertThat(rawConfig.parseAsNodeConfiguration().errors.single()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error ->
|
||||
assertThat(error.message).contains(missingPropertyPath)
|
||||
assertThat(error.typeName).isEqualTo(NodeConfiguration::class.java.simpleName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +194,10 @@ class NodeConfigurationImplTest {
|
||||
fun `fail on wrong cryptoServiceName`() {
|
||||
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
|
||||
rawConfig = rawConfig.withValue("cryptoServiceName", ConfigValueFactory.fromAnyRef("UNSUPPORTED"))
|
||||
assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.hasMessageStartingWith("UNSUPPORTED is not one of")
|
||||
|
||||
val config = rawConfig.parseAsNodeConfiguration()
|
||||
|
||||
assertThat(config.errors.asSequence().map(Configuration.Validation.Error::message).filter { it.contains("has no constant of the name 'UNSUPPORTED'") }.toList()).isNotEmpty
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -200,7 +206,7 @@ class NodeConfigurationImplTest {
|
||||
rawConfig = rawConfig.withoutPath("rpcSettings.address")
|
||||
rawConfig = rawConfig.withValue("rpcAddress", ConfigValueFactory.fromAnyRef("localhost:4444"))
|
||||
|
||||
assertThatCode { rawConfig.parseAsNodeConfiguration() }.doesNotThrowAnyException()
|
||||
assertThat(rawConfig.parseAsNodeConfiguration().isValid).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -210,11 +216,11 @@ class NodeConfigurationImplTest {
|
||||
|
||||
val config = rawConfig.parseAsNodeConfiguration()
|
||||
|
||||
assertThat(config.validate().filter { it.contains("rpcSettings.adminAddress") }).isNotEmpty
|
||||
assertThat(config.errors.asSequence().map(Configuration.Validation.Error::message).filter { it.contains("rpcSettings.adminAddress") }.toList()).isNotEmpty
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compatiilityZoneURL populates NetworkServices`() {
|
||||
fun `compatibilityZoneURL populates NetworkServices`() {
|
||||
val compatibilityZoneURL = URI.create("https://r3.com").toURL()
|
||||
val configuration = testConfiguration.copy(
|
||||
devMode = false,
|
||||
@ -228,7 +234,7 @@ class NodeConfigurationImplTest {
|
||||
@Test
|
||||
fun `jmxReporterType is null and defaults to Jokolia`() {
|
||||
val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
|
||||
val nodeConfig = rawConfig.parseAsNodeConfiguration()
|
||||
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow()
|
||||
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
|
||||
}
|
||||
|
||||
@ -236,7 +242,7 @@ class NodeConfigurationImplTest {
|
||||
fun `jmxReporterType is not null and is set to New Relic`() {
|
||||
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
|
||||
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("NEW_RELIC"))
|
||||
val nodeConfig = rawConfig.parseAsNodeConfiguration()
|
||||
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow()
|
||||
assertTrue(JmxReporterType.NEW_RELIC.toString() == nodeConfig.jmxReporterType.toString())
|
||||
}
|
||||
|
||||
@ -244,15 +250,15 @@ class NodeConfigurationImplTest {
|
||||
fun `jmxReporterType is not null and set to Jokolia`() {
|
||||
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
|
||||
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA"))
|
||||
val nodeConfig = rawConfig.parseAsNodeConfiguration()
|
||||
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow()
|
||||
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
|
||||
}
|
||||
|
||||
private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfiguration {
|
||||
private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfigurationImpl {
|
||||
return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions)
|
||||
}
|
||||
|
||||
private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration {
|
||||
private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfigurationImpl {
|
||||
return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer?.let { X500Principal(it) }, crlCheckSoftFail = crlCheckSoftFail)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user