package org.corfudb.integration; import org.corfudb.runtime.CorfuRuntime; import org.corfudb.runtime.exceptions.NetworkException; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import static org.assertj.core.api.Assertions.assertThat; /** * Tests the recovery of the Corfu instance. * WARNING: These tests kill all existing corfu instances on the node to run * fresh servers. * Created by zlokhandwala on 4/25/17. */ public class ServerRestartIT extends AbstractIT { // Total number of iterations of randomized failovers. static int ITERATIONS; // Percentage of Client restarts. static int CLIENT_RESTART_PERCENTAGE; // Percentage of Server restarts. static int SERVER_RESTART_PERCENTAGE; static String corfuSingleNodeHost; static int corfuSingleNodePort; @Before public void loadProperties() { ITERATIONS = Integer.parseInt((String) PROPERTIES.get("RandomizedRecoveryIterations")); CLIENT_RESTART_PERCENTAGE = Integer.parseInt((String) PROPERTIES.get("ClientRestartPercentage")); SERVER_RESTART_PERCENTAGE = Integer.parseInt((String) PROPERTIES.get("ServerRestartPercentage")); corfuSingleNodeHost = (String) PROPERTIES.get("corfuSingleNodeHost"); corfuSingleNodePort = Integer.parseInt((String) PROPERTIES.get("corfuSingleNodePort")); } /** * Randomized tests with mixed client and server failovers. * * @throws Exception */ @Test public void testRandomizedRecovery() throws Exception { // Total percentage. final int TOTAL_PERCENTAGE = 100; // Number of maps or streams to test recovery on. final int MAPS = 3; // Number of insertions in map in each iteration. final int INSERTIONS = 100; // Number of keys to be used throughout the test in each map. final int KEYS = 20; // Logs the server and client state in each iteration with the // maps used and keys and values inserted in each iteration. final boolean TEST_SEQUENCE_LOGGING = true; final File testSequenceLogFile = new File(TEST_SEQUENCE_LOG_PATH); if (!testSequenceLogFile.exists()) { testSequenceLogFile.createNewFile(); } // Keep this print at all times to reproduce any failed test. final Random randomSeed = new Random(); final long SEED = randomSeed.nextLong(); final Random rand = new Random(SEED); System.out.println("SEED = " + SEED); // Runs the corfu server. Expect slight delay until server is running. Process corfuServerProcess = runCorfuServer(corfuSingleNodeHost, corfuSingleNodePort); // List of runtimes to free resources when not needed. List<CorfuRuntime> runtimeList = new ArrayList<>(); List<Map<String, Integer>> smrMapList = new ArrayList<>(); for (int i = 0; i < MAPS; i++) { CorfuRuntime runtime = createDefaultRuntime(); runtimeList.add(runtime); smrMapList.add(createMap(runtime, Integer.toString(i))); } List<Map<String, Integer>> expectedMapList = new ArrayList<>(); for (int i = 0; i < MAPS; i++) { expectedMapList.add(new HashMap<>()); } try (final FileOutputStream fos = new FileOutputStream(testSequenceLogFile)) { for (int i = 0; i < ITERATIONS; i++) { System.out.println("Iteration #" + i); boolean serverRestart = rand.nextInt(TOTAL_PERCENTAGE) < SERVER_RESTART_PERCENTAGE; boolean clientRestart = rand.nextInt(TOTAL_PERCENTAGE) < CLIENT_RESTART_PERCENTAGE; if (TEST_SEQUENCE_LOGGING) { fos.write(getRestartStateRecord(i, serverRestart, clientRestart).getBytes()); } if (clientRestart) { smrMapList.clear(); runtimeList.forEach(CorfuRuntime::shutdown); for (int j = 0; j < MAPS; j++) { CorfuRuntime runtime = createDefaultRuntime(); runtimeList.add(runtime); smrMapList.add(createMap(runtime, Integer.toString(j))); } } // Map assertions while (true) { try { if (i != 0) { for (int j = 0; j < MAPS; j++) { assertThat(smrMapList.get(j)).isEqualTo(expectedMapList.get(j)); } } break; } catch (NetworkException ne) { Thread.sleep(PARAMETERS.TIMEOUT_SHORT.toMillis()); } } // Map insertions for (int j = 0; j < INSERTIONS; j++) { int value = rand.nextInt(); int map = rand.nextInt(MAPS); String key = Integer.toString(rand.nextInt(KEYS)); smrMapList.get(map).put(key, value); expectedMapList.get(map).put(key, value); if (TEST_SEQUENCE_LOGGING) { fos.write(getMapInsertion(i, map, key, value).getBytes()); } } if (TEST_SEQUENCE_LOGGING) { fos.write(getMapStateRecord(i, expectedMapList).getBytes()); } if (serverRestart) { assertThat(shutdownCorfuServer(corfuServerProcess)).isTrue(); corfuServerProcess = runCorfuServer(); } } assertThat(shutdownCorfuServer(corfuServerProcess)).isTrue(); } catch (Exception e) { throw e; } } private String getRestartStateRecord(int iteration, boolean serverRestart, boolean clientRestart) { return "[" + iteration + "]: ServerRestart=" + serverRestart + ", ClientRestart=" + clientRestart; } private String getMapStateRecord(int iteration, List<Map<String, Integer>> mapStateList) { StringBuilder sb = new StringBuilder(); sb.append("[" + iteration + "]: Map State :\n"); for (int i = 0; i < mapStateList.size(); i ++){ sb.append("map#" + i + " map = " + mapStateList.get(i).toString() + "\n"); } return sb.toString(); } private String getMapInsertion(int iteration, int streamId, String key, int value) { return "[" + iteration + "]: Map put => streamId=" + streamId + " key=" + key + " value=" + value; } }