solution: introduce labels for upstreams

This commit is contained in:
Igor Artamonov
2019-08-01 23:43:04 -04:00
parent 045c2fb3fa
commit 2c5551d818
13 changed files with 307 additions and 35 deletions

View File

@@ -3,6 +3,7 @@ package io.emeraldpay.dshackle.config
import java.net.URI
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
class UpstreamsConfig {
var version: String? = null
@@ -50,10 +51,10 @@ class UpstreamsConfig {
class Upstream<T : UpstreamConnection> {
var id: String? = null
var chain: String? = null
var provider: String? = null
var options: Options? = null
var isEnabled = true
var connection: T? = null
val labels = Labels()
}
open class UpstreamConnection
@@ -91,6 +92,10 @@ class UpstreamsConfig {
var key: String? = null
}
//TODO make it unmodifiable after initial load
class Labels: HashMap<String, String>() {
}
enum class UpstreamType private constructor(vararg code: String) {
ETHEREUM_JSON_RPC("ethereum"),
DSHACKLE("dshackle", "grpc"),

View File

@@ -1,11 +1,13 @@
package io.emeraldpay.dshackle.config
import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.nodes.CollectionNode
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.nodes.ScalarNode
import reactor.util.function.Tuples
import java.io.InputStream
import java.io.InputStreamReader
import java.lang.IllegalArgumentException
@@ -44,9 +46,8 @@ class UpstreamsConfigReader {
if (hasAny(connNode, "ethereum")) {
val connConfigNode = getMapping(connNode, "ethereum")!!
val upstream = UpstreamsConfig.Upstream<UpstreamsConfig.EthereumConnection>()
upstream.id = getValueAsString(upNode, "id")
upstream.provider = getValueAsString(upNode, "provider")
upstream.chain = getValueAsString(upNode, "chain")
readUpstreamCommon(upNode, upstream)
readUpstreamEthereum(upNode, upstream)
config.upstreams.add(upstream)
val connection = UpstreamsConfig.EthereumConnection()
upstream.connection = connection
@@ -70,8 +71,8 @@ class UpstreamsConfigReader {
} else if (hasAny(connNode, "grpc")) {
val connConfigNode = getMapping(connNode, "grpc")!!
val upstream = UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>()
upstream.id = getValueAsString(upNode, "id")
upstream.provider = getValueAsString(upNode, "provider")
readUpstreamCommon(upNode, upstream)
readUpstreamGrpc(upNode, upstream)
config.upstreams.add(upstream)
val connection = UpstreamsConfig.GrpcConnection()
upstream.connection = connection
@@ -88,6 +89,35 @@ class UpstreamsConfigReader {
return config
}
internal fun readUpstreamCommon(upNode: MappingNode, upstream: UpstreamsConfig.Upstream<*>) {
upstream.id = getValueAsString(upNode, "id")
}
internal fun readUpstreamGrpc(upNode: MappingNode, upstream: UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>) {
if (hasAny(upNode, "labels")) {
log.warn("Labels are not applied to gRPC upstream")
}
if (hasAny(upNode, "chain")) {
log.warn("Chain is not applied to gRPC upstream")
}
}
internal fun readUpstreamEthereum(upNode: MappingNode, upstream: UpstreamsConfig.Upstream<UpstreamsConfig.EthereumConnection>) {
upstream.chain = getValueAsString(upNode, "chain")
if (hasAny(upNode, "labels")) {
getMapping(upNode, "labels")?.let { labels ->
labels.value.stream()
.filter { n -> n.keyNode is ScalarNode && n.valueNode is ScalarNode }
.map { n -> Tuples.of((n.keyNode as ScalarNode).value, (n.valueNode as ScalarNode).value)}
.map { kv -> Tuples.of(kv.t1.trim(), kv.t2.trim()) }
.filter { kv -> StringUtils.isNotEmpty(kv.t1) && StringUtils.isNotEmpty(kv.t2) }
.forEach { kv ->
upstream.labels[kv.t1] = kv.t2
}
}
}
}
private fun readAuth(authNode: MappingNode?): UpstreamsConfig.Auth? {
return getValueAsString(authNode, "type")?.let {
return when (it) {

View File

@@ -2,10 +2,7 @@ package io.emeraldpay.dshackle.rpc
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
import io.emeraldpay.dshackle.upstream.ConfiguredUpstreams
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.Upstreams
import io.emeraldpay.dshackle.upstream.*
import io.emeraldpay.grpc.Chain
import io.grpc.stub.StreamObserver
import org.springframework.beans.factory.annotation.Autowired
@@ -23,18 +20,34 @@ class Describe(
val resp = BlockchainOuterClass.DescribeResponse.newBuilder()
upstreams.getAvailable().forEach { chain ->
upstreams.getUpstream(chain)?.let { chainUpstreams ->
val status = subscribeStatus.chainStatus(chain, chainUpstreams.getAll())
val targets = chainUpstreams.getSupportedTargets()
val chainDescription = BlockchainOuterClass.DescribeChain.newBuilder()
.setChain(Common.ChainRef.forNumber(chain.id))
.addAllSupportedTargets(targets)
.setStatus(status)
chainUpstreams.getAll().let { ups ->
if (ups.isNotEmpty()) {
val status = subscribeStatus.chainStatus(chain, ups)
val targets = chainUpstreams.getSupportedTargets()
val chainDescription = BlockchainOuterClass.DescribeChain.newBuilder()
.setChain(Common.ChainRef.forNumber(chain.id))
.addAllSupportedTargets(targets)
.setStatus(status)
.build()
resp.addChains(chainDescription)
ups.forEach { up ->
val nodes = NodeDetailsList()
if (up is EthereumUpstream) {
nodes.add(up.node)
} else if (up is GrpcUpstream) {
nodes.add(up.getNodes())
}
nodes.getNodes().forEach { node ->
val nodeDetails = BlockchainOuterClass.NodeDetails.newBuilder()
.setQuorum(node.quorum)
.addAllLabels(node.labels.entries.map { label ->
BlockchainOuterClass.Label.newBuilder()
.setName(label.key)
.setValue(label.value)
.build()
})
chainDescription.addNodes(nodeDetails)
}
}
}
resp.addChains(chainDescription.build())
}
}
resp.build()

View File

@@ -34,7 +34,7 @@ class SubscribeStatus(
fun chainStatus(chain: Chain, ups: List<Upstream>): BlockchainOuterClass.ChainStatus {
val available = ups.map { u ->
u.getStatus()
}.min()!!
}.min() ?: UpstreamAvailability.UNAVAILABLE
val quorum = ups.filter {
it.getStatus() > UpstreamAvailability.UNAVAILABLE
}.count()

View File

@@ -12,8 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.env.Environment
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Repository
import reactor.core.publisher.Flux
import reactor.core.publisher.TopicProcessor
import reactor.core.publisher.toFlux
import java.io.File
import java.net.URI
@@ -47,11 +45,11 @@ open class ConfiguredUpstreams(
val options = (up.options ?: UpstreamsConfig.Options())
.merge(UpstreamsConfig.Options.getDefaults())
if (up.provider == "dshackle") {
if (up.connection is UpstreamsConfig.GrpcConnection) {
buildGrpcUpstream(up.connection as UpstreamsConfig.GrpcConnection, options)
} else {
val chain = chainNames[up.chain] ?: return
buildEthereumUpstream(up.connection as UpstreamsConfig.EthereumConnection, chain, options)
buildEthereumUpstream(up.connection as UpstreamsConfig.EthereumConnection, chain, options, up.labels)
}
}
}
@@ -93,7 +91,8 @@ open class ConfiguredUpstreams(
private fun buildEthereumUpstream(up: UpstreamsConfig.EthereumConnection,
chain: Chain,
options: UpstreamsConfig.Options) {
options: UpstreamsConfig.Options,
labels: UpstreamsConfig.Labels) {
var rpcApi: EthereumApi? = null
var wsApi: EthereumWs? = null
val urls = ArrayList<URI>()
@@ -115,7 +114,7 @@ open class ConfiguredUpstreams(
}
if (rpcApi != null) {
log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}")
getOrCreateUpstream(chain).addUpstream(EthereumUpstream(chain, rpcApi!!, wsApi, options))
getOrCreateUpstream(chain).addUpstream(EthereumUpstream(chain, rpcApi!!, wsApi, options, NodeDetailsList.NodeDetails(1, labels)))
}
}

View File

@@ -13,7 +13,8 @@ class EthereumUpstream(
val chain: Chain,
private val api: EthereumApi,
private val ethereumWs: EthereumWs? = null,
private val options: UpstreamsConfig.Options
private val options: UpstreamsConfig.Options,
val node: NodeDetailsList.NodeDetails
): Upstream {
override fun getSupportedTargets(): Set<String> {

View File

@@ -23,7 +23,6 @@ import java.time.Duration
import java.util.*
import java.util.concurrent.atomic.AtomicReference
import java.util.function.Function
import kotlin.collections.ArrayList
open class GrpcUpstream(
private val chain: Chain,
@@ -39,7 +38,8 @@ open class GrpcUpstream(
private val headBlock = AtomicReference<BlockJson<TransactionId>>(null)
private val streamBlocks: TopicProcessor<BlockJson<TransactionId>> = TopicProcessor.create()
private var status = AtomicReference<UpstreamAvailability>(UpstreamAvailability.UNAVAILABLE)
private val status = AtomicReference<UpstreamAvailability>(UpstreamAvailability.UNAVAILABLE)
private val nodes = AtomicReference<NodeDetailsList>(NodeDetailsList())
private val head = Head(this)
private val api: EthereumApi
private val statusStream: TopicProcessor<UpstreamAvailability> = TopicProcessor.create()
@@ -97,6 +97,18 @@ open class GrpcUpstream(
fun init(conf: BlockchainOuterClass.DescribeChain) {
supportedMethods.addAll(conf.supportedTargetsList)
val nodes = NodeDetailsList()
conf.nodesList.forEach { node ->
val node = NodeDetailsList.NodeDetails(node.quorum,
node.labelsList.let { provided ->
val labels = UpstreamsConfig.Labels()
provided.forEach { labels.put(it.name, it.value) }
labels
}
)
nodes.add(node)
}
this.nodes.set(nodes)
conf.status?.let { status -> onStatus(status) }
}
@@ -112,6 +124,11 @@ open class GrpcUpstream(
status.set(value)
statusStream.onNext(value)
}
fun getNodes(): NodeDetailsList {
return nodes.get()
}
// ------------------------------------------------------------------------------------------
override fun getSupportedTargets(): Set<String> {

View File

@@ -0,0 +1,43 @@
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.config.UpstreamsConfig
import java.util.*
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.collections.ArrayList
import kotlin.concurrent.read
import kotlin.concurrent.write
class NodeDetailsList {
private val lock = ReentrantReadWriteLock()
private val nodes = ArrayList<NodeDetails>()
fun add(node: NodeDetails) {
lock.read {
val existing = nodes.find { it.labels == node.labels }
lock.write {
if (existing != null) {
val merged = NodeDetails(existing.quorum + node.quorum, existing.labels)
nodes.remove(existing)
nodes.add(merged)
} else {
nodes.add(node)
}
}
}
}
fun add(nodes: NodeDetailsList) {
nodes.nodes.forEach { node -> this.add(node) }
}
fun getNodes(): List<NodeDetails> {
return Collections.unmodifiableList(nodes)
}
class NodeDetails(val quorum: Int, val labels: UpstreamsConfig.Labels) {
companion object {
}
}
}