summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--SystemTest.log3
-rw-r--r--TestExpected.log3
-rw-r--r--TestExpectedByz.log5
-rw-r--r--TestExpectedCombined.log10
-rw-r--r--TestExpectedCrash.log5
-rw-r--r--src/main/java/derms/Replica.java2
-rw-r--r--src/main/java/derms/ReplicaManager.java41
-rw-r--r--src/main/java/derms/client/ResponderClient.java96
-rw-r--r--src/main/java/derms/client/ResponderClientCLI.java103
-rw-r--r--src/main/java/derms/frontend/DERMSServerImpl.java5
-rw-r--r--src/main/java/derms/frontend/FE.java3
-rw-r--r--src/main/java/derms/replica1/DERMSServerPublisher.java19
-rw-r--r--src/main/java/derms/replica1/Replica1.java32
-rw-r--r--src/main/java/derms/replica2/Replica2.java32
-rw-r--r--src/main/java/derms/replica3/Replica3.java33
-rw-r--r--src/main/java/derms/util/LogComparator.java27
-rw-r--r--src/main/java/derms/util/TestLogger.java27
-rw-r--r--src/test/java/derms/test/SystemTest.java183
19 files changed, 525 insertions, 110 deletions
diff --git a/.gitignore b/.gitignore
index 6828b80..4357efe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,11 @@
*.bbl
*.bcf
*.blg
-*.log
+QUEServer.log
+MTLServer.log
+SHEServer.log
+server.log
+SystemTest.log
*.run.xml
*.toc
javadoc
diff --git a/SystemTest.log b/SystemTest.log
new file mode 100644
index 0000000..a7e7939
--- /dev/null
+++ b/SystemTest.log
@@ -0,0 +1,3 @@
+[2024-12-03 09:37:37] REPLICA 1: {BYZANTINE: FALSE}
+[2024-12-03 09:37:37] REPLICA 1: {CRASH: FALSE}
+[2024-12-03 09:37:57] [FAILED: Fail: No response from any server]
diff --git a/TestExpected.log b/TestExpected.log
new file mode 100644
index 0000000..1cea96b
--- /dev/null
+++ b/TestExpected.log
@@ -0,0 +1,3 @@
+[2024-12-03 07:48:36] REPLICA 1: {BYZANTINE: FALSE}
+[2024-12-03 07:48:36] REPLICA 1: {CRASH: FALSE}
+[2024-12-03 08:53:10] [SUCCESS: OK] \ No newline at end of file
diff --git a/TestExpectedByz.log b/TestExpectedByz.log
new file mode 100644
index 0000000..b5cabee
--- /dev/null
+++ b/TestExpectedByz.log
@@ -0,0 +1,5 @@
+[2024-12-03 09:22:35] REPLICA 1: {BYZANTINE: TRUE}
+[2024-12-03 09:22:35] REPLICA 1: {CRASH: FALSE}
+[2024-12-03 09:22:35] REPLICA 1: {BYZANTINE: DETECTED}
+[2024-12-03 09:22:35] REPLICA 1: {RESTARTED}
+[2024-12-03 09:22:56] [SUCCESS: OK] \ No newline at end of file
diff --git a/TestExpectedCombined.log b/TestExpectedCombined.log
new file mode 100644
index 0000000..9d955ad
--- /dev/null
+++ b/TestExpectedCombined.log
@@ -0,0 +1,10 @@
+[2024-12-03 09:27:08] REPLICA 1: {BYZANTINE: TRUE}
+[2024-12-03 09:27:08] REPLICA 1: {CRASH: FALSE}
+[2024-12-03 09:27:08] REPLICA 1: {BYZANTINE: DETECTED}
+[2024-12-03 09:27:08] REPLICA 1: {RESTARTED}
+[2024-12-03 09:27:08] REPLICA 3: {BYZANTINE: FALSE}
+[2024-12-03 09:27:08] REPLICA 3: {CRASH: TRUE}
+[2024-12-03 09:27:08] REPLICA 3: {CRASH: DETECTED}
+[2024-12-03 09:27:08] REPLICA 3: {RESTARTED}
+[2024-12-03 09:27:28] [SUCCESS: OK]
+[2024-12-03 09:27:28] [SUCCESS: OK] \ No newline at end of file
diff --git a/TestExpectedCrash.log b/TestExpectedCrash.log
new file mode 100644
index 0000000..90a0640
--- /dev/null
+++ b/TestExpectedCrash.log
@@ -0,0 +1,5 @@
+[2024-12-03 09:22:35] REPLICA 1: {BYZANTINE: FALSE}
+[2024-12-03 09:22:35] REPLICA 1: {CRASH: TRUE}
+[2024-12-03 09:22:35] REPLICA 1: {CRASH: DETECTED}
+[2024-12-03 09:22:35] REPLICA 1: {RESTARTED}
+[2024-12-03 09:22:56] [SUCCESS: OK] \ No newline at end of file
diff --git a/src/main/java/derms/Replica.java b/src/main/java/derms/Replica.java
index eae0014..a717064 100644
--- a/src/main/java/derms/Replica.java
+++ b/src/main/java/derms/Replica.java
@@ -2,7 +2,7 @@ package derms;
public interface Replica {
boolean isAlive();
- void startProcess();
+ void startProcess(int byzantine, int crash);
void processRequest(Request request);
void restart();
int getId();
diff --git a/src/main/java/derms/ReplicaManager.java b/src/main/java/derms/ReplicaManager.java
index 83c1897..64ceb04 100644
--- a/src/main/java/derms/ReplicaManager.java
+++ b/src/main/java/derms/ReplicaManager.java
@@ -10,6 +10,7 @@ import derms.net.runicast.ReliableUnicastSender;
import derms.net.tomulticast.TotalOrderMulticastSender;
import derms.replica1.Replica1;
import derms.replica2.Replica2;
+import derms.util.*;
import java.io.IOException;
import java.net.InetAddress;
@@ -21,7 +22,7 @@ import java.io.ObjectInputStream;
import java.util.logging.Logger;
public class ReplicaManager {
- public static final String usage = "Usage: java ReplicaManager <replicaId> <city> <frontEndIP>";
+ public static final String usage = "Usage: java ReplicaManager <replicaId> <city> <frontEndIP> <byzantine(0 or 1)> <crash(0 or 1)>";
private final int replicaId;
private final String city;
private Replica replica;
@@ -31,12 +32,12 @@ public class ReplicaManager {
private ReliableUnicastSender<Response> unicastSender;
private TotalOrderMulticastReceiver multicastReceiver;
- public ReplicaManager(int replicaId, String city, InetAddress frontEndIP) throws IOException {
+ public ReplicaManager(int replicaId, String city, InetAddress frontEndIP, int byzantine, int crash) throws IOException {
this.replicaId = replicaId;
this.city = city;
this.log = Logger.getLogger(getClass().getName());
initUnicastSender(frontEndIP);
- initReplica();
+ initReplica(byzantine, crash);
initMulticastReceiver();
startHeartbeatThread();
}
@@ -47,7 +48,7 @@ public class ReplicaManager {
unicastSender = new ReliableUnicastSender<>(frontEndAddress);
}
- private void initReplica() throws IOException {
+ private void initReplica(int byzantine, int crash) throws IOException {
switch (replicaId) {
case 1:
replica = new Replica1(this);
@@ -62,7 +63,21 @@ public class ReplicaManager {
replica = new derms.replica2.Replica2(city, this);
break;
}
- replica.startProcess();
+
+ // [TEST] Logging
+ if (byzantine == 0) {
+ TestLogger.log("REPLICA " + replicaId + ": {BYZANTINE: FALSE}");
+ } else {
+ TestLogger.log("REPLICA " + replicaId + ": {BYZANTINE: TRUE}");
+ }
+
+ if (crash == 0) {
+ TestLogger.log("REPLICA " + replicaId + ": {CRASH: FALSE}");
+ } else {
+ TestLogger.log("REPLICA " + replicaId + ": {CRASH: TRUE}");
+ }
+
+ replica.startProcess(byzantine, crash);
}
private void initMulticastReceiver() throws IOException {
@@ -92,8 +107,12 @@ public class ReplicaManager {
new Thread(() -> {
while (true) {
if (!replica.isAlive()) {
+ // [TEST] Logging
+ TestLogger.log("REPLICA " + replicaId + ": {CRASH: DETECTED}");
+
informFrontEndRmIsDown(replica.getId());
replica.restart();
+ //TestLogger.log("REPLICA " + replicaId + ": {RESTARTED}");
}
try {
Thread.sleep(5000); // Example 5 seconds.
@@ -114,8 +133,13 @@ public class ReplicaManager {
public void handleByzantineFailure() {
log.severe("Byzantine failure detected in Replica " + replica.getId());
+
+ // [TEST] Logging
+ TestLogger.log("REPLICA " + replicaId + ": {BYZANTINE: DETECTED}");
+
replica.restart();
informFrontEndRmHasBug(replica.getId());
+ //TestLogger.log("REPLICA " + replicaId + ": {RESTARTED}");
}
private void informFrontEndRmIsDown(int replicaId) {
@@ -124,6 +148,7 @@ public class ReplicaManager {
out.writeObject("RM_DOWN:" + replicaId);
} catch (IOException e) {
log.severe("Failed to inform FE that RM is down: " + e.getMessage());
+ TestLogger.log("[FAILED TO INFORM FE (RM IS DOWN)]");
}
}
@@ -133,6 +158,7 @@ public class ReplicaManager {
out.writeObject("RM_BUG:" + replicaId);
} catch (IOException e) {
log.severe("Failed to inform FE that RM has a bug: " + e.getMessage());
+ TestLogger.log("[FAILED TO INFORM FE (RM HAS A BUG)]");
}
}
@@ -146,10 +172,13 @@ public class ReplicaManager {
int replicaId = Integer.parseInt(args[0]);
String city = args[1];
InetAddress frontEndIP = InetAddress.getByName(args[2]);
- ReplicaManager replicaManager = new ReplicaManager(replicaId, city, frontEndIP);
+ int byzantine = Integer.parseInt(args[3]);
+ int crash = Integer.parseInt(args[4]);
+ ReplicaManager replicaManager = new ReplicaManager(replicaId, city, frontEndIP, byzantine, crash);
System.out.println("ReplicaManager " + replicaId + " is running.");
} catch (IOException e) {
System.err.println("Failed to start ReplicaManager: " + e.getMessage());
+ TestLogger.log("[FAILED TO START RM]");
e.printStackTrace();
}
}
diff --git a/src/main/java/derms/client/ResponderClient.java b/src/main/java/derms/client/ResponderClient.java
index 23361d7..4db30c8 100644
--- a/src/main/java/derms/client/ResponderClient.java
+++ b/src/main/java/derms/client/ResponderClient.java
@@ -1,103 +1,27 @@
package derms.client;
import derms.frontend.DERMSInterface;
+import derms.util.TestLogger;
import java.net.MalformedURLException;
+import java.util.Objects;
-public class ResponderClient extends CLI {
- public static final String usage = "Usage: java derms.client.ResponderClient <FE host>";
-
+public class ResponderClient {
private final DERMSInterface server;
- private ResponderClient(String FEhost) throws MalformedURLException {
+ public ResponderClient(String FEhost) throws MalformedURLException {
server = Client.connectToServer(FEhost);
-
- commands.put("add", new Add());
- cmdDescriptions.add(new Description(
- "add <resource ID> <resource type> <duration>",
- "Add ad resource to the server"));
-
- commands.put("remove", new Remove());
- cmdDescriptions.add(new Description(
- "remove <resource ID> <duration>",
- "Decrease the duration of a resource. If duration is negative, the resource is removed entirely."));
-
- commands.put("list", new List());
- cmdDescriptions.add(new Description(
- "list <resource name>",
- "List available resources"));
}
- public static void main(String[] args) {
- if (args.length < 1) {
- System.err.println(usage);
- System.exit(1);
- }
-
- String FEhost = args[0];
-
- try {
- (new ResponderClient(FEhost)).run();
- } catch (MalformedURLException e) {
- e.printStackTrace();
- }
+ public String addResource(String resourceID, String resourceName, int duration) {
+ return server.addResource(resourceID, resourceName, duration);
}
- private class Add implements Command {
- @Override
- public void exec(String[] args) {
- if (args.length < 3)
- System.out.println("invalid arguments for 'add'");
- else
- add(args[0], args[1], args[2]);
- }
-
- private void add(String resourceID, String resourceName, String durationStr) {
- try {
- int duration = Integer.parseInt(durationStr);
- if (duration < 0) {
- throw new NumberFormatException("duration less than 0");
- }
- String response = server.addResource(resourceID, resourceName, duration);
- System.out.println(response);
- } catch (NumberFormatException e) {
- System.out.println("invalid duration: " + durationStr);
- }
- }
+ public String removeResource(String resourceID, int duration) {
+ return server.removeResource(resourceID, duration);
}
- private class Remove implements Command {
- @Override
- public void exec(String[] args) {
- if (args.length < 2)
- System.out.println("invalid arguments for 'remove'");
- else
- remove(args[0], args[1]);
- }
-
- private void remove(String resourceID, String durationStr) {
- try {
- int duration = Integer.parseInt(durationStr);
- String response = server.removeResource(resourceID, duration);
- System.out.println(response);
- } catch (NumberFormatException e) {
- System.out.println("invalid duration: " + durationStr);
- }
- }
- }
-
- private class List implements Command {
- @Override
- public void exec(String[] args) {
- if (args.length < 1)
- System.out.println("invalid arguments for 'list'");
- else
- list(args[0]);
- }
-
- private void list(String resourceName) {
- String response = server.listResourceAvailability(resourceName);
- System.out.println(response);
- }
+ public String listResourceAvailability(String resourceName) {
+ return server.listResourceAvailability(resourceName);
}
}
diff --git a/src/main/java/derms/client/ResponderClientCLI.java b/src/main/java/derms/client/ResponderClientCLI.java
new file mode 100644
index 0000000..86a5a9a
--- /dev/null
+++ b/src/main/java/derms/client/ResponderClientCLI.java
@@ -0,0 +1,103 @@
+package derms.client;
+
+import derms.frontend.DERMSInterface;
+
+import java.net.MalformedURLException;
+
+public class ResponderClientCLI extends CLI {
+ public static final String usage = "Usage: java derms.client.ResponderClient <FE host>";
+
+ private final DERMSInterface server;
+
+ private ResponderClientCLI(String FEhost) throws MalformedURLException {
+ server = Client.connectToServer(FEhost);
+
+ commands.put("add", new Add());
+ cmdDescriptions.add(new CLI.Description(
+ "add <resource ID> <resource type> <duration>",
+ "Add ad resource to the server"));
+
+ commands.put("remove", new Remove());
+ cmdDescriptions.add(new CLI.Description(
+ "remove <resource ID> <duration>",
+ "Decrease the duration of a resource. If duration is negative, the resource is removed entirely."));
+
+ commands.put("list", new List());
+ cmdDescriptions.add(new CLI.Description(
+ "list <resource name>",
+ "List available resources"));
+ }
+
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println(usage);
+ System.exit(1);
+ }
+
+ String FEhost = args[0];
+
+ try {
+ (new ResponderClientCLI(FEhost)).run();
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private class Add implements CLI.Command {
+ @Override
+ public void exec(String[] args) {
+ if (args.length < 3)
+ System.out.println("invalid arguments for 'add'");
+ else
+ add(args[0], args[1], args[2]);
+ }
+
+ private void add(String resourceID, String resourceName, String durationStr) {
+ try {
+ int duration = Integer.parseInt(durationStr);
+ if (duration < 0) {
+ throw new NumberFormatException("duration less than 0");
+ }
+ String response = server.addResource(resourceID, resourceName, duration);
+ System.out.println(response);
+ } catch (NumberFormatException e) {
+ System.out.println("invalid duration: " + durationStr);
+ }
+ }
+ }
+
+ private class Remove implements CLI.Command {
+ @Override
+ public void exec(String[] args) {
+ if (args.length < 2)
+ System.out.println("invalid arguments for 'remove'");
+ else
+ remove(args[0], args[1]);
+ }
+
+ private void remove(String resourceID, String durationStr) {
+ try {
+ int duration = Integer.parseInt(durationStr);
+ String response = server.removeResource(resourceID, duration);
+ System.out.println(response);
+ } catch (NumberFormatException e) {
+ System.out.println("invalid duration: " + durationStr);
+ }
+ }
+ }
+
+ private class List implements CLI.Command {
+ @Override
+ public void exec(String[] args) {
+ if (args.length < 1)
+ System.out.println("invalid arguments for 'list'");
+ else
+ list(args[0]);
+ }
+
+ private void list(String resourceName) {
+ String response = server.listResourceAvailability(resourceName);
+ System.out.println(response);
+ }
+ }
+}
diff --git a/src/main/java/derms/frontend/DERMSServerImpl.java b/src/main/java/derms/frontend/DERMSServerImpl.java
index 1877cef..2a5dfa9 100644
--- a/src/main/java/derms/frontend/DERMSServerImpl.java
+++ b/src/main/java/derms/frontend/DERMSServerImpl.java
@@ -5,10 +5,6 @@ package derms.frontend;
//import model.Resource;
import java.util.*;
import java.util.stream.Collectors;
-import java.util.Optional;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -23,6 +19,7 @@ import javax.jws.soap.SOAPBinding.Style;
//import interfaces.DERMSInterface;
import derms.Request;
import derms.Response;
+import derms.util.TestLogger;
@WebService(endpointInterface = "derms.frontend.DERMSInterface")
public class DERMSServerImpl implements DERMSInterface {
diff --git a/src/main/java/derms/frontend/FE.java b/src/main/java/derms/frontend/FE.java
index f29459c..b3e100f 100644
--- a/src/main/java/derms/frontend/FE.java
+++ b/src/main/java/derms/frontend/FE.java
@@ -10,6 +10,7 @@ import derms.Request;
import derms.Response;
import derms.net.runicast.ReliableUnicastReceiver;
import derms.net.runicast.ReliableUnicastSender;
+import derms.util.TestLogger;
import java.util.ArrayList;
import java.util.List;
@@ -51,6 +52,7 @@ public class FE {
System.out.println("Rm:" + RmNumber + "has bug");
// sendMulticastFaultMessageToRms(errorMessage);
sendUnicastToSequencer(errorMessage);
+ //TestLogger.log("FE: {BYZANTINE: INFORM REPLICA" + RmNumber + "}");
}
@Override
@@ -60,6 +62,7 @@ public class FE {
System.out.println("Rm:" + RmNumber + "is down");
// sendMulticastFaultMessageToRms(errorMessage);
sendUnicastToSequencer(errorMessage);
+ //TestLogger.log("FE: {CRASH: INFORM REPLICA" + RmNumber + "}");
}
@Override
diff --git a/src/main/java/derms/replica1/DERMSServerPublisher.java b/src/main/java/derms/replica1/DERMSServerPublisher.java
index 3edf16c..442f844 100644
--- a/src/main/java/derms/replica1/DERMSServerPublisher.java
+++ b/src/main/java/derms/replica1/DERMSServerPublisher.java
@@ -3,14 +3,25 @@ package derms.replica1;
import javax.xml.ws.Endpoint;
public class DERMSServerPublisher {
+
+ private static Endpoint[] endpoints = new Endpoint[3];
+
public static void main(String[] args) {
try {
- Endpoint.publish("http://localhost:8387/ws/derms", new DERMSServer("MTL"));
- Endpoint.publish("http://localhost:8081/ws/derms", new DERMSServer("QUE"));
- Endpoint.publish("http://localhost:8082/ws/derms", new DERMSServer("SHE"));
+ endpoints[0] = Endpoint.publish("http://localhost:8387/ws/derms", new DERMSServer("MTL"));
+ endpoints[1] = Endpoint.publish("http://localhost:8081/ws/derms", new DERMSServer("QUE"));
+ endpoints[3] = Endpoint.publish("http://localhost:8082/ws/derms", new DERMSServer("SHE"));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
- System.out.println("DERMS Web Service is published at http://localhost:8387/ws/derms");
+ }
+
+ public static void stop() {
+ for (Endpoint endpoint : endpoints) {
+ if (endpoint != null && endpoint.isPublished()) {
+ endpoint.stop();
+ System.out.println("DERMS Server is stopped.");
+ }
+ }
}
} \ No newline at end of file
diff --git a/src/main/java/derms/replica1/Replica1.java b/src/main/java/derms/replica1/Replica1.java
index a59ed4c..c9e080d 100644
--- a/src/main/java/derms/replica1/Replica1.java
+++ b/src/main/java/derms/replica1/Replica1.java
@@ -5,6 +5,7 @@ import derms.ReplicaManager;
import derms.Request;
import derms.Response;
import derms.replica2.DermsLogger;
+import derms.util.TestLogger;
import derms.util.ThreadPool;
import java.io.IOException;
@@ -26,6 +27,7 @@ public class Replica1 implements Replica {
private final String coordinatorClientID = "MTLC1111";
private final ReplicaManager replicaManager;
private DERMSServer server;
+ private boolean byzFailure;
public Replica1(ReplicaManager replicaManager) {
this.replicaManager = replicaManager;
@@ -50,7 +52,21 @@ public class Replica1 implements Replica {
}
@Override
- public void startProcess() {
+ public void startProcess(int byzantine, int crash) {
+ // [TEST] Detect crash
+ if (crash == 1) {
+ alive = false;
+ } else {
+ alive = true;
+ }
+
+ // [TEST] Detect byzantine failure
+ if (byzantine == 1) {
+ byzFailure = true;
+ } else {
+ byzFailure = false;
+ }
+
try {
server = new DERMSServer("MTL");
} catch (InterruptedException e) {
@@ -66,13 +82,20 @@ public class Replica1 implements Replica {
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
- alive = true;
+
log.info(getClass().getSimpleName() + " started.");
log.config("Local address is "+localAddr.toString());
}
@Override
public void processRequest(Request request) {
+ // [TEST] Simulate byzantine failure (return incorrect value)
+ if (byzFailure == true) {
+ Response response = new Response(request, replicaManager.getReplicaId(), "BYZANTINE FAILURE", false);
+ replicaManager.sendResponseToFE(response);
+ return;
+ }
+
log.info(request.toString());
String status = "";
@@ -121,7 +144,10 @@ public class Replica1 implements Replica {
ThreadPool.shutdown(pool, log);
alive = false;
log.info("Finished shutting down.");
- startProcess();
+
+ // [TEST] Restart process without byzantine failure or crash
+ TestLogger.log("REPLICA 1: {RESTARTED}");
+ startProcess(0, 0);
}
@Override
diff --git a/src/main/java/derms/replica2/Replica2.java b/src/main/java/derms/replica2/Replica2.java
index 3c9f764..cf21d74 100644
--- a/src/main/java/derms/replica2/Replica2.java
+++ b/src/main/java/derms/replica2/Replica2.java
@@ -1,6 +1,7 @@
package derms.replica2;
import derms.*;
+import derms.util.TestLogger;
import derms.util.ThreadPool;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
@@ -24,6 +25,7 @@ public class Replica2 implements Replica {
private final ReplicaManager replicaManager;
private final ExecutorService pool;
private boolean alive = false;
+ private boolean byzFailure;
public Replica2(City city, ReplicaManager replicaManager) throws IOException {
this.city = city;
@@ -57,7 +59,21 @@ public class Replica2 implements Replica {
public boolean isAlive() { return alive; }
@Override
- public void startProcess() {
+ public void startProcess(int byzantine, int crash) {
+ // [TEST] Detect crash
+ if (crash == 1) {
+ alive = false;
+ } else {
+ alive = true;
+ }
+
+ // [TEST] Detect byzantine failure
+ if (byzantine == 1) {
+ byzFailure = true;
+ } else {
+ byzFailure = false;
+ }
+
try {
pool.execute(new ResourceAvailability.Server(localAddr, resources));
} catch (IOException e) {
@@ -105,7 +121,7 @@ public class Replica2 implements Replica {
log.info("Running");
log.config("Local address is "+localAddr.toString());
- alive = true;
+ //alive = true;
log.info(getClass().getSimpleName() + " started.");
}
@@ -113,6 +129,13 @@ public class Replica2 implements Replica {
public void processRequest(Request request) {
log.info(request.toString());
+ // [TEST] Simulate byzantine failure (return incorrect value)
+ if (byzFailure == true) {
+ Response response = new Response(request, replicaManager.getReplicaId(), "BYZANTINE FAILURE", false);
+ replicaManager.sendResponseToFE(response);
+ return;
+ }
+
String status = "";
try {
switch (request.getFunction()) {
@@ -153,7 +176,10 @@ public class Replica2 implements Replica {
@Override
public void restart() {
shutdown();
- startProcess();
+
+ // [TEST] Restart process without byzantine failure or crash
+ TestLogger.log("REPLICA 2: {RESTARTED}");
+ startProcess(0, 0);
}
@Override
diff --git a/src/main/java/derms/replica3/Replica3.java b/src/main/java/derms/replica3/Replica3.java
index 8863c9e..9e262e3 100644
--- a/src/main/java/derms/replica3/Replica3.java
+++ b/src/main/java/derms/replica3/Replica3.java
@@ -13,6 +13,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import derms.replica3.Logger;
+import derms.util.TestLogger;
public class Replica3 implements Replica{
static final InetSocketAddress announceGroup = new InetSocketAddress("225.5.5.5", 5555);
@@ -27,6 +28,7 @@ public class Replica3 implements Replica{
// private final Logger log;
private boolean alive;
+ private boolean byzFailure;
private final ReplicaManager replicaManager;
public Replica3(City city, ReplicaManager replicaManager) throws IOException {
@@ -42,6 +44,7 @@ public class Replica3 implements Replica{
// log.config("Local address is "+localAddr.toString());
this.alive = true;
+ this.byzFailure = false;
}
public Replica3(String city, ReplicaManager replicaManager) throws IOException {
@@ -52,7 +55,21 @@ public class Replica3 implements Replica{
public boolean isAlive() { return alive; }
@Override
- public void startProcess() {
+ public void startProcess(int byzantine, int crash) {
+ // [TEST] Detect crash
+ if (crash == 1) {
+ alive = false;
+ } else {
+ alive = true;
+ }
+
+ // [TEST] Detect byzantine failure
+ if (byzantine == 1) {
+ byzFailure = true;
+ } else {
+ byzFailure = false;
+ }
+
// TODO
// log.info(getClass().getSimpleName() + " started.");
System.out.println("process started");
@@ -61,6 +78,14 @@ public class Replica3 implements Replica{
@Override
public void processRequest(Request request) {
// log.info(request.toString());
+
+ // [TEST] Simulate byzantine failure (return incorrect value)
+ if (byzFailure == true) {
+ Response response = new Response(request, replicaManager.getReplicaId(), "BYZANTINE FAILURE", false);
+ replicaManager.sendResponseToFE(response);
+ return;
+ }
+
System.out.println("process request and good");
String status = "";
try {
@@ -104,7 +129,10 @@ public class Replica3 implements Replica{
public void restart() {
// TODO
shutdown();
- startProcess();
+
+ // [TEST] Restart process without byzantine failure or crash
+ TestLogger.log("REPLICA 3: {RESTARTED}");
+ startProcess(0, 0);
}
@Override
@@ -112,6 +140,7 @@ public class Replica3 implements Replica{
private void shutdown() {
// TODO
+ alive = false;
}
public synchronized String addResource(String resourceID, String resourceName, int duration) {
diff --git a/src/main/java/derms/util/LogComparator.java b/src/main/java/derms/util/LogComparator.java
new file mode 100644
index 0000000..da3736c
--- /dev/null
+++ b/src/main/java/derms/util/LogComparator.java
@@ -0,0 +1,27 @@
+package derms.util;
+
+import java.io.*;
+import java.nio.file.*;
+
+public class LogComparator {
+ public static boolean compareLineCounts(String actualFilePath, String expectedFilePath) throws IOException {
+ long actualLineCount = Files.lines(Paths.get(actualFilePath)).count();
+ long expectedLineCount = Files.lines(Paths.get(expectedFilePath)).count();
+ System.out.println("XXXXXXXXX ACTUAL LINE: " + actualLineCount);
+ System.out.println("XXXXXXXXX EXPECTED: " + expectedLineCount);
+ return actualLineCount == expectedLineCount;
+ }
+public static boolean containsSuccess(String filePath) throws IOException {
+ return Files.lines(Paths.get(filePath)).anyMatch(line -> line.contains("SUCCESS"));
+}
+
+public static boolean compareFiles(String actualFilePath, String expectedFilePath) throws IOException {
+ boolean lineCountsMatch = compareLineCounts(actualFilePath, expectedFilePath);
+ boolean actualContainsSuccess = containsSuccess(actualFilePath);
+ System.out.println("XXXXXXXXX ACTUAL SUCCESS: " + actualContainsSuccess);
+ boolean expectedContainsSuccess = containsSuccess(expectedFilePath);
+ System.out.println("XXXXXXXXX EXPECTED SUCCESS: " + expectedContainsSuccess);
+
+ return lineCountsMatch && actualContainsSuccess && expectedContainsSuccess;
+}
+} \ No newline at end of file
diff --git a/src/main/java/derms/util/TestLogger.java b/src/main/java/derms/util/TestLogger.java
new file mode 100644
index 0000000..7bb34db
--- /dev/null
+++ b/src/main/java/derms/util/TestLogger.java
@@ -0,0 +1,27 @@
+package derms.util;
+
+import java.io.*;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class TestLogger {
+ private static final String LOG_FILE = "SystemTest.log";
+
+ public static void log(String message) {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(LOG_FILE, true))) {
+ String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ writer.write("[" + timestamp + "] " + message);
+ writer.newLine();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void clearLog() {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(LOG_FILE))) {
+ // Clear the file by overwriting it with nothing
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/java/derms/test/SystemTest.java b/src/test/java/derms/test/SystemTest.java
new file mode 100644
index 0000000..59b08fa
--- /dev/null
+++ b/src/test/java/derms/test/SystemTest.java
@@ -0,0 +1,183 @@
+package derms.test;
+
+import derms.ReplicaManager;
+import derms.Sequencer;
+import derms.client.ResponderClient;
+import derms.frontend.FE;
+import derms.replica1.DERMSServerPublisher;
+
+import org.junit.jupiter.api.*;
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.nio.file.*;
+import java.util.*;
+import derms.util.*;
+import derms.replica3.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SystemTest {
+
+ private static final String TEST_LOG_PATH = "SystemTest.log";
+ private static final String EXPECTED_LOG_PATH_NORM = "TestExpected.log";
+ private static final String EXPECTED_LOG_PATH_BYZ = "TestExpectedByz.log";
+ private static final String EXPECTED_LOG_PATH_CRASH = "TestExpectedCrash.log";
+ private static final String EXPECTED_LOG_PATH_COMBINED = "TestExpectedCombined.log";
+
+ // [TODO]
+ // input IP and NET config
+ private static String IP = "127.0.0.1";
+
+ @BeforeEach
+ void clearLogFile() throws IOException {
+ TestLogger.clearLog();
+ }
+
+ @BeforeAll
+ static void runMains() throws IOException {
+ String[] argsFE = {IP, IP};
+
+ Thread feThread = new Thread(() -> {
+ try {
+ FE.main(argsFE);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ feThread.start();
+
+ Thread sequencerThread = new Thread(() -> {
+ try {
+ InetAddress ip = InetAddress.getByName(IP);
+ NetworkInterface netIfc = NetworkInterface.getByInetAddress(ip);
+ Sequencer sequencer = new Sequencer(ip, netIfc);
+ sequencer.run();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ sequencerThread.start();
+ }
+
+ @AfterEach
+ void stopServers() {
+ // Stop the DERMSServerPublisher
+ DERMSServerPublisher.stop();
+ }
+
+ @Test
+ void testNormal() throws IOException {
+ // Replica 1
+ String[] argsRM = {"1", "MTL", IP, "0", "0"};
+
+ // [TODO]
+ // Run the main function of the desired replica, for example:
+ DERMSServerPublisher.main(new String[0]);
+
+ ReplicaManager.main(argsRM);
+ ResponderClient responderClient = new ResponderClient(IP);
+ responderClient.addResource("MTL1001", "ambulance", 10);
+
+ // Compare the number of lines in the log files, to determine if they match or not
+ assertTrue(LogComparator.compareFiles(TEST_LOG_PATH, EXPECTED_LOG_PATH_NORM));
+ }
+
+ @Test
+ void testByzantine() throws IOException {
+ // Replica 1
+ String[] argsRM = {"1", "MTL", IP, "1", "0"};
+
+ // [TODO]
+ // Run the main function of the desired replica, for example:
+ DERMSServerPublisher.main(new String[0]);
+
+ ReplicaManager.main(argsRM);
+ ResponderClient responderClient = new ResponderClient(IP);
+ responderClient.addResource("MTL1001", "ambulance", 10);
+
+ // Compare the number of lines in the log files, to determine if they match or not
+ assertTrue(LogComparator.compareFiles(TEST_LOG_PATH, EXPECTED_LOG_PATH_BYZ));
+ }
+
+ @Test
+ void testCrash() throws IOException {
+ // Replica 1
+ String[] argsRM = {"1", "MTL", IP, "0", "1"};
+
+ // [TODO]
+ // Run the main function of the desired replica, for example:
+ DERMSServerPublisher.main(new String[0]);
+
+ ReplicaManager.main(argsRM);
+ ResponderClient responderClient = new ResponderClient(IP);
+ responderClient.addResource("MTL1001", "ambulance", 10);
+
+ // Compare the number of lines in the log files, to determine if they match or not
+ assertTrue(LogComparator.compareFiles(TEST_LOG_PATH, EXPECTED_LOG_PATH_CRASH));
+ }
+
+ @Test
+ void testCombined() throws IOException {
+ // Replica 1 and 2
+ String[] argsRM1 = {"1", "MTL", IP, "1", "0"};
+ String[] argsRM3 = {"3", "MTL", IP, "0", "1"};
+
+ // [TODO]
+ // Run the main function of the desired TWO replicas, for example:
+ DERMSServerPublisher.main(new String[0]);
+ MTLServer.main(new String[0]);
+ QUEServer.main(new String[0]);
+ SHEServer.main(new String[0]);
+
+ ReplicaManager.main(argsRM1);
+ ReplicaManager.main(argsRM3);
+
+ Thread thread1 = new Thread(() -> {
+ ResponderClient responderClient = null;
+ try {
+ responderClient = new ResponderClient(IP);
+ } catch (MalformedURLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ if (responderClient != null) {
+ responderClient.addResource("MTL1001", "ambulance", 10);
+ }
+ }
+ });
+
+ Thread thread2 = new Thread(() -> {
+ ResponderClient responderClient2 = null;
+ try {
+ responderClient2 = new ResponderClient(IP);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ } finally {
+ if (responderClient2 != null) {
+ responderClient2.addResource("MTL1002", "ambulance", 11);
+ }
+ }
+ });
+
+ thread1.start();
+ thread2.start();
+
+ try {
+ thread1.join();
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ try {
+ thread2.join();
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ // Compare the number of lines in the log files, to determine if they match or not
+ assertTrue(LogComparator.compareFiles(TEST_LOG_PATH, EXPECTED_LOG_PATH_COMBINED));
+ }
+} \ No newline at end of file