Expand readiness check to check peer count and sync state (#1568)

Query params can be used to adjust the number of peers required and number of blocks tolerance to be considered in sync.
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Adrian Sutton 6 years ago committed by GitHub
parent cee72c8590
commit 8bf224707f
  1. 26
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java
  2. 48
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/health/HealthService.java
  3. 19
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/health/LivenessCheck.java
  4. 82
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/health/ReadinessCheck.java
  5. 36
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/health/ReadinessService.java
  6. 4
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetPeerCount.java
  7. 9
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java
  8. 1
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AdminJsonRpcHttpServiceTest.java
  9. 9
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceCorsTest.java
  10. 9
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java
  11. 9
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java
  12. 13
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java
  13. 16
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java
  14. 146
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/health/ReadinessCheckTest.java
  15. 9
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/P2PNetwork.java
  16. 11
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java

@ -91,32 +91,6 @@ public class JsonRpcHttpService {
private final HealthService livenessService;
private final HealthService readinessService;
/**
* Construct a JsonRpcHttpService handler
*
* @param vertx The vertx process that will be running this service
* @param dataDir The data directory where requests can be buffered
* @param config Configuration for the rpc methods being loaded
* @param metricsSystem The metrics service that activities should be reported to
* @param methods The json rpc methods that should be enabled
*/
public JsonRpcHttpService(
final Vertx vertx,
final Path dataDir,
final JsonRpcConfiguration config,
final MetricsSystem metricsSystem,
final Map<String, JsonRpcMethod> methods) {
this(
vertx,
dataDir,
config,
metricsSystem,
methods,
AuthenticationService.create(vertx, config),
new HealthService(() -> true),
new HealthService(() -> true));
}
public JsonRpcHttpService(
final Vertx vertx,
final Path dataDir,

@ -12,18 +12,23 @@
*/
package tech.pegasys.pantheon.ethereum.jsonrpc.health;
import com.google.common.collect.ImmutableMap;
import static java.util.Collections.singletonMap;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
public class HealthService {
public final class HealthService {
public static final HealthService ALWAYS_HEALTHY = new HealthService(params -> true);
public static final String LIVENESS_PATH = "/liveness";
public static final String READINESS_PATH = "/readiness";
private static final int HEALTHY_STATUS_CODE = HttpResponseStatus.OK.code();
private static final int NON_HEALTHY_STATUS_CODE = HttpResponseStatus.SERVICE_UNAVAILABLE.code();
private static final int UNHEALTHY_STATUS_CODE = HttpResponseStatus.SERVICE_UNAVAILABLE.code();
private static final String HEALTHY_STATUS_TEXT = "UP";
private static final String UNHEALTHY_STATUS_TEXT = "DOWN";
private final HealthCheck healthCheck;
@ -32,35 +37,28 @@ public class HealthService {
}
public void handleRequest(final RoutingContext routingContext) {
final HealthCheckResponse healthCheckResponse = buildResponse(isHealthy());
final int statusCode;
final String statusText;
if (healthCheck.isHealthy(name -> routingContext.queryParams().get(name))) {
statusCode = HEALTHY_STATUS_CODE;
statusText = HEALTHY_STATUS_TEXT;
} else {
statusCode = UNHEALTHY_STATUS_CODE;
statusText = UNHEALTHY_STATUS_TEXT;
}
routingContext
.response()
.setStatusCode(healthCheckResponse.statusCode)
.end(healthCheckResponse.responseBody);
}
public boolean isHealthy() {
return healthCheck.isHealthy();
}
private HealthCheckResponse buildResponse(final boolean healthy) {
return new HealthCheckResponse(
healthy ? HEALTHY_STATUS_CODE : NON_HEALTHY_STATUS_CODE,
new JsonObject(ImmutableMap.of("status", healthy ? "UP" : "DOWN")).encodePrettily());
.setStatusCode(statusCode)
.end(new JsonObject(singletonMap("status", statusText)).encodePrettily());
}
@FunctionalInterface
public interface HealthCheck {
boolean isHealthy();
boolean isHealthy(ParamSource paramSource);
}
private static class HealthCheckResponse {
private int statusCode;
private String responseBody;
private HealthCheckResponse(final int statusCode, final String responseBody) {
this.statusCode = statusCode;
this.responseBody = responseBody;
}
@FunctionalInterface
public interface ParamSource {
String getParam(String name);
}
}

@ -12,21 +12,18 @@
*/
package tech.pegasys.pantheon.ethereum.jsonrpc.health;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService.HealthCheck;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService.ParamSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LivenessService extends HealthService {
public class LivenessCheck implements HealthCheck {
private static final Logger LOG = LogManager.getLogger();
public LivenessService() {
super(buildHeathCheck());
}
private static HealthCheck buildHeathCheck() {
return () -> {
LOG.debug("Invoking liveness service.");
return true;
};
@Override
public boolean isHealthy(final ParamSource params) {
LOG.debug("Invoking liveness check.");
return true;
}
}

@ -0,0 +1,82 @@
/*
* Copyright 2018 ConsenSys AG.
*
* 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 tech.pegasys.pantheon.ethereum.jsonrpc.health;
import tech.pegasys.pantheon.ethereum.core.SyncStatus;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService.HealthCheck;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService.ParamSource;
import tech.pegasys.pantheon.ethereum.p2p.network.P2PNetwork;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ReadinessCheck implements HealthCheck {
private static final Logger LOG = LogManager.getLogger();
private static final int DEFAULT_MINIMUM_PEERS = 1;
private static final int DEFAULT_MAX_BLOCKS_BEHIND = 2;
private final P2PNetwork p2pNetwork;
private final Synchronizer synchronizer;
public ReadinessCheck(final P2PNetwork p2pNetwork, final Synchronizer synchronizer) {
this.p2pNetwork = p2pNetwork;
this.synchronizer = synchronizer;
}
@Override
public boolean isHealthy(final ParamSource params) {
LOG.debug("Invoking readiness check.");
if (p2pNetwork.isP2pEnabled()) {
final int peerCount = p2pNetwork.getPeerCount();
if (!hasMinimumPeers(params, peerCount)) {
return false;
}
}
return synchronizer
.getSyncStatus()
.map(syncStatus -> isInSync(syncStatus, params))
.orElse(true);
}
private boolean isInSync(final SyncStatus syncStatus, final ParamSource params) {
final String maxBlocksBehindParam = params.getParam("maxBlocksBehind");
final long maxBlocksBehind;
if (maxBlocksBehindParam == null) {
maxBlocksBehind = DEFAULT_MAX_BLOCKS_BEHIND;
} else {
try {
maxBlocksBehind = Long.parseLong(maxBlocksBehindParam);
} catch (final NumberFormatException e) {
LOG.debug("Invalid maxBlocksBehind: {}. Reporting as not ready.", maxBlocksBehindParam);
return false;
}
}
return syncStatus.getHighestBlock() - syncStatus.getCurrentBlock() <= maxBlocksBehind;
}
private boolean hasMinimumPeers(final ParamSource params, final int peerCount) {
final int minimumPeers;
final String peersParam = params.getParam("minPeers");
if (peersParam == null) {
minimumPeers = DEFAULT_MINIMUM_PEERS;
} else {
try {
minimumPeers = Integer.parseInt(peersParam);
} catch (final NumberFormatException e) {
LOG.debug("Invalid minPeers: {}. Reporting as not ready.", peersParam);
return false;
}
}
return peerCount >= minimumPeers;
}
}

@ -1,36 +0,0 @@
/*
* Copyright 2018 ConsenSys AG.
*
* 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 tech.pegasys.pantheon.ethereum.jsonrpc.health;
import tech.pegasys.pantheon.ethereum.p2p.network.P2PNetwork;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ReadinessService extends HealthService {
private static final Logger LOG = LogManager.getLogger();
public ReadinessService(final P2PNetwork p2PNetwork) {
super(buildHealthCheck(p2PNetwork));
}
private static HealthCheck buildHealthCheck(final P2PNetwork p2PNetwork) {
return () -> {
LOG.debug("Invoking readiness service.");
if (p2PNetwork.isP2pEnabled()) {
return p2PNetwork.isListening();
}
return true;
};
}
}

@ -37,8 +37,8 @@ public class NetPeerCount implements JsonRpcMethod {
@Override
public JsonRpcResponse response(final JsonRpcRequest req) {
try {
return new JsonRpcSuccessResponse(req.getId(), Quantity.create(p2pNetwork.getPeers().size()));
} catch (P2PDisabledException e) {
return new JsonRpcSuccessResponse(req.getId(), Quantity.create(p2pNetwork.getPeerCount()));
} catch (final P2PDisabledException e) {
return new JsonRpcErrorResponse(req.getId(), JsonRpcError.P2P_DISABLED);
}
}

@ -33,6 +33,7 @@ import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.PendingTransactions;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterIdGenerator;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterRepository;
@ -199,7 +200,13 @@ public abstract class AbstractEthJsonRpcHttpServiceTest {
config.setPort(0);
service =
new JsonRpcHttpService(
vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), methods);
vertx,
folder.newFolder().toPath(),
config,
new NoOpMetricsSystem(),
methods,
HealthService.ALWAYS_HEALTHY,
HealthService.ALWAYS_HEALTHY);
service.start().join();
client = new OkHttpClient();

@ -62,6 +62,7 @@ public class AdminJsonRpcHttpServiceTest extends JsonRpcHttpServiceTest {
peerList.add(MockPeerConnection.create(info3, addr30301, addr60303));
when(peerDiscoveryMock.getPeers()).thenReturn(peerList);
when(peerDiscoveryMock.getPeerCount()).thenReturn(peerList.size());
final String id = "123";
final RequestBody body =

@ -14,6 +14,7 @@ package tech.pegasys.pantheon.ethereum.jsonrpc;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import java.util.HashMap;
@ -188,7 +189,13 @@ public class JsonRpcHttpServiceCorsTest {
final JsonRpcHttpService jsonRpcHttpService =
new JsonRpcHttpService(
vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), new HashMap<>());
vertx,
folder.newFolder().toPath(),
config,
new NoOpMetricsSystem(),
new HashMap<>(),
HealthService.ALWAYS_HEALTHY,
HealthService.ALWAYS_HEALTHY);
jsonRpcHttpService.start().join();
return jsonRpcHttpService;

@ -22,6 +22,7 @@ import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries;
@ -120,7 +121,13 @@ public class JsonRpcHttpServiceHostWhitelistTest {
private JsonRpcHttpService createJsonRpcHttpService() throws Exception {
return new JsonRpcHttpService(
vertx, folder.newFolder().toPath(), jsonRpcConfig, new NoOpMetricsSystem(), rpcMethods);
vertx,
folder.newFolder().toPath(),
jsonRpcConfig,
new NoOpMetricsSystem(),
rpcMethods,
HealthService.ALWAYS_HEALTHY,
HealthService.ALWAYS_HEALTHY);
}
private static JsonRpcConfiguration createJsonRpcConfig() {

@ -23,6 +23,7 @@ import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationUtils;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthAccounts;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthBlockNumber;
@ -159,7 +160,13 @@ public class JsonRpcHttpServiceLoginTest {
config.setAuthenticationCredentialsFile(authTomlPath);
return new JsonRpcHttpService(
vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods);
vertx,
folder.newFolder().toPath(),
config,
new NoOpMetricsSystem(),
rpcMethods,
HealthService.ALWAYS_HEALTHY,
HealthService.ALWAYS_HEALTHY);
}
private static JsonRpcConfiguration createJsonRpcConfig() {

@ -26,6 +26,7 @@ import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries;
@ -207,7 +208,13 @@ public class JsonRpcHttpServiceRpcApisTest {
mock(MetricsConfiguration.class)));
final JsonRpcHttpService jsonRpcHttpService =
new JsonRpcHttpService(
vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods);
vertx,
folder.newFolder().toPath(),
config,
new NoOpMetricsSystem(),
rpcMethods,
HealthService.ALWAYS_HEALTHY,
HealthService.ALWAYS_HEALTHY);
jsonRpcHttpService.start().join();
baseUrl = jsonRpcHttpService.url();
@ -294,7 +301,9 @@ public class JsonRpcHttpServiceRpcApisTest {
folder.newFolder().toPath(),
jsonRpcConfiguration,
new NoOpMetricsSystem(),
rpcMethods);
rpcMethods,
HealthService.ALWAYS_HEALTHY,
HealthService.ALWAYS_HEALTHY);
jsonRpcHttpService.start().join();
baseUrl = jsonRpcHttpService.url();

@ -34,6 +34,7 @@ import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockWithMetadata;
@ -149,7 +150,13 @@ public class JsonRpcHttpServiceTest {
private static JsonRpcHttpService createJsonRpcHttpService(final JsonRpcConfiguration config)
throws Exception {
return new JsonRpcHttpService(
vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods);
vertx,
folder.newFolder().toPath(),
config,
new NoOpMetricsSystem(),
rpcMethods,
HealthService.ALWAYS_HEALTHY,
HealthService.ALWAYS_HEALTHY);
}
private static JsonRpcHttpService createJsonRpcHttpService() throws Exception {
@ -158,7 +165,9 @@ public class JsonRpcHttpServiceTest {
folder.newFolder().toPath(),
createJsonRpcConfig(),
new NoOpMetricsSystem(),
rpcMethods);
rpcMethods,
HealthService.ALWAYS_HEALTHY,
HealthService.ALWAYS_HEALTHY);
}
private static JsonRpcConfiguration createJsonRpcConfig() {
@ -341,7 +350,7 @@ public class JsonRpcHttpServiceTest {
@Test
public void netPeerCountSuccessful() throws Exception {
when(peerDiscoveryMock.getPeers()).thenReturn(Arrays.asList(null, null, null));
when(peerDiscoveryMock.getPeerCount()).thenReturn(3);
final String id = "123";
final RequestBody body =
@ -577,6 +586,7 @@ public class JsonRpcHttpServiceTest {
@Test
public void netPeerCountOfZero() throws Exception {
when(peerDiscoveryMock.getPeers()).thenReturn(Collections.emptyList());
when(peerDiscoveryMock.getPeerCount()).thenReturn(0);
final String id = "123";
final RequestBody body =

@ -0,0 +1,146 @@
/*
* Copyright 2019 ConsenSys AG.
*
* 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 tech.pegasys.pantheon.ethereum.jsonrpc.health;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.core.SyncStatus;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService.ParamSource;
import tech.pegasys.pantheon.ethereum.p2p.network.P2PNetwork;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.junit.Test;
public class ReadinessCheckTest {
private static final String MIN_PEERS_PARAM = "minPeers";
private final P2PNetwork p2pNetwork = mock(P2PNetwork.class);
private final Synchronizer synchronizer = mock(Synchronizer.class);
private final Map<String, String> params = new HashMap<>();
private final ParamSource paramSource = params::get;
private final ReadinessCheck readinessCheck = new ReadinessCheck(p2pNetwork, synchronizer);
@Test
public void shouldBeReadyWhenDefaultLimitsUsedAndReached() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(1);
when(synchronizer.getSyncStatus()).thenReturn(Optional.empty());
assertThat(readinessCheck.isHealthy(paramSource)).isTrue();
}
@Test
public void shouldBeReadyWithNoPeersWhenP2pIsDisabled() {
when(p2pNetwork.isP2pEnabled()).thenReturn(false);
when(p2pNetwork.getPeerCount()).thenReturn(0);
when(synchronizer.getSyncStatus()).thenReturn(Optional.empty());
assertThat(readinessCheck.isHealthy(paramSource)).isTrue();
}
@Test
public void shouldBeReadyWithNoPeersWhenMinimumPeersSetToZero() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(0);
when(synchronizer.getSyncStatus()).thenReturn(Optional.empty());
params.put(MIN_PEERS_PARAM, "0");
assertThat(readinessCheck.isHealthy(paramSource)).isTrue();
}
@Test
public void shouldNotBeReadyWhenIncreasedMinimumPeersNotReached() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(5);
when(synchronizer.getSyncStatus()).thenReturn(Optional.empty());
params.put(MIN_PEERS_PARAM, "10");
assertThat(readinessCheck.isHealthy(paramSource)).isFalse();
}
@Test
public void shouldNotBeReadyWhenMinimumPeersParamIsInvalid() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(500);
when(synchronizer.getSyncStatus()).thenReturn(Optional.empty());
params.put(MIN_PEERS_PARAM, "abc");
assertThat(readinessCheck.isHealthy(paramSource)).isFalse();
}
@Test
public void shouldBeReadyWhenLessThanDefaultMaxBlocksBehind() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(5);
when(synchronizer.getSyncStatus()).thenReturn(createSyncStatus(1000, 1002));
assertThat(readinessCheck.isHealthy(paramSource)).isTrue();
}
@Test
public void shouldNotBeReadyWhenLessThanDefaultMaxBlocksBehind() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(5);
when(synchronizer.getSyncStatus()).thenReturn(createSyncStatus(1000, 1003));
assertThat(readinessCheck.isHealthy(paramSource)).isFalse();
}
@Test
public void shouldBeReadyWhenLessThanCustomMaxBlocksBehind() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(5);
when(synchronizer.getSyncStatus()).thenReturn(createSyncStatus(500, 600));
params.put("maxBlocksBehind", "100");
assertThat(readinessCheck.isHealthy(paramSource)).isTrue();
}
@Test
public void shouldNotBeReadyWhenGreaterThanCustomMaxBlocksBehind() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(5);
when(synchronizer.getSyncStatus()).thenReturn(createSyncStatus(500, 601));
params.put("maxBlocksBehind", "100");
assertThat(readinessCheck.isHealthy(paramSource)).isFalse();
}
@Test
public void shouldNotBeReadyWhenCustomMaxBlocksBehindIsInvalid() {
when(p2pNetwork.isP2pEnabled()).thenReturn(true);
when(p2pNetwork.getPeerCount()).thenReturn(5);
when(synchronizer.getSyncStatus()).thenReturn(createSyncStatus(500, 500));
params.put("maxBlocksBehind", "abc");
assertThat(readinessCheck.isHealthy(paramSource)).isFalse();
}
private Optional<SyncStatus> createSyncStatus(final int currentBlock, final int highestBlock) {
return Optional.of(new SyncStatus(0, currentBlock, highestBlock));
}
}

@ -41,6 +41,15 @@ public interface P2PNetwork extends Closeable {
*/
Collection<PeerConnection> getPeers();
/**
* Returns the number of currently connected peers.
*
* @return the number of connected peers.
*/
default int getPeerCount() {
return getPeers().size();
}
/**
* Returns a stream of peers that have been discovered on the network. These peers are not
* necessarily connected.

@ -31,8 +31,9 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcHttpService;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcMethodsFactory;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.LivenessService;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.ReadinessService;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.LivenessCheck;
import tech.pegasys.pantheon.ethereum.jsonrpc.health.ReadinessCheck;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterIdGenerator;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterRepository;
@ -353,8 +354,8 @@ public class RunnerBuilder {
jsonRpcConfiguration,
metricsSystem,
jsonRpcMethods,
new LivenessService(),
new ReadinessService(peerNetwork)));
new HealthService(new LivenessCheck()),
new HealthService(new ReadinessCheck(peerNetwork, synchronizer))));
}
Optional<GraphQLHttpService> graphQLHttpService = Optional.empty();
@ -368,7 +369,7 @@ public class RunnerBuilder {
transactionPool,
miningCoordinator,
synchronizer);
GraphQL graphQL = null;
final GraphQL graphQL;
try {
graphQL = GraphQLProvider.buildGraphQL(fetchers);
} catch (final IOException ioe) {

Loading…
Cancel
Save