package edu.berkeley.cs162;
import static org.junit.Assert.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class EndToEndPutGet {
private final Integer numSlaves = 2;
private ServerRunner coordinatorRunner;
private Map<String, ServerRunner> slaveRunners;
private TPCMaster master;
@Before
public void setUp() throws Exception {
master = new TPCMaster(numSlaves);
// Set up Coordinator Server
SocketServer kvServer = new RobustSocketServer(
InetAddress.getLocalHost().getHostAddress(), 8888);
kvServer.addHandler(new KVClientHandler(master));
coordinatorRunner = new ServerRunner(kvServer,
"Coordinator Server",
"Handles KVClient requests to the system.");
coordinatorRunner.start();
System.out.println("INFO EndToEnd.setUp: Coordinator server ready.");
// Set up Registration Server
System.out.print("INFO EndToEnd.setUp: Running registration server... ");
master.run();
Thread.sleep(3000);
// Set up slaves
slaveRunners = new HashMap<String, ServerRunner>();
setUpSlave("Mr. Slave");
setUpSlave("Mrs. Slave");
Thread.sleep(3000);
}
private void setUpSlave(String name) throws UnknownHostException, IOException, KVException {
System.out.format("INFO EndToEnd.setUp: Set up %s%n", name);
SocketServer slave =
new RobustSocketServer(InetAddress.getLocalHost().getHostAddress(), 0);
long slaveId = hashTo64bit(name);
KVServer slaveKvs = new KVServer(100, 10);
TPCMasterHandler handler =
new TPCMasterHandler(slaveKvs, slaveId);
slave.addHandler(handler);
// Create TPCLog
String logPath = "slave" + slaveId + "@" + slave.getHostname();
TPCLog mrLog = new TPCLog(logPath, new KVServer(numSlaves, numSlaves));
// Set log for TPCMasterHandler
handler.setTPCLog(mrLog);
ServerRunner slaveRunner = new ServerRunner(slave, name, "A Slave Server");
slaveRunner.start();
slaveRunners.put(name, slaveRunner);
// Register with the Master. Assuming it always succeeds (not catching).
handler.registerWithMaster(InetAddress.getLocalHost().getHostAddress(), slave);
}
@After
public void tearDown() throws Exception {
coordinatorRunner.stop();
for (ServerRunner slaveRunner : slaveRunners.values()) {
slaveRunner.stop();
}
master = null;
}
public static class ServerRunner implements Runnable {
public static final int THREAD_STOP_TIMEOUT_MS = 1000 * 10; // Wait 10 seconds
public ServerRunner(SocketServer socs, String name, String desc) {
sockserver = socs;
runnerName = name;
runnerDesc = desc;
}
private final SocketServer sockserver;
public final String runnerName;
public final String runnerDesc;
private Thread thread = null;
private boolean isUp = false;
@Override
public void run() {
try {
sockserver.connect();
System.out.format("Running %s...%n", runnerName);
synchronized (this) {
isUp = true;
notifyAll();
}
sockserver.run();
synchronized (this) {
isUp = false;
notifyAll();
}
} catch (Exception e) {
System.out.println(String.format(
"SERVER-SIDE: Error from %s", runnerName));
e.printStackTrace();
}
}
public void start() {
if (thread == null) {
thread = new Thread(this, runnerName);
thread.setDaemon(true); // Allow JVM to exit if thread abandoned
System.out.format("INFO ServerRunner.start: Starting %s: %s%n",
runnerName, runnerDesc);
thread.start();
while (!isUp) {
try {
synchronized (this) {
this.wait(100);
}
} catch (InterruptedException e) {}
}
System.out.format("INFO ServerRunner.start: %s is now up.%n", runnerName, runnerDesc);
}
}
public void stop() {
System.out.format("INFO ServerRunner: Stopping %s%n", runnerName);
if (sockserver != null) {
sockserver.stop();
}
if (thread != null) {
try {
thread.join(THREAD_STOP_TIMEOUT_MS);
} catch (InterruptedException e) {
System.out.format("ERROR ServerRunner: " +
"Failed to stop Server (%s), giving up.%n", runnerName);
}
}
isUp = false;
thread = null;
}
}
public class RobustSocketServer extends SocketServer {
private boolean stopSocketServer;
public RobustSocketServer(String hostname, int port) {
super(hostname);
this.hostname = hostname;
this.port = port;
}
@Override
public void connect() throws IOException {
server = new ServerSocket(this.port);
server.setReuseAddress(true);
server.setSoTimeout(100); // Timeout after a while, instead of blocking forever
if (this.port == 0) {
this.port = server.getLocalPort();
}
}
@Override
public void run() throws IOException {
while (!stopSocketServer) {
try {
Socket clientConn = server.accept();
if (clientConn != null) {
handler.handle(clientConn);
// Don't close it here...it's queued for asynchronous handling!
}
} catch (SocketTimeoutException e) {
// Do nothing, this is normal
} catch (IOException e) {
if (server.isClosed() || !server.isBound()) throw e;
}
}
// Close the socket
closeSocket();
}
@Override
public void stop() {
stopSocketServer = true;
}
public void closeSocket() {
if (server.isClosed()) return;
try {
server.close();
} catch (IOException e) {
}
}
}
private static long hashTo64bit(String string) {
// Take a large prime
long h = 1125899906842597L;
int len = string.length();
for (int i = 0; i < len; i++) {
h = 31*h + string.charAt(i);
}
return h;
}
/**
* Simple end-to-end put/get
* @throws InterruptedException
* @throws KVException
* @throws UnknownHostException
*/
@Test(timeout = 15000)
public void testPutGet() throws InterruptedException, KVException, UnknownHostException {
System.out.println("INFO EndToEnd.testPutGet: Begin.");
KVClient client = new KVClient(InetAddress.getLocalHost().getHostAddress(), 8888);
try {
client.put("foo", "bar");
} catch (Exception e) {
e.printStackTrace();
System.out.println("INFO EndToEnd.testPutGet: Finished.");
fail("put failed");
}
try {
assertEquals("get failed", client.get("foo"), "bar");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("INFO EndToEnd.testPutGet: Finished.");
}
}
}