@@ -16,12 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle
|
package io.emeraldpay.dshackle
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.config.CacheConfig
|
import io.emeraldpay.dshackle.config.*
|
||||||
import io.emeraldpay.dshackle.config.MainConfig
|
|
||||||
import io.emeraldpay.dshackle.config.MainConfigReader
|
|
||||||
import io.emeraldpay.dshackle.config.MonitoringConfig
|
|
||||||
import io.emeraldpay.dshackle.config.TokensConfig
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
@@ -120,4 +115,9 @@ open class Config(
|
|||||||
open fun monitoringConfig(@Autowired mainConfig: MainConfig): MonitoringConfig {
|
open fun monitoringConfig(@Autowired mainConfig: MainConfig): MonitoringConfig {
|
||||||
return mainConfig.monitoring
|
return mainConfig.monitoring
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun healthConfig(@Autowired mainConfig: MainConfig): HealthConfig {
|
||||||
|
return mainConfig.health
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 EmeraldPay, Inc
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.emeraldpay.dshackle.config
|
||||||
|
|
||||||
|
import io.emeraldpay.grpc.Chain
|
||||||
|
|
||||||
|
class HealthConfig {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun default(): HealthConfig {
|
||||||
|
return HealthConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var port: Int = 8082
|
||||||
|
var host: String = "127.0.0.1"
|
||||||
|
var path: String = "/health"
|
||||||
|
val chains = HashMap<Chain, ChainConfig>()
|
||||||
|
|
||||||
|
fun isEnabled(): Boolean {
|
||||||
|
return chains.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configs(): Collection<ChainConfig> {
|
||||||
|
return chains.values
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ChainConfig(
|
||||||
|
val blockchain: Chain,
|
||||||
|
val minAvailable: Int = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 EmeraldPay, Inc
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.emeraldpay.dshackle.config
|
||||||
|
|
||||||
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.yaml.snakeyaml.nodes.CollectionNode
|
||||||
|
import org.yaml.snakeyaml.nodes.MappingNode
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
class HealthConfigReader : YamlConfigReader(), ConfigReader<HealthConfig> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(HealthConfigReader::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(input: InputStream): HealthConfig {
|
||||||
|
val configNode = readNode(input)
|
||||||
|
return read(configNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: MappingNode?): HealthConfig {
|
||||||
|
return readInternal(getMapping(input, "health"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readInternal(input: MappingNode?): HealthConfig {
|
||||||
|
if (input == null) {
|
||||||
|
return HealthConfig.default()
|
||||||
|
}
|
||||||
|
val config = HealthConfig()
|
||||||
|
getValueAsString(input, "host")?.let {
|
||||||
|
config.host = it
|
||||||
|
}
|
||||||
|
getValueAsInt(input, "port")?.let {
|
||||||
|
config.port = it
|
||||||
|
}
|
||||||
|
getValueAsString(input, "path")?.let {
|
||||||
|
config.path = it
|
||||||
|
}
|
||||||
|
readBlockchains(config, getList<MappingNode>(input, "blockchains"))
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readBlockchains(healthConfig: HealthConfig, input: CollectionNode<MappingNode>?) {
|
||||||
|
if (input == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
input.value.forEach { conf ->
|
||||||
|
val chain = getValueAsString(conf, "chain")
|
||||||
|
?.let { getBlockchain(it) }
|
||||||
|
if (chain == null) {
|
||||||
|
log.warn("Blockchain is not specified for a Health Check")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
if (chain == Chain.UNSPECIFIED) {
|
||||||
|
log.warn("Using UNSPECIFIED blockchain for Health Check. Always fails")
|
||||||
|
}
|
||||||
|
if (healthConfig.chains.containsKey(chain)) {
|
||||||
|
log.warn("Duplicate Health Check config for $chain. Replace previous with new")
|
||||||
|
}
|
||||||
|
val minAvailable = getValueAsInt(conf, "min-available") ?: 1
|
||||||
|
healthConfig.chains[chain] = HealthConfig.ChainConfig(chain, minAvailable.coerceAtLeast(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,4 +25,5 @@ class MainConfig {
|
|||||||
var tokens: TokensConfig? = null
|
var tokens: TokensConfig? = null
|
||||||
var monitoring: MonitoringConfig = MonitoringConfig.default()
|
var monitoring: MonitoringConfig = MonitoringConfig.default()
|
||||||
var accessLogConfig: AccessLogConfig = AccessLogConfig.default()
|
var accessLogConfig: AccessLogConfig = AccessLogConfig.default()
|
||||||
|
var health: HealthConfig = HealthConfig.default()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class MainConfigReader(
|
|||||||
private val tokensConfigReader = TokensConfigReader()
|
private val tokensConfigReader = TokensConfigReader()
|
||||||
private val monitoringConfigReader = MonitoringConfigReader()
|
private val monitoringConfigReader = MonitoringConfigReader()
|
||||||
private val accessLogReader = AccessLogReader()
|
private val accessLogReader = AccessLogReader()
|
||||||
|
private val healthConfigReader = HealthConfigReader()
|
||||||
|
|
||||||
fun read(input: InputStream): MainConfig? {
|
fun read(input: InputStream): MainConfig? {
|
||||||
val configNode = readNode(input)
|
val configNode = readNode(input)
|
||||||
@@ -71,6 +72,9 @@ class MainConfigReader(
|
|||||||
accessLogReader.read(input).let {
|
accessLogReader.read(input).let {
|
||||||
config.accessLogConfig = it
|
config.accessLogConfig = it
|
||||||
}
|
}
|
||||||
|
healthConfigReader.read(input).let {
|
||||||
|
config.health = it
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 EmeraldPay, Inc
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.emeraldpay.dshackle.monitoring
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpServer
|
||||||
|
import io.emeraldpay.dshackle.config.HealthConfig
|
||||||
|
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import javax.annotation.PostConstruct
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class HealthCheckSetup(
|
||||||
|
@Autowired private val healthConfig: HealthConfig,
|
||||||
|
@Autowired private val multistreamHolder: MultistreamHolder
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(HealthCheckSetup::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
fun start() {
|
||||||
|
if (!healthConfig.isEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// use standard JVM server with a single thread blocking processing
|
||||||
|
// health check is a rare operation, no reason to set up anything complex
|
||||||
|
try {
|
||||||
|
log.info("Run Health Server on ${healthConfig.host}:${healthConfig.port}${healthConfig.path}")
|
||||||
|
val server = HttpServer.create(
|
||||||
|
InetSocketAddress(
|
||||||
|
healthConfig.host,
|
||||||
|
healthConfig.port
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
server.createContext(healthConfig.path) { httpExchange ->
|
||||||
|
val response = getHealth()
|
||||||
|
val ok = response == "OK"
|
||||||
|
val code = if (ok) HttpStatus.OK else HttpStatus.SERVICE_UNAVAILABLE
|
||||||
|
httpExchange.sendResponseHeaders(code.value(), response.toByteArray().size.toLong())
|
||||||
|
httpExchange.responseBody.use { os ->
|
||||||
|
os.write(response.toByteArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Thread(server::start).start()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
log.error("Failed to start Health Server", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHealth(): String {
|
||||||
|
val errors = healthConfig.configs().mapNotNull {
|
||||||
|
val up = multistreamHolder.getUpstream(it.blockchain)
|
||||||
|
if (up == null || !up.isAvailable()) {
|
||||||
|
return@mapNotNull "${it.blockchain} UNAVAILABLE"
|
||||||
|
}
|
||||||
|
val avail = up.getAll().count { it.getStatus() == UpstreamAvailability.OK }
|
||||||
|
if (avail < it.minAvailable) {
|
||||||
|
return@mapNotNull "${it.blockchain} LACKS MIN AVAILABILITY"
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
return if (errors.isEmpty()) {
|
||||||
|
"OK"
|
||||||
|
} else {
|
||||||
|
errors.joinToString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 EmeraldPay, Inc
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.emeraldpay.dshackle.config
|
||||||
|
|
||||||
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
class HealthConfigReaderSpec extends Specification {
|
||||||
|
|
||||||
|
HealthConfigReader reader = new HealthConfigReader()
|
||||||
|
|
||||||
|
def "Read empty"() {
|
||||||
|
setup:
|
||||||
|
def config = this.class.getClassLoader().getResourceAsStream("dshackle-health-empty.yaml")
|
||||||
|
when:
|
||||||
|
def act = reader.read(config)
|
||||||
|
|
||||||
|
then:
|
||||||
|
!act.enabled
|
||||||
|
act.port == 8082
|
||||||
|
act.host == "127.0.0.1"
|
||||||
|
act.path == "/health"
|
||||||
|
act.configs().size() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Read single"() {
|
||||||
|
setup:
|
||||||
|
def config = this.class.getClassLoader().getResourceAsStream("dshackle-health-1.yaml")
|
||||||
|
when:
|
||||||
|
def act = reader.read(config)
|
||||||
|
|
||||||
|
then:
|
||||||
|
act.enabled
|
||||||
|
act.port == 8082
|
||||||
|
act.host == "127.0.0.1"
|
||||||
|
act.path == "/health"
|
||||||
|
with(act.configs()) {
|
||||||
|
size() == 1
|
||||||
|
with(it[0]) {
|
||||||
|
it.blockchain == Chain.ETHEREUM
|
||||||
|
it.minAvailable == 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Read multiple"() {
|
||||||
|
setup:
|
||||||
|
def config = this.class.getClassLoader().getResourceAsStream("dshackle-health-2.yaml")
|
||||||
|
when:
|
||||||
|
def act = reader.read(config)
|
||||||
|
|
||||||
|
then:
|
||||||
|
act.enabled
|
||||||
|
act.port == 10003
|
||||||
|
act.host == "0.0.0.0"
|
||||||
|
act.path == "/healtz"
|
||||||
|
with(act.configs().toSorted { it.blockchain.id }) {
|
||||||
|
size() == 2
|
||||||
|
with(it[0]) {
|
||||||
|
it.blockchain == Chain.BITCOIN
|
||||||
|
it.minAvailable == 1
|
||||||
|
}
|
||||||
|
with(it[1]) {
|
||||||
|
it.blockchain == Chain.ETHEREUM
|
||||||
|
it.minAvailable == 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.config
|
package io.emeraldpay.dshackle.config
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.FileResolver
|
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
@@ -73,6 +73,16 @@ class MainConfigReaderSpec extends Specification {
|
|||||||
blockchain == Chain.TESTNET_RINKEBY
|
blockchain == Chain.TESTNET_RINKEBY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
with(act.health) {
|
||||||
|
it.enabled
|
||||||
|
with(it.configs()) {
|
||||||
|
it.size() == 1
|
||||||
|
with(it[0]) {
|
||||||
|
it.blockchain == Chain.ETHEREUM
|
||||||
|
it.minAvailable == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
act.upstreams != null
|
act.upstreams != null
|
||||||
with(act.upstreams) {
|
with(act.upstreams) {
|
||||||
defaultOptions != null
|
defaultOptions != null
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 EmeraldPay, Inc
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.emeraldpay.dshackle.monitoring
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.config.HealthConfig
|
||||||
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
class HealthCheckSetupSpec extends Specification {
|
||||||
|
|
||||||
|
def "OK when meets availability - 1"() {
|
||||||
|
setup:
|
||||||
|
def config = new HealthConfig().tap {
|
||||||
|
it.chains[Chain.ETHEREUM] = new HealthConfig.ChainConfig(
|
||||||
|
Chain.ETHEREUM, 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
def up1 = Mock(Upstream)
|
||||||
|
def ethereumUpstreams = Mock(Multistream)
|
||||||
|
def multistream = Mock(MultistreamHolder)
|
||||||
|
def check = new HealthCheckSetup(config, multistream)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = check.health
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == "OK"
|
||||||
|
1 * multistream.getUpstream(Chain.ETHEREUM) >> ethereumUpstreams
|
||||||
|
1 * ethereumUpstreams.available >> true
|
||||||
|
1 * ethereumUpstreams.getAll() >> [up1]
|
||||||
|
1 * up1.status >> UpstreamAvailability.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
def "OK when meets availability - 1 - bitcoin"() {
|
||||||
|
setup:
|
||||||
|
def config = new HealthConfig().tap {
|
||||||
|
it.chains[Chain.BITCOIN] = new HealthConfig.ChainConfig(
|
||||||
|
Chain.BITCOIN, 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
def up1 = Mock(Upstream)
|
||||||
|
def bitcoinUpstreams = Mock(Multistream)
|
||||||
|
def multistream = Mock(MultistreamHolder)
|
||||||
|
def check = new HealthCheckSetup(config, multistream)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = check.health
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == "OK"
|
||||||
|
1 * multistream.getUpstream(Chain.BITCOIN) >> bitcoinUpstreams
|
||||||
|
1 * bitcoinUpstreams.available >> true
|
||||||
|
1 * bitcoinUpstreams.getAll() >> [up1]
|
||||||
|
1 * up1.status >> UpstreamAvailability.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
def "OK when meets availability - 2/3"() {
|
||||||
|
setup:
|
||||||
|
def config = new HealthConfig().tap {
|
||||||
|
it.chains[Chain.ETHEREUM] = new HealthConfig.ChainConfig(
|
||||||
|
Chain.ETHEREUM, 2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
def up1 = Mock(Upstream)
|
||||||
|
def up2 = Mock(Upstream)
|
||||||
|
def up3 = Mock(Upstream)
|
||||||
|
def ethereumUpstreams = Mock(Multistream)
|
||||||
|
def multistream = Mock(MultistreamHolder)
|
||||||
|
def check = new HealthCheckSetup(config, multistream)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = check.health
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == "OK"
|
||||||
|
1 * multistream.getUpstream(Chain.ETHEREUM) >> ethereumUpstreams
|
||||||
|
1 * ethereumUpstreams.available >> true
|
||||||
|
1 * ethereumUpstreams.getAll() >> [up1, up2, up3]
|
||||||
|
1 * up1.status >> UpstreamAvailability.OK
|
||||||
|
1 * up2.status >> UpstreamAvailability.SYNCING
|
||||||
|
1 * up3.status >> UpstreamAvailability.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
def "OK when doesn't meet availability - 2/3"() {
|
||||||
|
setup:
|
||||||
|
def config = new HealthConfig().tap {
|
||||||
|
it.chains[Chain.ETHEREUM] = new HealthConfig.ChainConfig(
|
||||||
|
Chain.ETHEREUM, 2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
def up1 = Mock(Upstream)
|
||||||
|
def up2 = Mock(Upstream)
|
||||||
|
def up3 = Mock(Upstream)
|
||||||
|
def ethereumUpstreams = Mock(Multistream)
|
||||||
|
def multistream = Mock(MultistreamHolder)
|
||||||
|
def check = new HealthCheckSetup(config, multistream)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = check.health
|
||||||
|
|
||||||
|
then:
|
||||||
|
act != "OK"
|
||||||
|
1 * multistream.getUpstream(Chain.ETHEREUM) >> ethereumUpstreams
|
||||||
|
1 * ethereumUpstreams.available >> true
|
||||||
|
1 * ethereumUpstreams.getAll() >> [up1, up2, up3]
|
||||||
|
1 * up1.status >> UpstreamAvailability.OK
|
||||||
|
1 * up2.status >> UpstreamAvailability.SYNCING
|
||||||
|
1 * up3.status >> UpstreamAvailability.LAGGING
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,12 @@ cache:
|
|||||||
enabled: true
|
enabled: true
|
||||||
host: redis-master
|
host: redis-master
|
||||||
|
|
||||||
|
health:
|
||||||
|
port: 8083
|
||||||
|
blockchains:
|
||||||
|
- chain: ethereum
|
||||||
|
min-available: 1
|
||||||
|
|
||||||
proxy:
|
proxy:
|
||||||
port: 8082
|
port: 8082
|
||||||
tls:
|
tls:
|
||||||
|
|||||||
6
src/test/resources/dshackle-health-1.yaml
Normal file
6
src/test/resources/dshackle-health-1.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: v1
|
||||||
|
|
||||||
|
health:
|
||||||
|
blockchains:
|
||||||
|
- chain: ethereum
|
||||||
|
min-available: 2
|
||||||
11
src/test/resources/dshackle-health-2.yaml
Normal file
11
src/test/resources/dshackle-health-2.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: v1
|
||||||
|
|
||||||
|
health:
|
||||||
|
port: 10003
|
||||||
|
host: 0.0.0.0
|
||||||
|
path: /healtz
|
||||||
|
blockchains:
|
||||||
|
- chain: ethereum
|
||||||
|
min-available: 2
|
||||||
|
- chain: bitcoin
|
||||||
|
min-available: 1
|
||||||
3
src/test/resources/dshackle-health-empty.yaml
Normal file
3
src/test/resources/dshackle-health-empty.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version: v1
|
||||||
|
|
||||||
|
health:
|
||||||
Reference in New Issue
Block a user