diff --git a/applications/workers/release/combined-worker/build.gradle b/applications/workers/release/combined-worker/build.gradle index 9a252412627..424ae9e9b62 100644 --- a/applications/workers/release/combined-worker/build.gradle +++ b/applications/workers/release/combined-worker/build.gradle @@ -68,6 +68,7 @@ dependencies { implementation project(':libs:crypto:crypto-config-impl') implementation project(':libs:crypto:crypto-core') implementation project(':libs:crypto:crypto-impl') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/combined-worker/src/main/kotlin/net/corda/applications/workers/combined/CombinedWorker.kt b/applications/workers/release/combined-worker/src/main/kotlin/net/corda/applications/workers/combined/CombinedWorker.kt index 907c5b11e55..49f238b482b 100644 --- a/applications/workers/release/combined-worker/src/main/kotlin/net/corda/applications/workers/combined/CombinedWorker.kt +++ b/applications/workers/release/combined-worker/src/main/kotlin/net/corda/applications/workers/combined/CombinedWorker.kt @@ -7,20 +7,20 @@ import net.corda.application.dbsetup.PostgresDbSetup import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.BusType import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.createConfigFromParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getBootstrapConfig import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.crypto.config.impl.createCryptoBootstrapParamsMap import net.corda.crypto.core.CryptoConsts.SOFT_HSM_ID import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.crypto.CryptoProcessor @@ -94,8 +94,8 @@ class CombinedWorker @Activate constructor( private val gatewayProcessor: GatewayProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -193,15 +193,15 @@ class CombinedWorker @Activate constructor( config.factory, ).run() - webServer.setupWebserver(params.defaultParams) - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) - + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("Combined Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) JavaSerialisationFilter.install() logger.info("CONFIG = $config") + webServer.start(params.defaultParams.workerServerPort) cryptoProcessor.start(config) dbProcessor.start(config) persistenceProcessor.start(config) diff --git a/applications/workers/release/crypto-worker/build.gradle b/applications/workers/release/crypto-worker/build.gradle index 162a4406951..550e154628d 100644 --- a/applications/workers/release/crypto-worker/build.gradle +++ b/applications/workers/release/crypto-worker/build.gradle @@ -15,6 +15,7 @@ dependencies { implementation project(':libs:configuration:configuration-validation') implementation project(':libs:crypto:cipher-suite-impl') implementation project(':libs:crypto:crypto-config-impl') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/crypto-worker/src/main/kotlin/net/corda/applications/workers/crypto/CryptoWorker.kt b/applications/workers/release/crypto-worker/src/main/kotlin/net/corda/applications/workers/crypto/CryptoWorker.kt index ef565a33b04..a06f10302e5 100644 --- a/applications/workers/release/crypto-worker/src/main/kotlin/net/corda/applications/workers/crypto/CryptoWorker.kt +++ b/applications/workers/release/crypto-worker/src/main/kotlin/net/corda/applications/workers/crypto/CryptoWorker.kt @@ -2,20 +2,20 @@ package net.corda.applications.workers.crypto import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getBootstrapConfig import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.crypto.config.impl.createCryptoBootstrapParamsMap import net.corda.libs.configuration.SmartConfig import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.crypto.CryptoProcessor @@ -39,8 +39,8 @@ class CryptoWorker @Activate constructor( private val processor: CryptoProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -65,17 +65,17 @@ class CryptoWorker @Activate constructor( JavaSerialisationFilter.install() val params = getParams(args, CryptoWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, CryptoWorker::class.java, shutDownService)) { return } if (params.hsmId.isBlank()) { throw IllegalStateException("Please specify which HSM the worker must handle, like --hsm-id SOFT") } - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("Crypto Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) - + webServer.start(params.defaultParams.workerServerPort) processor.start( buildBoostrapConfig(params, configurationValidatorFactory) ) diff --git a/applications/workers/release/db-worker/build.gradle b/applications/workers/release/db-worker/build.gradle index d1f8c099725..c20f73805a9 100644 --- a/applications/workers/release/db-worker/build.gradle +++ b/applications/workers/release/db-worker/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation project(":components:security-manager") implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:messaging:messaging') @@ -48,6 +49,7 @@ dependencies { testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" testImplementation project(':libs:application:addon') testImplementation project(':libs:application:banner') + testImplementation project(":libs:lifecycle:lifecycle") runtimeOnly("org.apache.felix:org.apache.felix.framework.security:$felixSecurityVersion") { exclude group: 'org.apache.felix' diff --git a/applications/workers/release/db-worker/src/main/kotlin/net/corda/applications/workers/db/DBWorker.kt b/applications/workers/release/db-worker/src/main/kotlin/net/corda/applications/workers/db/DBWorker.kt index 3e0fb4b39f4..2204c868f56 100644 --- a/applications/workers/release/db-worker/src/main/kotlin/net/corda/applications/workers/db/DBWorker.kt +++ b/applications/workers/release/db-worker/src/main/kotlin/net/corda/applications/workers/db/DBWorker.kt @@ -2,18 +2,18 @@ package net.corda.applications.workers.db import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getBootstrapConfig import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.db.DBProcessor @@ -42,8 +42,8 @@ class DBWorker @Activate constructor( private val schedulerProcessor: SchedulerProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -72,9 +72,9 @@ class DBWorker @Activate constructor( val params = getParams(args, DBWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, DBWorker::class.java, shutDownService)) return - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("DB Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) @@ -84,7 +84,7 @@ class DBWorker @Activate constructor( configurationValidatorFactory.createConfigValidator(), listOf(WorkerHelpers.createConfigFromParams(BOOT_DB, params.databaseParams)) ) - + webServer.start(params.defaultParams.workerServerPort) processor.start(config) tokenCacheProcessor.start(config) schedulerProcessor.start(config) diff --git a/applications/workers/release/db-worker/src/test/kotlin/net/corda/applications/workers/db/test/ConfigTests.kt b/applications/workers/release/db-worker/src/test/kotlin/net/corda/applications/workers/db/test/ConfigTests.kt index 12765e8cb53..7e1e9faca85 100644 --- a/applications/workers/release/db-worker/src/test/kotlin/net/corda/applications/workers/db/test/ConfigTests.kt +++ b/applications/workers/release/db-worker/src/test/kotlin/net/corda/applications/workers/db/test/ConfigTests.kt @@ -5,7 +5,6 @@ import net.corda.application.addon.CordaAddonResolver import net.corda.application.banner.StartupBanner import net.corda.applications.workers.db.DBWorker import net.corda.applications.workers.workercommon.ApplicationBanner -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.SmartConfig import net.corda.libs.configuration.SmartConfigImpl import net.corda.libs.configuration.secret.EncryptionSecretsServiceFactory @@ -13,6 +12,9 @@ import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidator import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.LifecycleCoordinatorName +import net.corda.lifecycle.registry.CoordinatorStatus +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Shutdown import net.corda.processors.db.DBProcessor import net.corda.schema.configuration.BootConfig.BOOT_DB @@ -54,7 +56,7 @@ class ConfigTests { mock(), mock(), DummyShutdown(), - DummyWorkerMonitor(), + DummyLifecycleRegistry(), DummyWebServer(), DummyValidatorFactory(), DummyPlatformInfoProvider(), @@ -99,7 +101,7 @@ class ConfigTests { mock(), mock(), DummyShutdown(), - DummyWorkerMonitor(), + DummyLifecycleRegistry(), DummyWebServer(), DummyValidatorFactory(), DummyPlatformInfoProvider(), @@ -108,7 +110,6 @@ class ConfigTests { ) val args = defaultArgs + arrayOf( - FLAG_DISABLE_MONITOR, FLAG_MONITOR_PORT, "9999" ) dbWorker.startup(args.toTypedArray()) @@ -134,7 +135,7 @@ class ConfigTests { mock(), mock(), DummyShutdown(), - DummyWorkerMonitor(), + DummyLifecycleRegistry(), DummyWebServer(), DummyValidatorFactory(), DummyPlatformInfoProvider(), @@ -167,7 +168,7 @@ class ConfigTests { mock(), mock(), DummyShutdown(), - DummyWorkerMonitor(), + DummyLifecycleRegistry(), DummyWebServer(), DummyValidatorFactory(), DummyPlatformInfoProvider(), @@ -194,7 +195,7 @@ class ConfigTests { mock(), mock(), DummyShutdown(), - DummyWorkerMonitor(), + DummyLifecycleRegistry(), DummyWebServer(), DummyValidatorFactory(), DummyPlatformInfoProvider(), @@ -228,9 +229,11 @@ class ConfigTests { override fun shutdown(bundle: Bundle) = Unit } - /** A no-op [WorkerMonitor]. */ - private class DummyWorkerMonitor : WorkerMonitor { - override fun registerEndpoints(workerType: String) = Unit + private class DummyLifecycleRegistry : LifecycleRegistry { + override fun componentStatus(): Map { + TODO("Not yet implemented") + } + } private class DummyWebServer : WebServer { diff --git a/applications/workers/release/flow-mapper-worker/build.gradle b/applications/workers/release/flow-mapper-worker/build.gradle index ed31588b9b1..fae985d34ec 100644 --- a/applications/workers/release/flow-mapper-worker/build.gradle +++ b/applications/workers/release/flow-mapper-worker/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation project(":components:security-manager") implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info:') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/flow-mapper-worker/src/main/kotlin/net.corda.applications.workers.flow.mapper/FlowMapperWorker.kt b/applications/workers/release/flow-mapper-worker/src/main/kotlin/net.corda.applications.workers.flow.mapper/FlowMapperWorker.kt index b24b9b355cc..d4222ea18ee 100644 --- a/applications/workers/release/flow-mapper-worker/src/main/kotlin/net.corda.applications.workers.flow.mapper/FlowMapperWorker.kt +++ b/applications/workers/release/flow-mapper-worker/src/main/kotlin/net.corda.applications.workers.flow.mapper/FlowMapperWorker.kt @@ -2,17 +2,17 @@ package net.corda.applications.workers.flow.mapper import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getBootstrapConfig import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.flow.mapper.FlowMapperProcessor @@ -33,8 +33,8 @@ class FlowMapperWorker @Activate constructor( private val flowMapperProcessor: FlowMapperProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = ConfigurationValidatorFactory::class) private val configurationValidatorFactory: ConfigurationValidatorFactory, @Reference(service = PlatformInfoProvider::class) @@ -65,12 +65,12 @@ class FlowMapperWorker @Activate constructor( JavaSerialisationFilter.install() val params = getParams(args, FlowMapperWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, FlowMapperWorker::class.java, shutDownService)) return - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("Flow Mapper Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) - + webServer.start(params.defaultParams.workerServerPort) val config = getBootstrapConfig( secretsServiceFactoryResolver, params.defaultParams, diff --git a/applications/workers/release/flow-worker/build.gradle b/applications/workers/release/flow-worker/build.gradle index 75d5ab4391d..e18aa05dc35 100644 --- a/applications/workers/release/flow-worker/build.gradle +++ b/applications/workers/release/flow-worker/build.gradle @@ -24,6 +24,7 @@ dependencies { implementation project(":components:security-manager") implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info:') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/flow-worker/src/main/kotlin/net/corda/applications/workers/flow/FlowWorker.kt b/applications/workers/release/flow-worker/src/main/kotlin/net/corda/applications/workers/flow/FlowWorker.kt index 9d845d366f5..0935fd4c955 100644 --- a/applications/workers/release/flow-worker/src/main/kotlin/net/corda/applications/workers/flow/FlowWorker.kt +++ b/applications/workers/release/flow-worker/src/main/kotlin/net/corda/applications/workers/flow/FlowWorker.kt @@ -2,17 +2,17 @@ package net.corda.applications.workers.flow import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getBootstrapConfig import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.flow.FlowProcessor @@ -33,8 +33,8 @@ class FlowWorker @Activate constructor( private val flowProcessor: FlowProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -66,12 +66,12 @@ class FlowWorker @Activate constructor( val params = getParams(args, FlowWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, FlowWorker::class.java, shutDownService)) return - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("Flow Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) - + webServer.start(params.defaultParams.workerServerPort) val config = getBootstrapConfig( secretsServiceFactoryResolver, params.defaultParams, diff --git a/applications/workers/release/member-worker/build.gradle b/applications/workers/release/member-worker/build.gradle index 2fc196d3e39..1aa6b082569 100644 --- a/applications/workers/release/member-worker/build.gradle +++ b/applications/workers/release/member-worker/build.gradle @@ -13,6 +13,7 @@ dependencies { implementation project(':applications:workers:worker-common') implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/member-worker/src/main/kotlin/net/corda/applications/workers/member/MemberWorker.kt b/applications/workers/release/member-worker/src/main/kotlin/net/corda/applications/workers/member/MemberWorker.kt index ce8b4c0cf2d..44aa3979931 100644 --- a/applications/workers/release/member-worker/src/main/kotlin/net/corda/applications/workers/member/MemberWorker.kt +++ b/applications/workers/release/member-worker/src/main/kotlin/net/corda/applications/workers/member/MemberWorker.kt @@ -2,16 +2,16 @@ package net.corda.applications.workers.member import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getBootstrapConfig import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.member.MemberProcessor @@ -32,8 +32,8 @@ class MemberWorker @Activate constructor( private val processor: MemberProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -58,12 +58,12 @@ class MemberWorker @Activate constructor( applicationBanner.show("Member Worker", platformInfoProvider) val params = getParams(args, MemberWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, MemberWorker::class.java, shutDownService)) return - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("Member Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) - + webServer.start(params.defaultParams.workerServerPort) val config = getBootstrapConfig( secretsServiceFactoryResolver, params.defaultParams, diff --git a/applications/workers/release/p2p-gateway-worker/build.gradle b/applications/workers/release/p2p-gateway-worker/build.gradle index 20d55127420..014d363e05f 100644 --- a/applications/workers/release/p2p-gateway-worker/build.gradle +++ b/applications/workers/release/p2p-gateway-worker/build.gradle @@ -14,6 +14,7 @@ dependencies { implementation project(':applications:workers:worker-common') implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/p2p-gateway-worker/src/main/kotlin/net.corda.applications.workers.p2p.gateway/GatewayWorker.kt b/applications/workers/release/p2p-gateway-worker/src/main/kotlin/net.corda.applications.workers.p2p.gateway/GatewayWorker.kt index fb03c018f9b..543cc89dd8b 100644 --- a/applications/workers/release/p2p-gateway-worker/src/main/kotlin/net.corda.applications.workers.p2p.gateway/GatewayWorker.kt +++ b/applications/workers/release/p2p-gateway-worker/src/main/kotlin/net.corda.applications.workers.p2p.gateway/GatewayWorker.kt @@ -2,13 +2,14 @@ package net.corda.applications.workers.p2p.gateway import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.p2p.gateway.GatewayProcessor @@ -28,8 +29,8 @@ class GatewayWorker @Activate constructor( private val shutDownService: Shutdown, @Reference(service = GatewayProcessor::class) private val gatewayProcessor: GatewayProcessor, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -53,9 +54,9 @@ class GatewayWorker @Activate constructor( applicationBanner.show("P2P Gateway Worker", platformInfoProvider) val params = WorkerHelpers.getParams(args, GatewayWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (WorkerHelpers.printHelpOrVersion(params.defaultParams, this::class.java, shutDownService)) return - WorkerHelpers.setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("P2P Gateway Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) @@ -64,7 +65,7 @@ class GatewayWorker @Activate constructor( params.defaultParams, configurationValidatorFactory.createConfigValidator() ) - + webServer.start(params.defaultParams.workerServerPort) gatewayProcessor.start(config) } diff --git a/applications/workers/release/p2p-link-manager-worker/build.gradle b/applications/workers/release/p2p-link-manager-worker/build.gradle index fff7e6db59e..50ec7922113 100644 --- a/applications/workers/release/p2p-link-manager-worker/build.gradle +++ b/applications/workers/release/p2p-link-manager-worker/build.gradle @@ -14,6 +14,7 @@ dependencies { implementation project(':applications:workers:worker-common') implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/p2p-link-manager-worker/src/main/kotlin/net.corda.applications.workers.p2p.linkmanager/LinkManagerWorker.kt b/applications/workers/release/p2p-link-manager-worker/src/main/kotlin/net.corda.applications.workers.p2p.linkmanager/LinkManagerWorker.kt index cf428ff2da1..eaddc640206 100644 --- a/applications/workers/release/p2p-link-manager-worker/src/main/kotlin/net.corda.applications.workers.p2p.linkmanager/LinkManagerWorker.kt +++ b/applications/workers/release/p2p-link-manager-worker/src/main/kotlin/net.corda.applications.workers.p2p.linkmanager/LinkManagerWorker.kt @@ -2,13 +2,14 @@ package net.corda.applications.workers.p2p.linkmanager import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.p2p.linkmanager.LinkManagerProcessor @@ -28,8 +29,8 @@ class LinkManagerWorker @Activate constructor( private val shutDownService: Shutdown, @Reference(service = LinkManagerProcessor::class) private val linkManagerProcessor: LinkManagerProcessor, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -53,9 +54,9 @@ class LinkManagerWorker @Activate constructor( applicationBanner.show("P2P Link Manager Worker", platformInfoProvider) val params = WorkerHelpers.getParams(args, LinkManagerWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (WorkerHelpers.printHelpOrVersion(params.defaultParams, this::class.java, shutDownService)) return - WorkerHelpers.setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("P2P Link Manager Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) @@ -64,7 +65,7 @@ class LinkManagerWorker @Activate constructor( params.defaultParams, configurationValidatorFactory.createConfigValidator() ) - + webServer.start(params.defaultParams.workerServerPort) linkManagerProcessor.start(config) } diff --git a/applications/workers/release/persistence-worker/build.gradle b/applications/workers/release/persistence-worker/build.gradle index d5db062af08..bb468b352b9 100644 --- a/applications/workers/release/persistence-worker/build.gradle +++ b/applications/workers/release/persistence-worker/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation project(":components:security-manager") implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/persistence-worker/src/main/kotlin/net/corda/applications/workers/db/PersistenceWorker.kt b/applications/workers/release/persistence-worker/src/main/kotlin/net/corda/applications/workers/db/PersistenceWorker.kt index 6b2f4745b76..049919c966a 100644 --- a/applications/workers/release/persistence-worker/src/main/kotlin/net/corda/applications/workers/db/PersistenceWorker.kt +++ b/applications/workers/release/persistence-worker/src/main/kotlin/net/corda/applications/workers/db/PersistenceWorker.kt @@ -2,17 +2,17 @@ package net.corda.applications.workers.db import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.persistence.PersistenceProcessor @@ -35,8 +35,8 @@ class PersistenceWorker @Activate constructor( private val persistenceProcessor: PersistenceProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = PlatformInfoProvider::class) val platformInfoProvider: PlatformInfoProvider, @Reference(service = ApplicationBanner::class) @@ -63,9 +63,9 @@ class PersistenceWorker @Activate constructor( JavaSerialisationFilter.install() val params = getParams(args, PersistenceWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, PersistenceWorker::class.java, shutDownService)) return - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("Persistence Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) @@ -75,7 +75,7 @@ class PersistenceWorker @Activate constructor( configurationValidatorFactory.createConfigValidator(), listOf(WorkerHelpers.createConfigFromParams(BootConfig.BOOT_DB, params.databaseParams)) ) - + webServer.start(params.defaultParams.workerServerPort) persistenceProcessor.start(config) } diff --git a/applications/workers/release/rest-worker/build.gradle b/applications/workers/release/rest-worker/build.gradle index e1194015487..f2c34add954 100644 --- a/applications/workers/release/rest-worker/build.gradle +++ b/applications/workers/release/rest-worker/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation platform("net.corda:corda-api:$cordaApiVersion") implementation project(':applications:workers:worker-common') implementation project(':libs:configuration:configuration-core') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:configuration:configuration-validation') diff --git a/applications/workers/release/rest-worker/src/main/kotlin/net/corda/applications/workers/rest/RestWorker.kt b/applications/workers/release/rest-worker/src/main/kotlin/net/corda/applications/workers/rest/RestWorker.kt index 37ccaa2960c..0e6c058ddd4 100644 --- a/applications/workers/release/rest-worker/src/main/kotlin/net/corda/applications/workers/rest/RestWorker.kt +++ b/applications/workers/release/rest-worker/src/main/kotlin/net/corda/applications/workers/rest/RestWorker.kt @@ -2,18 +2,18 @@ package net.corda.applications.workers.rest import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getBootstrapConfig import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.rest.RestProcessor @@ -39,8 +39,8 @@ class RestWorker @Activate constructor( private val processor: RestProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -68,9 +68,9 @@ class RestWorker @Activate constructor( val params = getParams(args, RestWorkerParams()) params.validate() - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, RestWorker::class.java, shutDownService)) return - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("REST Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) @@ -80,7 +80,7 @@ class RestWorker @Activate constructor( configurationValidatorFactory.createConfigValidator(), listOf(WorkerHelpers.createConfigFromParams(BOOT_REST, params.restParams)) ) - + webServer.start(params.defaultParams.workerServerPort) processor.start(config) } diff --git a/applications/workers/release/uniqueness-worker/build.gradle b/applications/workers/release/uniqueness-worker/build.gradle index 32ec0f8fc64..1ea5e705a46 100644 --- a/applications/workers/release/uniqueness-worker/build.gradle +++ b/applications/workers/release/uniqueness-worker/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation project(":components:security-manager") implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/uniqueness-worker/src/main/kotlin/net/corda/applications/workers/uniqueness/UniquenessWorker.kt b/applications/workers/release/uniqueness-worker/src/main/kotlin/net/corda/applications/workers/uniqueness/UniquenessWorker.kt index e0502f6c34a..0a9e786e17b 100644 --- a/applications/workers/release/uniqueness-worker/src/main/kotlin/net/corda/applications/workers/uniqueness/UniquenessWorker.kt +++ b/applications/workers/release/uniqueness-worker/src/main/kotlin/net/corda/applications/workers/uniqueness/UniquenessWorker.kt @@ -2,17 +2,17 @@ package net.corda.applications.workers.uniqueness import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.uniqueness.UniquenessProcessor @@ -35,8 +35,8 @@ class UniquenessWorker @Activate constructor( private val uniquenessProcessor: UniquenessProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = PlatformInfoProvider::class) @@ -63,9 +63,9 @@ class UniquenessWorker @Activate constructor( JavaSerialisationFilter.install() val params = getParams(args, UniquenessWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, UniquenessWorker::class.java, shutDownService)) return - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("Uniqueness Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) @@ -75,7 +75,7 @@ class UniquenessWorker @Activate constructor( configurationValidatorFactory.createConfigValidator(), listOf(WorkerHelpers.createConfigFromParams(BootConfig.BOOT_DB, params.databaseParams)) ) - + webServer.start(params.defaultParams.workerServerPort) uniquenessProcessor.start(config) } diff --git a/applications/workers/release/verification-worker/build.gradle b/applications/workers/release/verification-worker/build.gradle index 2c08c9b1d46..3338de315e6 100644 --- a/applications/workers/release/verification-worker/build.gradle +++ b/applications/workers/release/verification-worker/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation project(':libs:ledger:ledger-common-data') implementation project(':libs:configuration:configuration-core') implementation project(':libs:configuration:configuration-validation') + implementation project(":libs:lifecycle:registry") implementation project(':libs:platform-info:') implementation project(':libs:tracing') implementation project(':libs:web:web') diff --git a/applications/workers/release/verification-worker/src/main/kotlin/net/corda/applications/workers/verification/VerificationWorker.kt b/applications/workers/release/verification-worker/src/main/kotlin/net/corda/applications/workers/verification/VerificationWorker.kt index 98361ce681a..5e1b5a0d17e 100644 --- a/applications/workers/release/verification-worker/src/main/kotlin/net/corda/applications/workers/verification/VerificationWorker.kt +++ b/applications/workers/release/verification-worker/src/main/kotlin/net/corda/applications/workers/verification/VerificationWorker.kt @@ -2,17 +2,17 @@ package net.corda.applications.workers.verification import net.corda.applications.workers.workercommon.ApplicationBanner import net.corda.applications.workers.workercommon.DefaultWorkerParams +import net.corda.applications.workers.workercommon.Health import net.corda.applications.workers.workercommon.JavaSerialisationFilter +import net.corda.applications.workers.workercommon.Metrics import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getBootstrapConfig import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.getParams import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.loggerStartupInfo import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.printHelpOrVersion -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupMonitor -import net.corda.applications.workers.workercommon.WorkerHelpers.Companion.setupWebserver -import net.corda.applications.workers.workercommon.WorkerMonitor import net.corda.libs.configuration.secret.SecretsServiceFactoryResolver import net.corda.libs.configuration.validation.ConfigurationValidatorFactory import net.corda.libs.platform.PlatformInfoProvider +import net.corda.lifecycle.registry.LifecycleRegistry import net.corda.osgi.api.Application import net.corda.osgi.api.Shutdown import net.corda.processors.verification.VerificationProcessor @@ -33,8 +33,8 @@ class VerificationWorker @Activate constructor( private val verificationProcessor: VerificationProcessor, @Reference(service = Shutdown::class) private val shutDownService: Shutdown, - @Reference(service = WorkerMonitor::class) - private val workerMonitor: WorkerMonitor, + @Reference(service = LifecycleRegistry::class) + private val lifecycleRegistry: LifecycleRegistry, @Reference(service = WebServer::class) private val webServer: WebServer, @Reference(service = ConfigurationValidatorFactory::class) @@ -65,9 +65,9 @@ class VerificationWorker @Activate constructor( JavaSerialisationFilter.install() val params = getParams(args, VerificationWorkerParams()) - webServer.setupWebserver(params.defaultParams) if (printHelpOrVersion(params.defaultParams, VerificationWorker::class.java, shutDownService)) return - setupMonitor(workerMonitor, params.defaultParams, this.javaClass.simpleName) + Metrics.configure(webServer, this.javaClass.simpleName) + Health.configure(webServer, lifecycleRegistry) configureTracing("Verification Worker", params.defaultParams.zipkinTraceUrl, params.defaultParams.traceSamplesPerSecond) @@ -75,7 +75,7 @@ class VerificationWorker @Activate constructor( secretsServiceFactoryResolver, params.defaultParams, configurationValidatorFactory.createConfigValidator()) - + webServer.start(params.defaultParams.workerServerPort) verificationProcessor.start(config) } diff --git a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/internal/Constants.kt b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Constants.kt similarity index 72% rename from applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/internal/Constants.kt rename to applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Constants.kt index 618d7a51db1..5ae3ba55e77 100644 --- a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/internal/Constants.kt +++ b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Constants.kt @@ -1,9 +1,9 @@ -package net.corda.applications.workers.workercommon.internal +package net.corda.applications.workers.workercommon internal const val HTTP_OK_CODE = 200 internal const val HTTP_SERVICE_UNAVAILABLE_CODE = 503 internal const val HTTP_HEALTH_ROUTE = "/isHealthy" internal const val HTTP_METRICS_ROUTE = "/metrics" internal const val HTTP_STATUS_ROUTE = "/status" -internal const val WORKER_MONITOR_PORT = 7000 +internal const val WORKER_SERVER_PORT = 7000 internal const val NO_CACHE = "no-cache" diff --git a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/DefaultWorkerParams.kt b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/DefaultWorkerParams.kt index d4e2b0ea7a2..5af1645d81a 100644 --- a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/DefaultWorkerParams.kt +++ b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/DefaultWorkerParams.kt @@ -1,12 +1,11 @@ package net.corda.applications.workers.workercommon -import net.corda.applications.workers.workercommon.internal.WORKER_MONITOR_PORT import net.corda.schema.configuration.BootConfig import picocli.CommandLine.Option import java.nio.file.Path /** The startup parameters handled by all workers. */ -class DefaultWorkerParams(healthPortOverride: Int = WORKER_MONITOR_PORT) { +class DefaultWorkerParams(healthPortOverride: Int = WORKER_SERVER_PORT) { @Option(names = ["-h", "--help"], usageHelp = true, description = ["Display help and exit."]) var helpRequested = false @@ -34,14 +33,11 @@ class DefaultWorkerParams(healthPortOverride: Int = WORKER_MONITOR_PORT) { ) var maxAllowedMessageSize: Int? = null - @Option(names = ["-n", "--disable-worker-monitor"], description = ["Disables the worker monitor."]) - var disableWorkerMonitor = false - @Option( - names = ["-p", "--worker-monitor-port"], - description = ["The port the worker monitor should listen on. Defaults to $WORKER_MONITOR_PORT."] + names = ["-p", "--worker-server-port"], + description = ["The port the worker http server should listen on. Defaults to $WORKER_SERVER_PORT."] ) - var workerMonitorPort = healthPortOverride + var workerServerPort = healthPortOverride @Option(names = ["-m", "--messaging-params"], description = ["Messaging parameters for the worker."]) var messaging = emptyMap() diff --git a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Health.kt b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Health.kt new file mode 100644 index 00000000000..c894e667838 --- /dev/null +++ b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Health.kt @@ -0,0 +1,69 @@ +package net.corda.applications.workers.workercommon + +import com.fasterxml.jackson.databind.ObjectMapper +import io.javalin.core.util.Header +import net.corda.lifecycle.LifecycleStatus +import net.corda.lifecycle.registry.LifecycleRegistry +import net.corda.rest.ResponseCode +import net.corda.web.api.Endpoint +import net.corda.web.api.HTTPMethod +import net.corda.web.api.WebHandler +import net.corda.web.api.WebServer +import org.slf4j.LoggerFactory +import java.util.concurrent.ConcurrentHashMap + +object Health { + private val logger = LoggerFactory.getLogger(Health::class.java) + private val objectMapper = ObjectMapper() + private val lastLogMessage = ConcurrentHashMap(mapOf(HTTP_HEALTH_ROUTE to "", HTTP_STATUS_ROUTE to "")) + + fun configure(webServer: WebServer, lifecycleRegistry: LifecycleRegistry) { + val healthRouteHandler = WebHandler { context -> + val unhealthyComponents = lifecycleRegistry.componentWithStatus(setOf(LifecycleStatus.ERROR)) + val status = if (unhealthyComponents.isEmpty()) { + clearLastLogMessageForRoute(HTTP_HEALTH_ROUTE) + ResponseCode.OK + } else { + logIfDifferentFromLastMessage( + HTTP_HEALTH_ROUTE, + "Status is unhealthy. The status of $unhealthyComponents has error." + ) + ResponseCode.SERVICE_UNAVAILABLE + } + context.status(status) + context.header(Header.CACHE_CONTROL, NO_CACHE) + context + } + webServer.registerEndpoint(Endpoint(HTTPMethod.GET, HTTP_HEALTH_ROUTE, healthRouteHandler)) + + val statusRouteHandler = WebHandler { context -> + val notReadyComponents = lifecycleRegistry.componentWithStatus(setOf(LifecycleStatus.DOWN, LifecycleStatus.ERROR)) + val status = if (notReadyComponents.isEmpty()) { + clearLastLogMessageForRoute(HTTP_STATUS_ROUTE) + ResponseCode.OK + } else { + logIfDifferentFromLastMessage( + HTTP_STATUS_ROUTE, + "There are components with error or down state: $notReadyComponents." + ) + ResponseCode.SERVICE_UNAVAILABLE + } + context.status(status) + context.result(objectMapper.writeValueAsString(lifecycleRegistry.componentStatus())) + context.header(Header.CACHE_CONTROL, NO_CACHE) + context + } + webServer.registerEndpoint(Endpoint(HTTPMethod.GET, HTTP_STATUS_ROUTE, statusRouteHandler)) + } + + private fun clearLastLogMessageForRoute(route: String) { + lastLogMessage[route] = "" + } + + private fun logIfDifferentFromLastMessage(route: String, logMessage: String) { + val lastLogMessage = lastLogMessage.put(route, logMessage) + if (logMessage != lastLogMessage) { + logger.warn(logMessage) + } + } +} \ No newline at end of file diff --git a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Metrics.kt b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Metrics.kt new file mode 100644 index 00000000000..fc5dd334d01 --- /dev/null +++ b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/Metrics.kt @@ -0,0 +1,72 @@ +package net.corda.applications.workers.workercommon + +import io.javalin.core.util.Header +import io.micrometer.cloudwatch2.CloudWatchConfig +import io.micrometer.cloudwatch2.CloudWatchMeterRegistry +import io.micrometer.core.instrument.Clock +import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics +import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics +import io.micrometer.core.instrument.binder.jvm.JvmHeapPressureMetrics +import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics +import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics +import io.micrometer.core.instrument.binder.system.FileDescriptorMetrics +import io.micrometer.core.instrument.binder.system.ProcessorMetrics +import io.micrometer.core.instrument.binder.system.UptimeMetrics +import io.micrometer.prometheus.PrometheusConfig +import io.micrometer.prometheus.PrometheusMeterRegistry +import net.corda.metrics.CordaMetrics +import net.corda.web.api.Endpoint +import net.corda.web.api.HTTPMethod +import net.corda.web.api.WebHandler +import net.corda.web.api.WebServer +import org.slf4j.LoggerFactory +import software.amazon.awssdk.auth.credentials.WebIdentityTokenFileCredentialsProvider +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient + +object Metrics { + private const val CORDA_NAMESPACE = "CORDA" + private const val K8S_NAMESPACE_KEY = "K8S_NAMESPACE" + private const val CLOUDWATCH_ENABLED_KEY = "ENABLE_CLOUDWATCH" + private val logger = LoggerFactory.getLogger(Metrics::class.java) + private val prometheusRegistry: PrometheusMeterRegistry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT) + private val cloudwatchConfig = object : CloudWatchConfig { + + override fun get(key: String): String? { + return null + } + + override fun namespace(): String { + val suffix = System.getenv(K8S_NAMESPACE_KEY)?.let { + "/$it" + } ?: "" + return "$CORDA_NAMESPACE$suffix" + } + } + fun configure(webServer: WebServer, name: String) { + logger.info("Creating Prometheus metric registry") + CordaMetrics.configure(name, prometheusRegistry) + if (System.getenv(CLOUDWATCH_ENABLED_KEY) == "true") { + logger.info("Enabling the cloudwatch metrics registry") + val cloudwatchClient = CloudWatchAsyncClient.builder() + .credentialsProvider(WebIdentityTokenFileCredentialsProvider.create()) + .build() + CordaMetrics.configure(name, CloudWatchMeterRegistry(cloudwatchConfig, Clock.SYSTEM, cloudwatchClient)) + } + + ClassLoaderMetrics().bindTo(CordaMetrics.registry) + JvmMemoryMetrics().bindTo(CordaMetrics.registry) + JvmGcMetrics().bindTo(CordaMetrics.registry) + JvmHeapPressureMetrics().bindTo(CordaMetrics.registry) + ProcessorMetrics().bindTo(CordaMetrics.registry) + JvmThreadMetrics().bindTo(CordaMetrics.registry) + UptimeMetrics().bindTo(CordaMetrics.registry) + FileDescriptorMetrics().bindTo(CordaMetrics.registry) + + val metricsRouteHandler = WebHandler { context -> + context.result(prometheusRegistry.scrape()) + context.header(Header.CACHE_CONTROL, NO_CACHE) + context + } + webServer.registerEndpoint(Endpoint(HTTPMethod.GET, HTTP_METRICS_ROUTE, metricsRouteHandler)) + } +} \ No newline at end of file diff --git a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/WorkerHelpers.kt b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/WorkerHelpers.kt index 0d314e3d47f..1dae746c254 100644 --- a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/WorkerHelpers.kt +++ b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/WorkerHelpers.kt @@ -20,7 +20,6 @@ import org.slf4j.Logger import picocli.CommandLine import java.io.InputStream import java.lang.management.ManagementFactory -import net.corda.web.api.WebServer import kotlin.math.absoluteValue import kotlin.random.Random @@ -233,21 +232,6 @@ class WorkerHelpers { return url.openStream() } - /** Sets up the [workerMonitor] based on the [params]. */ - fun setupMonitor(workerMonitor: WorkerMonitor, params: DefaultWorkerParams, workerType: String) { - if (!params.disableWorkerMonitor) { - workerMonitor.registerEndpoints(workerType) - } - } - - fun WebServer.setupWebserver(params: DefaultWorkerParams) { - this.start(params.workerMonitorPort) - } - - fun startBanner() { - - } - /** * Prints help if `params.helpRequested` is true. Else prints version if `params.versionRequested` is true. * diff --git a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/WorkerMonitor.kt b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/WorkerMonitor.kt deleted file mode 100644 index 288e81d1b6a..00000000000 --- a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/WorkerMonitor.kt +++ /dev/null @@ -1,17 +0,0 @@ -package net.corda.applications.workers.workercommon - -/** - * Exposes an HTTP endpoint to report health, status and metrics for the worker. - * - * A worker indicates its healthiness/readiness by returning a 200 code for HTTP requests to - * `HTTP_HEALTH_ROUTE`/`HTTP_READINESS_ROUTE`. - * Worker metrics are reported on `HTTP_METRICS_ROUTE`. - * - * A worker is considered healthy if no component has a `LifecycleStatus` of `LifecycleStatus.ERROR`. A worker is - * considered ready if no component has a `LifecycleStatus` of either `LifecycleStatus.DOWN` or `LifecycleStatus.ERROR`. - */ -interface WorkerMonitor { - /** registers health and readiness endpoints against the workers webserver. */ - fun registerEndpoints(workerType: String) - -} \ No newline at end of file diff --git a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/internal/WorkerMonitorImpl.kt b/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/internal/WorkerMonitorImpl.kt deleted file mode 100644 index e87eb754717..00000000000 --- a/applications/workers/worker-common/src/main/kotlin/net/corda/applications/workers/workercommon/internal/WorkerMonitorImpl.kt +++ /dev/null @@ -1,162 +0,0 @@ -package net.corda.applications.workers.workercommon.internal - -import com.fasterxml.jackson.databind.ObjectMapper -import io.javalin.core.util.Header -import io.micrometer.cloudwatch2.CloudWatchConfig -import io.micrometer.cloudwatch2.CloudWatchMeterRegistry -import io.micrometer.core.instrument.Clock -import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics -import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics -import io.micrometer.core.instrument.binder.jvm.JvmHeapPressureMetrics -import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics -import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics -import io.micrometer.core.instrument.binder.system.FileDescriptorMetrics -import io.micrometer.core.instrument.binder.system.ProcessorMetrics -import io.micrometer.core.instrument.binder.system.UptimeMetrics -import io.micrometer.prometheus.PrometheusConfig -import io.micrometer.prometheus.PrometheusMeterRegistry -import net.corda.applications.workers.workercommon.WorkerMonitor -import net.corda.lifecycle.LifecycleStatus -import net.corda.lifecycle.registry.LifecycleRegistry -import net.corda.metrics.CordaMetrics -import org.osgi.service.component.annotations.Activate -import org.osgi.service.component.annotations.Component -import org.osgi.service.component.annotations.Reference -import org.slf4j.LoggerFactory -import software.amazon.awssdk.auth.credentials.WebIdentityTokenFileCredentialsProvider -import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient -import java.util.concurrent.ConcurrentHashMap -import net.corda.rest.ResponseCode -import net.corda.web.api.Endpoint -import net.corda.web.api.HTTPMethod -import net.corda.web.api.WebHandler -import net.corda.web.api.WebServer - -/** - * An implementation of [WorkerMonitor]. - * - * @property webServer The server that serves worker health and readiness. - */ -@Component(service = [WorkerMonitor::class]) -@Suppress("Unused") -internal class WorkerMonitorImpl @Activate constructor( - @Reference(service = LifecycleRegistry::class) - private val lifecycleRegistry: LifecycleRegistry, - @Reference(service = WebServer::class) - private val webServer: WebServer -) : WorkerMonitor { - - private companion object { - private val logger = LoggerFactory.getLogger(this::class.java.enclosingClass) - private const val CORDA_NAMESPACE = "CORDA" - private const val K8S_NAMESPACE_KEY = "K8S_NAMESPACE" - private const val CLOUDWATCH_ENABLED_KEY = "ENABLE_CLOUDWATCH" - } - - private val objectMapper = ObjectMapper() - private val prometheusRegistry: PrometheusMeterRegistry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT) - private val cloudwatchConfig = object : CloudWatchConfig { - - override fun get(key: String): String? { - return null - } - - override fun namespace(): String { - val suffix = System.getenv(K8S_NAMESPACE_KEY)?.let { - "/$it" - } ?: "" - return "$CORDA_NAMESPACE$suffix" - } - } - private val lastLogMessage = ConcurrentHashMap(mapOf(HTTP_HEALTH_ROUTE to "", HTTP_STATUS_ROUTE to "")) - - private fun setupMetrics(name: String) { - logger.info("Creating Prometheus metric registry") - CordaMetrics.configure(name, prometheusRegistry) - if (System.getenv(CLOUDWATCH_ENABLED_KEY) == "true") { - logger.info("Enabling the cloudwatch metrics registry") - val cloudwatchClient = CloudWatchAsyncClient.builder() - .credentialsProvider(WebIdentityTokenFileCredentialsProvider.create()) - .build() - CordaMetrics.configure(name, CloudWatchMeterRegistry(cloudwatchConfig, Clock.SYSTEM, cloudwatchClient)) - } - - ClassLoaderMetrics().bindTo(CordaMetrics.registry) - JvmMemoryMetrics().bindTo(CordaMetrics.registry) - JvmGcMetrics().bindTo(CordaMetrics.registry) - JvmHeapPressureMetrics().bindTo(CordaMetrics.registry) - ProcessorMetrics().bindTo(CordaMetrics.registry) - JvmThreadMetrics().bindTo(CordaMetrics.registry) - UptimeMetrics().bindTo(CordaMetrics.registry) - FileDescriptorMetrics().bindTo(CordaMetrics.registry) - } - - - override fun registerEndpoints(workerType: String) { - setupMetrics(workerType) - - val healthRouteHandler = WebHandler { context -> - val unhealthyComponents = componentWithStatus(setOf(LifecycleStatus.ERROR)) - val status = if (unhealthyComponents.isEmpty()) { - clearLastLogMessageForRoute(HTTP_HEALTH_ROUTE) - ResponseCode.OK - } else { - logIfDifferentFromLastMessage( - HTTP_HEALTH_ROUTE, - "Status is unhealthy. The status of $unhealthyComponents has error." - ) - ResponseCode.SERVICE_UNAVAILABLE - } - context.status(status) - context.header(Header.CACHE_CONTROL, NO_CACHE) - context - } - - val statusRouteHandler = WebHandler { context -> - val notReadyComponents = componentWithStatus(setOf(LifecycleStatus.DOWN, LifecycleStatus.ERROR)) - val status = if (notReadyComponents.isEmpty()) { - clearLastLogMessageForRoute(HTTP_STATUS_ROUTE) - ResponseCode.OK - } else { - logIfDifferentFromLastMessage( - HTTP_STATUS_ROUTE, - "There are components with error or down state: $notReadyComponents." - ) - ResponseCode.SERVICE_UNAVAILABLE - } - context.status(status) - context.result(objectMapper.writeValueAsString(lifecycleRegistry.componentStatus())) - context.header(Header.CACHE_CONTROL, NO_CACHE) - context - } - - val metricsRouteHandler = WebHandler { context -> - context.result(prometheusRegistry.scrape()) - context.header(Header.CACHE_CONTROL, NO_CACHE) - context - } - - webServer.registerEndpoint(Endpoint(HTTPMethod.GET, HTTP_HEALTH_ROUTE, healthRouteHandler)) - webServer.registerEndpoint(Endpoint(HTTPMethod.GET, HTTP_STATUS_ROUTE, statusRouteHandler)) - webServer.registerEndpoint(Endpoint(HTTPMethod.GET, HTTP_METRICS_ROUTE, metricsRouteHandler)) - } - - private fun clearLastLogMessageForRoute(route: String) { - lastLogMessage[route] = "" - } - - private fun logIfDifferentFromLastMessage(route: String, logMessage: String) { - val lastLogMessage = lastLogMessage.put(route, logMessage) - if (logMessage != lastLogMessage) { - logger.warn(logMessage) - } - } - - /** Indicates whether any components exist with at least one of the given [statuses]. */ - private fun componentWithStatus(statuses: Collection) = - lifecycleRegistry.componentStatus().values.filter { coordinatorStatus -> - statuses.contains(coordinatorStatus.status) - }.map { - it.name - } -} \ No newline at end of file diff --git a/applications/workers/worker-common/src/test/kotlin/net/corda/applications/workers/workercommon/internal/HealthAndStatusTests.kt b/applications/workers/worker-common/src/test/kotlin/net/corda/applications/workers/workercommon/internal/HealthAndStatusTests.kt new file mode 100644 index 00000000000..c7b785ad28a --- /dev/null +++ b/applications/workers/worker-common/src/test/kotlin/net/corda/applications/workers/workercommon/internal/HealthAndStatusTests.kt @@ -0,0 +1,117 @@ +package net.corda.applications.workers.workercommon.internal + +import net.corda.applications.workers.workercommon.HTTP_HEALTH_ROUTE +import net.corda.applications.workers.workercommon.HTTP_STATUS_ROUTE +import net.corda.applications.workers.workercommon.Health +import net.corda.lifecycle.LifecycleCoordinatorName +import net.corda.lifecycle.LifecycleStatus +import net.corda.lifecycle.registry.CoordinatorStatus +import net.corda.lifecycle.registry.LifecycleRegistry +import net.corda.rest.ResponseCode +import net.corda.web.api.Endpoint +import net.corda.web.api.HTTPMethod +import net.corda.web.api.WebContext +import net.corda.web.api.WebServer +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mockito.kotlin.argThat +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class HealthAndStatusTests { + private val lifecycleRegistry = mock() + private val webServer = mock() + private val endpointCaptor = argumentCaptor() + + @Test + fun `registers status endpoint`() { + doNothing().whenever(webServer).registerEndpoint(endpointCaptor.capture()) + Health.configure(webServer, lifecycleRegistry) + + assertThat(endpointCaptor.allValues.any { + it.path == HTTP_STATUS_ROUTE && it.methodType == HTTPMethod.GET + }) + } + + @Test + fun `registers health endpoint`() { + doNothing().whenever(webServer).registerEndpoint(endpointCaptor.capture()) + Health.configure(webServer, lifecycleRegistry) + + assertThat(endpointCaptor.allValues.any { + it.path == HTTP_HEALTH_ROUTE && it.methodType == HTTPMethod.GET + }) + } + + @Test + fun `status returns OK when no DOWN or ERROR componets`() { + whenever(lifecycleRegistry.componentWithStatus(listOf(LifecycleStatus.DOWN, LifecycleStatus.ERROR))).doReturn(emptyList()) + doNothing().whenever(webServer).registerEndpoint(endpointCaptor.capture()) + Health.configure(webServer, lifecycleRegistry) + + val handler = endpointCaptor.allValues.single { it.path == HTTP_STATUS_ROUTE }.webHandler + val context = mock() + handler.handle(context) + verify(lifecycleRegistry).componentWithStatus(argThat {statuses: Collection -> + statuses.size == 2 && statuses.containsAll(listOf(LifecycleStatus.DOWN, LifecycleStatus.ERROR)) + }) + verify(context).status(ResponseCode.OK) + } + + @Test + fun `health returns OK when no ERROR componets`() { + whenever(lifecycleRegistry.componentWithStatus(listOf(LifecycleStatus.ERROR))).doReturn(emptyList()) + doNothing().whenever(webServer).registerEndpoint(endpointCaptor.capture()) + Health.configure(webServer, lifecycleRegistry) + + val handler = endpointCaptor.allValues.single { it.path == HTTP_HEALTH_ROUTE }.webHandler + val context = mock() + handler.handle(context) + verify(lifecycleRegistry).componentWithStatus(argThat {statuses: Collection -> + statuses.single() == LifecycleStatus.ERROR + }) + verify(context).status(ResponseCode.OK) + } + + @Test + fun `OK returns NOT OK when ERROR or DOWN components`() { + val registry = object : LifecycleRegistry { + override fun componentStatus(): Map = emptyMap() + override fun componentWithStatus(statuses: Collection): List { + return listOf(LifecycleCoordinatorName("superman")) + } + + } + doNothing().whenever(webServer).registerEndpoint(endpointCaptor.capture()) + Health.configure(webServer, registry) + + val handler = endpointCaptor.allValues.single { it.path == HTTP_STATUS_ROUTE }.webHandler + val context = mock() + handler.handle(context) + + verify(context).status(ResponseCode.SERVICE_UNAVAILABLE) + } + + @Test + fun `health returns NOT OK when ERROR components`() { + val registry = object : LifecycleRegistry { + override fun componentStatus(): Map = emptyMap() + override fun componentWithStatus(statuses: Collection): List { + return listOf(LifecycleCoordinatorName("superman")) + } + + } + doNothing().whenever(webServer).registerEndpoint(endpointCaptor.capture()) + Health.configure(webServer, registry) + + val handler = endpointCaptor.allValues.single { it.path == HTTP_HEALTH_ROUTE }.webHandler + val context = mock() + handler.handle(context) + + verify(context).status(ResponseCode.SERVICE_UNAVAILABLE) + } +} diff --git a/applications/workers/worker-common/src/test/kotlin/net/corda/applications/workers/workercommon/internal/WorkerMonitorImplTests.kt b/applications/workers/worker-common/src/test/kotlin/net/corda/applications/workers/workercommon/internal/WorkerMonitorImplTests.kt deleted file mode 100644 index ed907d315af..00000000000 --- a/applications/workers/worker-common/src/test/kotlin/net/corda/applications/workers/workercommon/internal/WorkerMonitorImplTests.kt +++ /dev/null @@ -1,143 +0,0 @@ -package net.corda.applications.workers.workercommon.internal - -import io.javalin.Javalin -import net.corda.applications.workers.workercommon.WorkerMonitor -import net.corda.libs.platform.PlatformInfoProvider -import net.corda.lifecycle.LifecycleCoordinator -import net.corda.lifecycle.LifecycleCoordinatorFactory -import net.corda.lifecycle.LifecycleCoordinatorName -import net.corda.lifecycle.LifecycleStatus -import net.corda.lifecycle.registry.CoordinatorStatus -import net.corda.lifecycle.registry.LifecycleRegistry -import net.corda.web.server.JavalinServer -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.mockito.kotlin.any -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock -import java.net.HttpURLConnection -import java.net.ServerSocket -import java.net.URL - -/** Tests of [WorkerMonitorImpl]. */ -class WorkerMonitorImplTests { - - private val lifecycleCoordinator = mock() - private val lifecycleCoordinatorFactory = mock { - on { createCoordinator(any(), any()) }.doReturn(lifecycleCoordinator) - } - private val infoProviderMock = mock { - on { localWorkerSoftwareShortVersion } doReturn ("1.2") - } - private val webServer = JavalinServer(lifecycleCoordinatorFactory, { Javalin.create() }, infoProviderMock ) - private val port = ServerSocket(0).use { - it.localPort - } - - @BeforeEach - fun setupServer() { - webServer.start(port) - } - - @AfterEach - fun teardownServer() { - webServer.stop() - } - @Test - fun `worker is considered healthy and ready if there are no components in the lifecycle registry`() { - startHealthMonitor(emptyMap()) - val (healthyCode, readyCode) = getHealthAndReadinessCodes(port) - - assertEquals(HTTP_OK_CODE, healthyCode) - assertEquals(HTTP_OK_CODE, readyCode) - } - - @Test - fun `worker is considered healthy if all components in the lifecycle registry are up or down`() { - val componentStatuses = mapOf( - createComponentStatus(LifecycleStatus.UP), - createComponentStatus(LifecycleStatus.DOWN) - ) - startHealthMonitor(componentStatuses) - val (healthyCode, _) = getHealthAndReadinessCodes(port) - - assertEquals(HTTP_OK_CODE, healthyCode) - } - - @Test - fun `worker is considered unhealthy if any components in the lifecycle registry are errored`() { - val componentStatuses = mapOf( - createComponentStatus(LifecycleStatus.UP), - createComponentStatus(LifecycleStatus.DOWN), - createComponentStatus(LifecycleStatus.ERROR) - ) - startHealthMonitor(componentStatuses) - val (healthyCode, _) = getHealthAndReadinessCodes(port) - - assertEquals(HTTP_SERVICE_UNAVAILABLE_CODE, healthyCode) - } - - @Test - fun `worker is considered ready if all components in the lifecycle registry are up`() { - val componentStatuses = mapOf( - createComponentStatus(LifecycleStatus.UP) - ) - startHealthMonitor(componentStatuses) - val (_, readyCode) = getHealthAndReadinessCodes(port) - - assertEquals(HTTP_OK_CODE, readyCode) - } - - @Test - fun `worker is considered not ready if any components are down`() { - val componentStatuses = mapOf( - createComponentStatus(LifecycleStatus.UP), - createComponentStatus(LifecycleStatus.DOWN) - ) - startHealthMonitor(componentStatuses) - val (_, readyCode) = getHealthAndReadinessCodes(port) - - assertEquals(HTTP_SERVICE_UNAVAILABLE_CODE, readyCode) - } - - @Test - fun `worker is considered not ready if any components are errored`() { - val componentStatuses = mapOf( - createComponentStatus(LifecycleStatus.UP), - createComponentStatus(LifecycleStatus.ERROR) - ) - startHealthMonitor(componentStatuses) - val (_, readyCode) = getHealthAndReadinessCodes(port) - - assertEquals(HTTP_SERVICE_UNAVAILABLE_CODE, readyCode) - } - - /** Creates a pair of [LifecycleCoordinatorName], [CoordinatorStatus] for a given [status]. */ - private fun createComponentStatus(status: LifecycleStatus): Pair { - val name = LifecycleCoordinatorName("") - return name to CoordinatorStatus(name, status, "") - } - - /** Creates and starts a [WorkerMonitor] that wraps a [LifecycleRegistry] with the given [componentStatuses]. */ - private fun startHealthMonitor(componentStatuses: Map): WorkerMonitor { - val lifecycleRegistry = TestLifecycleRegistry(componentStatuses) - val healthMonitor = WorkerMonitorImpl(lifecycleRegistry, webServer) - healthMonitor.registerEndpoints(this.javaClass.simpleName) - return healthMonitor - } - - /** Retrieves the HTTP codes of the health and readiness endpoints of a running [WorkerMonitor]. */ - private fun getHealthAndReadinessCodes(port: Int): Pair { - val responseCodeHealthy = (URL("http://localhost:$port$HTTP_HEALTH_ROUTE").openConnection() as HttpURLConnection).responseCode - val responseCodeReady = (URL("http://localhost:$port$HTTP_STATUS_ROUTE").openConnection() as HttpURLConnection).responseCode - return responseCodeHealthy to responseCodeReady - } -} - -/** A test [LifecycleRegistry] implementation with a hardcoded map of [componentStatuses]. */ -private class TestLifecycleRegistry(private val componentStatuses: Map) : - LifecycleRegistry { - override fun componentStatus(): Map = componentStatuses -} \ No newline at end of file diff --git a/charts/corda-lib/templates/_bootstrap.tpl b/charts/corda-lib/templates/_bootstrap.tpl index 07a1dafa8fc..2156888c0fb 100644 --- a/charts/corda-lib/templates/_bootstrap.tpl +++ b/charts/corda-lib/templates/_bootstrap.tpl @@ -67,12 +67,12 @@ spec: labels: {{- include "corda.selectorLabels" . | nindent 8 }} spec: - {{- include "corda.imagePullSecrets" . | nindent 6 }} + {{- include "corda.imagePullSecrets" . | indent 6 }} {{- include "corda.tolerations" . | nindent 6 }} serviceAccountName: {{ include "corda.bootstrapPreinstallServiceAccountName" . }} {{- with .Values.podSecurityContext }} securityContext: - {{ . | toYaml | nindent 8 }} + {{- . | toYaml | nindent 8 }} {{- end }} containers: - name: preinstall-checks @@ -132,69 +132,193 @@ spec: labels: {{- include "corda.selectorLabels" . | nindent 8 }} spec: - {{- include "corda.imagePullSecrets" . | nindent 6 }} - {{- include "corda.tolerations" $ | nindent 6 }} - {{- include "corda.bootstrapServiceAccount" . | nindent 6 }} + {{- include "corda.imagePullSecrets" . | indent 6 }} + {{- include "corda.tolerations" $ | indent 6 }} + {{- include "corda.bootstrapServiceAccount" . | indent 6 }} {{- with .Values.podSecurityContext }} securityContext: - {{ . | toYaml | nindent 8 }} + {{- . | toYaml | nindent 8 }} {{- end }} - containers: - - name: fin + initContainers: + - name: generate image: {{ include "corda.bootstrapCliImage" . }} imagePullPolicy: {{ .Values.imagePullPolicy }} {{- include "corda.bootstrapResources" . | nindent 10 }} {{- include "corda.containerSecurityContext" . | nindent 10 }} - command: - - /bin/bash - - -e - - -c - args: ["echo", "'DB Bootstrapped'"] + command: [ 'sh', '-c', '-e' ] + args: + - | + #!/bin/sh + set -ev + + JDBC_URL="jdbc:{{ include "corda.clusterDbType" . }}://{{ required "A db host is required" .Values.db.cluster.host }}:{{ include "corda.clusterDbPort" . }}/{{ include "corda.clusterDbName" . }}" + + echo 'Generating DB specification' + mkdir /tmp/db + java -Dpf4j.pluginsDir=/opt/override/plugins -Dlog4j2.debug=false -jar /opt/override/cli.jar database spec \ + -g "config:${DB_CLUSTER_SCHEMA},rbac:${DB_RBAC_SCHEMA},crypto:${DB_CRYPTO_SCHEMA},stateManager:${DB_STATE_MANAGER_SCHEMA}" \ + -u "${PGUSER}" -p "${PGPASSWORD}" \ + --jdbc-url "${JDBC_URL}" \ + -c -l /tmp/db + + echo 'Generating RBAC initial DB configuration' + mkdir /tmp/rbac + java -Dpf4j.pluginsDir=/opt/override/plugins -Dlog4j2.debug=false -jar /opt/override/cli.jar initial-config create-db-config \ + -u "${RBAC_DB_USER_USERNAME}" -p "${RBAC_DB_USER_PASSWORD}" \ + --name "corda-rbac" \ + --jdbc-url "${JDBC_URL}?currentSchema=${DB_RBAC_SCHEMA}" \ + --jdbc-pool-max-size {{ .Values.bootstrap.db.rbac.dbConnectionPool.maxSize | quote }} \ + {{- if not ( kindIs "invalid" .Values.bootstrap.db.rbac.dbConnectionPool.minSize ) }} + --jdbc-pool-min-size {{ .Values.bootstrap.db.rbac.dbConnectionPool.minSize | quote }} + {{- end }} + --idle-timeout {{ .Values.bootstrap.db.rbac.dbConnectionPool.idleTimeoutSeconds | quote }} \ + --max-lifetime {{ .Values.bootstrap.db.rbac.dbConnectionPool.maxLifetimeSeconds | quote }} \ + --keepalive-time {{ .Values.bootstrap.db.rbac.dbConnectionPool.keepaliveTimeSeconds | quote }} \ + --validation-timeout {{ .Values.bootstrap.db.rbac.dbConnectionPool.validationTimeoutSeconds | quote }} \ + {{- if (((.Values).config).vault).url }} + -t "VAULT" --vault-path "dbsecrets" --key "rbac-db-password" \ + {{- else }} + --salt "${SALT}" --passphrase "${PASSPHRASE}" \ + {{- end }} + -l /tmp/rbac + + echo 'Generating virtual nodes initial DB configuration' + mkdir /tmp/vnodes + java -Dpf4j.pluginsDir=/opt/override/plugins -Dlog4j2.debug=false -jar /opt/override/cli.jar initial-config create-db-config \ + -a -u "${DB_CLUSTER_USERNAME}" -p "${DB_CLUSTER_PASSWORD}" \ + --name "corda-virtual-nodes" \ + --jdbc-url "${JDBC_URL}" \ + --jdbc-pool-max-size {{ .Values.bootstrap.db.rbac.dbConnectionPool.maxSize | quote }} \ + {{- if not ( kindIs "invalid" .Values.bootstrap.db.rbac.dbConnectionPool.minSize ) }} + --jdbc-pool-min-size {{ .Values.bootstrap.db.rbac.dbConnectionPool.minSize | quote }} + {{- end }} + --idle-timeout {{ .Values.bootstrap.db.rbac.dbConnectionPool.idleTimeoutSeconds | quote }} \ + --max-lifetime {{ .Values.bootstrap.db.rbac.dbConnectionPool.maxLifetimeSeconds | quote }} \ + --keepalive-time {{ .Values.bootstrap.db.rbac.dbConnectionPool.keepaliveTimeSeconds | quote }} \ + --validation-timeout {{ .Values.bootstrap.db.rbac.dbConnectionPool.validationTimeoutSeconds | quote }} \ + {{- if (((.Values).config).vault).url }} + -t "VAULT" --vault-path "dbsecrets" --key "vnodes-db-password" \ + {{- else }} + --salt "${SALT}" --passphrase "${PASSPHRASE}" \ + {{- end }} + -l /tmp/vnodes + + echo 'Generating crypto initial DB configuration' + mkdir /tmp/crypto + java -Dpf4j.pluginsDir=/opt/override/plugins -Dlog4j2.debug=false -jar /opt/override/cli.jar initial-config create-db-config \ + -u "${CRYPTO_DB_USER_USERNAME}" -p "${CRYPTO_DB_USER_PASSWORD}" \ + --name "corda-crypto" \ + --jdbc-url "${JDBC_URL}?currentSchema=${DB_CRYPTO_SCHEMA}" \ + --jdbc-pool-max-size {{ .Values.bootstrap.db.crypto.dbConnectionPool.maxSize | quote }} \ + {{- if not ( kindIs "invalid" .Values.bootstrap.db.crypto.dbConnectionPool.minSize ) }} + --jdbc-pool-min-size {{ .Values.bootstrap.db.crypto.dbConnectionPool.minSize | quote }} + {{- end }} + --idle-timeout {{ .Values.bootstrap.db.crypto.dbConnectionPool.idleTimeoutSeconds | quote }} \ + --max-lifetime {{ .Values.bootstrap.db.crypto.dbConnectionPool.maxLifetimeSeconds | quote }} \ + --keepalive-time {{ .Values.bootstrap.db.crypto.dbConnectionPool.keepaliveTimeSeconds | quote }} \ + --validation-timeout {{ .Values.bootstrap.db.crypto.dbConnectionPool.validationTimeoutSeconds | quote }} \ + {{- if (((.Values).config).vault).url }} + -t "VAULT" --vault-path "dbsecrets" --key "crypto-db-password" \ + {{- else }} + --salt "${SALT}" --passphrase "${PASSPHRASE}" \ + {{- end }} + -l /tmp/crypto + + echo 'Generating REST API user initial configuration' + java -Dpf4j.pluginsDir=/opt/override/plugins -Dlog4j2.debug=false -jar /opt/override/cli.jar initial-config create-user-config \ + -u "${REST_API_ADMIN_USERNAME}" -p "${REST_API_ADMIN_PASSWORD}" \ + -l /tmp + + echo 'Generating crypto initial configuration' + java -Dpf4j.pluginsDir=/opt/override/plugins -Dlog4j2.debug=false -jar /opt/override/cli.jar initial-config create-crypto-config \ + --salt "${SALT}" --passphrase "${PASSPHRASE}" \ + {{- if (((.Values).config).vault).url }} + -t "VAULT" --vault-path "cryptosecrets" -ks "salt" -kp "passphrase" \ + {{- end }} + -l /tmp workingDir: /tmp volumeMounts: - mountPath: /tmp name: temp - initContainers: - {{- include "corda.generateAndExecuteSql" ( dict "name" "db" "Values" .Values "Chart" .Chart "Release" .Release "schema" "RBAC" "namePostfix" "schemas" "sequenceNumber" 1) | nindent 8 }} - {{- include "corda.generateAndExecuteSql" ( dict "name" "rbac" "Values" .Values "Chart" .Chart "Release" .Release "environmentVariablePrefix" "RBAC_DB_USER" "schema" "RBAC" "sequenceNumber" 3) | nindent 8 }} - {{- include "corda.generateAndExecuteSql" ( dict "name" "vnodes" "longName" "virtual-nodes" "dbName" "rbac" "admin" "true" "Values" .Values "Chart" .Chart "Release" .Release "environmentVariablePrefix" "DB_CLUSTER" "sequenceNumber" 5) | nindent 8 }} - {{- include "corda.generateAndExecuteSql" ( dict "name" "crypto" "Values" .Values "Chart" .Chart "Release" .Release "environmentVariablePrefix" "CRYPTO_DB_USER" "schema" "CRYPTO" "sequenceNumber" 7) | nindent 8 }} - {{- include "corda.generateAndExecuteSql" ( dict "name" "rest" "Values" .Values "Chart" .Chart "Release" .Release "environmentVariablePrefix" "REST_API_ADMIN" "schema" "RBAC" "searchPath" "RBAC" "subCommand" "create-user-config" "namePostfix" "admin" "sqlFile" "rbac-config.sql" "sequenceNumber" 9) | nindent 8 }} - - name: 11-create-db-users-and-grant + {{- include "corda.log4jVolumeMount" . | nindent 12 }} + env: + - name: DB_CLUSTER_SCHEMA + value: {{ .Values.db.cluster.schema | quote }} + - name: DB_RBAC_SCHEMA + value: {{ .Values.bootstrap.db.rbac.schema | quote }} + - name: DB_CRYPTO_SCHEMA + value: {{ .Values.bootstrap.db.crypto.schema | quote }} + - name: DB_STATE_MANAGER_SCHEMA + value: {{ .Values.bootstrap.db.stateManager.schema | quote }} + {{- include "corda.bootstrapClusterDbEnv" . | nindent 12 }} + {{- include "corda.configSaltAndPassphraseEnv" . | nindent 12 }} + {{- include "corda.bootstrapCliEnv" . | nindent 12 }} + {{- include "corda.rbacDbUserEnv" . | nindent 12 }} + {{- include "corda.clusterDbEnv" . | nindent 12 }} + {{- include "corda.restApiAdminSecretEnv" . | nindent 12 }} + {{- include "corda.cryptoDbUsernameEnv" . | nindent 12 }} + {{- include "corda.cryptoDbPasswordEnv" . | nindent 12 }} + containers: + - name: apply image: {{ include "corda.bootstrapDbClientImage" . }} imagePullPolicy: {{ .Values.imagePullPolicy }} {{- include "corda.bootstrapResources" . | nindent 10 }} {{- include "corda.containerSecurityContext" . | nindent 10 }} - command: [ '/bin/bash', '-e', '-c' ] + command: [ 'sh', '-c', '-e' ] args: - | - psql -v ON_ERROR_STOP=1 -h {{ required "A db host is required" .Values.db.cluster.host }} -p {{ include "corda.clusterDbPort" . }} {{ include "corda.clusterDbName" . }} << SQL - GRANT USAGE ON SCHEMA {{ .Values.db.cluster.schema }} TO "$DB_CLUSTER_USERNAME"; - GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA {{ .Values.db.cluster.schema }} TO "$DB_CLUSTER_USERNAME"; - GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA {{ .Values.db.cluster.schema }} TO "$DB_CLUSTER_USERNAME"; - DO \$\$ BEGIN IF EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$RBAC_DB_USER_USERNAME') THEN RAISE NOTICE 'Role "$RBAC_DB_USER_USERNAME" already exists'; ELSE CREATE USER "$RBAC_DB_USER_USERNAME" WITH ENCRYPTED PASSWORD '$RBAC_DB_USER_PASSWORD'; END IF; END \$\$; - GRANT USAGE ON SCHEMA {{ .Values.bootstrap.db.rbac.schema }} TO "$RBAC_DB_USER_USERNAME"; - GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA {{ .Values.bootstrap.db.rbac.schema }} TO "$RBAC_DB_USER_USERNAME"; - DO \$\$ BEGIN IF EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$CRYPTO_DB_USER_USERNAME') THEN RAISE NOTICE 'Role "$CRYPTO_DB_USER_USERNAME" already exists'; ELSE CREATE USER "$CRYPTO_DB_USER_USERNAME" WITH ENCRYPTED PASSWORD '$CRYPTO_DB_USER_PASSWORD'; END IF; END \$\$; - GRANT USAGE ON SCHEMA {{ .Values.bootstrap.db.crypto.schema }} TO "$CRYPTO_DB_USER_USERNAME"; - GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA {{ .Values.bootstrap.db.crypto.schema }} TO "$CRYPTO_DB_USER_USERNAME"; + #!/bin/sh + set -ev + + echo 'Applying DB specification' + find /tmp/db -iname "*.sql" | xargs printf -- ' -f %s' | xargs psql -v ON_ERROR_STOP=1 -h "${DB_CLUSTER_HOST}" -p "${DB_CLUSTER_PORT}" --dbname "${DB_CLUSTER_NAME}" + + echo 'Applying initial configurations' + psql -v ON_ERROR_STOP=1 -h "${DB_CLUSTER_HOST}" -p "${DB_CLUSTER_PORT}" -f /tmp/rbac/db-config.sql -f /tmp/vnodes/db-config.sql -f /tmp/crypto/db-config.sql -f /tmp/crypto-config.sql --dbname "dbname=${DB_CLUSTER_NAME} options=--search_path=${DB_CLUSTER_SCHEMA}" + + echo 'Applying initial RBAC configuration' + psql -v ON_ERROR_STOP=1 -h "${DB_CLUSTER_HOST}" -p "${DB_CLUSTER_PORT}" -f /tmp/rbac-config.sql --dbname "dbname=${DB_CLUSTER_NAME} options=--search_path=${DB_RBAC_SCHEMA}" + + echo 'Creating users and granting permissions' + psql -v ON_ERROR_STOP=1 -h "${DB_CLUSTER_HOST}" -p "${DB_CLUSTER_PORT}" "${DB_CLUSTER_NAME}" << SQL + GRANT USAGE ON SCHEMA ${DB_CLUSTER_SCHEMA} TO "${DB_CLUSTER_USERNAME}"; + GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA ${DB_CLUSTER_SCHEMA} TO "${DB_CLUSTER_USERNAME}"; + GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${DB_CLUSTER_SCHEMA} TO "${DB_CLUSTER_USERNAME}"; + DO \$\$ BEGIN IF EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${RBAC_DB_USER_USERNAME}') THEN RAISE NOTICE 'Role "${RBAC_DB_USER_USERNAME}" already exists'; ELSE CREATE USER "${RBAC_DB_USER_USERNAME}" WITH ENCRYPTED PASSWORD '${RBAC_DB_USER_PASSWORD}'; END IF; END \$\$; + GRANT USAGE ON SCHEMA ${DB_RBAC_SCHEMA} TO "$RBAC_DB_USER_USERNAME"; + GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA ${DB_RBAC_SCHEMA} TO "$RBAC_DB_USER_USERNAME"; + DO \$\$ BEGIN IF EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${CRYPTO_DB_USER_USERNAME}') THEN RAISE NOTICE 'Role "${CRYPTO_DB_USER_USERNAME}" already exists'; ELSE CREATE USER "${CRYPTO_DB_USER_USERNAME}" WITH ENCRYPTED PASSWORD '$CRYPTO_DB_USER_PASSWORD'; END IF; END \$\$; + GRANT USAGE ON SCHEMA ${DB_CRYPTO_SCHEMA} TO "${CRYPTO_DB_USER_USERNAME}"; + GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA ${DB_CRYPTO_SCHEMA} TO "${CRYPTO_DB_USER_USERNAME}"; SQL + + echo 'DB Bootstrapped' volumeMounts: - mountPath: /tmp name: temp env: - {{- include "corda.bootstrapClusterDbEnv" . | nindent 12 }} - {{ include "corda.rbacDbUserEnv" . | nindent 12 }} - {{ include "corda.cryptoDbUserEnv" . | nindent 12 }} - {{- include "corda.clusterDbEnv" . | nindent 12 }} - {{- include "corda.generateAndExecuteSql" ( dict "name" "crypto-config" "subCommand" "create-crypto-config" "Values" .Values "Chart" .Chart "Release" .Release "schema" "CRYPTO" "namePostfix" "worker-config" "sqlFile" "crypto-config.sql" "sequenceNumber" 12) | nindent 8 }} + - name: DB_CLUSTER_HOST + value: {{ required "A db host is required" .Values.db.cluster.host | quote }} + - name: DB_CLUSTER_PORT + value: {{ include "corda.clusterDbPort" . | quote }} + - name: DB_CLUSTER_NAME + value: {{ include "corda.clusterDbName" . | quote }} + - name: DB_CLUSTER_SCHEMA + value: {{ .Values.db.cluster.schema | quote }} + - name: DB_RBAC_SCHEMA + value: {{ .Values.bootstrap.db.rbac.schema | quote }} + - name: DB_CRYPTO_SCHEMA + value: {{ .Values.bootstrap.db.crypto.schema | quote }} + {{- include "corda.bootstrapClusterDbEnv" . | nindent 12 }} + {{- include "corda.rbacDbUserEnv" . | nindent 12 }} + {{- include "corda.cryptoDbUsernameEnv" . | nindent 12 }} + {{- include "corda.cryptoDbPasswordEnv" . | nindent 12 }} + {{- include "corda.clusterDbEnv" . | nindent 12 }} volumes: - name: temp emptyDir: {} - {{ include "corda.log4jVolume" . | nindent 8 }} - - {{- include "corda.bootstrapNodeSelector" . | nindent 6 }} - + {{- include "corda.log4jVolume" . | nindent 8 }} + {{- include "corda.bootstrapNodeSelector" . | indent 6 }} restartPolicy: Never backoffLimit: 0 {{- end }} @@ -220,12 +344,12 @@ spec: labels: {{- include "corda.selectorLabels" . | nindent 8 }} spec: - {{- include "corda.imagePullSecrets" . | nindent 6 }} - {{- include "corda.tolerations" . | nindent 6 }} - {{- include "corda.bootstrapServiceAccount" . | nindent 6 }} + {{- include "corda.imagePullSecrets" . | indent 6 }} + {{- include "corda.tolerations" . | indent 6 }} + {{- include "corda.bootstrapServiceAccount" . | indent 6 }} {{- with .Values.podSecurityContext }} securityContext: - {{ . | toYaml | nindent 8 }} + {{- . | toYaml | nindent 8 }} {{- end }} containers: - name: create-topics @@ -261,9 +385,9 @@ spec: name: certs readOnly: true {{- end }} - {{ include "corda.log4jVolumeMount" . | nindent 12 }} + {{- include "corda.log4jVolumeMount" . | nindent 12 }} env: - {{ include "corda.bootstrapCliEnv" . | nindent 12 }} + {{- include "corda.bootstrapCliEnv" . | nindent 12 }} {{- if .Values.kafka.sasl.enabled }} {{- range $k, $v := .Values.workers }} - name: {{ ( printf "KAFKA_SASL_USERNAME_%s" ( include "corda.workerTypeUpperSnakeCase" $k )) | quote }} @@ -291,8 +415,8 @@ spec: {{- include "corda.bootstrapResources" . | nindent 10 }} {{- include "corda.containerSecurityContext" . | nindent 10 }} env: - {{- include "corda.bootstrapKafkaSaslUsernameAndPasswordEnv" . | nindent 12 }} - {{- include "corda.kafkaTlsPassword" . | nindent 12 }} + {{- include "corda.bootstrapKafkaSaslUsernameAndPasswordEnv" . | indent 12 }} + {{- include "corda.kafkaTlsPassword" . | indent 12 }} command: - /bin/bash - -c @@ -339,9 +463,9 @@ spec: - key: {{ .Values.kafka.tls.truststore.valueFrom.secretKeyRef.key | quote }} path: ca.crt {{- end }} - {{ include "corda.log4jVolume" . | nindent 8 }} + {{- include "corda.log4jVolume" . | nindent 8 }} restartPolicy: Never - {{- include "corda.bootstrapNodeSelector" . | nindent 6 }} + {{- include "corda.bootstrapNodeSelector" . | indent 6 }} backoffLimit: 0 {{- end }} {{- end }} @@ -366,12 +490,12 @@ spec: labels: {{- include "corda.selectorLabels" . | nindent 8 }} spec: - {{- include "corda.imagePullSecrets" . | nindent 6 }} - {{- include "corda.tolerations" . | nindent 6 }} - {{- include "corda.bootstrapServiceAccount" . | nindent 6 }} + {{- include "corda.imagePullSecrets" . | indent 6 }} + {{- include "corda.tolerations" . | indent 6 }} + {{- include "corda.bootstrapServiceAccount" . | indent 6 }} {{- with .Values.podSecurityContext }} securityContext: - {{ . | toYaml | nindent 8 }} + {{- . | toYaml | nindent 8 }} {{- end }} containers: - name: create-rbac-role-user-admin @@ -379,51 +503,21 @@ spec: imagePullPolicy: {{ .Values.imagePullPolicy }} {{- include "corda.bootstrapResources" . | nindent 10 }} {{- include "corda.containerSecurityContext" . | nindent 10 }} - args: ['initial-rbac', 'user-admin', '--yield', '300', '--user', "$(REST_API_ADMIN_USERNAME)", + args: ['initial-rbac', 'all-cluster-roles', '--yield', '300', '--user', "$(REST_API_ADMIN_USERNAME)", '--password', "$(REST_API_ADMIN_PASSWORD)", '--target', "https://{{ include "corda.fullname" . }}-rest-worker:443", '--insecure'] volumeMounts: - mountPath: /tmp name: temp - {{ include "corda.log4jVolumeMount" . | nindent 12 }} + {{- include "corda.log4jVolumeMount" . | nindent 12 }} env: - {{ include "corda.restApiAdminSecretEnv" . | nindent 12 }} - {{ include "corda.bootstrapCliEnv" . | nindent 12 }} - - name: create-rbac-role-vnode-creator - image: {{ include "corda.bootstrapCliImage" . }} - imagePullPolicy: {{ .Values.imagePullPolicy }} - {{- include "corda.bootstrapResources" . | nindent 10 }} - {{- include "corda.containerSecurityContext" . | nindent 10 }} - args: ['initial-rbac', 'vnode-creator', '--yield', '300', '--user', "$(REST_API_ADMIN_USERNAME)", - '--password', "$(REST_API_ADMIN_PASSWORD)", - '--target', "https://{{ include "corda.fullname" . }}-rest-worker:443", '--insecure'] - volumeMounts: - - mountPath: /tmp - name: temp - {{ include "corda.log4jVolumeMount" . | nindent 12 }} - env: - {{ include "corda.restApiAdminSecretEnv" . | nindent 12 }} - {{ include "corda.bootstrapCliEnv" . | nindent 12 }} - - name: create-rbac-role-corda-dev - image: {{ include "corda.bootstrapCliImage" . }} - imagePullPolicy: {{ .Values.imagePullPolicy }} - {{- include "corda.bootstrapResources" . | nindent 10 }} - {{- include "corda.containerSecurityContext" . | nindent 10 }} - args: ['initial-rbac', 'corda-developer', '--yield', '300', '--user', "$(REST_API_ADMIN_USERNAME)", - '--password', "$(REST_API_ADMIN_PASSWORD)", - '--target', "https://{{ include "corda.fullname" . }}-rest-worker:443", '--insecure'] - volumeMounts: - - mountPath: /tmp - name: temp - {{ include "corda.log4jVolumeMount" . | nindent 12 }} - env: - {{ include "corda.restApiAdminSecretEnv" . | nindent 12 }} - {{ include "corda.bootstrapCliEnv" . | nindent 12 }} - {{- include "corda.bootstrapNodeSelector" . | nindent 6 }} + {{- include "corda.restApiAdminSecretEnv" . | nindent 12 }} + {{- include "corda.bootstrapCliEnv" . | nindent 12 }} + {{- include "corda.bootstrapNodeSelector" . | indent 6 }} volumes: - name: temp emptyDir: {} - {{ include "corda.log4jVolume" . | nindent 8 }} + {{- include "corda.log4jVolume" . | nindent 8 }} restartPolicy: Never backoffLimit: 0 {{- end }} @@ -446,7 +540,7 @@ Bootstrap DB client image {{/* Bootstrap resources */}} -{{- define "corda.bootstrapResources" }} +{{- define "corda.bootstrapResources" -}} resources: requests: {{- with .Values.bootstrap.resources.requests.cpu }} @@ -467,7 +561,7 @@ resources: {{/* Bootstrap node selector */}} -{{- define "corda.bootstrapNodeSelector" }} +{{- define "corda.bootstrapNodeSelector" -}} {{- with .Values.bootstrap.nodeSelector | default .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 2 }} @@ -477,7 +571,7 @@ nodeSelector: {{/* Bootstrap service account */}} -{{- define "corda.bootstrapServiceAccount" }} +{{- define "corda.bootstrapServiceAccount" -}} {{- with .Values.bootstrap.serviceAccount.name | default .Values.serviceAccount.name }} serviceAccountName: {{ . }} {{- end }} @@ -496,108 +590,3 @@ Bootstrap Corda CLI environment variables - name: CORDA_CLI_HOME_DIR value: "/tmp" {{- end }} - - -{{/* -Bootstrap declaration to declare an initial container for running corda-cli initial-config, then -a second init container to execute the output SQL to the relevant database -*/}} - -{{- define "corda.generateAndExecuteSql" -}} -{{- /* define 2 init containers, which run in sequence. First run corda-cli initial-config to generate some SQL, storing in a persistent volume called working-volume. Second is a postgres image which mounts the same persistent volume and executes the SQL. */ -}} -- name: {{ printf "%02d-create-%s" .sequenceNumber .name }} - image: {{ include "corda.bootstrapCliImage" . }} - imagePullPolicy: {{ .Values.imagePullPolicy }} - {{- include "corda.bootstrapResources" . | nindent 2 }} - {{- include "corda.containerSecurityContext" . | nindent 2 }} - {{- if eq .name "db" }} - args: [ 'database', 'spec', '-g', 'config:{{ .Values.db.cluster.schema }},rbac:{{ .Values.bootstrap.db.rbac.schema }},crypto:{{ .Values.bootstrap.db.crypto.schema }},stateManager:{{ .Values.bootstrap.db.stateManager.schema }}', '-c', '-l', '/tmp', '--jdbc-url', 'jdbc:{{ include "corda.clusterDbType" . }}://{{ required "A db host is required" .Values.db.cluster.host }}:{{ include "corda.clusterDbPort" . }}/{{ include "corda.clusterDbName" . }}', '-u', $(PGUSER), '-p', $(PGPASSWORD) ] - {{- else }} - args: [ 'initial-config', '{{ .subCommand | default "create-db-config" }}',{{ " " -}} - - {{- /* request admin access in some cases, only when the optional admin argument to this function (named template) is specified as true */ -}} - {{- if eq .admin "true" -}} '-a',{{- end -}} - - {{- if and (not (eq .name "db")) (not (eq .name "crypto-config")) -}} - {{- /* specify DB user */ -}} - {{- "'-u'" -}}, '$({{ .environmentVariablePrefix -}}_USERNAME)', - - {{- /* specify DB password */ -}} - {{- " '-p'" -}}, '$({{ .environmentVariablePrefix -}}_PASSWORD)', - {{- end -}} - - {{- if and (not (eq .name "rest")) (not (eq .subCommand "create-crypto-config")) -}} - {{- " '--name'" -}}, 'corda-{{ .longName | default .name }}', - {{- " '--jdbc-url'" -}}, 'jdbc:{{ include "corda.clusterDbType" . }}://{{ required "A db host is required" .Values.db.cluster.host }}:{{ include "corda.clusterDbPort" . }}/{{ include "corda.clusterDbName" . }}{{- if .schema }}?currentSchema={{.schema }}{{- end -}}', - {{- " '--jdbc-pool-max-size'" -}}, {{ (index .Values.bootstrap.db (.dbName | default .name)).dbConnectionPool.maxSize | quote }}, - {{- if not (kindIs "invalid" (index .Values.bootstrap.db (.dbName | default .name)).dbConnectionPool.minSize) -}} - {{- " '--jdbc-pool-min-size'" -}}, {{ (index .Values.bootstrap.db (.dbName | default .name)).dbConnectionPool.minSize | quote }}, - {{- end -}} - {{- " '--idle-timeout'" -}}, {{ (index .Values.bootstrap.db (.dbName | default .name)).dbConnectionPool.idleTimeoutSeconds | quote }}, - {{- " '--max-lifetime'" -}}, {{ (index .Values.bootstrap.db (.dbName | default .name)).dbConnectionPool.maxLifetimeSeconds | quote }}, - {{- " '--keepalive-time'" -}}, {{ (index .Values.bootstrap.db (.dbName | default .name)).dbConnectionPool.keepaliveTimeSeconds | quote }}, - {{- " '--validation-timeout'" -}}, {{ (index .Values.bootstrap.db (.dbName | default .name)).dbConnectionPool.validationTimeoutSeconds | quote }}, {{- " " -}} - {{- end -}} - - {{- if not (eq .name "rest") -}} - {{- if and (((.Values).config).vault).url (not (eq .name "crypto-config")) -}} - '-t', 'VAULT', '--vault-path', 'dbsecrets', '--key', {{ (printf "%s-db-password" .name)| quote }}, - {{- else -}} - {{- /* using encryption secrets service, so provide its salt and passphrase */ -}} - '--salt', "$(SALT)", '--passphrase', "$(PASSPHRASE)", - {{- end -}} - {{- end -}} - - {{- if and (eq .name "crypto-config") (((.Values).config).vault).url -}} - {{- /* when configuring the crypto service and using Vault then specify where to find the wrapping key salt and passphrase in Vault */ -}} - '-t', 'VAULT', '--vault-path', 'cryptosecrets', '-ks', 'salt', '-kp', 'passphrase', - {{- end -}} - - {{- " '-l'" -}}, '/tmp'] - {{- end }} - workingDir: /tmp - volumeMounts: - - mountPath: /tmp - name: temp - {{ include "corda.log4jVolumeMount" . | nindent 4 }} - env: - {{- if eq .name "db" -}} - {{- include "corda.bootstrapClusterDbEnv" . | nindent 4 }} - {{- end -}} - {{- if or (eq .name "rest") (eq .name "rbac") (eq .name "vnodes") (eq .name "crypto") -}} - {{- "\n " -}} {{- /* legacy whitespace compliance */ -}} - {{- end -}} - {{- if and (not (eq .name "rest")) (not (eq .name "db")) -}} - {{ include "corda.configSaltAndPassphraseEnv" . | nindent 4 -}} - {{- end -}} - {{- if or (eq .name "rbac") (eq .name "crypto") (eq .name "vnodes") (eq .name "db") -}} - {{- "\n " -}} {{- /* legacy whitespace compliance */ -}} - {{- end -}} - - {{- include "corda.bootstrapCliEnv" . | nindent 4 -}}{{- /* set JAVA_TOOL_OPTIONS, CONSOLE_LOG*, CORDA_CLI_HOME_DIR */ -}} - - {{- if or (eq .name "rbac") (eq .name "vnodes") }} - {{ include "corda.rbacDbUserEnv" . | nindent 4 }} - {{- end -}} - - {{- if eq .name "vnodes" -}} - {{ include "corda.clusterDbEnv" . | nindent 4 -}} - {{- end -}} - {{- if eq .name "rest" -}} - {{- include "corda.restApiAdminSecretEnv" . | nindent 4 }} - {{- end -}} - {{- if eq .environmentVariablePrefix "CRYPTO_DB_USER" -}} - {{- include "corda.cryptoDbUserEnv" . | nindent 4 -}} - {{- end }} -- name: {{ printf "%02d-apply-%s" (add .sequenceNumber 1) .name }} - image: {{ include "corda.bootstrapDbClientImage" . }} - imagePullPolicy: {{ .Values.imagePullPolicy }} - {{- include "corda.bootstrapResources" . | nindent 2 }} - {{- include "corda.containerSecurityContext" . | nindent 2 }} - command: [ 'sh', '-c', '-e',{{- if eq .name "db" }} 'for f in /tmp/*.sql; do psql -v ON_ERROR_STOP=1 -h {{ required "A db host is required" .Values.db.cluster.host }} -p {{ include "corda.clusterDbPort" . }} -f "$f" --dbname {{ include "corda.clusterDbName" . }}; done'{{- else }} 'psql -v ON_ERROR_STOP=1 -h {{ required "A db host is required" .Values.db.cluster.host }} -p {{ include "corda.clusterDbPort" . }} -f /tmp/{{ .sqlFile | default "db-config.sql" }} --dbname "dbname={{ include "corda.clusterDbName" . }} options=--search_path={{ .searchPath | default .Values.db.cluster.schema }}"' {{- end }} ] - volumeMounts: - - mountPath: /tmp - name: temp - env: - {{- include "corda.bootstrapClusterDbEnv" . | nindent 4 }} -{{- end }} diff --git a/charts/corda-lib/templates/_helpers.tpl b/charts/corda-lib/templates/_helpers.tpl index 786fb7b87f7..acbdac293c6 100644 --- a/charts/corda-lib/templates/_helpers.tpl +++ b/charts/corda-lib/templates/_helpers.tpl @@ -71,10 +71,10 @@ imagePullSecrets: Container security context */}} {{- define "corda.containerSecurityContext" -}} -{{- if not .Values.dumpHostPath }} -{{- with .Values.containerSecurityContext }} +{{- if not .Values.dumpHostPath -}} +{{- with .Values.containerSecurityContext -}} securityContext: - {{ . | toYaml | nindent 2}} + {{- . | toYaml | nindent 2}} {{- end }} {{- end }} {{- end }} @@ -93,9 +93,9 @@ topologySpreadConstraints: tolerations for node taints */}} {{- define "corda.tolerations" -}} -{{- if .Values.tolerations }} +{{- with .Values.tolerations }} tolerations: -{{- range .Values.tolerations }} +{{- range . }} - key: {{ required "Must specify key for toleration" .key }} {{- with .operator }} operator: {{ . }} @@ -424,7 +424,7 @@ Default name for crypto DB secret {{/* Crypto worker environment variable */}} -{{- define "corda.cryptoDbUserEnv" -}} +{{- define "corda.cryptoDbUsernameEnv" -}} - name: CRYPTO_DB_USER_USERNAME valueFrom: secretKeyRef: @@ -435,6 +435,8 @@ Crypto worker environment variable name: {{ include "corda.cryptoDbDefaultSecretName" . | quote }} key: "username" {{- end }} +{{- end }} +{{- define "corda.cryptoDbPasswordEnv" -}} - name: CRYPTO_DB_USER_PASSWORD valueFrom: secretKeyRef: diff --git a/charts/corda-lib/templates/_worker.tpl b/charts/corda-lib/templates/_worker.tpl index 95786ea2206..5abfab053a3 100644 --- a/charts/corda-lib/templates/_worker.tpl +++ b/charts/corda-lib/templates/_worker.tpl @@ -59,9 +59,11 @@ metadata: {{- range $key, $value := . }} {{ $key }}: {{ $value | quote }} {{- end }} - {{- end}} + {{- end }} spec: - type: {{ .type }} + {{- with .type }} + type: {{ . }} + {{- end }} {{- if .externalTrafficPolicy }} externalTrafficPolicy: {{ .externalTrafficPolicy }} {{- else if .loadBalancerSourceRanges }} @@ -106,16 +108,16 @@ spec: {{- if and ( not $.Values.dumpHostPath ) ( not .profiling.enabled ) }} {{- with $.Values.podSecurityContext }} securityContext: - {{ . | toYaml | nindent 8 }} + {{- . | toYaml | nindent 8 }} {{- end }} {{- end }} - {{- include "corda.imagePullSecrets" $ | nindent 6 }} - {{- include "corda.tolerations" $ | nindent 6 }} + {{- include "corda.imagePullSecrets" $ | indent 6 }} + {{- include "corda.tolerations" $ | indent 6 }} {{- with $.Values.serviceAccount.name }} serviceAccountName: {{ . }} {{- end }} {{- include "corda.topologySpreadConstraints" $ | indent 6 }} - {{- include "corda.affinity" (list $ . $worker ) | nindent 6 }} + {{- include "corda.affinity" (list $ . $worker ) | indent 6 }} containers: - name: {{ $workerName | quote }} image: {{ include "corda.workerImage" ( list $ . ) }} @@ -228,7 +230,9 @@ spec: value: {{ required (printf "Must specify workers.%s.kafka.sasl.password.value, workers.%s.kafka.sasl.password.valueFrom.secretKeyRef.name, kafka.sasl.password.value, or kafka.sasl.password.valueFrom.secretKeyRef.name" $worker $worker) $.Values.kafka.sasl.password.value }} {{- end }} {{- end }} + {{- if not (($.Values).vault).url }} {{- include "corda.configSaltAndPassphraseEnv" $ | nindent 10 }} + {{- end }} {{- /* TODO-[CORE-16419]: isolate StateManager database from the Cluster database */ -}} {{- if or $optionalArgs.clusterDbAccess $optionalArgs.stateManagerDbAccess }} {{- include "corda.clusterDbEnv" $ | nindent 10 }} @@ -432,8 +436,8 @@ Worker type in upper snake case Worker common labels */}} {{- define "corda.workerLabels" -}} -{{- $ := index . 0 }} -{{- $worker := index . 1 }} +{{- $ := index . 0 -}} +{{- $worker := index . 1 -}} {{ include "corda.labels" $ }} {{ include "corda.workerComponentLabel" $worker }} {{- end }} @@ -442,8 +446,8 @@ Worker common labels Worker selector labels */}} {{- define "corda.workerSelectorLabels" -}} -{{- $ := index . 0 }} -{{- $worker := index . 1 }} +{{- $ := index . 0 -}} +{{- $worker := index . 1 -}} {{ include "corda.selectorLabels" $ }} {{ include "corda.workerComponentLabel" $worker }} {{- end }} diff --git a/components/flow/flow-service/src/main/kotlin/net/corda/flow/external/events/impl/executor/ExternalEventExecutorImpl.kt b/components/flow/flow-service/src/main/kotlin/net/corda/flow/external/events/impl/executor/ExternalEventExecutorImpl.kt index cdd7959adc2..3a679f9f308 100644 --- a/components/flow/flow-service/src/main/kotlin/net/corda/flow/external/events/impl/executor/ExternalEventExecutorImpl.kt +++ b/components/flow/flow-service/src/main/kotlin/net/corda/flow/external/events/impl/executor/ExternalEventExecutorImpl.kt @@ -1,6 +1,5 @@ package net.corda.flow.external.events.impl.executor -import java.util.UUID import net.corda.flow.external.events.executor.ExternalEventExecutor import net.corda.flow.external.events.factory.ExternalEventFactory import net.corda.flow.fiber.FlowFiber @@ -11,6 +10,7 @@ import net.corda.v5.serialization.SingletonSerializeAsToken import org.osgi.service.component.annotations.Activate import org.osgi.service.component.annotations.Component import org.osgi.service.component.annotations.Reference +import java.util.UUID @Component(service = [ExternalEventExecutor::class, SingletonSerializeAsToken::class]) class ExternalEventExecutorImpl @Activate constructor( @@ -20,10 +20,13 @@ class ExternalEventExecutorImpl @Activate constructor( @Suspendable override fun execute( - requestId: String, factoryClass: Class>, parameters: PARAMETERS ): RESUME { + // `requestId` is a unique id per event. It is used to achieve idempotency by de-duplicating events processing, + // on Kafka consumers side. Consuming duplicate events can happen from retrying an event from Kafka which however + // did some persistent work previously but did not fully succeed (Kafka was not notified), therefore we retry/ reprocess it. + val requestId = UUID.randomUUID().toString() @Suppress("unchecked_cast") return with(flowFiberService.getExecutingFiber()) { suspend( @@ -44,12 +47,4 @@ class ExternalEventExecutorImpl @Activate constructor( platformContextProperties = this.flattenPlatformProperties() ) } - - @Suspendable - override fun execute( - factoryClass: Class>, - parameters: PARAMETERS - ): RESUME { - return execute(UUID.randomUUID().toString(), factoryClass, parameters) - } } diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/query/execution/impl/VaultNamedQueryExecutorImpl.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/query/execution/impl/VaultNamedQueryExecutorImpl.kt index 581cefb9812..120ff8d87e0 100644 --- a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/query/execution/impl/VaultNamedQueryExecutorImpl.kt +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/query/execution/impl/VaultNamedQueryExecutorImpl.kt @@ -1,6 +1,5 @@ package net.corda.ledger.persistence.query.execution.impl -import net.corda.data.KeyValuePair import net.corda.data.KeyValuePairList import net.corda.data.persistence.EntityResponse import net.corda.data.persistence.FindWithNamedQuery @@ -11,12 +10,17 @@ import net.corda.ledger.utxo.data.transaction.UtxoComponentGroup import net.corda.ledger.utxo.data.transaction.UtxoTransactionOutputDto import net.corda.orm.utils.transaction import net.corda.persistence.common.exceptions.NullParameterException +import net.corda.utilities.debug import net.corda.utilities.serialization.deserialize +import net.corda.utilities.trace import net.corda.v5.application.serialization.SerializationService +import net.corda.v5.base.annotations.CordaSerializable import net.corda.v5.ledger.utxo.ContractState import net.corda.v5.ledger.utxo.StateAndRef import org.slf4j.LoggerFactory import java.nio.ByteBuffer +import java.sql.Timestamp +import java.time.Instant import javax.persistence.EntityManagerFactory import javax.persistence.Tuple @@ -36,11 +40,87 @@ class VaultNamedQueryExecutorImpl( val log = LoggerFactory.getLogger(VaultNamedQueryExecutorImpl::class.java) } + /* + * Captures data passed back and forth between this query execution and the caller in a flow + * processor to enable subsequent pages to know where to resume from. Data is opaque outside + * this class. + * + * This class is not part of the corda-api data module because it is not exposed outside of the + * internal query API. + */ + @CordaSerializable + data class ResumePoint( + val created: Instant, + val txId: String, + val leafIdx: Int + ) + + /* + * Stores query results following processing / filtering, in a form ready to return to the + * caller. + */ + private data class ProcessedQueryResults( + val results: List>, + val resumePoint: ResumePoint?, + val numberOfRowsFromQuery: Int + ) + + /* + * Stores the raw query data retrieved from an SQL query row. + */ + private inner class RawQueryData(sqlRow: Tuple) { + + private val txId = sqlRow[0] as String + private val leafIdx = sqlRow[1] as Int + private val outputInfoData = sqlRow[2] as ByteArray + private val outputData = sqlRow[3] as ByteArray + private val created = (sqlRow[4] as Timestamp).toInstant() + + val stateAndRef: StateAndRef by lazy { + UtxoTransactionOutputDto(txId, leafIdx, outputInfoData, outputData) + .toStateAndRef(serializationService) + } + + val resumePoint: ResumePoint? by lazy { + created?.let { ResumePoint(created, txId, leafIdx) } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RawQueryData + + if (txId != other.txId) return false + if (leafIdx != other.leafIdx) return false + if (created != other.created) return false + + return true + } + + override fun hashCode(): Int { + var result = txId.hashCode() + result = 31 * result + leafIdx + result = 31 * result + (created?.hashCode() ?: 0) + return result + } + } + + /* + * Stores a set of raw query data returned from a single database query invocation. To support + * paging, this not only returns the raw query data, but also a `hasMore` flag to indicate + * whether another page of data is available. + */ + private data class RawQueryResults( + val results: List, + val hasMore: Boolean + ) + override fun executeQuery( request: FindWithNamedQuery ): EntityResponse { - log.debug("Executing query: ${request.queryName}") + log.debug { "Executing query: ${request.queryName}" } // Get the query from the registry and make sure it exists val vaultNamedQuery = registry.getQuery(request.queryName) @@ -52,17 +132,20 @@ class VaultNamedQueryExecutorImpl( // Deserialize the parameters into readable objects instead of bytes val deserializedParams = request.parameters.mapValues { - serializationService.deserialize(it.value.array(), Any::class.java) + serializationService.deserialize(it.value.array()) } // Fetch and filter the results and try to fill up the page size then map the results // mapNotNull has no effect as of now, but we keep it for safety purposes - val (fetchedRecords, numberOfRowsFromQuery) = filterResultsAndFillPageSize( + val (fetchedRecords, resumePoint, numberOfRowsReturned) = filterResultsAndFillPageSize( request, vaultNamedQuery, deserializedParams ) + log.trace { "Fetched ${fetchedRecords.size} records in this page " + + "(${numberOfRowsReturned - fetchedRecords.size} records filtered)" } + val filteredAndTransformedResults = fetchedRecords.mapNotNull { vaultNamedQuery.mapper?.transform(it, deserializedParams) ?: it } @@ -73,12 +156,14 @@ class VaultNamedQueryExecutorImpl( deserializedParams )?.results?.filterNotNull() ?: filteredAndTransformedResults - // Return the filtered/transformed/collected (if present) result and the offset to continue the paging from to the caller - return EntityResponse.newBuilder() + // Return the filtered/transformed/collected (if present) result to the caller + val response = EntityResponse.newBuilder() .setResults(collectedResults.map { ByteBuffer.wrap(serializationService.serialize(it).bytes) }) - .setMetadata(KeyValuePairList(listOf( - KeyValuePair("numberOfRowsFromQuery", numberOfRowsFromQuery.toString()) - ))).build() + + response.resumePoint = resumePoint?.let { ByteBuffer.wrap(serializationService.serialize(it).bytes) } + response.metadata = KeyValuePairList(emptyList()) + + return response.build() } /** @@ -97,86 +182,119 @@ class VaultNamedQueryExecutorImpl( * If any of these conditions happen, we just return the result set as-is without filling * up the "page size". * - * Will return a pair of the fetched and "filtered" results from the database and the offset - * that the paging can be continued from. + * The returned [ProcessedQueryResults] object provides the collated query results + * post-filtering, a [ResumePoint] if there is another page of data to be returned, and the + * total number of rows returned from executed queries for informational purposes. */ private fun filterResultsAndFillPageSize( request: FindWithNamedQuery, vaultNamedQuery: VaultNamedQuery, deserializedParams: Map - ): FilterResult { - val filteredResults = mutableListOf>() + ): ProcessedQueryResults { + val filteredRawData = mutableListOf() var currentRetry = 0 var numberOfRowsFromQuery = 0 + var currentResumePoint = request.resumePoint?.let { + serializationService.deserialize(request.resumePoint.array()) + } - while (filteredResults.size < request.limit && currentRetry < RESULT_SET_FILL_RETRY_LIMIT) { + while (filteredRawData.size < request.limit && currentRetry < RESULT_SET_FILL_RETRY_LIMIT ) { ++currentRetry - log.trace("Executing try: $currentRetry, fetched ${filteredResults.size} number of results so far.") + log.trace { "Executing try: $currentRetry, fetched ${filteredRawData.size} number of results so far." } // Fetch the state and refs for the given transaction IDs - val contractStateResults = fetchStateAndRefs( + val rawResults = fetchStateAndRefs( request, vaultNamedQuery.query.query, - offset = request.offset + numberOfRowsFromQuery + currentResumePoint ) // If we have no filter, there's no need to continue the loop if (vaultNamedQuery.filter == null) { - return FilterResult( - results = contractStateResults, - numberOfRowsFromQuery = contractStateResults.size - ) - } - - // If we can't fetch more states we just return the result set as-is - if (contractStateResults.isEmpty()) { - break + with (rawResults) { + return ProcessedQueryResults( + results.map { it.stateAndRef }, + if (hasMore) results.last().resumePoint else null, + results.size + ) + } } - contractStateResults.forEach { contractStateResult -> + rawResults.results.forEach { result -> ++numberOfRowsFromQuery - if (vaultNamedQuery.filter.filter(contractStateResult, deserializedParams)) { - filteredResults.add(contractStateResult) + if (vaultNamedQuery.filter.filter(result.stateAndRef, deserializedParams)) { + filteredRawData.add(result) } - if (filteredResults.size >= request.limit) { - return FilterResult( - results = filteredResults, - numberOfRowsFromQuery = numberOfRowsFromQuery + + if (filteredRawData.size >= request.limit) { + // Page filled. We need to set the resume point based on the final filtered + // result (as we may be throwing out additional records returned by the query). + // Note that we should never get to the > part of the condition; this is a + // purely defensive check. + // + // There are more results if either we didn't get through all the results + // returned by the query invocation, or if the query itself indicated there are + // more results to return. + val moreResults = (result != rawResults.results.last()) || rawResults.hasMore + + return ProcessedQueryResults( + filteredRawData.map { it.stateAndRef }, + if (moreResults) filteredRawData.last().resumePoint else null, + numberOfRowsFromQuery ) } } + + // If we can't fetch more states we just return the result set as-is + if (!rawResults.hasMore) { + currentResumePoint = null + break + } else { + currentResumePoint = rawResults.results.last().resumePoint + } } - return FilterResult( - results = filteredResults, - numberOfRowsFromQuery = numberOfRowsFromQuery + return ProcessedQueryResults( + filteredRawData.map { it.stateAndRef }, + currentResumePoint, + numberOfRowsFromQuery ) } /** - * A function that fetches the contract states that belong to the given transaction IDs. The data stored in the - * component table will be deserialized into contract states using component groups. + * A function that fetches the contract states that belong to the given transaction IDs. + * The data stored in the component table will be deserialized into contract states using + * component groups. + * + * Each invocation of this function represents a single distinct query to the database. */ private fun fetchStateAndRefs( request: FindWithNamedQuery, whereJson: String?, - offset: Int - ): List> { + resumePoint: ResumePoint? + ): RawQueryResults { validateParameters(request) @Suppress("UNCHECKED_CAST") - return entityManagerFactory.transaction { em -> + val resultList = entityManagerFactory.transaction { em -> + + val resumePointExpr = resumePoint?.let { + " AND ((tc_output.created > :created) OR " + + "(tc_output.created = :created AND tc_output.transaction_id > :txId) OR " + + "(tc_output.created = :created AND tc_output.transaction_id = :txId AND tc_output.leaf_idx > :leafIdx))" + } ?: "" val query = em.createNativeQuery( """ SELECT tc_output.transaction_id, tc_output.leaf_idx, tc_output_info.data as output_info_data, - tc_output.data AS output_data + tc_output.data AS output_data, + tc_output.created AS created FROM $UTXO_VISIBLE_TX_TABLE AS visible_states JOIN $UTXO_TX_COMPONENT_TABLE AS tc_output_info ON tc_output_info.transaction_id = visible_states.transaction_id @@ -187,28 +305,38 @@ class VaultNamedQueryExecutorImpl( AND tc_output_info.leaf_idx = tc_output.leaf_idx AND tc_output.group_idx = ${UtxoComponentGroup.OUTPUTS.ordinal} WHERE ($whereJson) + $resumePointExpr AND visible_states.created <= :$TIMESTAMP_LIMIT_PARAM_NAME ORDER BY tc_output.created, tc_output.transaction_id, tc_output.leaf_idx """, - Tuple::class.java - ) + Tuple::class.java) + + if (resumePoint != null) { + log.trace { "Query is resuming from $resumePoint" } + query.setParameter("created", resumePoint.created) + query.setParameter("txId", resumePoint.txId) + query.setParameter("leafIdx", resumePoint.leafIdx) + } request.parameters.filter { it.value != null }.forEach { rec -> val bytes = rec.value.array() query.setParameter(rec.key, serializationService.deserialize(bytes)) } - query.firstResult = offset - query.maxResults = request.limit + query.firstResult = request.offset + // Getting one more than requested allows us to identify if there are more results to + // return in a subsequent page + query.maxResults = request.limit + 1 query.resultList as List - }.map { t -> - UtxoTransactionOutputDto( - t[0] as String, // transactionId - t[1] as Int, // leaf ID - t[2] as ByteArray, // outputs info data - t[3] as ByteArray // outputs data - ).toStateAndRef(serializationService) + } + + return if (resultList.size > request.limit) { + // We need to truncate the list to the number requested, but also flag that there is + // another page to be returned + RawQueryResults(resultList.subList(0, request.limit).map { RawQueryData(it) }, hasMore = true) + } else { + RawQueryResults(resultList.map { RawQueryData(it) }, hasMore = false) } } @@ -221,9 +349,4 @@ class VaultNamedQueryExecutorImpl( throw NullParameterException(msg) } } - - private data class FilterResult( - val results: List>, - val numberOfRowsFromQuery: Int - ) } diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/UtxoPersistenceService.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/UtxoPersistenceService.kt index eedbcef5d2a..a4a9fcd54bb 100644 --- a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/UtxoPersistenceService.kt +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/UtxoPersistenceService.kt @@ -41,6 +41,8 @@ interface UtxoPersistenceService { fun findUnconsumedVisibleStatesByType(stateClass: Class): List + fun findUnconsumedVisibleStatesByExactType(stateClass: Class): List + fun resolveStateRefs(stateRefs: List): List fun persistTransaction( diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/UtxoRepository.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/UtxoRepository.kt index 083dbdd86cb..7977760e6fb 100644 --- a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/UtxoRepository.kt +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/UtxoRepository.kt @@ -25,11 +25,17 @@ interface UtxoRepository { transactionId: String ): Map> - /** Retrieves transaction component leafs related to visible unspent states */ + /** Retrieves transaction component leaves related to visible unspent states and subclass states.*/ fun findUnconsumedVisibleStatesByType( entityManager: EntityManager ): List + /** Retrieves transaction component leaves related to visible unspent states */ + fun findUnconsumedVisibleStatesByExactType( + entityManager: EntityManager, + stateClassType: String + ): List + /** Retrieves transaction component leafs related to specific StateRefs */ fun resolveStateRefs( entityManager: EntityManager, diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/AbstractUtxoQueryProvider.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/AbstractUtxoQueryProvider.kt index 1f8e03dfe6b..6e29a9b4ed7 100644 --- a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/AbstractUtxoQueryProvider.kt +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/AbstractUtxoQueryProvider.kt @@ -46,6 +46,32 @@ abstract class AbstractUtxoQueryProvider : UtxoQueryProvider { ORDER BY tc_output.created, tc_output.transaction_id, tc_output.leaf_idx""" .trimIndent() + override val findUnconsumedVisibleStatesByExactType: String + get() = """ + SELECT tc_output.transaction_id, + tc_output.leaf_idx, + tc_output_info.data as output_info_data, + tc_output.data AS output_data + FROM {h-schema}utxo_visible_transaction_state AS rts + JOIN {h-schema}utxo_transaction_component AS tc_output_info + ON tc_output_info.transaction_id = rts.transaction_id + AND tc_output_info.leaf_idx = rts.leaf_idx + AND tc_output_info.group_idx = ${UtxoComponentGroup.OUTPUTS_INFO.ordinal} + JOIN {h-schema}utxo_transaction_component AS tc_output + ON tc_output.transaction_id = tc_output_info.transaction_id + AND tc_output.leaf_idx = tc_output_info.leaf_idx + AND tc_output.group_idx = ${UtxoComponentGroup.OUTPUTS.ordinal} + JOIN {h-schema}utxo_transaction_output AS tx_o + ON tx_o.transaction_id = tc_output.transaction_id + AND tx_o.leaf_idx = tc_output.leaf_idx + JOIN {h-schema}utxo_transaction_status AS ts + ON ts.transaction_id = tx_o.transaction_id + WHERE tx_o.type = :type + AND rts.consumed IS NULL + AND ts.status = :verified + ORDER BY tc_output.created, tc_output.transaction_id, tc_output.leaf_idx""" + .trimIndent() + override val findTransactionSignatures: String get() = """ SELECT signature diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoPersistenceServiceImpl.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoPersistenceServiceImpl.kt index 0153b869458..8f0d1112efc 100644 --- a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoPersistenceServiceImpl.kt +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoPersistenceServiceImpl.kt @@ -108,6 +108,12 @@ class UtxoPersistenceServiceImpl( } } + override fun findUnconsumedVisibleStatesByExactType(stateClass: Class): List { + return entityManagerFactory.transaction { em -> + repository.findUnconsumedVisibleStatesByExactType(em, stateClass.canonicalName) + } + } + override fun resolveStateRefs(stateRefs: List): List { return entityManagerFactory.transaction { em -> repository.resolveStateRefs(em, stateRefs) diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoQueryProvider.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoQueryProvider.kt index 84e0d11bc78..58a5079312c 100644 --- a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoQueryProvider.kt +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoQueryProvider.kt @@ -25,6 +25,11 @@ interface UtxoQueryProvider { */ val findUnconsumedVisibleStatesByType: String + /** + * @property findUnconsumedVisibleStatesByExactType SQL text for [UtxoRepositoryImpl.findUnconsumedVisibleStatesByExactType]. + */ + val findUnconsumedVisibleStatesByExactType: String + /** * @property findTransactionSignatures SQL text for [UtxoRepositoryImpl.findTransactionSignatures]. */ diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoRepositoryImpl.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoRepositoryImpl.kt index c4b8fe76ff8..b5b7331f507 100644 --- a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoRepositoryImpl.kt +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoRepositoryImpl.kt @@ -90,20 +90,32 @@ class UtxoRepositoryImpl @Activate constructor( .mapToComponentGroups(UtxoComponentGroupMapper(transactionId)) } + private fun findUnconsumedVisibleStates( + entityManager: EntityManager, + query: String, + stateClassType: String? + ): List { + val queryObj = entityManager.createNativeQuery(query, Tuple::class.java) + .setParameter("verified", TransactionStatus.VERIFIED.value) + + if (stateClassType != null) { + queryObj.setParameter("type", stateClassType) + } + + return queryObj.mapToUtxoTransactionOutputDto() + } + override fun findUnconsumedVisibleStatesByType( entityManager: EntityManager ): List { - return entityManager.createNativeQuery(queryProvider.findUnconsumedVisibleStatesByType, Tuple::class.java) - .setParameter("verified", TransactionStatus.VERIFIED.value) - .resultListAsTuples() - .map { t -> - UtxoTransactionOutputDto( - t[0] as String, // transactionId - t[1] as Int, // leaf ID - t[2] as ByteArray, // outputs info data - t[3] as ByteArray // outputs data - ) - } + return findUnconsumedVisibleStates(entityManager, queryProvider.findUnconsumedVisibleStatesByType, null) + } + + override fun findUnconsumedVisibleStatesByExactType( + entityManager: EntityManager, + stateClassType: String + ): List { + return findUnconsumedVisibleStates(entityManager, queryProvider.findUnconsumedVisibleStatesByExactType, stateClassType) } override fun resolveStateRefs( @@ -114,15 +126,7 @@ class UtxoRepositoryImpl @Activate constructor( .setParameter("transactionIds", stateRefs.map { it.transactionId.toString() }) .setParameter("stateRefs", stateRefs.map { it.toString() }) .setParameter("verified", TransactionStatus.VERIFIED.value) - .resultListAsTuples() - .map { t -> - UtxoTransactionOutputDto( - t[0] as String, // transactionId - t[1] as Int, // leaf ID - t[2] as ByteArray, // outputs info data - t[3] as ByteArray // outputs data - ) - } + .mapToUtxoTransactionOutputDto() } override fun findTransactionSignatures( @@ -362,4 +366,16 @@ class UtxoRepositoryImpl @Activate constructor( @Suppress("UNCHECKED_CAST") private fun Query.resultListAsTuples() = resultList as List + + private fun Query.mapToUtxoTransactionOutputDto(): List { + return resultListAsTuples() + .map { t -> + UtxoTransactionOutputDto( + t[0] as String, // transactionId + t[1] as Int, // leaf ID + t[2] as ByteArray, // outputs info data + t[3] as ByteArray // outputs data + ) + } + } } diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoRequestHandlerSelectorImpl.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoRequestHandlerSelectorImpl.kt index 8698e6c9845..a83b5ca518b 100644 --- a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoRequestHandlerSelectorImpl.kt +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/UtxoRequestHandlerSelectorImpl.kt @@ -3,6 +3,7 @@ package net.corda.ledger.persistence.utxo.impl import net.corda.data.ledger.persistence.FindSignedGroupParameters import net.corda.data.ledger.persistence.FindSignedLedgerTransaction import net.corda.data.ledger.persistence.FindTransaction +import net.corda.data.ledger.persistence.FindUnconsumedStatesByExactType import net.corda.data.ledger.persistence.FindUnconsumedStatesByType import net.corda.data.ledger.persistence.LedgerPersistenceRequest import net.corda.data.ledger.persistence.LedgerTypes @@ -22,6 +23,7 @@ import net.corda.ledger.persistence.utxo.impl.request.handlers.UtxoExecuteNamedQ import net.corda.ledger.persistence.utxo.impl.request.handlers.UtxoFindSignedGroupParametersRequestHandler import net.corda.ledger.persistence.utxo.impl.request.handlers.UtxoFindSignedLedgerTransactionRequestHandler import net.corda.ledger.persistence.utxo.impl.request.handlers.UtxoFindTransactionRequestHandler +import net.corda.ledger.persistence.utxo.impl.request.handlers.UtxoFindUnconsumedStatesByExactTypeRequestHandler import net.corda.ledger.persistence.utxo.impl.request.handlers.UtxoFindUnconsumedStatesByTypeRequestHandler import net.corda.ledger.persistence.utxo.impl.request.handlers.UtxoPersistSignedGroupParametersIfDoNotExistRequestHandler import net.corda.ledger.persistence.utxo.impl.request.handlers.UtxoPersistTransactionIfDoesNotExistRequestHandler @@ -95,6 +97,15 @@ class UtxoRequestHandlerSelectorImpl @Activate constructor( outputRecordFactory ) } + is FindUnconsumedStatesByExactType -> { + UtxoFindUnconsumedStatesByExactTypeRequestHandler( + req, + sandbox, + externalEventContext, + persistenceService, + outputRecordFactory + ) + } is ResolveStateRefs -> { UtxoResolveStateRefsRequestHandler( req, diff --git a/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/request/handlers/UtxoFindUnconsumedStatesByExactTypeRequestHandler.kt b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/request/handlers/UtxoFindUnconsumedStatesByExactTypeRequestHandler.kt new file mode 100644 index 00000000000..c0df0902855 --- /dev/null +++ b/components/ledger/ledger-persistence/src/main/kotlin/net/corda/ledger/persistence/utxo/impl/request/handlers/UtxoFindUnconsumedStatesByExactTypeRequestHandler.kt @@ -0,0 +1,34 @@ +package net.corda.ledger.persistence.utxo.impl.request.handlers + +import net.corda.data.flow.event.external.ExternalEventContext +import net.corda.data.ledger.persistence.FindUnconsumedStatesByExactType +import net.corda.ledger.persistence.common.RequestHandler +import net.corda.ledger.persistence.utxo.UtxoOutputRecordFactory +import net.corda.ledger.persistence.utxo.UtxoPersistenceService +import net.corda.messaging.api.records.Record +import net.corda.sandboxgroupcontext.SandboxGroupContext +import net.corda.v5.ledger.utxo.ContractState + +@Suppress("LongParameterList") +class UtxoFindUnconsumedStatesByExactTypeRequestHandler( + private val findUnconsumedStatesByExactType: FindUnconsumedStatesByExactType, + private val sandbox: SandboxGroupContext, + private val externalEventContext: ExternalEventContext, + private val persistenceService: UtxoPersistenceService, + private val utxoOutputRecordFactory: UtxoOutputRecordFactory +) : RequestHandler { + + @Suppress("UNCHECKED_CAST") + override fun execute(): List> { + val stateType = sandbox.sandboxGroup.loadClassFromMainBundles(findUnconsumedStatesByExactType.stateClassName) + require(ContractState::class.java.isAssignableFrom(stateType)) { + "Provided ${findUnconsumedStatesByExactType.stateClassName} is not type of ContractState" + } + + val visibleStates = persistenceService.findUnconsumedVisibleStatesByExactType( + stateType as Class + ) + + return listOf(utxoOutputRecordFactory.getStatesSuccessRecord(visibleStates, externalEventContext)) + } +} diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/UtxoLedgerServiceImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/UtxoLedgerServiceImpl.kt index 78589d677a3..eabc13477c0 100644 --- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/UtxoLedgerServiceImpl.kt +++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/UtxoLedgerServiceImpl.kt @@ -105,6 +105,11 @@ class UtxoLedgerServiceImpl @Activate constructor( return utxoLedgerStateQueryService.findUnconsumedStatesByType(type) } + @Suspendable + override fun findUnconsumedStatesByExactType(type: Class): List> { + return utxoLedgerStateQueryService.findUnconsumedStatesByExactType(type) + } + @Suspendable override fun finalize( signedTransaction: UtxoSignedTransaction, diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/LedgerPersistenceMetricOperationName.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/LedgerPersistenceMetricOperationName.kt index 6b9a311ff07..1d81d8fec1f 100644 --- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/LedgerPersistenceMetricOperationName.kt +++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/LedgerPersistenceMetricOperationName.kt @@ -5,6 +5,7 @@ enum class LedgerPersistenceMetricOperationName { FindGroupParameters, FindSignedLedgerTransactionWithStatus, FindTransactionWithStatus, + FindUnconsumedStatesByExactType, FindUnconsumedStatesByType, FindWithNamedQuery, PersistSignedGroupParametersIfDoNotExist, diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerStateQueryService.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerStateQueryService.kt index bec83e73145..2cf004582dc 100644 --- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerStateQueryService.kt +++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerStateQueryService.kt @@ -11,7 +11,7 @@ import net.corda.v5.ledger.utxo.StateRef */ interface UtxoLedgerStateQueryService { /** - * Find unconsumed visible states of type [stateClass]. + * Find unconsumed visible states of type [stateClass] and that of subclasses. * * @param stateClass The class of the aimed states. * @return The result [StateAndRef]s. @@ -21,6 +21,17 @@ interface UtxoLedgerStateQueryService { @Suspendable fun findUnconsumedStatesByType(stateClass: Class): List> + /** + * Find unconsumed visible states of type [stateClass]. + * + * @param stateClass The class of the aimed states. + * @return The result [StateAndRef]s. + * + * @throws CordaPersistenceException if an error happens during find operation. + */ + @Suspendable + fun findUnconsumedStatesByExactType(stateClass: Class): List> + /** * Resolve [StateRef]s to [StateAndRef]s * diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerStateQueryServiceImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerStateQueryServiceImpl.kt index 8b84c171a33..b04c00935d9 100644 --- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerStateQueryServiceImpl.kt +++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerStateQueryServiceImpl.kt @@ -4,8 +4,10 @@ import io.micrometer.core.instrument.Timer import net.corda.flow.external.events.executor.ExternalEventExecutor import net.corda.flow.fiber.metrics.recordSuspendable import net.corda.ledger.utxo.flow.impl.cache.StateAndRefCache +import net.corda.ledger.utxo.flow.impl.persistence.LedgerPersistenceMetricOperationName.FindUnconsumedStatesByExactType import net.corda.ledger.utxo.flow.impl.persistence.LedgerPersistenceMetricOperationName.FindUnconsumedStatesByType import net.corda.ledger.utxo.flow.impl.persistence.LedgerPersistenceMetricOperationName.ResolveStateRefs +import net.corda.ledger.utxo.flow.impl.persistence.external.events.FindUnconsumedStatesByExactTypeExternalEventFactory import net.corda.ledger.utxo.flow.impl.persistence.external.events.FindUnconsumedStatesByTypeExternalEventFactory import net.corda.ledger.utxo.flow.impl.persistence.external.events.FindUnconsumedStatesByTypeParameters import net.corda.ledger.utxo.flow.impl.persistence.external.events.ResolveStateRefsExternalEventFactory @@ -52,6 +54,18 @@ class UtxoLedgerStateQueryServiceImpl @Activate constructor( } } + @Suspendable + override fun findUnconsumedStatesByExactType(stateClass: Class): List> { + return recordSuspendable({ ledgerPersistenceFlowTimer(FindUnconsumedStatesByExactType) }) @Suspendable { + wrapWithPersistenceException { + externalEventExecutor.execute( + FindUnconsumedStatesByExactTypeExternalEventFactory::class.java, + FindUnconsumedStatesByTypeParameters(stateClass) + ) + }.map { it.toStateAndRef(serializationService) } + } + } + @Suspendable override fun resolveStateRefs(stateRefs: Iterable): List> { return recordSuspendable({ ledgerPersistenceFlowTimer(ResolveStateRefs) }) @Suspendable { diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/VaultNamedParameterizedQueryImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/VaultNamedParameterizedQueryImpl.kt index 4bd1c8e6da2..063fcd6295f 100644 --- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/VaultNamedParameterizedQueryImpl.kt +++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/VaultNamedParameterizedQueryImpl.kt @@ -12,6 +12,7 @@ import net.corda.utilities.time.Clock import net.corda.v5.application.persistence.PagedQuery import net.corda.v5.base.annotations.Suspendable import net.corda.v5.ledger.utxo.query.VaultNamedParameterizedQuery +import java.lang.UnsupportedOperationException import java.time.Instant // TODO CORE-12032 use delegation to create this class @@ -39,9 +40,7 @@ class VaultNamedParameterizedQueryImpl( } override fun setOffset(offset: Int): VaultNamedParameterizedQuery { - require (offset >= 0) { "Offset cannot be negative" } - this.offset = offset - return this + throw UnsupportedOperationException("This query does not support offset functionality.") } override fun setParameter(name: String, value: Any): VaultNamedParameterizedQuery { @@ -69,14 +68,17 @@ class VaultNamedParameterizedQueryImpl( val resultSet = resultSetFactory.create( parameters, limit, - offset, resultClass - ) @Suspendable { serializedParameters, offset -> + ) @Suspendable { serializedParameters, resumePoint -> recordSuspendable(::ledgerPersistenceFlowTimer) @Suspendable { wrapWithPersistenceException { externalEventExecutor.execute( VaultNamedQueryExternalEventFactory::class.java, - VaultNamedQueryEventParams(queryName, serializedParameters, offset, limit) + VaultNamedQueryEventParams( + queryName, + serializedParameters, + limit, + resumePoint) ) } } diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/ExecuteCustomQueryExternalEventFactory.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/ExecuteCustomQueryExternalEventFactory.kt index eb7d395e04e..f4b611e18b9 100644 --- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/ExecuteCustomQueryExternalEventFactory.kt +++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/ExecuteCustomQueryExternalEventFactory.kt @@ -7,7 +7,7 @@ import net.corda.data.persistence.EntityResponse import net.corda.data.persistence.FindWithNamedQuery import net.corda.flow.external.events.factory.ExternalEventFactory import net.corda.flow.external.events.factory.ExternalEventRecord -import net.corda.flow.persistence.query.OffsetResultSetExecutor +import net.corda.flow.persistence.query.StableResultSetExecutor import net.corda.flow.state.FlowCheckpoint import net.corda.schema.Schemas import net.corda.virtualnode.toAvro @@ -18,7 +18,7 @@ import java.time.Clock @Component(service = [ExternalEventFactory::class]) class VaultNamedQueryExternalEventFactory( private val clock: Clock = Clock.systemUTC() -) : ExternalEventFactory { +) : ExternalEventFactory { override val responseType = EntityResponse::class.java @@ -36,9 +36,9 @@ class VaultNamedQueryExternalEventFactory( FindWithNamedQuery( parameters.queryName, parameters.queryParameters, - parameters.offset, + 0, parameters.limit, - null + parameters.resumePoint ) ) .setFlowExternalEventContext(flowExternalEventContext) @@ -47,10 +47,10 @@ class VaultNamedQueryExternalEventFactory( ) } - override fun resumeWith(checkpoint: FlowCheckpoint, response: EntityResponse): OffsetResultSetExecutor.Results { - return OffsetResultSetExecutor.Results( + override fun resumeWith(checkpoint: FlowCheckpoint, response: EntityResponse): StableResultSetExecutor.Results { + return StableResultSetExecutor.Results( serializedResults = response.results, - numberOfRowsFromQuery = response.metadata.items.single { it.key == "numberOfRowsFromQuery" }.value.toInt() + resumePoint = response.resumePoint ) } } @@ -58,6 +58,6 @@ class VaultNamedQueryExternalEventFactory( data class VaultNamedQueryEventParams( val queryName: String, val queryParameters: Map, - val offset: Int, - val limit: Int + val limit: Int, + val resumePoint: ByteBuffer? ) diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/FindUnconsumedStatesByExactTypeExternalEventFactory.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/FindUnconsumedStatesByExactTypeExternalEventFactory.kt new file mode 100644 index 00000000000..e2ef8953358 --- /dev/null +++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/FindUnconsumedStatesByExactTypeExternalEventFactory.kt @@ -0,0 +1,54 @@ +package net.corda.ledger.utxo.flow.impl.persistence.external.events + +import net.corda.data.flow.event.external.ExternalEventContext +import net.corda.data.ledger.persistence.FindUnconsumedStatesByExactType +import net.corda.data.ledger.persistence.LedgerPersistenceRequest +import net.corda.data.ledger.persistence.LedgerTypes +import net.corda.data.ledger.persistence.UtxoTransactionOutputs +import net.corda.flow.external.events.factory.ExternalEventFactory +import net.corda.flow.external.events.factory.ExternalEventRecord +import net.corda.flow.state.FlowCheckpoint +import net.corda.ledger.utxo.data.transaction.UtxoTransactionOutputDto +import net.corda.schema.Schemas +import net.corda.virtualnode.toAvro +import org.osgi.service.component.annotations.Activate +import org.osgi.service.component.annotations.Component +import java.time.Clock + +@Component(service = [ExternalEventFactory::class]) +class FindUnconsumedStatesByExactTypeExternalEventFactory( + private val clock: Clock +) : ExternalEventFactory> +{ + @Activate + constructor() : this(Clock.systemUTC()) + + override val responseType = UtxoTransactionOutputs::class.java + + override fun createExternalEvent( + checkpoint: FlowCheckpoint, + flowExternalEventContext: ExternalEventContext, + parameters: FindUnconsumedStatesByTypeParameters + ): ExternalEventRecord { + return ExternalEventRecord( + topic = Schemas.Persistence.PERSISTENCE_LEDGER_PROCESSOR_TOPIC, + payload = LedgerPersistenceRequest.newBuilder() + .setTimestamp(clock.instant()) + .setHoldingIdentity(checkpoint.holdingIdentity.toAvro()) + .setRequest(createRequest(parameters)) + .setFlowExternalEventContext(flowExternalEventContext) + .setLedgerType(LedgerTypes.UTXO) + .build() + ) + } + + private fun createRequest(parameters: FindUnconsumedStatesByTypeParameters): Any { + return FindUnconsumedStatesByExactType(parameters.stateClass.canonicalName) + } + + override fun resumeWith(checkpoint: FlowCheckpoint, response: UtxoTransactionOutputs): List { + return response.transactionOutputs.map { + UtxoTransactionOutputDto(it.transactionId, it.index, it.info.array(), it.data.array()) + } + } +} \ No newline at end of file diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/VaultNamedParameterizedQueryImplTest.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/VaultNamedParameterizedQueryImplTest.kt index df0c5a21bda..22ca4e1e217 100644 --- a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/VaultNamedParameterizedQueryImplTest.kt +++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/VaultNamedParameterizedQueryImplTest.kt @@ -1,8 +1,8 @@ package net.corda.ledger.utxo.flow.impl.persistence import net.corda.flow.external.events.executor.ExternalEventExecutor -import net.corda.flow.persistence.query.OffsetResultSetExecutor import net.corda.flow.persistence.query.ResultSetFactory +import net.corda.flow.persistence.query.StableResultSetExecutor import net.corda.ledger.utxo.flow.impl.persistence.external.events.ALICE_X500_HOLDING_IDENTITY import net.corda.ledger.utxo.flow.impl.persistence.external.events.VaultNamedQueryExternalEventFactory import net.corda.sandboxgroupcontext.CurrentSandboxGroupContext @@ -42,7 +42,7 @@ class VaultNamedParameterizedQueryImplTest { private val resultSetFactory = mock() private val resultSet = mock>() private val clock = mock() - private val resultSetExecutorCaptor = argumentCaptor>() + private val resultSetExecutorCaptor = argumentCaptor>() private val mapCaptor = argumentCaptor>() private val query = VaultNamedParameterizedQueryImpl( @@ -59,7 +59,7 @@ class VaultNamedParameterizedQueryImplTest { @BeforeEach fun beforeEach() { - whenever(resultSetFactory.create(mapCaptor.capture(), any(), any(), any(), resultSetExecutorCaptor.capture())).thenReturn(resultSet) + whenever(resultSetFactory.create(mapCaptor.capture(), any(), any(), resultSetExecutorCaptor.capture())).thenReturn(resultSet) whenever(resultSet.next()).thenReturn(results) whenever(clock.instant()).thenReturn(later) whenever(sandbox.virtualNodeContext).thenReturn(virtualNodeContext) @@ -70,21 +70,16 @@ class VaultNamedParameterizedQueryImplTest { @Test fun `setLimit updates the limit`() { query.execute() - verify(resultSetFactory).create(any(), eq(1), any(), any>(), any()) + verify(resultSetFactory).create(any(), eq(1), any>(), any()) query.setLimit(10) query.execute() - verify(resultSetFactory).create(any(), eq(10), any(), any>(), any()) + verify(resultSetFactory).create(any(), eq(10), any>(), any()) } @Test - fun `setOffset updates the offset`() { - query.execute() - verify(resultSetFactory).create(any(), any(), eq(0), any>(), any()) - - query.setOffset(10) - query.execute() - verify(resultSetFactory).create(any(), any(), eq(10), any>(), any()) + fun `setOffset is not supported`() { + assertThatThrownBy { query.setOffset(10) }.isInstanceOf(UnsupportedOperationException::class.java) } @Test @@ -97,11 +92,6 @@ class VaultNamedParameterizedQueryImplTest { assertThatThrownBy { query.setLimit(0) }.isInstanceOf(IllegalArgumentException::class.java) } - @Test - fun `setOffset cannot be negative`() { - assertThatThrownBy { query.setOffset(-1) }.isInstanceOf(IllegalArgumentException::class.java) - } - @Test fun `cannot set timestamp limit to a future date`() { assertThatThrownBy { query.setCreatedTimestampLimit(Instant.now().plusMillis(1.days.toMillis())) } @@ -175,7 +165,7 @@ class VaultNamedParameterizedQueryImplTest { @Test fun `execute creates a result set, gets the next page and returns the result set`() { assertThat(query.execute()).isEqualTo(resultSet) - verify(resultSetFactory).create(any(), any(), any(), any>(), any()) + verify(resultSetFactory).create(any(), any(), any>(), any()) verify(resultSet).next() } @@ -187,7 +177,7 @@ class VaultNamedParameterizedQueryImplTest { query.execute() val resultSetExecutor = resultSetExecutorCaptor.firstValue - assertThatThrownBy { resultSetExecutor.execute(emptyMap(), 0) }.isInstanceOf(CordaPersistenceException::class.java) + assertThatThrownBy { resultSetExecutor.execute(emptyMap(), null) }.isInstanceOf(CordaPersistenceException::class.java) } @Test @@ -198,6 +188,6 @@ class VaultNamedParameterizedQueryImplTest { query.execute() val resultSetExecutor = resultSetExecutorCaptor.firstValue - assertThatThrownBy { resultSetExecutor.execute(emptyMap(), 0) }.isInstanceOf(IllegalStateException::class.java) + assertThatThrownBy { resultSetExecutor.execute(emptyMap(), null) }.isInstanceOf(IllegalStateException::class.java) } } diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/FindUnconsumedStatesByExactTypeExternalEventFactoryTest.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/FindUnconsumedStatesByExactTypeExternalEventFactoryTest.kt new file mode 100644 index 00000000000..cbc8bf10d85 --- /dev/null +++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/external/events/FindUnconsumedStatesByExactTypeExternalEventFactoryTest.kt @@ -0,0 +1,62 @@ +package net.corda.ledger.utxo.flow.impl.persistence.external.events + +import net.corda.data.KeyValuePairList +import net.corda.data.flow.event.external.ExternalEventContext +import net.corda.data.ledger.persistence.FindUnconsumedStatesByExactType +import net.corda.data.ledger.persistence.LedgerPersistenceRequest +import net.corda.data.ledger.persistence.LedgerTypes +import net.corda.flow.state.FlowCheckpoint +import net.corda.schema.Schemas +import net.corda.v5.ledger.utxo.ContractState +import net.corda.virtualnode.toCorda +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.security.PublicKey +import java.time.Clock +import java.time.Instant +import java.time.ZoneId + +class FindUnconsumedStatesByExactTypeExternalEventFactoryTest { + + class TestContractState : ContractState { + override fun getParticipants(): List { + return emptyList() + } + } + + @Test + fun `creates a record containing an UtxoLedgerRequest with a FindUnconsumedStatesByExactType payload`() { + val checkpoint = mock() + val stateClass = TestContractState()::class.java + val externalEventContext = ExternalEventContext( + "request id", + "flow id", + KeyValuePairList(emptyList()) + ) + val testClock = Clock.fixed(Instant.now(), ZoneId.of("UTC")) + + whenever(checkpoint.holdingIdentity).thenReturn(ALICE_X500_HOLDING_IDENTITY.toCorda()) + + val externalEventRecord = FindUnconsumedStatesByExactTypeExternalEventFactory(testClock).createExternalEvent( + checkpoint, + externalEventContext, + FindUnconsumedStatesByTypeParameters(stateClass) + ) + + assertEquals(Schemas.Persistence.PERSISTENCE_LEDGER_PROCESSOR_TOPIC, externalEventRecord.topic) + assertNull(externalEventRecord.key) + assertEquals( + LedgerPersistenceRequest( + testClock.instant(), + ALICE_X500_HOLDING_IDENTITY, + LedgerTypes.UTXO, + FindUnconsumedStatesByExactType(stateClass.canonicalName), + externalEventContext + ), + externalEventRecord.payload + ) + } +} diff --git a/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/factories/TokenCacheEventProcessorFactoryImpl.kt b/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/factories/TokenCacheEventProcessorFactoryImpl.kt index 69dbf3b04d2..ce40a702b28 100644 --- a/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/factories/TokenCacheEventProcessorFactoryImpl.kt +++ b/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/factories/TokenCacheEventProcessorFactoryImpl.kt @@ -55,8 +55,7 @@ class TokenCacheEventProcessorFactoryImpl @Activate constructor( virtualNodeInfoService, dbConnectionManager, jpaEntitiesRegistry, - utxoTokenRepository, - serviceConfiguration + utxoTokenRepository ) val eventHandlerMap = mapOf, TokenEventHandler>( @@ -64,7 +63,8 @@ class TokenCacheEventProcessorFactoryImpl @Activate constructor( TokenClaimQueryEventHandler( tokenFilterStrategy, recordFactory, - availableTokenService + availableTokenService, + serviceConfiguration ) ), createHandler(TokenClaimReleaseEventHandler(recordFactory)), diff --git a/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/handlers/TokenClaimQueryEventHandler.kt b/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/handlers/TokenClaimQueryEventHandler.kt index ede1bda65e9..505c6206a33 100644 --- a/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/handlers/TokenClaimQueryEventHandler.kt +++ b/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/handlers/TokenClaimQueryEventHandler.kt @@ -10,11 +10,13 @@ import net.corda.ledger.utxo.token.cache.services.AvailableTokenService import net.corda.ledger.utxo.token.cache.services.TokenFilterStrategy import net.corda.messaging.api.records.Record import java.math.BigDecimal +import net.corda.ledger.utxo.token.cache.services.ServiceConfiguration class TokenClaimQueryEventHandler( private val filterStrategy: TokenFilterStrategy, private val recordFactory: RecordFactory, - private val availableTokenService: AvailableTokenService + private val availableTokenService: AvailableTokenService, + private val serviceConfiguration: ServiceConfiguration ) : TokenEventHandler { override fun handle( @@ -28,8 +30,17 @@ class TokenClaimQueryEventHandler( // if we didn't reach the target amount, reload the cache to ensure it's full and retry if (selectionResult.first < event.targetAmount) { - val findResult = availableTokenService.findAvailTokens(event.poolKey, event.ownerHash, event.tagRegex) - tokenCache.add(findResult.tokens) + // The max. number of tokens retrieved should be the configured size plus the number of claimed tokens + // This way the cache size will be equal to the configured size once the claimed tokens are removed + // from the query results + val maxTokens = serviceConfiguration.cachedTokenPageSize + state.claimedTokens().size + val findResult = availableTokenService.findAvailTokens(event.poolKey, event.ownerHash, event.tagRegex, maxTokens) + + // Remove the claimed tokens from the query results + val tokens = findResult.tokens.filterNot { state.isTokenClaimed(it.stateRef) } + + // Replace the tokens in the cache with the ones from the query result that have not been claimed + tokenCache.add(tokens) selectionResult = selectTokens(tokenCache, state, event) } @@ -37,6 +48,8 @@ class TokenClaimQueryEventHandler( val selectedTokens = selectionResult.second return if (selectedAmount >= event.targetAmount) { + // Claimed tokens should not be stored in the token cache + tokenCache.removeAll(selectedTokens.map { it.stateRef }.toSet()) state.addNewClaim(event.externalEventRequestId, selectedTokens) recordFactory.getSuccessfulClaimResponse( event.flowId, diff --git a/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/services/AvailableTokenService.kt b/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/services/AvailableTokenService.kt index a6d12dd8f9e..1f2cfc67215 100644 --- a/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/services/AvailableTokenService.kt +++ b/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/services/AvailableTokenService.kt @@ -7,7 +7,7 @@ import net.corda.ledger.utxo.token.cache.entities.TokenPoolKey interface AvailableTokenService { - fun findAvailTokens(poolKey: TokenPoolKey, ownerHash: String?, tagRegex: String?): AvailTokenQueryResult + fun findAvailTokens(poolKey: TokenPoolKey, ownerHash: String?, tagRegex: String?, maxTokens: Int): AvailTokenQueryResult fun queryBalance(poolKey: TokenPoolKey, ownerHash: String?, tagRegex: String?, claimedTokens: Collection): TokenBalance diff --git a/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/services/internal/AvailableTokenServiceImpl.kt b/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/services/internal/AvailableTokenServiceImpl.kt index ed23debfd83..e4aac837d96 100644 --- a/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/services/internal/AvailableTokenServiceImpl.kt +++ b/components/ledger/ledger-utxo-token-cache/src/main/kotlin/net/corda/ledger/utxo/token/cache/services/internal/AvailableTokenServiceImpl.kt @@ -11,7 +11,6 @@ import net.corda.ledger.utxo.token.cache.repositories.UtxoTokenRepository import net.corda.ledger.utxo.token.cache.entities.TokenBalance import net.corda.ledger.utxo.token.cache.entities.TokenPoolKey import net.corda.ledger.utxo.token.cache.services.AvailableTokenService -import net.corda.ledger.utxo.token.cache.services.ServiceConfiguration import net.corda.orm.JpaEntitiesRegistry import net.corda.virtualnode.read.VirtualNodeInfoReadService import net.corda.virtualnode.VirtualNodeInfo @@ -20,10 +19,9 @@ class AvailableTokenServiceImpl( private val virtualNodeInfoService: VirtualNodeInfoReadService, private val dbConnectionManager: DbConnectionManager, private val jpaEntitiesRegistry: JpaEntitiesRegistry, - private val utxoTokenRepository: UtxoTokenRepository, - private val serviceConfiguration: ServiceConfiguration + private val utxoTokenRepository: UtxoTokenRepository ) : AvailableTokenService, SingletonSerializeAsToken { - override fun findAvailTokens(poolKey: TokenPoolKey, ownerHash: String?, tagRegex: String?): AvailTokenQueryResult { + override fun findAvailTokens(poolKey: TokenPoolKey, ownerHash: String?, tagRegex: String?, maxTokens: Int): AvailTokenQueryResult { val virtualNode = getVirtualNodeInfo(poolKey) val entityManagerFactory = getOrCreateEntityManagerFactory(virtualNode) @@ -33,7 +31,7 @@ class AvailableTokenServiceImpl( poolKey, ownerHash, tagRegex, - serviceConfiguration.cachedTokenPageSize + maxTokens ) } diff --git a/components/ledger/ledger-utxo-token-cache/src/test/kotlin/net/corda/ledger/utxo/token/cache/impl/handlers/TokenClaimQueryEventHandlerTest.kt b/components/ledger/ledger-utxo-token-cache/src/test/kotlin/net/corda/ledger/utxo/token/cache/impl/handlers/TokenClaimQueryEventHandlerTest.kt index b302da80dbf..6b6de23bd70 100644 --- a/components/ledger/ledger-utxo-token-cache/src/test/kotlin/net/corda/ledger/utxo/token/cache/impl/handlers/TokenClaimQueryEventHandlerTest.kt +++ b/components/ledger/ledger-utxo-token-cache/src/test/kotlin/net/corda/ledger/utxo/token/cache/impl/handlers/TokenClaimQueryEventHandlerTest.kt @@ -59,10 +59,10 @@ class TokenClaimQueryEventHandlerTest { @Test fun `empty cache should return non found`() { - val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService) + val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService, mock()) val claimQuery = createClaimQuery(100) whenever(recordFactory.getFailedClaimResponse(any(), any(), any())).thenReturn(claimQueryResult) - whenever(availableTokenService.findAvailTokens(any(), any(), any())) + whenever(availableTokenService.findAvailTokens(any(), any(), any(), any())) .thenReturn(AvailTokenQueryResult(claimQuery.poolKey, emptySet())) val result = target.handle(tokenCache, poolCacheState, claimQuery) @@ -73,10 +73,10 @@ class TokenClaimQueryEventHandlerTest { @Test fun `when non found no claim should be created`() { - val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService) + val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService, mock()) val claimQuery = createClaimQuery(100) whenever(recordFactory.getFailedClaimResponse(any(), any(), any())).thenReturn(claimQueryResult) - whenever(availableTokenService.findAvailTokens(any(), any(), any())) + whenever(availableTokenService.findAvailTokens(any(), any(), any(), any())) .thenReturn(AvailTokenQueryResult(claimQuery.poolKey, emptySet())) val result = target.handle(tokenCache, poolCacheState, claimQuery) @@ -87,10 +87,10 @@ class TokenClaimQueryEventHandlerTest { @Test fun `when tokens selected a claim should be created`() { - val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService) + val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService, mock()) val claimQuery = createClaimQuery(100) whenever(recordFactory.getSuccessfulClaimResponse(any(), any(), any(), any())).thenReturn(claimQueryResult) - whenever(availableTokenService.findAvailTokens(any(), any(), any())) + whenever(availableTokenService.findAvailTokens(any(), any(), any(), any())) .thenReturn(AvailTokenQueryResult(claimQuery.poolKey, emptySet())) cachedTokens += token101 @@ -102,10 +102,10 @@ class TokenClaimQueryEventHandlerTest { @Test fun `query for tokens finds none when sum of available tokens is less than target`() { - val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService) + val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService, mock()) val claimQuery = createClaimQuery(100) whenever(recordFactory.getFailedClaimResponse(any(), any(), any())).thenReturn(claimQueryResult) - whenever(availableTokenService.findAvailTokens(any(), any(), any())) + whenever(availableTokenService.findAvailTokens(any(), any(), any(), any())) .thenReturn(AvailTokenQueryResult(claimQuery.poolKey, emptySet())) cachedTokens += token99 @@ -117,10 +117,10 @@ class TokenClaimQueryEventHandlerTest { @Test fun `query for tokens with exact amount should claim token`() { - val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService) + val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService, mock()) val claimQuery = createClaimQuery(100) whenever(recordFactory.getSuccessfulClaimResponse(any(), any(), any(), any())).thenReturn(claimQueryResult) - whenever(availableTokenService.findAvailTokens(any(), any(), any())) + whenever(availableTokenService.findAvailTokens(any(), any(), any(), any())) .thenReturn(AvailTokenQueryResult(claimQuery.poolKey, emptySet())) cachedTokens += token100 @@ -132,10 +132,10 @@ class TokenClaimQueryEventHandlerTest { @Test fun `query for tokens should select multiple to reach target amount`() { - val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService) + val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService, mock()) val claimQuery = createClaimQuery(110) whenever(recordFactory.getSuccessfulClaimResponse(any(), any(), any(), any())).thenReturn(claimQueryResult) - whenever(availableTokenService.findAvailTokens(any(), any(), any())) + whenever(availableTokenService.findAvailTokens(any(), any(), any(), any())) .thenReturn(AvailTokenQueryResult(claimQuery.poolKey, emptySet())) cachedTokens += token99 cachedTokens += token100 @@ -149,10 +149,10 @@ class TokenClaimQueryEventHandlerTest { @Test fun `query for tokens should return none when claimed tokens stop target being reached`() { - val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService) + val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService, mock()) val claimQuery = createClaimQuery(100) whenever(recordFactory.getFailedClaimResponse(any(), any(), any())).thenReturn(claimQueryResult) - whenever(availableTokenService.findAvailTokens(any(), any(), any())) + whenever(availableTokenService.findAvailTokens(any(), any(), any(), any())) .thenReturn(AvailTokenQueryResult(claimQuery.poolKey, emptySet())) whenever(poolCacheState.isTokenClaimed(token100Ref)).thenReturn(true) whenever(poolCacheState.isTokenClaimed(token101Ref)).thenReturn(true) @@ -168,10 +168,10 @@ class TokenClaimQueryEventHandlerTest { @Test fun `query for tokens should not include tokens already claimed`() { - val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService) + val target = TokenClaimQueryEventHandler(filterStrategy, recordFactory, availableTokenService, mock()) val claimQuery = createClaimQuery(110) whenever(recordFactory.getSuccessfulClaimResponse(any(), any(), any(), any())).thenReturn(claimQueryResult) - whenever(availableTokenService.findAvailTokens(any(), any(), any())) + whenever(availableTokenService.findAvailTokens(any(), any(), any(), any())) .thenReturn(AvailTokenQueryResult(claimQuery.poolKey, emptySet())) whenever(poolCacheState.isTokenClaimed(token100Ref)).thenReturn(true) cachedTokens += token99 diff --git a/components/ledger/ledger-utxo-token-cache/src/test/kotlin/net/corda/ledger/utxo/token/cache/impl/services/AvailableTokenServiceImplTest.kt b/components/ledger/ledger-utxo-token-cache/src/test/kotlin/net/corda/ledger/utxo/token/cache/impl/services/AvailableTokenServiceImplTest.kt index 21e7ea9001c..09ff6297d61 100644 --- a/components/ledger/ledger-utxo-token-cache/src/test/kotlin/net/corda/ledger/utxo/token/cache/impl/services/AvailableTokenServiceImplTest.kt +++ b/components/ledger/ledger-utxo-token-cache/src/test/kotlin/net/corda/ledger/utxo/token/cache/impl/services/AvailableTokenServiceImplTest.kt @@ -13,7 +13,6 @@ import net.corda.ledger.utxo.token.cache.entities.CachedToken import net.corda.ledger.utxo.token.cache.entities.TokenPoolKey import net.corda.ledger.utxo.token.cache.repositories.UtxoTokenRepository import net.corda.ledger.utxo.token.cache.services.internal.AvailableTokenServiceImpl -import net.corda.ledger.utxo.token.cache.services.ServiceConfiguration import net.corda.orm.JpaEntitiesRegistry import net.corda.orm.JpaEntitiesSet import net.corda.v5.crypto.DigestAlgorithmName @@ -44,8 +43,6 @@ class AvailableTokenServiceImplTest { whenever(queryBalance(any(), any(), isNull(), isNull())).thenReturn(totalBalance) } - private val serviceConfiguration = mock() - private val poolKey = mock().apply { whenever(shortHolderId).thenReturn(SecureHashImpl(DigestAlgorithmName.SHA2_256.name, "random".toByteArray()).toHexString()) } @@ -54,12 +51,11 @@ class AvailableTokenServiceImplTest { whenever(get(any())).thenReturn(JpaEntitiesSet.create("empty", emptySet())) } - val availableTokenServiceImpl = AvailableTokenServiceImpl( + private val availableTokenServiceImpl = AvailableTokenServiceImpl( virtualNodeInfoService, dbConnectionManager, jpaEntitiesRegistry, - utxoTokenRepository, - serviceConfiguration + utxoTokenRepository ) /** diff --git a/libs/flows/flow-api/src/main/kotlin/net/corda/flow/external/events/executor/ExternalEventExecutor.kt b/libs/flows/flow-api/src/main/kotlin/net/corda/flow/external/events/executor/ExternalEventExecutor.kt index cc51462814f..4951959a669 100644 --- a/libs/flows/flow-api/src/main/kotlin/net/corda/flow/external/events/executor/ExternalEventExecutor.kt +++ b/libs/flows/flow-api/src/main/kotlin/net/corda/flow/external/events/executor/ExternalEventExecutor.kt @@ -15,7 +15,6 @@ interface ExternalEventExecutor { * - Response of type [RESUME]. * - An exception. * - * @param requestId The unique request id of the event. * @param factoryClass The [ExternalEventFactory] that is called to create the event to send and convert the * received response into an acceptable object to resume with. * @param parameters The [PARAMETERS] object. @@ -26,29 +25,6 @@ interface ExternalEventExecutor { * @return The object that the flow will resume with. */ @Suspendable - fun execute( - requestId: String, - factoryClass: Class>, - parameters: PARAMETERS - ): RESUME - - /** - * Sends an event to an external processor and awaits its response. - * - * [execute] resumes with either a: - * - Response of type [RESUME]. - * - An exception. - * - * @param factoryClass The [ExternalEventFactory] that is called to create the event to send and convert the - * received response into an acceptable object to resume with. - * @param parameters The [PARAMETERS] object. - * - * @param PARAMETERS The type to pass to the factory when suspending/creating the event. - * @param RESPONSE The type received as a response from the external processor. - * @param RESUME The type the flow resumes with after calling [execute]. - * @return The object that the flow will resume with. - */ - @Suspendable fun execute( factoryClass: Class>, parameters: PARAMETERS diff --git a/libs/lifecycle/registry/src/main/kotlin/net/corda/lifecycle/registry/LifecycleRegistry.kt b/libs/lifecycle/registry/src/main/kotlin/net/corda/lifecycle/registry/LifecycleRegistry.kt index c51b0202866..b8d2d03883b 100644 --- a/libs/lifecycle/registry/src/main/kotlin/net/corda/lifecycle/registry/LifecycleRegistry.kt +++ b/libs/lifecycle/registry/src/main/kotlin/net/corda/lifecycle/registry/LifecycleRegistry.kt @@ -1,6 +1,7 @@ package net.corda.lifecycle.registry import net.corda.lifecycle.LifecycleCoordinatorName +import net.corda.lifecycle.LifecycleStatus /** * Obtain information about the current running status of coordinators in the system. @@ -24,4 +25,14 @@ interface LifecycleRegistry { * @return A map of coordinator names to their current statuses. */ fun componentStatus(): Map + + /** + * Returns all [LifecycleCoordinatorName] in the given statuses + */ + fun componentWithStatus(statuses: Collection) = + componentStatus().values.filter { coordinatorStatus -> + statuses.contains(coordinatorStatus.status) + }.map { + it.name + } } \ No newline at end of file diff --git a/libs/lifecycle/registry/src/test/kotlin/LifecycleRegistryTests.kt b/libs/lifecycle/registry/src/test/kotlin/LifecycleRegistryTests.kt new file mode 100644 index 00000000000..df5508db33a --- /dev/null +++ b/libs/lifecycle/registry/src/test/kotlin/LifecycleRegistryTests.kt @@ -0,0 +1,79 @@ +import net.corda.lifecycle.LifecycleCoordinatorName +import net.corda.lifecycle.LifecycleStatus +import net.corda.lifecycle.registry.CoordinatorStatus +import net.corda.lifecycle.registry.LifecycleRegistry +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class LifecycleRegistryTests { + private val superman = LifecycleCoordinatorName("superman") + private val batman = LifecycleCoordinatorName("batman") + + @Test + fun `when empty componentWithStatus returns empty`() { + val registry = object : LifecycleRegistry { + override fun componentStatus(): Map = emptyMap() + + } + + assertThat(registry.componentWithStatus(listOf(LifecycleStatus.UP))).isEmpty() + } + + @Test + fun `when not empty filter componentWithStatus - single match`() { + val registry = object : LifecycleRegistry { + override fun componentStatus(): Map = + mapOf( + superman to CoordinatorStatus(superman, LifecycleStatus.UP, "foo"), + batman to CoordinatorStatus(batman, LifecycleStatus.DOWN, "foo"), + ) + + } + + assertThat(registry.componentWithStatus(listOf(LifecycleStatus.UP))).contains(superman) + } + + @Test + fun `when not empty filter componentWithStatus - multiple match`() { + val registry = object : LifecycleRegistry { + override fun componentStatus(): Map = + mapOf( + superman to CoordinatorStatus(superman, LifecycleStatus.UP, "foo"), + batman to CoordinatorStatus(batman, LifecycleStatus.UP, "foo"), + ) + + } + + assertThat(registry.componentWithStatus(listOf(LifecycleStatus.UP))) + .containsExactlyInAnyOrder(superman, batman) + } + + @Test + fun `when not empty filter componentWithStatus - multiple filter`() { + val registry = object : LifecycleRegistry { + override fun componentStatus(): Map = + mapOf( + superman to CoordinatorStatus(superman, LifecycleStatus.UP, "foo"), + batman to CoordinatorStatus(batman, LifecycleStatus.DOWN, "foo"), + ) + + } + + assertThat(registry.componentWithStatus(listOf(LifecycleStatus.UP, LifecycleStatus.DOWN))) + .containsExactlyInAnyOrder(superman, batman) + } + + @Test + fun `when not empty filter componentWithStatus - no match`() { + val registry = object : LifecycleRegistry { + override fun componentStatus(): Map = + mapOf( + superman to CoordinatorStatus(superman, LifecycleStatus.UP, "foo"), + batman to CoordinatorStatus(batman, LifecycleStatus.UP, "foo"), + ) + + } + + assertThat(registry.componentWithStatus(listOf(LifecycleStatus.DOWN))).isEmpty() + } +} \ No newline at end of file diff --git a/simulator/runtime/src/main/kotlin/net/corda/simulator/runtime/ledger/utxo/SimUtxoLedgerService.kt b/simulator/runtime/src/main/kotlin/net/corda/simulator/runtime/ledger/utxo/SimUtxoLedgerService.kt index d0128c23897..9cdc1937029 100644 --- a/simulator/runtime/src/main/kotlin/net/corda/simulator/runtime/ledger/utxo/SimUtxoLedgerService.kt +++ b/simulator/runtime/src/main/kotlin/net/corda/simulator/runtime/ledger/utxo/SimUtxoLedgerService.kt @@ -29,6 +29,7 @@ import net.corda.v5.membership.NotaryInfo /** * Simulator implementation of [UtxoLedgerService] */ +@Suppress("TooManyFunctions") class SimUtxoLedgerService( member: MemberX500Name, private val fiber: SimFiber, @@ -129,6 +130,10 @@ class SimUtxoLedgerService( return stateAndRefs } + override fun findUnconsumedStatesByExactType(type: Class): List> { + TODO("Not implemented yet") + } + /** * Resolves [StateRef] list to [StateAndRef] list */ diff --git a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/InitialRbacPlugin.kt b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/InitialRbacPlugin.kt index bc399faf6a1..bc710228293 100644 --- a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/InitialRbacPlugin.kt +++ b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/InitialRbacPlugin.kt @@ -1,6 +1,7 @@ package net.corda.cli.plugin.initialRbac import net.corda.cli.api.CordaCliPlugin +import net.corda.cli.plugin.initialRbac.commands.AllClusterRolesSubcommand import net.corda.cli.plugin.initialRbac.commands.UserAdminSubcommand import net.corda.cli.plugin.initialRbac.commands.CordaDeveloperSubcommand import net.corda.cli.plugin.initialRbac.commands.FlowExecutorSubcommand @@ -22,7 +23,8 @@ class InitialRbacPlugin : Plugin() { @CommandLine.Command( name = "initial-rbac", subcommands = [UserAdminSubcommand::class, VNodeCreatorSubcommand::class, - CordaDeveloperSubcommand::class, FlowExecutorSubcommand::class], + CordaDeveloperSubcommand::class, FlowExecutorSubcommand::class, + AllClusterRolesSubcommand::class], description = ["Creates common RBAC roles"] ) class PluginEntryPoint : CordaCliPlugin diff --git a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/AllClusterRolesSubcommand.kt b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/AllClusterRolesSubcommand.kt new file mode 100644 index 00000000000..814646ca6d6 --- /dev/null +++ b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/AllClusterRolesSubcommand.kt @@ -0,0 +1,35 @@ +package net.corda.cli.plugin.initialRbac.commands + +import net.corda.cli.plugins.common.RestCommand +import picocli.CommandLine +import java.util.concurrent.Callable +import kotlin.reflect.KMutableProperty +import kotlin.reflect.full.declaredMemberProperties + +@CommandLine.Command( + name = "all-cluster-roles", + description = ["""Creates all of the cluster-scoped roles: + - '$CORDA_DEV_ROLE' + - '$USER_ADMIN_ROLE' + - '$VNODE_CREATOR_ROLE'"""] +) +class AllClusterRolesSubcommand : RestCommand(), Callable { + + override fun call(): Int { + // If a subcommand fails with a return code of 5 (role already exists), + // continue on to process the other roles. All other failures + // (e.g. due to lack of connectivity) result in an exception being propagated. + return setProperties(CordaDeveloperSubcommand()).call() + + setProperties(UserAdminSubcommand()).call() + + setProperties(VNodeCreatorSubcommand()).call() + } + + private fun setProperties(other: T): T { + RestCommand::class.declaredMemberProperties.forEach { property -> + if (property is KMutableProperty<*>) { + property.setter.call(other, property.get(this)) + } + } + return other + } +} \ No newline at end of file diff --git a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/CordaDeveloperSubcommand.kt b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/CordaDeveloperSubcommand.kt index 068fbe4999b..3ca091826fb 100644 --- a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/CordaDeveloperSubcommand.kt +++ b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/CordaDeveloperSubcommand.kt @@ -7,7 +7,7 @@ import net.corda.rbac.schema.RbacKeys.VNODE_SHORT_HASH_REGEX import picocli.CommandLine import java.util.concurrent.Callable -private const val CORDA_DEV_ROLE = "CordaDeveloperRole" +const val CORDA_DEV_ROLE = "CordaDeveloperRole" @CommandLine.Command( name = "corda-developer", diff --git a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/UserAdminSubcommand.kt b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/UserAdminSubcommand.kt index 151d1a39d18..0065dfa5765 100644 --- a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/UserAdminSubcommand.kt +++ b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/UserAdminSubcommand.kt @@ -8,7 +8,7 @@ import net.corda.rbac.schema.RbacKeys.UUID_REGEX import picocli.CommandLine import java.util.concurrent.Callable -private const val USER_ADMIN_ROLE = "UserAdminRole" +const val USER_ADMIN_ROLE = "UserAdminRole" @CommandLine.Command( name = "user-admin", diff --git a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/VNodeCreatorSubcommand.kt b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/VNodeCreatorSubcommand.kt index d6fc348c095..adbcadc2978 100644 --- a/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/VNodeCreatorSubcommand.kt +++ b/tools/plugins/initial-rbac/src/main/kotlin/net/corda/cli/plugin/initialRbac/commands/VNodeCreatorSubcommand.kt @@ -9,7 +9,7 @@ import net.corda.rbac.schema.RbacKeys.VNODE_STATE_REGEX import picocli.CommandLine import java.util.concurrent.Callable -private const val VNODE_CREATOR_ROLE = "VNodeCreatorRole" +const val VNODE_CREATOR_ROLE = "VNodeCreatorRole" @CommandLine.Command( name = "vnode-creator",