diff --git a/.gitignore b/.gitignore index b1db24e6b8..5f04cf20a8 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ fe2-android/app/src/main/resources/protocol .DS_Store **/.DS_Store + +# Scala Metals +*.metals \ No newline at end of file diff --git a/be1-go/cli/cli.go b/be1-go/cli/cli.go index 34b173615d..1b5f44f3d1 100644 --- a/be1-go/cli/cli.go +++ b/be1-go/cli/cli.go @@ -5,8 +5,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/rs/zerolog" - "golang.org/x/exp/slices" "net/url" "os" popstellar "popstellar" @@ -24,6 +22,9 @@ import ( "sync" "time" + "github.com/rs/zerolog" + "golang.org/x/exp/slices" + "github.com/rs/zerolog/log" "go.dedis.ch/kyber/v3" @@ -57,6 +58,7 @@ type ServerConfig struct { ServerPort int `json:"server-port"` AuthPort int `json:"auth-port"` OtherServers []string `json:"other-servers"` + DatabasePath string `json:"database-path"` } func (s *ServerConfig) newHub(l *zerolog.Logger) (hub.Hub, error) { @@ -69,12 +71,6 @@ func (s *ServerConfig) newHub(l *zerolog.Logger) (hub.Hub, error) { s.ServerAddress = fmt.Sprintf("ws://%s:%d/server", s.PublicAddress, s.ServerPort) } - path := "./database-a/" + sqlite.DefaultPath - - if s.ClientPort == 9002 { - path = "./database-b/" + sqlite.DefaultPath - } - var point kyber.Point = nil err := ownerKey(s.PublicKey, &point) if err != nil { @@ -86,7 +82,7 @@ func (s *ServerConfig) newHub(l *zerolog.Logger) (hub.Hub, error) { return nil, err } - db, err := sqlite.NewSQLite(path, true) + db, err := sqlite.NewSQLite(s.DatabasePath, true) if err != nil { return nil, err } @@ -407,6 +403,7 @@ func startWithFlags(cliCtx *cli.Context) (ServerConfig, error) { ServerPort: serverPort, AuthPort: authPort, OtherServers: cliCtx.StringSlice("other-servers"), + DatabasePath: cliCtx.String("database-path"), }, nil } diff --git a/be1-go/cli/pop.go b/be1-go/cli/pop.go index d0ecb91606..9889cc7635 100644 --- a/be1-go/cli/pop.go +++ b/be1-go/cli/pop.go @@ -93,6 +93,11 @@ func run(ctx context.Context, args []string) { Aliases: []string{"cf"}, Usage: "path to the config file which will override other flags if present", } + databasePathFlag := &cli.StringFlag{ + Name: "database-path", + Aliases: []string{"dbp"}, + Usage: "path to the database file", + } app := &cli.App{ Name: "pop", @@ -119,6 +124,7 @@ func run(ctx context.Context, args []string) { authServerPortFlag, otherServersFlag, configFileFlag, + databasePathFlag, }, Action: func(c *cli.Context) error { err := Serve(c) diff --git a/be1-go/configServer1.json b/be1-go/configServer1.json index fb8796572b..96684c66a7 100644 --- a/be1-go/configServer1.json +++ b/be1-go/configServer1.json @@ -8,5 +8,6 @@ "client-port" : 9000, "server-port" : 9001, "auth-port" : 9100, - "other-servers": [] + "other-servers": [], + "database-path" : "./database-a/sqlite.db" } \ No newline at end of file diff --git a/be1-go/configServer2.json b/be1-go/configServer2.json index 327ec28b77..e07e143a84 100644 --- a/be1-go/configServer2.json +++ b/be1-go/configServer2.json @@ -10,5 +10,6 @@ "auth-port" : 9101, "other-servers": [ "localhost:9001" - ] + ], + "database-path" : "./database-b/sqlite.db" } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/config/RuntimeEnvironment.scala b/be2-scala/src/main/scala/ch/epfl/pop/config/RuntimeEnvironment.scala index 374b42a674..2a81d582fd 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/config/RuntimeEnvironment.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/config/RuntimeEnvironment.scala @@ -24,6 +24,7 @@ object RuntimeEnvironment { private val configParam = "scala.config" private val securityParam = "scala.security" private val dbPathParam = "scala.db" + private val peerListParam = "scala.peerlist" private val dbFolder = "database" private val testParam = "test" @@ -45,12 +46,7 @@ object RuntimeEnvironment { // Needed for unit tests lazy val isTestMode: Boolean = testMode(testParam) - lazy val serverPeersListPath: String = - if (isTestMode) { - confDir + File.separator + "server-peers-list-mock.conf" - } else { - confDir + File.separator + "server-peers-list.conf" - } + lazy val serverPeersListPath: String = getPeerListPath() def readServerPeers(): List[String] = { val source = @@ -102,6 +98,17 @@ object RuntimeEnvironment { } } + private def getPeerListPath(): String = { + val path = sp(peerListParam) + if (path != null && path.trim.nonEmpty) { + path.trim + } else if (isTestMode) { + confDir + File.separator + "server-peers-list-mock.conf" + } else { + confDir + File.separator + "server-peers-list.conf" + } + } + private def testMode(testParam: String): Boolean = { sp(testParam) != null } diff --git a/tests/karate/src/test/java/be/features/LAO/create.feature b/tests/karate/src/test/java/be/features/LAO/create.feature index 57b5b46fe9..30cd0900ab 100644 --- a/tests/karate/src/test/java/be/features/LAO/create.feature +++ b/tests/karate/src/test/java/be/features/LAO/create.feature @@ -110,7 +110,7 @@ Feature: Create a pop LAO @create6 Scenario: Create Lao request with public key different from the sender public key should fail - Given def notOrganizer = call createMockClient + Given def notOrganizer = call createMockFrontend And def laoCreateRequest = """ { diff --git a/tests/karate/src/test/java/be/features/election/castVote.feature b/tests/karate/src/test/java/be/features/election/castVote.feature index 83f0c46bb5..b94f529bad 100644 --- a/tests/karate/src/test/java/be/features/election/castVote.feature +++ b/tests/karate/src/test/java/be/features/election/castVote.feature @@ -153,7 +153,7 @@ Feature: Cast a vote # upon a non-attendee casting a valid vote. @castVote6 Scenario: Non attendee casting a vote should return an error - Given def nonAttendee = call createMockClient + Given def nonAttendee = call createMockFrontend And def validCastVote = """ { diff --git a/tests/karate/src/test/java/be/features/election/electionOpen.feature b/tests/karate/src/test/java/be/features/election/electionOpen.feature index bb5e70695e..3b9cd4992b 100644 --- a/tests/karate/src/test/java/be/features/election/electionOpen.feature +++ b/tests/karate/src/test/java/be/features/election/electionOpen.feature @@ -78,7 +78,7 @@ Feature: Open an Election # if an open election message is sent by a non-organizer @electionOpen4 Scenario: Non organizer opening the election should result in an error - Given def notOrganizer = call createMockClient + Given def notOrganizer = call createMockFrontend And def validElectionOpen = """ { diff --git a/tests/karate/src/test/java/be/features/rollCall/closeRollCall.feature b/tests/karate/src/test/java/be/features/rollCall/closeRollCall.feature index c5828e0cad..780bb3ffa4 100644 --- a/tests/karate/src/test/java/be/features/rollCall/closeRollCall.feature +++ b/tests/karate/src/test/java/be/features/rollCall/closeRollCall.feature @@ -39,7 +39,7 @@ Feature: Close a Roll Call @closeRollCall2 Scenario: Non-organizer closing a roll call should fail - Given def notOrganizer = call createMockClient + Given def notOrganizer = call createMockFrontend And def validCloseRollCall = """ { diff --git a/tests/karate/src/test/java/be/features/rollCall/createRollCall.feature b/tests/karate/src/test/java/be/features/rollCall/createRollCall.feature index 680ad3706c..991ce05eda 100644 --- a/tests/karate/src/test/java/be/features/rollCall/createRollCall.feature +++ b/tests/karate/src/test/java/be/features/rollCall/createRollCall.feature @@ -67,7 +67,7 @@ Feature: Create a Roll Call # a non-organizer should result in an error message being sent by the backend. @createRollCall3 Scenario: Roll Call Creation with non-organizer as sender should return an error - Given def notOrganizer = call createMockClient + Given def notOrganizer = call createMockFrontend And def validCreateRollCall = """ { diff --git a/tests/karate/src/test/java/be/features/rollCall/openRollCall.feature b/tests/karate/src/test/java/be/features/rollCall/openRollCall.feature index 554e8099b8..9b9d0477b1 100644 --- a/tests/karate/src/test/java/be/features/rollCall/openRollCall.feature +++ b/tests/karate/src/test/java/be/features/rollCall/openRollCall.feature @@ -38,7 +38,7 @@ Feature: Roll Call Open @openRollCall2 Scenario: Opening a Roll Call with non-organizer as sender should fail - Given def notOrganizer = call createMockClient + Given def notOrganizer = call createMockFrontend And def validOpenRollCall = """ { diff --git a/tests/karate/src/test/java/be/utils/GoServer.java b/tests/karate/src/test/java/be/utils/GoServer.java index f6806408a7..b9a0e5ccac 100644 --- a/tests/karate/src/test/java/be/utils/GoServer.java +++ b/tests/karate/src/test/java/be/utils/GoServer.java @@ -1,32 +1,57 @@ package be.utils; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.nio.file.Files; +import java.io.IOException; public class GoServer extends Server implements Configurable { + private String dbPath; - @Override - public boolean start() { - return super.start(getCmd(), getDir(), getLogPath()); + public GoServer(String host, int clientPort, int serverPort, int authPort, String dbPath, String logPath) { + super(host, clientPort, serverPort, authPort, logPath); + this.dbPath = dbPath; } - @Override - public void stop() { - super.stop(); + public GoServer() { + this("localhost", 9000, 9001, 9100, null, null); } @Override - public String[] getCmd() { + public String[] getCmd() throws IOException { + Map args = new HashMap<>(); + args.put("client-port", String.valueOf(clientPort)); + args.put("server-port", String.valueOf(serverPort)); + args.put("auth-port", String.valueOf(authPort)); + args.put("server-public-address", host); + args.put("server-listen-address", host); + + if (dbPath == null) { + dbPath = Files.createTempFile("go_database", ".sqlite").toString(); + } + args.put("database-path", dbPath); + + if (peers.size() > 0) { + args.put("other-servers", String.join(",", peers)); + } + + String cmd = "server serve"; + for (Map.Entry entry : args.entrySet()) { + cmd += " --" + entry.getKey() + " " + entry.getValue(); + } + if (isWindowsOS()) { return new String[]{ "cmd", "/c", - "\"pop.exe server serve\"" + "\"pop.exe " + cmd + "\"" }; } else { return new String[]{ "bash", "-c", - "./pop server serve" + "./pop " + cmd }; } } @@ -36,15 +61,14 @@ public String getDir() { return Paths.get("..", "..", "be1-go").toString(); } - @Override - public String getLogPath() { - return Paths.get("go.log").toString(); - } - @Override public void deleteDatabaseDir() { - //TODO: delete GO backend database if necessary - System.out.println("No database to delete for GO backend"); - + if (dbPath != null) { + try { + Files.deleteIfExists(Paths.get(dbPath)); + } catch (Exception e) { + e.printStackTrace(); + } + } } } diff --git a/tests/karate/src/test/java/be/utils/ScalaServer.java b/tests/karate/src/test/java/be/utils/ScalaServer.java index 559c324717..c38e820e7f 100644 --- a/tests/karate/src/test/java/be/utils/ScalaServer.java +++ b/tests/karate/src/test/java/be/utils/ScalaServer.java @@ -12,27 +12,49 @@ import java.util.stream.Stream; public class ScalaServer extends Server implements Configurable { + private String dbPath; - @Override - public boolean start() throws IOException { - return super.start(getCmd(), getDir(), getLogPath()); + public ScalaServer(String host, int port, String dbPath, String logPath) { + super(host, port, port, port, logPath); + this.dbPath = dbPath; } - @Override - public void stop() { - super.stop(); + public ScalaServer() { + this("127.0.0.1", 8000, null, null); } @Override public String[] getCmd() throws IOException { - String configPath = Paths.get("src", "main", "scala", "ch", "epfl", "pop", "config").toString(); + Path workingDirectory = Paths.get("").toAbsolutePath(); + Path tempDir = Files.createTempDirectory("scala-server"); + + // Create a temporary config file + Path configTemplate = workingDirectory.resolve("src/test/java/data/scala.template.conf"); + String config = new String(Files.readAllBytes(configTemplate)); + config = config.replace("{{host}}", host); + config = config.replace("{{port}}", String.valueOf(serverPort)); + File configFile = new File(tempDir.toFile(), "application.conf"); + Files.write(configFile.toPath(), config.getBytes()); + + // Create a temporary peer file + File peerFile = File.createTempFile("scala-peers", ".conf"); + for (String peer : peers) { + Files.write(peerFile.toPath(), ("ws://" + peer + "/server\n").getBytes()); + } + + if (this.dbPath == null) { + dbPath = Files.createTempDirectory("scala-db").toString(); + } + String securityDirPath = Paths.get("src", "security").toString(); File targetJar = getTargetJar(); return new String[] { "java", - "-Dscala.config=" + configPath, + "-Dscala.config=" + tempDir.toString(), + "-Dscala.peerlist=" + peerFile.getCanonicalPath(), "-Dscala.security=" + securityDirPath, + "-Dscala.db=" + dbPath, "-jar", targetJar.getCanonicalPath() }; } @@ -67,15 +89,14 @@ public String getDir() { return Paths.get("..", "..", "be2-scala").toString(); } - @Override - public String getLogPath() { - return Paths.get("scala.log").toString(); - } - @Override public void deleteDatabaseDir() { - System.out.println("Deleting database..."); - Path path = Paths.get("..", "..", "be2-scala", "database"); + if (dbPath == null) { + System.out.println("No database to delete"); + return; + } + + Path path = Paths.get(dbPath); try (Stream walk = Files.walk(path)) { walk.sorted(Comparator.reverseOrder()) .map(Path::toFile) diff --git a/tests/karate/src/test/java/be/utils/Server.java b/tests/karate/src/test/java/be/utils/Server.java index a388dc1f1d..32c2c53fce 100644 --- a/tests/karate/src/test/java/be/utils/Server.java +++ b/tests/karate/src/test/java/be/utils/Server.java @@ -1,5 +1,7 @@ package be.utils; +import static org.junit.jupiter.api.Assertions.assertFalse; + import java.io.File; import java.io.IOException; import java.util.List; @@ -9,11 +11,82 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.List; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.Files; +import java.util.Comparator; public abstract class Server implements Runnable { - // Main server process (may have children processes) private Process process; + private String logPath; + protected String host; + protected int clientPort; + protected int serverPort; + protected int authPort; + protected List peers = new ArrayList<>(); + + public Server(String host, int clientPort, int serverPort, int authPort) { + this(host, clientPort, serverPort, authPort, null); + } + + public Server(String host, int clientPort, int serverPort, int authPort, String logPath) { + super(); + this.host = host; + this.clientPort = clientPort; + this.serverPort = serverPort; + this.authPort = authPort; + this.logPath = logPath; + } + + /** + * Adds a peer to the server + * + * @param peer server to add as peer + */ + public void addPeer(Server peer) { + peers.add(peer.host + ":" + peer.serverPort); + } + + /** + * Pair two servers together. + * Equivalent to calling addPeer on both servers. + * + * @param peer server to pair with + */ + public void pairWith(Server peer) { + addPeer(peer); + peer.addPeer(this); + } + + /** + * Get the server port + * + * @return server port + */ + public int getServerPort() { + return serverPort; + } + + /** + * Get the host + * + * @return host + */ + public String getHost() { + return host; + } + + /** + * Get the log path + * + * @return log path + */ + public String getLogPath() { + return logPath; + } /** * Builds a server process @@ -31,9 +104,10 @@ private static ProcessBuilder build(String[] cmd, String dir, String logPath) { processBuilder.inheritIO(); } else { File logFile = new File(logPath); + ProcessBuilder.Redirect redirect = ProcessBuilder.Redirect.appendTo(logFile); processBuilder - .redirectOutput(logFile) - .redirectError(logFile); + .redirectOutput(redirect) + .redirectError(redirect); } return processBuilder; } @@ -99,6 +173,16 @@ public boolean start(String[] cmd, String dir) { return start(cmd, dir, null); } + /** + * Runs the server command in specified directory + * + * @return true if the server has started correctly, false otherwise + */ + @Override + public boolean start() throws IOException { + return start(getCmd(), getDir(), getLogPath()); + } + /** * Runs the server command in specified directory * @@ -126,10 +210,25 @@ public boolean start(String[] cmd, String dir, String logPath) { return false; } } - /** - * Deletes database of the server + + /** + * Deletes the database directory + */ + public abstract void deleteDatabaseDir(); + + /** + * Get the command to start the server + * + * @return command to start the server + */ + public abstract String[] getCmd() throws IOException; + + /** + * Get the directory where the server is located + * + * @return directory where the server is located */ - abstract void deleteDatabaseDir(); + public abstract String getDir(); public static boolean isWindowsOS() { return System.getProperty("os.name").toLowerCase().contains("windows"); diff --git a/tests/karate/src/test/java/data/scala.template.conf b/tests/karate/src/test/java/data/scala.template.conf new file mode 100644 index 0000000000..07cddcee3e --- /dev/null +++ b/tests/karate/src/test/java/data/scala.template.conf @@ -0,0 +1,32 @@ +## Akka Http configs +akka.http.client.idle-timeout = 600 s +akka.http.server.idle-timeout = 600 s +akka.http.server.request-timeout = 600 s + + +##Akka SLf4J logs config +akka { + #Setup list off loggers to config + loggers = ["akka.event.slf4j.Slf4jLogger"] + #Setup + logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" + # Options: OFF, ERROR, WARNING, INFO, DEBUG + loglevel = "INFO" + #Set to OFF to turn off logs for sys startup & shuttdown logs + stdout-loglevel = "INFO" +} + +#POP Server config default +#Example of external address: wss://pop-demo.online, will default to ws://interface:port if empty +ch_epfl_pop_Server { + http { + interface = "{{host}}" + port = "{{port}}" + client-path = "client" + server-path = "server" + authentication-path = "authorize" + response-endpoint = "response" + publicKey-endpoint = "publicKey" + external-address = "" + } +}