/* * Copyright (c) 2011-2017 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.test.core; import io.vertx.core.Launcher; import io.vertx.core.impl.VertxInternal; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.core.spi.cluster.ClusterManager; import org.junit.Test; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; import java.util.stream.IntStream; import static java.util.concurrent.TimeUnit.*; /** * @author Thomas Segismont */ public abstract class FaultToleranceTest extends VertxTestBase { protected static final int NODE_COUNT = 3; protected static final int ADDRESSES_COUNT = 10; protected final List<Process> externalNodes = new ArrayList<>(); protected final AtomicLong externalNodesStarted = new AtomicLong(); protected final AtomicLong pongsReceived = new AtomicLong(); protected final AtomicLong noHandlersErrors = new AtomicLong(); protected long timeoutMs = 60_000; protected VertxInternal vertx; @Test public void testFaultTolerance() throws Exception { startNodes(1); vertx = (VertxInternal) vertices[0]; vertx.eventBus().<String>consumer("control", msg -> { switch (msg.body()) { case "start": externalNodesStarted.incrementAndGet(); break; case "pong": pongsReceived.incrementAndGet(); break; case "noHandlers": noHandlersErrors.incrementAndGet(); break; } }); for (int i = 0; i < NODE_COUNT; i++) { Process process = startExternalNode(i); externalNodes.add(process); afterNodeStarted(i, process); } afterNodesStarted(); JsonArray message1 = new JsonArray(); IntStream.range(0, NODE_COUNT).forEach(message1::add); vertx.eventBus().publish("ping", message1); assertEqualsEventually("All pongs", Long.valueOf(NODE_COUNT * NODE_COUNT * ADDRESSES_COUNT), pongsReceived::get); for (int i = 0; i < NODE_COUNT - 1; i++) { Process process = externalNodes.get(i); process.destroyForcibly(); afterNodeKilled(i, process); } afterNodesKilled(); pongsReceived.set(0); JsonArray message2 = new JsonArray().add(NODE_COUNT - 1); vertx.eventBus().publish("ping", message2); assertEqualsEventually("Survivor pongs", Long.valueOf(ADDRESSES_COUNT), pongsReceived::get); JsonArray message3 = new JsonArray(); IntStream.range(0, NODE_COUNT - 1).forEach(message3::add); vertx.eventBus().publish("ping", message3); assertEqualsEventually("Dead errors", Long.valueOf((NODE_COUNT - 1) * ADDRESSES_COUNT), noHandlersErrors::get); } protected void afterNodeStarted(int i, Process process) throws Exception { } protected void afterNodesStarted() throws Exception { assertEqualsEventually("Nodes ready", Long.valueOf(NODE_COUNT), externalNodesStarted::get); } protected void afterNodeKilled(int i, Process process) throws Exception { } protected void afterNodesKilled() throws Exception { ClusterManager clusterManager = vertx.getClusterManager(); assertEqualsEventually("Remaining members", Integer.valueOf(2), () -> clusterManager.getNodes().size()); } protected Process startExternalNode(int id) throws Exception { String javaHome = System.getProperty("java.home"); String classpath = System.getProperty("java.class.path"); List<String> command = new ArrayList<>(); command.add(javaHome + File.separator + "bin" + File.separator + "java"); command.add("-classpath"); command.add(classpath); command.addAll(getExternalNodeSystemProperties()); command.add(Launcher.class.getName()); command.add("run"); command.add(FaultToleranceVerticle.class.getName()); command.add("-cluster"); command.add("-conf"); command.add(new JsonObject().put("id", id).put("addressesCount", ADDRESSES_COUNT).encode()); return new ProcessBuilder(command).inheritIO().start(); } protected List<String> getExternalNodeSystemProperties() { return Collections.emptyList(); } protected void assertEqualsEventually(String msg, Object expected, Supplier<Object> actual) { for (long start = System.currentTimeMillis(); System.currentTimeMillis() - start < timeoutMs; ) { Object act = actual.get(); if (expected == null ? act == null : expected.equals(act)) { return; } try { MILLISECONDS.sleep(100); } catch (InterruptedException ignored) { } } assertEquals(msg, expected, actual.get()); } @Override protected void tearDown() throws Exception { externalNodes.forEach(Process::destroyForcibly); super.tearDown(); } }