package org.corfudb.integration;
import org.apache.commons.io.FileUtils;
import org.corfudb.AbstractCorfuTest;
import org.corfudb.runtime.CorfuRuntime;
import org.corfudb.runtime.collections.SMRMap;
import org.junit.After;
import org.junit.Before;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
/**
* Integration tests.
* Created by zlokhandwala on 4/28/17.
*/
public class AbstractIT extends AbstractCorfuTest {
static final String DEFAULT_HOST = "localhost";
static final int DEFAULT_PORT = 9000;
static final String DEFAULT_ENDPOINT = DEFAULT_HOST + ":" + DEFAULT_PORT;
private static final String CORFU_LOG_PATH = PARAMETERS.TEST_TEMP_DIR;
private static final String CORFU_PROJECT_DIR = new File("..").getAbsolutePath() + File.separator;
private static final String CORFU_CONSOLELOG = CORFU_LOG_PATH + File.separator + "consolelog";
private static final String KILL_COMMAND = "pkill -9 -P ";
private static final String FORCE_KILL_ALL_CORFU_COMMAND = "jps | grep CorfuServer|awk '{print $1}'| xargs kill -9";
private static final int SHUTDOWN_RETRIES = 10;
private static final long SHUTDOWN_RETRY_WAIT = 500;
static public Properties PROPERTIES;
public static final String TEST_SEQUENCE_LOG_PATH = CORFU_LOG_PATH + File.separator + "testSequenceLog";
public AbstractIT() {
CorfuRuntime.overrideGetRouterFunction = null;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream input = classLoader.getResourceAsStream("CorfuDB.properties");
PROPERTIES = new Properties();
try {
PROPERTIES.load(input);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Cleans up the corfu log directory before running any test.
*
* @throws Exception
*/
@Before
public void setUp() throws Exception {
forceShutdownAllCorfuServers();
FileUtils.cleanDirectory(new File(CORFU_LOG_PATH));
}
/**
* Cleans up all Corfu instances after the tests.
*
* @throws Exception
*/
@After
public void cleanUp() throws Exception {
forceShutdownAllCorfuServers();
}
/**
* Runs the CorfuServer in a separate bash process.
* By default the server starts on localhost:9000.
*
* @return
* @throws IOException
* @throws InterruptedException
*/
public static Process runCorfuServer() throws IOException, InterruptedException {
return runCorfuServer(DEFAULT_HOST, DEFAULT_PORT);
}
/**
* Runs the CorfuServer in a separate bash process.
*
* @param host
* @param port
* @return
* @throws IOException
* @throws InterruptedException
*/
public static Process runCorfuServer(String host, int port) throws IOException, InterruptedException {
File logPath = new File(getCorfuServerLogPath(host, port));
if (!logPath.exists()) {
logPath.mkdir();
}
ProcessBuilder builder = new ProcessBuilder();
builder.command("sh", "-c", getRunServerCommand(host, port, getCorfuServerLogPath(host, port)));
builder.directory(new File(CORFU_PROJECT_DIR));
Process corfuServerProcess = builder.start();
StreamGobbler streamGobbler = new StreamGobbler(corfuServerProcess.getInputStream(), CORFU_CONSOLELOG);
Executors.newSingleThreadExecutor().submit(streamGobbler);
return corfuServerProcess;
}
public static String getCorfuServerLogPath(String host, int port) {
return CORFU_LOG_PATH + File.separator + host + "_" + port + "_log";
}
private static String getRunServerCommand(String host, int port, String logPath) {
return "bin/corfu_server -a " + host + " -sl " + logPath + " -d TRACE " + port;
}
/**
* Shuts down all corfu instances running on the node.
*
* @throws IOException
* @throws InterruptedException
*/
public static void forceShutdownAllCorfuServers() throws IOException, InterruptedException {
ProcessBuilder builder = new ProcessBuilder();
builder.command("sh", "-c", FORCE_KILL_ALL_CORFU_COMMAND);
Process p = builder.start();
p.waitFor();
}
/**
* Shuts down all corfu instances.
* TODO: Should be able to gracefully kill a single specified corfu server.
*
* @param corfuServerProcess
* @return
* @throws IOException
* @throws InterruptedException
*/
public static boolean shutdownCorfuServer(Process corfuServerProcess) throws IOException, InterruptedException {
int retries = SHUTDOWN_RETRIES;
while (true) {
long pid = getPid(corfuServerProcess);
ProcessBuilder builder = new ProcessBuilder();
builder.command("sh", "-c", KILL_COMMAND + pid);
Process p = builder.start();
p.waitFor();
if (retries == 0) {
return false;
}
if (corfuServerProcess.isAlive()) {
retries--;
Thread.sleep(SHUTDOWN_RETRY_WAIT);
} else {
return true;
}
}
}
public static long getPid(Process p) {
long pid = -1;
try {
if (p.getClass().getName().equals("java.lang.UNIXProcess")) {
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
pid = f.getLong(p);
f.setAccessible(false);
}
} catch (Exception e) {
pid = -1;
}
return pid;
}
public static CorfuRuntime createDefaultRuntime() {
return createRuntime(DEFAULT_ENDPOINT);
}
public static CorfuRuntime createRuntime(String endpoint) {
CorfuRuntime rt = new CorfuRuntime(endpoint)
.setCacheDisabled(true)
.connect();
return rt;
}
public static Map<String, Integer> createMap(CorfuRuntime rt, String streamName) {
Map<String, Integer> map = rt.getObjectsView()
.build()
.setStreamName(streamName)
.setType(SMRMap.class)
.open();
return map;
}
public static class StreamGobbler implements Runnable {
private InputStream inputStream;
private String logfile;
public StreamGobbler(InputStream inputStream, String logfile) {
this.inputStream = inputStream;
this.logfile = logfile;
}
@Override
public void run() {
new BufferedReader(new InputStreamReader(inputStream)).lines()
.forEach((x) -> {
try {
Files.write(Paths.get(logfile), x.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
);
}
}
}