diff options
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | SystemTest.log | 3 | ||||
| -rw-r--r-- | TestExpected.log | 3 | ||||
| -rw-r--r-- | TestExpectedByz.log | 5 | ||||
| -rw-r--r-- | TestExpectedCombined.log | 10 | ||||
| -rw-r--r-- | TestExpectedCrash.log | 5 | ||||
| -rw-r--r-- | src/main/java/derms/Replica.java | 2 | ||||
| -rw-r--r-- | src/main/java/derms/ReplicaManager.java | 41 | ||||
| -rw-r--r-- | src/main/java/derms/client/ResponderClient.java | 96 | ||||
| -rw-r--r-- | src/main/java/derms/client/ResponderClientCLI.java | 103 | ||||
| -rw-r--r-- | src/main/java/derms/frontend/DERMSServerImpl.java | 5 | ||||
| -rw-r--r-- | src/main/java/derms/frontend/FE.java | 3 | ||||
| -rw-r--r-- | src/main/java/derms/replica1/DERMSServerPublisher.java | 19 | ||||
| -rw-r--r-- | src/main/java/derms/replica1/Replica1.java | 32 | ||||
| -rw-r--r-- | src/main/java/derms/replica2/Replica2.java | 32 | ||||
| -rw-r--r-- | src/main/java/derms/replica3/Replica3.java | 33 | ||||
| -rw-r--r-- | src/main/java/derms/util/LogComparator.java | 27 | ||||
| -rw-r--r-- | src/main/java/derms/util/TestLogger.java | 27 | ||||
| -rw-r--r-- | src/test/java/derms/test/SystemTest.java | 183 |
19 files changed, 525 insertions, 110 deletions
@@ -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 |