Check if config was reloaded, remove all upstream metrics if it was r… (#327)

This commit is contained in:
KirillPamPam
2023-10-25 15:28:51 +04:00
committed by GitHub
parent 788db75b73
commit 8aa89a1945
12 changed files with 74 additions and 26 deletions

View File

@@ -43,9 +43,11 @@ class ReloadConfigSetup(
try { try {
log.info("Reloading config...") log.info("Reloading config...")
reloadConfig() if (reloadConfig()) {
log.info("Config is reloaded")
log.info("Config is reloaded") } else {
log.info("There is nothing to reload, config is the same")
}
} finally { } finally {
reloadLock.unlock() reloadLock.unlock()
} }
@@ -54,10 +56,14 @@ class ReloadConfigSetup(
} }
} }
private fun reloadConfig() { private fun reloadConfig(): Boolean {
val newUpstreamsConfig = reloadConfigService.readUpstreamsConfig() val newUpstreamsConfig = reloadConfigService.readUpstreamsConfig()
val currentUpstreamsConfig = reloadConfigService.currentUpstreamsConfig() val currentUpstreamsConfig = reloadConfigService.currentUpstreamsConfig()
if (newUpstreamsConfig == currentUpstreamsConfig) {
return false
}
val chainsToReload = analyzeDefaultOptions( val chainsToReload = analyzeDefaultOptions(
currentUpstreamsConfig.defaultOptions, currentUpstreamsConfig.defaultOptions,
newUpstreamsConfig.defaultOptions, newUpstreamsConfig.defaultOptions,
@@ -76,6 +82,8 @@ class ReloadConfigSetup(
reloadConfigUpstreamService.reloadUpstreams(chainsToReload, upstreamsToRemove, upstreamsToAdd, newUpstreamsConfig) reloadConfigUpstreamService.reloadUpstreams(chainsToReload, upstreamsToRemove, upstreamsToAdd, newUpstreamsConfig)
reloadConfigService.updateUpstreamsConfig(newUpstreamsConfig) reloadConfigService.updateUpstreamsConfig(newUpstreamsConfig)
return true
} }
private fun analyzeUpstreams( private fun analyzeUpstreams(

View File

@@ -10,7 +10,7 @@ import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class ReloadConfigUpstreamService( open class ReloadConfigUpstreamService(
private val eventPublisher: ApplicationEventPublisher, private val eventPublisher: ApplicationEventPublisher,
private val multistreamHolder: CurrentMultistreamHolder, private val multistreamHolder: CurrentMultistreamHolder,
private val configuredUpstreams: ConfiguredUpstreams, private val configuredUpstreams: ConfiguredUpstreams,

View File

@@ -4,3 +4,7 @@ import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
typealias JsonRpcReader = Reader<JsonRpcRequest, JsonRpcResponse> typealias JsonRpcReader = Reader<JsonRpcRequest, JsonRpcResponse>
interface JsonRpcHttpReader : JsonRpcReader {
fun onStop()
}

View File

@@ -38,7 +38,6 @@ import io.emeraldpay.dshackle.config.UpstreamsConfig.HttpEndpoint
import io.emeraldpay.dshackle.config.UpstreamsConfig.RpcConnection import io.emeraldpay.dshackle.config.UpstreamsConfig.RpcConnection
import io.emeraldpay.dshackle.foundation.ChainOptions import io.emeraldpay.dshackle.foundation.ChainOptions
import io.emeraldpay.dshackle.foundation.ChainOptions.Options import io.emeraldpay.dshackle.foundation.ChainOptions.Options
import io.emeraldpay.dshackle.reader.JsonRpcReader
import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.BlockValidator
import io.emeraldpay.dshackle.upstream.CallTargetsHolder import io.emeraldpay.dshackle.upstream.CallTargetsHolder
import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Head
@@ -343,7 +342,7 @@ open class ConfiguredUpstreams(
log.warn("Upstream doesn't have API configuration") log.warn("Upstream doesn't have API configuration")
return null return null
} }
val directApi: JsonRpcReader = httpFactory.create(config.id, chain) val directApi = httpFactory.create(config.id, chain)
val esplora = conn.esplora?.let { endpoint -> val esplora = conn.esplora?.let { endpoint ->
val tls = endpoint.tls?.let { tls -> val tls = endpoint.tls?.let { tls ->
tls.ca?.let { ca -> tls.ca?.let { ca ->

View File

@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice
import io.micrometer.core.instrument.Gauge import io.micrometer.core.instrument.Gauge
import io.micrometer.core.instrument.Meter
import io.micrometer.core.instrument.Metrics import io.micrometer.core.instrument.Metrics
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import reactor.core.Disposable import reactor.core.Disposable
@@ -32,7 +33,6 @@ import java.util.concurrent.Executors
import java.util.concurrent.Future import java.util.concurrent.Future
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
abstract class AbstractHead @JvmOverloads constructor( abstract class AbstractHead @JvmOverloads constructor(
@@ -57,14 +57,24 @@ abstract class AbstractHead @JvmOverloads constructor(
private var future: Future<*>? = null private var future: Future<*>? = null
private val delayed = AtomicBoolean(false) private val delayed = AtomicBoolean(false)
private val metrics = mutableSetOf<Meter>()
init { init {
val className = this.javaClass.simpleName val className = this.javaClass.simpleName
Gauge.builder("stuck_head", delayed) { Gauge.builder("stuck_head", delayed) {
if (it.get()) 1.0 else 0.0 if (it.get()) 1.0 else 0.0
}.tag("upstream", upstreamId).tag("class", className).register(Metrics.globalRegistry) }
.tag("upstream", upstreamId)
.tag("class", className)
.register(Metrics.globalRegistry)
.also { metrics.add(it) }
Gauge.builder("current_head", forkChoice) { Gauge.builder("current_head", forkChoice) {
it.getHead()?.height?.toDouble() ?: 0.0 it.getHead()?.height?.toDouble() ?: 0.0
}.tag("upstream", upstreamId).tag("class", className).register(Metrics.globalRegistry) }
.tag("upstream", upstreamId)
.tag("class", className)
.register(Metrics.globalRegistry)
.also { metrics.add(it) }
} }
fun follow(source: Flux<BlockContainer>): Disposable { fun follow(source: Flux<BlockContainer>): Disposable {
@@ -147,6 +157,7 @@ abstract class AbstractHead @JvmOverloads constructor(
it.cancel(true) it.cancel(true)
} }
future = null future = null
metrics.forEach { Metrics.globalRegistry.remove(it) }
} }
protected open fun onNoHeadUpdates() { protected open fun onNoHeadUpdates() {
@@ -176,10 +187,4 @@ abstract class AbstractHead @JvmOverloads constructor(
) )
} }
} }
private fun toHeadCountMetric(counter: AtomicInteger, status: String) {
Gauge.builder("head_count", counter) {
it.get().toDouble()
}.tag("class", this.javaClass.simpleName).tag("status", status).register(Metrics.globalRegistry)
}
} }

View File

@@ -1,8 +1,8 @@
package io.emeraldpay.dshackle.upstream package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.reader.JsonRpcReader import io.emeraldpay.dshackle.reader.JsonRpcHttpReader
interface HttpFactory { interface HttpFactory {
fun create(id: String?, chain: Chain): JsonRpcReader fun create(id: String?, chain: Chain): JsonRpcHttpReader
} }

View File

@@ -2,7 +2,7 @@ package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.config.AuthConfig import io.emeraldpay.dshackle.config.AuthConfig
import io.emeraldpay.dshackle.reader.JsonRpcReader import io.emeraldpay.dshackle.reader.JsonRpcHttpReader
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcHttpClient import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcHttpClient
import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics
import io.micrometer.core.instrument.Counter import io.micrometer.core.instrument.Counter
@@ -15,7 +15,7 @@ open class HttpRpcFactory(
private val basicAuth: AuthConfig.ClientBasicAuth?, private val basicAuth: AuthConfig.ClientBasicAuth?,
private val tls: ByteArray?, private val tls: ByteArray?,
) : HttpFactory { ) : HttpFactory {
override fun create(id: String?, chain: Chain): JsonRpcReader { override fun create(id: String?, chain: Chain): JsonRpcHttpReader {
val metricsTags = listOf( val metricsTags = listOf(
// "unknown" is not supposed to happen // "unknown" is not supposed to happen
Tag.of("upstream", id ?: "unknown"), Tag.of("upstream", id ?: "unknown"),

View File

@@ -19,6 +19,7 @@ import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.ChainsConfig
import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.foundation.ChainOptions import io.emeraldpay.dshackle.foundation.ChainOptions
import io.emeraldpay.dshackle.reader.JsonRpcHttpReader
import io.emeraldpay.dshackle.reader.JsonRpcReader import io.emeraldpay.dshackle.reader.JsonRpcReader
import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.startup.QuorumForLabels
import io.emeraldpay.dshackle.upstream.Capability import io.emeraldpay.dshackle.upstream.Capability
@@ -32,7 +33,7 @@ import reactor.core.Disposable
open class BitcoinRpcUpstream( open class BitcoinRpcUpstream(
id: String, id: String,
chain: Chain, chain: Chain,
private val directApi: JsonRpcReader, private val directApi: JsonRpcHttpReader,
private val head: Head, private val head: Head,
options: ChainOptions.Options, options: ChainOptions.Options,
role: UpstreamsConfig.UpstreamRole, role: UpstreamsConfig.UpstreamRole,
@@ -115,5 +116,6 @@ open class BitcoinRpcUpstream(
head.stop() head.stop()
} }
validatorSubscription?.dispose() validatorSubscription?.dispose()
directApi.onStop()
} }
} }

View File

@@ -2,6 +2,7 @@ package io.emeraldpay.dshackle.upstream.generic.connectors
import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.cache.CachesEnabled
import io.emeraldpay.dshackle.reader.JsonRpcHttpReader
import io.emeraldpay.dshackle.reader.JsonRpcReader import io.emeraldpay.dshackle.reader.JsonRpcReader
import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.BlockValidator
import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.DefaultUpstream
@@ -31,7 +32,7 @@ import java.time.Duration
class GenericRpcConnector( class GenericRpcConnector(
connectorType: ConnectorMode, connectorType: ConnectorMode,
private val directReader: JsonRpcReader, private val directReader: JsonRpcHttpReader,
wsFactory: WsConnectionPoolFactory?, wsFactory: WsConnectionPoolFactory?,
upstream: DefaultUpstream, upstream: DefaultUpstream,
forkChoice: ForkChoice, forkChoice: ForkChoice,
@@ -137,6 +138,7 @@ class GenericRpcConnector(
head.stop() head.stop()
} }
pool?.close() pool?.close()
directReader.onStop()
} }
override fun getIngressReader(): JsonRpcReader { override fun getIngressReader(): JsonRpcReader {

View File

@@ -16,9 +16,10 @@
package io.emeraldpay.dshackle.upstream.rpcclient package io.emeraldpay.dshackle.upstream.rpcclient
import io.emeraldpay.dshackle.config.AuthConfig import io.emeraldpay.dshackle.config.AuthConfig
import io.emeraldpay.dshackle.reader.JsonRpcReader import io.emeraldpay.dshackle.reader.JsonRpcHttpReader
import io.emeraldpay.etherjar.rpc.RpcException import io.emeraldpay.etherjar.rpc.RpcException
import io.emeraldpay.etherjar.rpc.RpcResponseError import io.emeraldpay.etherjar.rpc.RpcResponseError
import io.micrometer.core.instrument.Metrics
import io.netty.buffer.Unpooled import io.netty.buffer.Unpooled
import io.netty.handler.codec.http.HttpHeaderNames import io.netty.handler.codec.http.HttpHeaderNames
import io.netty.handler.codec.http.HttpHeaders import io.netty.handler.codec.http.HttpHeaders
@@ -47,7 +48,7 @@ class JsonRpcHttpClient(
private val metrics: RpcMetrics, private val metrics: RpcMetrics,
basicAuth: AuthConfig.ClientBasicAuth? = null, basicAuth: AuthConfig.ClientBasicAuth? = null,
tlsCAAuth: ByteArray? = null, tlsCAAuth: ByteArray? = null,
) : JsonRpcReader { ) : JsonRpcHttpReader {
private val parser = ResponseRpcParser() private val parser = ResponseRpcParser()
private val httpClient: HttpClient private val httpClient: HttpClient
@@ -104,6 +105,11 @@ class JsonRpcHttpClient(
}.single() }.single()
} }
override fun onStop() {
Metrics.globalRegistry.remove(metrics.timer)
Metrics.globalRegistry.remove(metrics.fails)
}
override fun read(key: JsonRpcRequest): Mono<JsonRpcResponse> { override fun read(key: JsonRpcRequest): Mono<JsonRpcResponse> {
val startTime = StopWatch() val startTime = StopWatch()
return Mono.just(key) return Mono.just(key)

View File

@@ -19,6 +19,7 @@ package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.ChainsConfig
import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.reader.JsonRpcHttpReader
import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.startup.QuorumForLabels
import io.emeraldpay.dshackle.test.EthereumApiStub import io.emeraldpay.dshackle.test.EthereumApiStub
import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.test.TestingCommons
@@ -51,7 +52,7 @@ class FilteredApisSpec extends Specification {
[test: "baz"] [test: "baz"]
].collect { ].collect {
def httpFactory = Mock(HttpFactory) { def httpFactory = Mock(HttpFactory) {
create(_, _) >> TestingCommons.api().tap { it.id = "${i++}" } create(_, _) >> Stub(JsonRpcHttpReader)
} }
def connectorFactory = new GenericConnectorFactory( def connectorFactory = new GenericConnectorFactory(
GenericConnectorFactory.ConnectorMode.RPC_ONLY, GenericConnectorFactory.ConnectorMode.RPC_ONLY,

View File

@@ -17,8 +17,10 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times import org.mockito.kotlin.times
import org.mockito.kotlin.verify import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever import org.mockito.kotlin.whenever
@@ -99,7 +101,7 @@ class ReloadConfigTest {
} }
@Test @Test
fun `stop multistream of there are no upstreams left`() { fun `stop multistream if there are no upstreams left`() {
val up1 = upstream("local1") val up1 = upstream("local1")
val up2 = upstream("local2") val up2 = upstream("local2")
val up3 = upstream("local3") val up3 = upstream("local3")
@@ -154,6 +156,25 @@ class ReloadConfigTest {
) )
} }
@Test
fun `reload the same config cause to nothing`() {
val initialConfigFile = ResourceUtils.getFile("classpath:configs/upstreams-initial.yaml")
val initialConfig = upstreamsConfigReader.read(initialConfigFile.inputStream())!!
mainConfig.upstreams = initialConfig
val reloadConfigUpstreamService = mock<ReloadConfigUpstreamService>()
val reloadConfig = ReloadConfigSetup(reloadConfigService, reloadConfigUpstreamService)
whenever(config.getConfigPath()).thenReturn(initialConfigFile)
reloadConfig.handle(Signal("HUP"))
verify(reloadConfigUpstreamService, never()).reloadUpstreams(any(), any(), any(), any())
assertEquals(initialConfig, mainConfig.upstreams)
}
private fun upstream(id: String): Upstream = private fun upstream(id: String): Upstream =
mock { mock {
on { getId() } doReturn id on { getId() } doReturn id