diff --git a/Dockerfile b/Dockerfile index 0fa0af5d4..73d6ca71d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM miyurud/jasminegraph +FROM miyurud/jasminegraph:20230909T110050 ENV HOME="/home/ubuntu" ENV JASMINEGRAPH_HOME="/home/ubuntu/software/jasminegraph" @@ -7,14 +7,15 @@ WORKDIR /home/ubuntu/software/jasminegraph COPY ./GraphSAGE ./GraphSAGE COPY ./build.sh ./build.sh -COPY ./run-docker.sh ./run-docker.sh COPY ./CMakeLists.txt ./CMakeLists.txt -COPY ./src_python ./src_python COPY ./main.h ./main.h COPY ./main.cpp ./main.cpp COPY ./src ./src RUN sh build.sh + +COPY ./run-docker.sh ./run-docker.sh +COPY ./src_python ./src_python COPY ./conf ./conf ENTRYPOINT ["/home/ubuntu/software/jasminegraph/run-docker.sh"] diff --git a/run-docker.sh b/run-docker.sh index a2fb5baf4..a44c03e1d 100755 --- a/run-docker.sh +++ b/run-docker.sh @@ -1,78 +1,71 @@ #!/usr/bin/env bash - MODE=${MODE} MASTERIP=${MASTERIP} WORKERS=${WORKERS} WORKERIP=${WORKERIP} HOST_NAME=${HOST_NAME} -SERVER_PORT=${SERVER_PORT}; -SERVER_DATA_PORT=${SERVER_DATA_PORT}; +SERVER_PORT=${SERVER_PORT} +SERVER_DATA_PORT=${SERVER_DATA_PORT} ENABLE_NMON=${ENABLE_NMON} while [ $# -gt 0 ]; do - if [[ $1 == *"--"* ]]; then + if [[ $1 == *"--"* ]]; then param="${1/--/}" declare $param="$2" echo $1 $2 // Optional to see the parameter:value result - fi + fi - shift + shift done -if [ -z "$MODE" ] ; -then +if [ -z "$MODE" ]; then echo "MODE OF OPERATION SHOULD BE SPECIFIED" echo "Use argument 1 to start JasmineGraph in Master mode." echo "Use 2 to start in Worker mode." exit 1 fi -if [ $MODE -eq 1 ] ; -then - if [ -z "$MASTERIP" ] ; - then - echo "MASTER IP SHOULD BE SPECIFIED" - exit 1 +if [ $MODE -eq 1 ]; then + if [ -z "$MASTERIP" ]; then + echo "MASTER IP SHOULD BE SPECIFIED" + exit 1 fi - if [ -z "$WORKERS" ] ; - then - echo "Number of workers SHOULD BE SPECIFIED" - exit 1 + if [ -z "$WORKERS" ]; then + echo "Number of workers SHOULD BE SPECIFIED" + exit 1 fi - if [ -z "$WORKERIP" ] ; - then - echo "Worker IPs SHOULD BE SPECIFIED" - exit 1 + if [ -z "$WORKERIP" ]; then + echo "Worker IPs SHOULD BE SPECIFIED" + exit 1 fi else - - if [ -z "$MASTERIP" ] ; - then - echo "MASTER IP SHOULD BE SPECIFIED" - exit 1 + if [ -z "$MASTERIP" ]; then + echo "MASTER IP SHOULD BE SPECIFIED" + exit 1 fi - if [ -z "$SERVER_PORT" ] ; - then - echo "SERVER PORT SHOULD BE SPECIFIED" - exit 1 + if [ -z "$SERVER_PORT" ]; then + echo "SERVER PORT SHOULD BE SPECIFIED" + exit 1 fi - if [ -z "$SERVER_DATA_PORT" ] ; - then - echo "SERVER DATA PORT SHOULD BE SPECIFIED" - exit 1 + if [ -z "$SERVER_DATA_PORT" ]; then + echo "SERVER DATA PORT SHOULD BE SPECIFIED" + exit 1 fi fi export LD_LIBRARY_PATH=/usr/local/lib -if [ $MODE -eq 1 ] ; -then +if [ $MODE -eq 1 ]; then ./JasmineGraph "docker" $MODE $MASTERIP $WORKERS $WORKERIP $ENABLE_NMON else ./JasmineGraph "docker" $MODE $HOST_NAME $MASTERIP $SERVER_PORT $SERVER_DATA_PORT $ENABLE_NMON -fi \ No newline at end of file +fi + +if [ "$TESTING" = "true" ]; then + chmod -R go+w /tmp/jasminegraph +fi diff --git a/run.sh b/run.sh index 15ccecc71..35ed06ad5 100755 --- a/run.sh +++ b/run.sh @@ -1,19 +1,17 @@ #!/usr/bin/env bash -JASMINEGRAPH_DIR="`dirname \"$0\"`" -PROFILE=$1; -MODE=$2; - -JASMINEGRAPH_DIR="`( cd \"$JASMINEGRAPH_DIR\" && pwd )`" -if [ -z "$JASMINEGRAPH_DIR" ] ; -then - exit 1 # fail +JASMINEGRAPH_DIR="$(dirname \"$0\")" +PROFILE=$1 +MODE=$2 + +JASMINEGRAPH_DIR="$(cd \"$JASMINEGRAPH_DIR\" && pwd)" +if [ -z "$JASMINEGRAPH_DIR" ]; then + exit 1 # fail fi export JASMINEGRAPH_HOME="$JASMINEGRAPH_DIR" -if [ -z "$MODE" ] ; -then +if [ -z "$MODE" ]; then echo "MODE OF OPERATION SHOULD BE SPECIFIED" echo "Use argument 1 to start JasmineGraph in Master mode." echo "Use 2 to start in Worker mode." @@ -22,33 +20,29 @@ fi cd $JASMINEGRAPH_DIR -if [ $MODE -eq 1 ] ; -then +if [ $MODE -eq 1 ]; then echo "STARTING MASTER MODE" - MASTER_HOST_NAME=$3; - NUMBER_OF_WORKERS=$4; - WORKER_IPS=$5; + MASTER_HOST_NAME=$3 + NUMBER_OF_WORKERS=$4 + WORKER_IPS=$5 ./JasmineGraph "native" $MODE $MASTER_HOST_NAME $NUMBER_OF_WORKERS $WORKER_IPS "false" else - WORKER_HOST=$3; - MASTER_HOST_NAME=$4; - SERVER_PORT=$5; - SERVER_DATA_PORT=$6; + WORKER_HOST=$3 + MASTER_HOST_NAME=$4 + SERVER_PORT=$5 + SERVER_DATA_PORT=$6 - if [ -z "$SERVER_PORT" ] ; - then + if [ -z "$SERVER_PORT" ]; then echo "SERVER PORT SHOULD BE SPECIFIED" exit 1 fi - if [ -z "$SERVER_DATA_PORT" ] ; - then - echo "SERVER DATA PORT SHOULD BE SPECIFIED" - exit 1 + if [ -z "$SERVER_DATA_PORT" ]; then + echo "SERVER DATA PORT SHOULD BE SPECIFIED" + exit 1 fi ./JasmineGraph "native" $MODE $WORKER_HOST $MASTER_HOST_NAME $SERVER_PORT $SERVER_DATA_PORT "false" fi - diff --git a/src/ml/trainer/JasmineGraphTrainingSchedular.cpp b/src/ml/trainer/JasmineGraphTrainingSchedular.cpp index 8833cf592..1fd59c804 100644 --- a/src/ml/trainer/JasmineGraphTrainingSchedular.cpp +++ b/src/ml/trainer/JasmineGraphTrainingSchedular.cpp @@ -24,30 +24,28 @@ Logger trainScheduler_logger; map> JasmineGraphTrainingSchedular::schedulePartitionTraining(std::string graphID) { - map> scheduleForEachWorker; + map> scheduleForEachHost; vector> hostData; SQLiteDBInterface refToSqlite = *new SQLiteDBInterface(); refToSqlite.init(); - string sqlStatement = "SELECT worker_idworker, name FROM worker_has_partition INNER JOIN worker ON worker_idworker = " - "idworker WHERE partition_graph_idgraph = " + graphID; - + string sqlStatement = "SELECT host_idhost, name FROM worker_has_partition INNER JOIN worker ON worker_idworker = " + "idworker WHERE partition_graph_idgraph = " + graphID + " group by host_idhost"; std::vector>> result = refToSqlite.runSelect(sqlStatement); for (vector>>::iterator i = result.begin(); i != result.end(); ++i) { int count = 0; - string workerID; + string hostID; string hostname; for (std::vector>::iterator j = (i->begin()); j != i->end(); ++j) { if (count == 0) { - workerID = j->second; + hostID = j->second; } else { hostname = j->second; - hostData.push_back(pair(workerID, hostname)); + hostData.push_back(pair(hostID, hostname)); } count++; } } - string id_partition = ""; string vertexCount = ""; string centralvertexCount = ""; @@ -57,8 +55,9 @@ map> JasmineGraphTrainingSchedular::schedulePartition trainScheduler_logger.log("Scheduling training order for each worker", "info"); for (std::vector>::iterator j = (hostData.begin()); j != hostData.end(); ++j) { sqlStatement = "SELECT idpartition, vertexcount, central_vertexcount, graph_idgraph FROM partition INNER JOIN " - "worker_has_partition w ON w.partition_idpartition = partition.idpartition" - " WHERE partition.graph_idgraph = "+ graphID +" AND w.worker_idworker = " + j->first; + "(SELECT host_idhost, partition_idpartition, partition_graph_idgraph, worker_idworker FROM " + "worker_has_partition INNER JOIN worker ON worker_idworker = idworker) AS a ON partition.idpartition = " + "a.partition_idpartition WHERE partition.graph_idgraph = "+ graphID +" AND a.host_idhost = " + j->first; std::vector>> results = refToSqlite.runSelect(sqlStatement); std::map vertexCountToPartitionId; for (std::vector>>::iterator i = results.begin(); i != results.end(); ++i) { @@ -89,9 +88,9 @@ map> JasmineGraphTrainingSchedular::schedulePartition } std::map scheduledPartitionSets = packPartitionsToMemory(memoryEstimationForEachPartition, availableMemory); - scheduleForEachWorker.insert(make_pair(j->first, scheduledPartitionSets)); + scheduleForEachHost.insert(make_pair(j->second, scheduledPartitionSets)); } - return scheduleForEachWorker; + return scheduleForEachHost; } long JasmineGraphTrainingSchedular::estimateMemory(int vertexCount, string graph_id) { diff --git a/src/server/JasmineGraphServer.cpp b/src/server/JasmineGraphServer.cpp index dfcbd8b15..548a8098a 100644 --- a/src/server/JasmineGraphServer.cpp +++ b/src/server/JasmineGraphServer.cpp @@ -334,6 +334,14 @@ void JasmineGraphServer::startRemoteWorkers(std::vector workerPortsVector, char *env_testing = getenv("TESTING"); bool is_testing = (env_testing != NULL && strcasecmp(env_testing, "true") == 0); for (int i =0 ; i < workerPortsVector.size() ; i++) { + std::string worker_logdir = "/tmp/jasminegraph/worker_" + to_string(i); + if (access(worker_logdir.c_str(), F_OK) != 0) { + if (mkdir(worker_logdir.c_str(), 0777)) { + server_logger.error("Couldn't create worker log dir: " + worker_logdir); + } + } else { + chmod(worker_logdir.c_str(), 0777); + } if (masterHost == host || host == "localhost") { if (is_testing) { serverStartScript = "docker run -p " + @@ -341,14 +349,15 @@ void JasmineGraphServer::startRemoteWorkers(std::vector workerPortsVector, std::to_string(workerPortsVector.at(i)) + " -p " + std::to_string(workerDataPortsVector.at(i)) + ":" + std::to_string(workerDataPortsVector.at(i)) + + " -v " + worker_logdir + ":/tmp/jasminegraph" + " -e WORKER_ID=" + to_string(i) + " jasminegraph:test --MODE 2 --HOST_NAME " + host + " --MASTERIP " + masterHost + " --SERVER_PORT " + std::to_string(workerPortsVector.at(i)) + " --SERVER_DATA_PORT " + std::to_string(workerDataPortsVector.at(i)) + " --ENABLE_NMON " + enableNmon + - " >/tmp/worker_logs/worker_" + to_string(i) + ".log 2>&1"; + " >" + worker_logdir + "/worker.log 2>&1"; } else { - serverStartScript = "docker run -v " + instanceFolderLocal + ":" + instanceFolder + + serverStartScript = "docker run -v " + instanceDataFolder + ":" + instanceDataFolder + " -v " + aggregateDataFolder + ":" + aggregateDataFolder + " -v " + nmonFileLocation + ":" + nmonFileLocation + " -v " + graphsagelocation + ":" + graphsagelocation + @@ -363,7 +372,6 @@ void JasmineGraphServer::startRemoteWorkers(std::vector workerPortsVector, std::to_string(workerPortsVector.at(i)) + " --SERVER_DATA_PORT " + std::to_string(workerDataPortsVector.at(i)) + " --ENABLE_NMON " + enableNmon; } - } else { if (is_testing) { serverStartScript = "docker -H ssh://" + host + " run -p " + @@ -376,9 +384,9 @@ void JasmineGraphServer::startRemoteWorkers(std::vector workerPortsVector, " --MASTERIP " + masterHost + " --SERVER_PORT " + std::to_string(workerPortsVector.at(i)) + " --SERVER_DATA_PORT " + std::to_string(workerDataPortsVector.at(i)) + " --ENABLE_NMON " + enableNmon + - " >/tmp/worker_logs/worker_" + to_string(i) + ".log 2>&1"; + " >" + worker_logdir + "/worker.log 2>&1"; } else { - serverStartScript = "docker -H ssh://" + host + " run -v " + instanceFolderLocal + ":" + instanceFolder + + serverStartScript = "docker -H ssh://" + host + " run -v " + instanceDataFolder + ":" + instanceDataFolder + " -v " + aggregateDataFolder + ":" + aggregateDataFolder + " -v " + nmonFileLocation + ":" + nmonFileLocation + " -v " + graphsagelocation + ":" + graphsagelocation + @@ -2501,8 +2509,8 @@ std::map JasmineGraphServer::g int serverDataPort = std::stoi(rowData.at(3).second); string partitionId = rowData.at(4).second; - cout << "name : " << name << " workerID : "<< workerID << " sport : " << serverPort << " sdport : " <(workerID, {name, serverPort, serverDataPort, @@ -3072,35 +3080,37 @@ void JasmineGraphServer::initiateFiles(std::string graphID, std::string training int count = 0; JasmineGraphTrainingSchedular *schedular = new JasmineGraphTrainingSchedular(); map> scheduleForAllHosts = schedular->schedulePartitionTraining(graphID); - std::map graphPartitionedHosts = this->getGraphPartitionedHosts( + std::map graphPartitionedHosts = this->getWorkerPartitions( graphID); int partition_count = 0; - std::map::iterator mapIterator; + std::map::iterator mapIterator; for (mapIterator = graphPartitionedHosts.begin(); mapIterator != graphPartitionedHosts.end(); mapIterator++) { - JasmineGraphServer::workerPartitions workerPartition = mapIterator->second; - std::vector partitions = workerPartition.partitionID; + JasmineGraphServer::workerPartition workerPartition = mapIterator->second; + std::vector partitions; + partitions.push_back(workerPartition.partitionID); std::vector::iterator it; for (it = partitions.begin(); it < partitions.end(); it++) { partition_count++; } } - std::thread *workerThreads = new std::thread[partition_count]; + std::thread *workerThreads = new std::thread[partition_count + 1]; Utils utils; string prefix = utils.getJasmineGraphProperty("org.jasminegraph.server.instance.trainedmodelfolder"); string attr_prefix = utils.getJasmineGraphProperty("org.jasminegraph.server.instance.datafolder"); trainingArgs = trainingArgs; - std::map::iterator j; + std::map::iterator j; for (j = graphPartitionedHosts.begin(); j != graphPartitionedHosts.end(); j++) { - JasmineGraphServer::workerPartitions workerPartition = j->second; - std::vector partitions = workerPartition.partitionID; + JasmineGraphServer::workerPartition workerPartition = j->second; + std::vector partitions; + partitions.push_back(workerPartition.partitionID); string partitionCount = std::to_string(partitions.size()); std::vector::iterator k; map scheduleOfHost = scheduleForAllHosts[j->first]; for (k = partitions.begin(); k != partitions.end(); k++) { int iterationOfPart = scheduleOfHost[stoi(*k)]; - workerThreads[count] = std::thread(initiateTrain, j->first, workerPartition.port, workerPartition.dataPort, + workerThreads[count] = std::thread(initiateTrain, workerPartition.hostname, workerPartition.port, workerPartition.dataPort, trainingArgs + " " + *k, iterationOfPart, partitionCount); count++; sleep(3); @@ -3222,12 +3232,12 @@ void JasmineGraphServer::initiateOrgCommunication(std::string graphID, std::stri if (i==0) { - workerThreads[threadID] = std::thread(initiateOrgServer,"localhost", serverPort, + workerThreads[threadID] = std::thread(initiateOrgServer,workerInstance.hostname, serverPort, serverDataPort,trainingArgs,fl_clients, to_string(i)); threadID++; } - workerThreads[threadID] = std::thread(initiateClient,"localhost", serverPort, serverDataPort,trainingArgs + + workerThreads[threadID] = std::thread(initiateClient,workerInstance.hostname, serverPort, serverDataPort,trainingArgs + " " + to_string(i), fl_clients, to_string(i)); threadID++; @@ -3267,7 +3277,6 @@ void JasmineGraphServer::initiateMerge(std::string graphID, std::string training Utils utils; trainingArgs = trainingArgs; - int fl_clients = stoi(utils.getJasmineGraphProperty("org.jasminegraph.fl_clients")); std::map::iterator j; for (j = graphPartitionedHosts.begin(); j != graphPartitionedHosts.end(); j++) { @@ -3279,20 +3288,17 @@ void JasmineGraphServer::initiateMerge(std::string graphID, std::string training map scheduleOfHost = scheduleForAllHosts[j->first]; for (k = partitions.begin(); k != partitions.end(); k++) { int iterationOfPart = scheduleOfHost[stoi(*k)]; - workerThreads[count] = std::thread(mergeFiles, workerPartition.hostname, workerPartition.port, workerPartition.dataPort, trainingArgs + " " + *k, fl_clients, *k); count++; } + } - std::cout << count << std::endl; - - for (int threadCount = 0; threadCount < count; threadCount++) { - workerThreads[threadCount].join(); - } - - server_logger.log("Merge Commands Sent", "info"); + for (int threadCount = 0; threadCount < count; threadCount++) { + workerThreads[threadCount].join(); } + + server_logger.log("Merge Commands Sent", "info"); } bool JasmineGraphServer::initiateTrain(std::string host, int port, int dataPort,std::string trainingArgs,int iteration, string partCount) { @@ -3449,15 +3455,6 @@ bool JasmineGraphServer::initiateServer(std::string host, int port, int dataPort server_logger.log("Received : " + JasmineGraphInstanceProtocol::HOST_OK, "info"); } - bzero(data, 301); - read(sockfd, data, 300); - response = (data); - if (response.compare(JasmineGraphInstanceProtocol::HOST_OK) == 0) { - server_logger.log("Received : " + JasmineGraphInstanceProtocol::HOST_OK, "info"); - } else { - server_logger.log("Received : " + response, "error"); - } - write(sockfd, JasmineGraphInstanceProtocol::INITIATE_SERVER.c_str(), JasmineGraphInstanceProtocol::INITIATE_SERVER.size()); server_logger.log("Sent fed : " + JasmineGraphInstanceProtocol::INITIATE_SERVER, "info"); @@ -3542,14 +3539,6 @@ bool JasmineGraphServer::initiateClient(std::string host, int port, int dataPort server_logger.log("Received : " + JasmineGraphInstanceProtocol::HOST_OK, "info"); } - bzero(data, 301); - read(sockfd, data, 300); - response = (data); - if (response.compare(JasmineGraphInstanceProtocol::HOST_OK) == 0) { - server_logger.log("Received : " + JasmineGraphInstanceProtocol::HOST_OK, "info"); - } else { - server_logger.log("Received : " + response, "error"); - } write(sockfd, JasmineGraphInstanceProtocol::INITIATE_CLIENT.c_str(), JasmineGraphInstanceProtocol::INITIATE_CLIENT.size()); diff --git a/test-docker.sh b/test-docker.sh index 35f7ad120..dc6278a39 100755 --- a/test-docker.sh +++ b/test-docker.sh @@ -6,7 +6,7 @@ TIMEOUT_SECONDS=600 RUN_ID="$(date +%y%m%d_%H%M%S)" LOG_DIR="${PROJECT_ROOT}/logs/${RUN_ID}" while [ -d "$LOG_DIR"]; do - tmp_id="$((${tmp_id}+1))" + tmp_id="$((tmp_id + 1))" new_run="${RUN_ID}_${tmp_id}" LOG_DIR="${PROJECT_ROOT}/logs/${RUN_ID}" done @@ -18,17 +18,23 @@ mkdir "$LOG_DIR" BUILD_LOG="${LOG_DIR}/build.log" RUN_LOG="${LOG_DIR}/run_master.log" TEST_LOG="${LOG_DIR}/test.log" +WORKER_LOG_DIR="/tmp/jasminegraph" +rm -rf "${WORKER_LOG_DIR}" +mkdir -p "${WORKER_LOG_DIR}" - -stop_and_remove_containers () { - if [ "$(docker ps -q)" ]; then - docker ps -a -q | xargs docker rm -f &>/dev/null +stop_and_remove_containers() { + if [ "$(docker ps -a -q)" ]; then + docker ps -a -q | xargs docker rm -f &>/dev/null else echo "No containers to stop and remove." - fi + fi + docker run -v '/tmp/jasminegraph:/tmp/jasminegraph' --entrypoint /bin/bash jasminegraph:test -c 'rm -rf /tmp/jasminegraph/*' || echo 'Not removing existing tmp logs' + if [ "$(docker ps -a -q)" ]; then + docker ps -a -q | xargs docker rm -f &>/dev/null + fi } -build_and_run_docker () { +build_and_run_docker() { stop_and_remove_containers cd "$PROJECT_ROOT" docker build -t jasminegraph:test . |& tee "$BUILD_LOG" @@ -40,13 +46,12 @@ build_and_run_docker () { rm -rf "${TEST_ROOT}/env" exit "$build_status" fi - docker compose -f "${TEST_ROOT}/docker-compose.yml" up |& tee "$RUN_LOG" &>/dev/null & + docker compose -f "${TEST_ROOT}/docker-compose.yml" up >"$RUN_LOG" 2>&1 & } cd "$TEST_ROOT" rm -rf env cp -r env_init env -mkdir -p env/logs cd "$PROJECT_ROOT" build_and_run_docker @@ -78,23 +83,44 @@ if [ "$exit_code" == '124' ]; then fi cd "$TEST_ROOT" -for f in env/logs/*; do - fname="$(basename ${f})" - cp "$f" "${LOG_DIR}/run_${fname}" +for d in "${WORKER_LOG_DIR}"/worker_*; do + echo + worker_name="$(basename ${d})" + cp -r "$d" "${LOG_DIR}/${worker_name}" done + cd "$LOG_DIR" if [ "$exit_code" != '0' ]; then echo echo -e '\e[33;1mMaster log:\e[0m' cat "$RUN_LOG" - for f in run_worker_*; do + for d in worker_*; do + cd "${LOG_DIR}/${d}" echo - echo -e '\e[33;1m'"${f:4:-4}"' log:\e[0m' - cat "$f" + echo -e '\e[33;1m'"${d}"' log:\e[0m' + cat worker.log + + for f in merge_*.log; do + echo + echo -e '\e[33;1m'"${d} ${f::-4}"' log:\e[0m' + cat "$f" + done + + for f in fl_client_*.log; do + echo + echo -e '\e[33;1m'"${d} ${f::-4}"' log:\e[0m' + cat "$f" + done + + for f in fl_server_*.log; do + echo + echo -e '\e[33;1m'"${d} ${f::-4}"' log:\e[0m' + cat "$f" + done done fi -rm -rf "${TEST_ROOT}/env" stop_and_remove_containers +rm -rf "${TEST_ROOT}/env" "${WORKER_LOG_DIR}" exit "$exit_code" diff --git a/tests/integration/docker-compose.yml b/tests/integration/docker-compose.yml index 5f132eb42..7523ef618 100644 --- a/tests/integration/docker-compose.yml +++ b/tests/integration/docker-compose.yml @@ -10,7 +10,7 @@ services: - './env/databases/metadb:/home/ubuntu/software/jasminegraph/metadb' - './env/databases/performancedb:/home/ubuntu/software/jasminegraph/performancedb' - './env/data:/var/tmp/data' - - './env/logs:/tmp/worker_logs' + - '/tmp/jasminegraph:/tmp/jasminegraph' environment: - TESTING=true networks: