new upstream change type — updated, triggered when upstream methods changed (#148)

This commit is contained in:
Vyacheslav Shebanov
2023-02-23 20:25:46 +02:00
committed by GitHub
parent 1698bf7002
commit 1ec3d44484
9 changed files with 93 additions and 37 deletions

View File

@@ -45,6 +45,11 @@ class UpstreamChangeEvent(
*/ */
ADDED, ADDED,
/**
* Some upstream details changed
*/
UPDATED,
/** /**
* Upstream become available after being temporally off * Upstream become available after being temporally off
*/ */

View File

@@ -361,28 +361,35 @@ abstract class Multistream(
val chain = event.chain val chain = event.chain
if (this.chain == chain) { if (this.chain == chain) {
eventLock.withLock { eventLock.withLock {
if (event.type == UpstreamChangeEvent.ChangeType.REMOVED) { when (event.type) {
removeUpstream(event.upstream.getId()).takeIf { it }?.let { UpstreamChangeEvent.ChangeType.REVALIDATED -> {}
try { UpstreamChangeEvent.ChangeType.UPDATED -> {
removedUpstreams.emitNext(event.upstream) { _, res -> res == Sinks.EmitResult.FAIL_NON_SERIALIZED } onUpstreamsUpdated()
log.info("Upstream ${event.upstream.getId()} with chain $chain has been removed")
} catch (e: Sinks.EmissionException) {
log.error("error during event processing $event", e)
}
} }
} else { UpstreamChangeEvent.ChangeType.ADDED -> {
if (event.upstream is CachesEnabled) {
event.upstream.setCaches(caches)
}
addUpstream(event.upstream).takeIf { it }?.let {
if (!started) { if (!started) {
start() start()
} }
try { if (event.upstream is CachesEnabled) {
addedUpstreams.emitNext(event.upstream) { _, res -> res == Sinks.EmitResult.FAIL_NON_SERIALIZED } event.upstream.setCaches(caches)
log.info("Upstream ${event.upstream.getId()} with chain $chain has been added") }
} catch (e: Sinks.EmissionException) { addUpstream(event.upstream).takeIf { it }?.let {
log.error("error during event processing $event", e) try {
addedUpstreams.emitNext(event.upstream) { _, res -> res == Sinks.EmitResult.FAIL_NON_SERIALIZED }
log.info("Upstream ${event.upstream.getId()} with chain $chain has been added")
} catch (e: Sinks.EmissionException) {
log.error("error during event processing $event", e)
}
}
}
UpstreamChangeEvent.ChangeType.REMOVED -> {
removeUpstream(event.upstream.getId()).takeIf { it }?.let {
try {
removedUpstreams.emitNext(event.upstream) { _, res -> res == Sinks.EmitResult.FAIL_NON_SERIALIZED }
log.info("Upstream ${event.upstream.getId()} with chain $chain has been removed")
} catch (e: Sinks.EmissionException) {
log.error("error during event processing $event", e)
}
} }
} }
} }

View File

@@ -149,9 +149,11 @@ class BitcoinGrpcUpstream(
override fun stop() { override fun stop() {
} }
override fun update(conf: BlockchainOuterClass.DescribeChain) { override fun update(conf: BlockchainOuterClass.DescribeChain): Boolean {
upstreamStatus.update(conf) val newCapabilities = RemoteCapabilities.extract(conf)
this.capabilities = RemoteCapabilities.extract(conf)
conf.status?.let { status -> onStatus(status) } conf.status?.let { status -> onStatus(status) }
return (upstreamStatus.update(conf) || (newCapabilities != capabilities)).also {
capabilities = newCapabilities
}
} }
} }

View File

@@ -131,10 +131,12 @@ open class EthereumGrpcUpstream(
override fun stop() { override fun stop() {
} }
override fun update(conf: BlockchainOuterClass.DescribeChain) { override fun update(conf: BlockchainOuterClass.DescribeChain): Boolean {
upstreamStatus.update(conf) val newCapabilities = RemoteCapabilities.extract(conf)
capabilities = RemoteCapabilities.extract(conf)
conf.status?.let { status -> onStatus(status) } conf.status?.let { status -> onStatus(status) }
return (upstreamStatus.update(conf) || (newCapabilities != capabilities)).also {
capabilities = newCapabilities
}
} }
override fun getQuorumByLabel(): QuorumForLabels { override fun getQuorumByLabel(): QuorumForLabels {

View File

@@ -125,10 +125,12 @@ open class EthereumPosGrpcUpstream(
override fun stop() { override fun stop() {
} }
override fun update(conf: BlockchainOuterClass.DescribeChain) { override fun update(conf: BlockchainOuterClass.DescribeChain): Boolean {
upstreamStatus.update(conf) val newCapabilities = RemoteCapabilities.extract(conf)
capabilities = RemoteCapabilities.extract(conf)
conf.status?.let { status -> onStatus(status) } conf.status?.let { status -> onStatus(status) }
return (upstreamStatus.update(conf) || (newCapabilities != capabilities)).also {
capabilities = newCapabilities
}
} }
override fun getQuorumByLabel(): QuorumForLabels { override fun getQuorumByLabel(): QuorumForLabels {

View File

@@ -27,7 +27,7 @@ interface GrpcUpstream : Upstream {
* Update the configuration of the upstream with the new data. * Update the configuration of the upstream with the new data.
* Called on the first creation, and each time a new state received from upstream * Called on the first creation, and each time a new state received from upstream
*/ */
fun update(conf: BlockchainOuterClass.DescribeChain) fun update(conf: BlockchainOuterClass.DescribeChain): Boolean
fun getBlockchainApi(): ReactorBlockchainGrpc.ReactorBlockchainStub fun getBlockchainApi(): ReactorBlockchainGrpc.ReactorBlockchainStub

View File

@@ -35,7 +35,7 @@ class GrpcUpstreamStatus(
private val nodes = AtomicReference(QuorumForLabels()) private val nodes = AtomicReference(QuorumForLabels())
private var targets: CallMethods? = null private var targets: CallMethods? = null
fun update(conf: BlockchainOuterClass.DescribeChain) { fun update(conf: BlockchainOuterClass.DescribeChain): Boolean {
val updateLabels = ArrayList<UpstreamsConfig.Labels>() val updateLabels = ArrayList<UpstreamsConfig.Labels>()
val updateNodes = QuorumForLabels() val updateNodes = QuorumForLabels()
@@ -55,7 +55,9 @@ class GrpcUpstreamStatus(
this.nodes.set(updateNodes) this.nodes.set(updateNodes)
this.allLabels.set(Collections.unmodifiableCollection(updateLabels)) this.allLabels.set(Collections.unmodifiableCollection(updateLabels))
val changed = conf.supportedMethodsList.toSet() != this.targets?.getSupportedMethods()
this.targets = DirectCallMethods(conf.supportedMethodsList.toSet()) this.targets = DirectCallMethods(conf.supportedMethodsList.toSet())
return changed
} }
fun getLabels(): Collection<UpstreamsConfig.Labels> { fun getLabels(): Collection<UpstreamsConfig.Labels> {

View File

@@ -149,8 +149,10 @@ class GrpcUpstreams(
try { try {
val chain = Chain.byId(chainDetails.chain.number) val chain = Chain.byId(chainDetails.chain.number)
val up = getOrCreate(chain) val up = getOrCreate(chain)
(up.upstream as GrpcUpstream).update(chainDetails) val changed = (up.upstream as GrpcUpstream).update(chainDetails)
up up.takeUnless {
changed && it.type == UpstreamChangeEvent.ChangeType.REVALIDATED
} ?: UpstreamChangeEvent(up.chain, up.upstream, UpstreamChangeEvent.ChangeType.UPDATED)
} catch (e: Throwable) { } catch (e: Throwable) {
log.warn("Skip unsupported upstream ${chainDetails.chain} on $id: ${e.message}") log.warn("Skip unsupported upstream ${chainDetails.chain} on $id: ${e.message}")
null null
@@ -161,16 +163,20 @@ class GrpcUpstreams(
it.type == UpstreamChangeEvent.ChangeType.ADDED it.type == UpstreamChangeEvent.ChangeType.ADDED
} }
val updated = current.filter {
it.type == UpstreamChangeEvent.ChangeType.UPDATED
}
val removed = known.filterNot { kv -> val removed = known.filterNot { kv ->
val stillCurrent = current.any { c -> c.chain == kv.key } val stillCurrent = current.any { c -> c.chain == kv.key }
stillCurrent stillCurrent
}.map { }.map {
UpstreamChangeEvent(it.key, known.remove(it.key)!!, UpstreamChangeEvent.ChangeType.REMOVED) UpstreamChangeEvent(it.key, known.remove(it.key)!!, UpstreamChangeEvent.ChangeType.REMOVED)
} }
if (removed.isNotEmpty() || added.isNotEmpty()) { if (removed.isNotEmpty() || added.isNotEmpty() || updated.isNotEmpty()) {
log.info("Finished processing of grpc upstream description for $id with content delta added ${added.map { it.chain }} and removed ${removed.map { it.chain }}") log.info("Finished processing of grpc upstream description for $id with content delta added ${added.map { it.chain }}, updated ${updated.map { it.chain }} and removed ${removed.map { it.chain }}")
} }
return Flux.fromIterable(removed + added) return Flux.fromIterable(removed + added + updated)
} }
private fun withTls(auth: AuthConfig.ClientTlsAuth): SslContext { private fun withTls(auth: AuthConfig.ClientTlsAuth): SslContext {

View File

@@ -65,7 +65,7 @@ class GrpcUpstreamStatusSpec extends Specification {
// more values // more values
when: when:
status.update( def result = status.update(
BlockchainOuterClass.DescribeChain.newBuilder() BlockchainOuterClass.DescribeChain.newBuilder()
.addNodes( .addNodes(
BlockchainOuterClass.NodeDetails.newBuilder() BlockchainOuterClass.NodeDetails.newBuilder()
@@ -81,6 +81,7 @@ class GrpcUpstreamStatusSpec extends Specification {
) )
act = status.getLabels() act = status.getLabels()
then: then:
!result
act.toList() == [ act.toList() == [
UpstreamsConfig.Labels.fromMap([test1: "bar", test2: "baz"]) UpstreamsConfig.Labels.fromMap([test1: "bar", test2: "baz"])
] ]
@@ -109,7 +110,7 @@ class GrpcUpstreamStatusSpec extends Specification {
// replace with new value // replace with new value
when: when:
status.update( def result = status.update(
BlockchainOuterClass.DescribeChain.newBuilder() BlockchainOuterClass.DescribeChain.newBuilder()
.addNodes( .addNodes(
BlockchainOuterClass.NodeDetails.newBuilder() BlockchainOuterClass.NodeDetails.newBuilder()
@@ -124,6 +125,7 @@ class GrpcUpstreamStatusSpec extends Specification {
) )
act = status.getLabels() act = status.getLabels()
then: then:
!result
act.toList() == [ act.toList() == [
UpstreamsConfig.Labels.fromMap([test: "bar", fix: "value"]) UpstreamsConfig.Labels.fromMap([test: "bar", fix: "value"])
] ]
@@ -202,7 +204,7 @@ class GrpcUpstreamStatusSpec extends Specification {
setup: setup:
def status = new GrpcUpstreamStatus(null) def status = new GrpcUpstreamStatus(null)
when: when:
status.update( def result = status.update(
BlockchainOuterClass.DescribeChain.newBuilder() BlockchainOuterClass.DescribeChain.newBuilder()
.addAllSupportedMethods([ .addAllSupportedMethods([
"test_1", "test_1",
@@ -212,7 +214,35 @@ class GrpcUpstreamStatusSpec extends Specification {
) )
def act = status.getCallMethods() def act = status.getCallMethods()
then: then:
result
act.supportedMethods == ["test_1", "test_2"].toSet() act.supportedMethods == ["test_1", "test_2"].toSet()
act instanceof DirectCallMethods act instanceof DirectCallMethods
} }
def "Updates with new methods"() {
setup:
def status = new GrpcUpstreamStatus(null)
status.update(
BlockchainOuterClass.DescribeChain.newBuilder()
.addAllSupportedMethods([
"test_1",
"test_2"
])
.build()
)
when:
def result = status.update(
BlockchainOuterClass.DescribeChain.newBuilder()
.addAllSupportedMethods([
"test_1",
"test_2",
"test_3"
])
.build())
def act = status.getCallMethods()
then:
result
act.supportedMethods == ["test_1", "test_2", "test_3"].toSet()
act instanceof DirectCallMethods
}
} }