From 3e2e75884a24d6bce9b0c4c117620a02ad7d51eb Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Sun, 17 May 2020 15:42:56 +0530 Subject: [PATCH 01/26] WIP restructre to use nodes and hosts separately --- Cargo.lock | 132 +++++++----- config.sample.json | 53 +++-- src/cli/get_module_info.rs | 4 +- src/cli/list_modules.rs | 4 +- src/cli/list_processes.rs | 4 +- src/cli/restart_process.rs | 5 +- src/exec/host_runner.rs | 250 ++++++++++++++++++++++ src/exec/juno_module.rs | 30 +-- src/exec/mod.rs | 1 + src/exec/process.rs | 56 ++++- src/exec/runner.rs | 422 +++++++++---------------------------- src/main.rs | 1 + src/models/config_types.rs | 41 ++-- src/models/mod.rs | 4 +- src/models/parser.rs | 142 +++++++++---- src/utils/constants.rs | 5 + 16 files changed, 666 insertions(+), 488 deletions(-) create mode 100644 src/exec/host_runner.rs diff --git a/Cargo.lock b/Cargo.lock index 4c5bdbb..4d84380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da71fef07bc806586090247e971229289f64c210a278ee5ae419314eb386b31d" +checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" dependencies = [ "proc-macro2", "quote", @@ -91,9 +91,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "cc" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" +checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" [[package]] name = "cfg-if" @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.0" +version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" dependencies = [ "ansi_term", "atty", @@ -223,9 +223,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" dependencies = [ "futures-channel", "futures-core", @@ -238,9 +238,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" dependencies = [ "futures-core", "futures-sink", @@ -248,15 +248,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" [[package]] name = "futures-executor" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" +checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" dependencies = [ "futures-core", "futures-task", @@ -265,15 +265,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" [[package]] name = "futures-macro" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -283,15 +283,18 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" [[package]] name = "futures-task" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] [[package]] name = "futures-timer" @@ -307,9 +310,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" dependencies = [ "futures-channel", "futures-core", @@ -318,6 +321,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", + "pin-project", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -371,9 +375,9 @@ checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "juno" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30daf3321be539ae2c45992f6309ae78d98844f40f34b448bbabb2ae554415be" +checksum = "01e3ce61bde449eae0e4f3aff3aec02e06b03b9bfe2acb6bc96a6e2fe89afcc6" dependencies = [ "async-std", "async-trait", @@ -394,9 +398,9 @@ dependencies = [ [[package]] name = "kv-log-macro" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" +checksum = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" dependencies = [ "log", ] @@ -409,9 +413,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" [[package]] name = "log" @@ -445,9 +449,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" dependencies = [ "cfg-if", "fuchsia-zircon", @@ -464,9 +468,9 @@ dependencies = [ [[package]] name = "mio-uds" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" dependencies = [ "iovec", "libc", @@ -487,9 +491,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" dependencies = [ "cfg-if", "libc", @@ -540,15 +544,35 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.3.1" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + +[[package]] +name = "pin-project" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d480cb4e89522ccda96d0eed9af94180b7a5f93fb28f66e1fd7d68431663d1" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +checksum = "a82996f11efccb19b685b14b5df818de31c1edcee3daa256ab5775dd98e72feb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "pin-project-lite" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" +checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" [[package]] name = "pin-utils" @@ -570,18 +594,18 @@ checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1f4b0efa5fc5e8ceb705136bfee52cfdb6a4e3509f770b478cd6ed434232a7" +checksum = "42934bc9c8ab0d3b273a16d8551c8f0fcff46be73276ca083ec2414c15c4ba5e" dependencies = [ "proc-macro2", ] @@ -600,15 +624,15 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.106" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" [[package]] name = "serde_derive" -version = "1.0.106" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ "proc-macro2", "quote", @@ -617,9 +641,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" dependencies = [ "itoa", "ryu", @@ -640,9 +664,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" +checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" dependencies = [ "proc-macro2", "quote", @@ -691,9 +715,9 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "vec_map" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "void" diff --git a/config.sample.json b/config.sample.json index 77d692f..58bf906 100644 --- a/config.sample.json +++ b/config.sample.json @@ -1,42 +1,55 @@ { - "version": "1.0.0", + "version": 1, "configs": [ { - "env": { - "target_family": "windows", - "target_os": "windows", - "target_arch": "x86_64", - "target_endian": "little" + "target": { + "family": "windows", + "os": "windows", + "arch": "x86_64", + "endian": "little" }, "config": { - "juno": { + "name": "runner1", + "logs": "./logs", + "host": { "path": "./service/juno", "connection_type": "inet_socket", "port": 2203, - "bind-addr": "127.0.0.1" - }, - "modules": { - "path": "./modules", - "logs": "./logs" + "bind_addr": "127.0.0.1" } } }, { - "env": { - "target_family": "unix", - "target_os": "linux", - "target_arch": "x86_64", - "target_endian": "little" + "target": { + "family": "unix", + "os": "linux", + "arch": "x86_64", + "endian": "little" }, "config": { - "juno": { + "name": "runner1", + "logs": "./logs", + "host": { "path": "./service/juno", "connection_type": "unix_socket", "socket_path": "./juno.sock" }, "modules": { - "path": "./modules", - "logs": "./logs" + "directory": "./modules" + } + } + }, + { + "config": { + "name": "runner2", + "logs": "./logs", + "node": { + "connection_type": "inet_socket", + "port": 2203, + "ip": "10.0.0.1" + }, + "modules": { + "directory": "./modules" } } } diff --git a/src/cli/get_module_info.rs b/src/cli/get_module_info.rs index eca1994..5c386ab 100644 --- a/src/cli/get_module_info.rs +++ b/src/cli/get_module_info.rs @@ -1,4 +1,4 @@ -use crate::{logger, models::GuillotineSpecificConfig, utils::constants}; +use crate::{logger, models::RunnerConfig, utils::constants}; use clap::ArgMatches; use cli_table::{ @@ -10,7 +10,7 @@ use cli_table::{ use juno::{models::Value, JunoModule}; use std::collections::HashMap; -pub async fn get_module_info(config: GuillotineSpecificConfig, args: &ArgMatches<'_>) { +pub async fn get_module_info(config: RunnerConfig, args: &ArgMatches<'_>) { let mut module = if config.juno.connection_type == "unix_socket" { let socket_path = config.juno.socket_path.as_ref().unwrap(); JunoModule::from_unix_socket(&socket_path) diff --git a/src/cli/list_modules.rs b/src/cli/list_modules.rs index c9c7abf..c1e387d 100644 --- a/src/cli/list_modules.rs +++ b/src/cli/list_modules.rs @@ -1,4 +1,4 @@ -use crate::{logger, models::GuillotineSpecificConfig, utils::constants}; +use crate::{logger, models::RunnerConfig, utils::constants}; use cli_table::{ format::{ @@ -9,7 +9,7 @@ use cli_table::{ use juno::JunoModule; use std::collections::HashMap; -pub async fn list_modules(config: GuillotineSpecificConfig) { +pub async fn list_modules(config: RunnerConfig) { let mut module = if config.juno.connection_type == "unix_socket" { let socket_path = config.juno.socket_path.as_ref().unwrap(); JunoModule::from_unix_socket(&socket_path) diff --git a/src/cli/list_processes.rs b/src/cli/list_processes.rs index a11a2eb..7911945 100644 --- a/src/cli/list_processes.rs +++ b/src/cli/list_processes.rs @@ -1,4 +1,4 @@ -use crate::{logger, models::GuillotineSpecificConfig, utils::constants}; +use crate::{logger, models::RunnerConfig, utils::constants}; use cli_table::{ format::{ @@ -9,7 +9,7 @@ use cli_table::{ use juno::JunoModule; use std::collections::HashMap; -pub async fn list_processes(config: GuillotineSpecificConfig) { +pub async fn list_processes(config: RunnerConfig) { let mut module = if config.juno.connection_type == "unix_socket" { let socket_path = config.juno.socket_path.as_ref().unwrap(); JunoModule::from_unix_socket(&socket_path) diff --git a/src/cli/restart_process.rs b/src/cli/restart_process.rs index b73b1d6..09ec5fb 100644 --- a/src/cli/restart_process.rs +++ b/src/cli/restart_process.rs @@ -1,4 +1,4 @@ -use crate::{logger, models::GuillotineSpecificConfig, utils::constants}; +use crate::{logger, models::RunnerConfig, utils::constants}; use clap::ArgMatches; use cli_table::{ @@ -13,7 +13,7 @@ use juno::{ }; use std::collections::HashMap; -pub async fn restart_process(config: GuillotineSpecificConfig, args: &ArgMatches<'_>) { +pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { let mut module = if config.juno.connection_type == "unix_socket" { let socket_path = config.juno.socket_path.as_ref().unwrap(); JunoModule::from_unix_socket(&socket_path) @@ -62,6 +62,7 @@ pub async fn restart_process(config: GuillotineSpecificConfig, args: &ArgMatches }) .await .unwrap(); + module.close().await; drop(module); if !response.is_object() { diff --git a/src/exec/host_runner.rs b/src/exec/host_runner.rs new file mode 100644 index 0000000..6ec43af --- /dev/null +++ b/src/exec/host_runner.rs @@ -0,0 +1,250 @@ +use crate::{ + exec::{juno_module, process::ProcessRunner}, + models::{GuillotineMessage, HostConfig}, + utils::logger, +}; +use std::{ + collections::HashMap, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use async_std::{io::Error, net::TcpStream, sync::Mutex, task}; +use futures::{ + channel::mpsc::unbounded, + future::{self, Either}, + StreamExt, +}; +use futures_timer::Delay; +use juno::models::{Number, Value}; + +lazy_static! { + static ref CLOSE_FLAG: Mutex = Mutex::new(false); +} + +pub async fn run( + mut juno_process: ProcessRunner, + juno_config: HostConfig, + mut processes: Vec, +) { + // Spawn juno before spawing any modules + while !juno_process.is_process_running() { + juno_process.respawn().await; + ensure_juno_initialized(&juno_config).await; + } + + // Initialize the guillotine juno module + let (mut sender, mut command_receiver) = unbounded::(); + let mut juno_module = juno_module::setup_host_module(&juno_config, sender).await; + + let mut timer_future = Delay::new(Duration::from_millis(100)); + let mut command_future = command_receiver.next(); + loop { + let selection = future::select(timer_future, command_future).await; + match selection { + Either::Left((_, next_command_future)) => { + if *CLOSE_FLAG.lock().await { + break; + } + + // Timer expired + command_future = next_command_future; + timer_future = Delay::new(Duration::from_millis(100)); + + // Make sure juno is running before checking any other modules + if !juno_process.is_process_running() { + juno_module.close().await; + drop(juno_module); + juno_process.respawn().await; + ensure_juno_initialized(&juno_config).await; + + let channel = unbounded::(); + sender = channel.0; + command_receiver = channel.1; + + command_future = command_receiver.next(); + juno_module = juno_module::setup_host_module(&juno_config, sender).await; + } + + for module in processes.iter_mut() { + // If a module isn't running, respawn it. Simple. + if module.get_runner() == "host" { + if !module.is_process_running() { + module.respawn().await; + } + } else { + // The module belongs to another node. + let result = juno_module + .call_function( + &format!( + "guillotine-node-{}.isProcessRunning", + module.get_runner() + ), + HashMap::new(), + ) + .await; + if result.is_err() { + continue; + } + if result.unwrap() == Value::Bool(false) { + let _ = juno_module + .call_function( + &format!( + "guillotine-node-{}.respawnProcess", + module.get_runner() + ), + vec![( + String::from("pid"), + Value::Number(Number::PosInt(module.get_module_id())), + )] + .into_iter() + .collect(), + ) + .await; + } + } + } + } + Either::Right((command_value, next_timer_future)) => { + // Got a command from juno + timer_future = next_timer_future; + command_future = command_receiver.next(); + + match command_value { + Some(cmd) => match cmd { + GuillotineMessage::ListProcesses(sender) => { + let mut runners = vec![juno_process.copy()]; + processes + .iter() + .for_each(|process| runners.push(process.copy())); + sender.send(runners).unwrap(); + } + GuillotineMessage::RestartProcess(pid, response_sender) => { + if pid == 0 { + response_sender.send(true).unwrap(); + juno_module.close().await; + drop(juno_module); + juno_process.respawn().await; + ensure_juno_initialized(&juno_config).await; + + let channel = unbounded::(); + sender = channel.0; + command_receiver = channel.1; + + command_future = command_receiver.next(); + juno_module = + juno_module::setup_host_module(&juno_config, sender).await; + continue; + } + + let module = processes + .iter_mut() + .find(|process| process.get_module_id() == pid); + if module.is_none() { + response_sender.send(false).unwrap(); + continue; + } + module.unwrap().respawn().await; + response_sender.send(true).unwrap(); + } + _ => {} + }, + None => { + println!("Got None as a command. Is the sender closed?"); + } + } + } + } + } + + // Execute exit actions + // Kill all modules first + processes.iter_mut().for_each(|module| { + logger::info(&format!("Quitting process: {}", module.get_name())); + module.send_quit_signal(); + }); + let quit_time = get_current_millis(); + loop { + // Give the processes some time to die. + task::sleep(Duration::from_millis(100)).await; + // If all of the processes are not running, then break + if processes + .iter_mut() + .all(|module| !module.is_process_running()) + { + break; + } + // If some of the processes are running, check if they've been given enough time. + if get_current_millis() > quit_time + 1000 { + // They've been trying to quit for more than 1 second. Kill them all and quit + processes.iter_mut().for_each(|module| { + logger::info(&format!("Killing process: {}", module.get_name())); + module.kill(); + }); + break; + } + } + + // Now quit juno similarly + logger::info(&format!("Quitting process: {}", juno_process.get_name())); + juno_process.send_quit_signal(); + let quit_time = get_current_millis(); + loop { + // Give the process some time to die. + task::sleep(Duration::from_millis(100)).await; + + // If the process is not running, then break + if !juno_process.is_process_running() { + break; + } + // If the processes is running, check if it's been given enough time. + if get_current_millis() > quit_time + 1000 { + // It's been trying to quit for more than 1 second. Kill it and quit + logger::info(&format!("Killing process: {}", juno_process.get_name())); + juno_process.kill(); + break; + } + } +} + +pub async fn on_exit() { + *CLOSE_FLAG.lock().await = true; +} + +async fn ensure_juno_initialized(host: &HostConfig) { + if host.port.is_some() { + let port = host.port.unwrap(); + // Attempt to connect to the port until you can connect + let port = format!("127.0.0.1:{}", port); + let mut connection = TcpStream::connect(&port).await; + if connection.is_err() { + // If connection failed, wait and try again + Delay::new(Duration::from_millis(1000)).await; + connection = TcpStream::connect(&port).await; + } + } else { + let unix_socket = host.socket_path.unwrap(); + let mut connection = connect_to_unix_socket(&unix_socket).await; + if connection.is_err() { + // If connection failed, wait and try again + Delay::new(Duration::from_millis(1000)).await; + connection = connect_to_unix_socket(&unix_socket).await; + } + } +} + +#[cfg(target_family = "unix")] +async fn connect_to_unix_socket(socket_path: &str) -> Result<(), Error> { + async_std::os::unix::net::UnixStream::connect(socket_path).await?; + Ok(()) +} +#[cfg(target_family = "windows")] +async fn connect_to_unix_socket(_: &str) -> Result<(), Error> { + panic!("Unix sockets are not supported on Windows"); +} + +fn get_current_millis() -> u128 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards. Wtf?") + .as_millis() +} diff --git a/src/exec/juno_module.rs b/src/exec/juno_module.rs index 574e5f4..50ced6c 100644 --- a/src/exec/juno_module.rs +++ b/src/exec/juno_module.rs @@ -1,6 +1,6 @@ use crate::{ exec::process::ProcessRunner, - models::{GuillotineMessage, GuillotineSpecificConfig, ModuleRunningStatus}, + models::{GuillotineMessage, HostConfig, ModuleRunningStatus}, utils::constants, }; use std::{collections::HashMap, sync::Mutex}; @@ -19,20 +19,18 @@ lazy_static! { static ref MESSAGE_SENDER: Mutex>> = Mutex::new(None); } -pub async fn setup_module( - config: GuillotineSpecificConfig, +pub async fn setup_host_module( + config: &HostConfig, sender: UnboundedSender, ) -> JunoModule { - let mut message_sender = MESSAGE_SENDER.lock().unwrap(); - *message_sender = Some(sender); - drop(message_sender); + MESSAGE_SENDER.lock().unwrap().replace(sender); - let mut module = if config.juno.connection_type == "unix_socket" { - let socket_path = config.juno.socket_path.as_ref().unwrap(); + let mut module = if config.connection_type == "unix_socket" { + let socket_path = config.socket_path.as_ref().unwrap(); JunoModule::from_unix_socket(&socket_path) } else { - let port = config.juno.port.as_ref().unwrap(); - let bind_addr = config.juno.bind_addr.as_ref().unwrap(); + let port = config.port.as_ref().unwrap(); + let bind_addr = config.bind_addr.as_ref().unwrap(); JunoModule::from_inet_socket(&bind_addr, *port) }; @@ -40,7 +38,10 @@ pub async fn setup_module( module .initialize(constants::APP_NAME, constants::APP_VERSION, HashMap::new()) .await - .expect("Could not initialize Guillotine Juno Module"); + .expect(&format!( + "Could not initialize {} Juno Module", + constants::APP_NAME + )); module .declare_function("listProcesses", list_processes) @@ -72,9 +73,12 @@ fn list_processes(_: HashMap) -> Value { map.insert( String::from("id"), - Value::Number(Number::PosInt(process.module_id)), + Value::Number(Number::PosInt(process.get_module_id())), + ); + map.insert( + String::from("name"), + Value::String(process.get_name().to_string()), ); - map.insert(String::from("name"), Value::String(process.config.name)); map.insert( String::from("status"), Value::String(String::from(match process.status { diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 025779f..8a7cdaa 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -1,3 +1,4 @@ pub mod juno_module; pub mod process; pub mod runner; +pub mod host_runner; diff --git a/src/exec/process.rs b/src/exec/process.rs index f5c6676..345f3e3 100644 --- a/src/exec/process.rs +++ b/src/exec/process.rs @@ -4,25 +4,29 @@ use crate::{ }; use async_std::task; use std::{ + collections::HashMap, fs::OpenOptions, path::Path, process::{Child, Command, Stdio}, time::{Duration, SystemTime, UNIX_EPOCH}, }; +use juno::JunoModule; + #[derive(Debug)] pub struct ProcessRunner { process: Option, - pub log_dir: Option, - pub working_dir: String, - pub module_id: u64, - pub config: ModuleRunnerConfig, - pub status: ModuleRunningStatus, - pub restarts: i64, - pub uptime: u64, - pub last_started_at: u64, - pub crashes: u64, - pub created_at: u64, + log_dir: Option, + runner: String, + working_dir: String, + module_id: u64, + config: ModuleRunnerConfig, + status: ModuleRunningStatus, + restarts: i64, + uptime: u64, + last_started_at: u64, + crashes: u64, + created_at: u64, } impl ProcessRunner { @@ -35,6 +39,7 @@ impl ProcessRunner { ProcessRunner { process: None, log_dir, + runner: String::from("host"), working_dir, module_id, config, @@ -47,6 +52,26 @@ impl ProcessRunner { } } + pub fn get_name(&self) -> &str { + &self.config.name + } + + pub fn get_module_id(&self) -> u64 { + self.module_id + } + + pub fn set_module_id(&mut self, module_id: u64) { + self.module_id = module_id; + } + + pub fn get_runner(&self) -> &str { + &self.runner + } + + pub fn set_runner(&mut self, runner: String) { + self.runner = runner; + } + pub fn is_process_running(&mut self) -> bool { if self.process.is_none() { return false; @@ -248,6 +273,7 @@ impl ProcessRunner { ProcessRunner { process: None, log_dir: self.log_dir.clone(), + runner: self.runner.clone(), working_dir: self.working_dir.clone(), module_id: self.module_id, config: self.config.clone(), @@ -259,6 +285,16 @@ impl ProcessRunner { created_at: self.created_at, } } + + async fn update_data(&mut self, module: &JunoModule) { + let result = module.call_function(&format!("guillotine-node-{}", self.runner), HashMap::new()).await; + if result.is_err() { + return; + } + let result = result.unwrap(); + + + } } fn get_current_time() -> u64 { diff --git a/src/exec/runner.rs b/src/exec/runner.rs index ce93a40..b62c723 100644 --- a/src/exec/runner.rs +++ b/src/exec/runner.rs @@ -1,133 +1,132 @@ use crate::{ - exec::{juno_module, process::ProcessRunner}, - models::{GuillotineMessage, GuillotineSpecificConfig, ModuleRunnerConfig}, - utils::logger, + exec::{host_runner, process::ProcessRunner}, + models::{ModuleRunnerConfig, RunnerConfig}, + utils::constants, }; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; use async_std::{ fs::{self, DirEntry}, io::Error, - net::TcpStream, path::Path, prelude::*, - sync::Mutex, - task, }; -use futures::{ - channel::mpsc::unbounded, - future::{self, Either}, -}; -use futures_timer::Delay; - -lazy_static! { - static ref CLOSE_FLAG: Mutex = Mutex::new(false); -} -pub async fn run(config: GuillotineSpecificConfig) { - let juno_path = config.juno.path.clone(); - let mut pid = 0; +pub async fn run(config: RunnerConfig) { + if config.logs.is_some() { + let log_dir = config.logs.as_ref().unwrap(); + let main_dir = Path::new(log_dir); + if !main_dir.exists().await { + fs::create_dir(&main_dir).await.unwrap(); + } + } - let juno_process = if config.juno.connection_type == "unix_socket" { - let socket_path = config.juno.socket_path.as_ref().unwrap(); - ProcessRunner::new( - pid, - ModuleRunnerConfig::juno_default( - juno_path.clone(), - vec!["--socket-location".to_string(), socket_path.clone()], - ), - match &config.modules { - Some(modules) => { - if let Some(log_dir) = &modules.logs { - let main_dir = Path::new(log_dir); - if !main_dir.exists().await { - fs::create_dir(&main_dir).await.unwrap(); - } - let sub_dir = main_dir.join("Juno"); + if config.host.is_some() { + let host = config.host.unwrap(); + let mut pid = 0; + + let juno_process = if host.connection_type == constants::connection_type::UNIX_SOCKET { + let socket_path = host.socket_path.as_ref().unwrap(); + ProcessRunner::new( + pid, + ModuleRunnerConfig::juno_default( + host.path.clone(), + vec![ + String::from("--socket-location"), + host.socket_path.as_ref().unwrap().clone(), + ], + ), + match &config.logs { + Some(log_dir) => { + let sub_dir = Path::new(log_dir).join("Juno"); if !sub_dir.exists().await { fs::create_dir(&sub_dir).await.unwrap(); } Some(String::from(sub_dir.to_str().unwrap())) - } else { - None } - } - None => None, - }, - Path::new(&juno_path) - .parent() - .unwrap() - .to_str() - .unwrap() - .to_string(), - ) - } else { - let port = config.juno.port.as_ref().unwrap(); - let bind_addr = config.juno.bind_addr.as_ref().unwrap(); - - ProcessRunner::new( - pid, - ModuleRunnerConfig::juno_default( - juno_path.clone(), - vec![ - "--port".to_string(), - format!("{}", port), - "--bind-addr".to_string(), - bind_addr.clone(), - ], - ), - match &config.modules { - Some(modules) => { - if let Some(log_dir) = &modules.logs { - let main_dir = Path::new(log_dir); - if !main_dir.exists().await { - fs::create_dir(&main_dir).await.unwrap(); - } - let sub_dir = main_dir.join("Juno"); + None => None, + }, + Path::new(&host.path) + .parent() + .unwrap() + .to_str() + .unwrap() + .to_string(), + ) + } else { + let port = host.port.as_ref().unwrap(); + let bind_addr = host.bind_addr.as_ref().unwrap(); + ProcessRunner::new( + pid, + ModuleRunnerConfig::juno_default( + host.path.clone(), + vec![ + "--port".to_string(), + format!("{}", port), + "--bind-addr".to_string(), + bind_addr.clone(), + ], + ), + match &config.logs { + Some(log_dir) => { + let sub_dir = Path::new(log_dir).join("Juno"); if !sub_dir.exists().await { fs::create_dir(&sub_dir).await.unwrap(); } Some(String::from(sub_dir.to_str().unwrap())) - } else { - None } - } - None => None, - }, - Path::new(&juno_path) - .parent() - .unwrap() - .to_str() - .unwrap() - .to_string(), + None => None, + }, + Path::new(&host.path) + .parent() + .unwrap() + .to_str() + .unwrap() + .to_string(), + ) + }; + pid += 1; + + let tracked_modules = get_all_available_modules(pid, &config).await; + + host_runner::run( + juno_process, + config.host.as_ref().unwrap().clone(), + tracked_modules, ) - }; - pid += 1; + .await; + } else if config.node.is_some() { + + } else { + return; + } +} - let tracked_modules = match &config.modules { - Some(modules) => { - let mut tracked_modules = Vec::new(); - let modules_path = Path::new(&modules.path); - if modules_path.exists().await && modules_path.is_dir().await { - // Get all modules and add them to the list - let mut dir_iterator = modules_path.read_dir().await.unwrap(); - while let Some(path) = dir_iterator.next().await { - if let Some(module) = get_module_from_path(pid, path, &modules.logs).await { - tracked_modules.push(module); - pid += 1; - } - } +pub async fn on_exit() { + host_runner::on_exit().await; +} + +async fn get_all_available_modules(starting_pid: u64, config: &RunnerConfig) -> Vec { + if config.modules.is_none() { + return vec![]; + } + let modules = config.modules.as_ref().unwrap(); + + let mut tracked_modules = vec![]; + let mut pid = starting_pid; + + let modules_path = Path::new(&modules.directory); + if modules_path.exists().await && modules_path.is_dir().await { + // Get all modules and add them to the list + let mut dir_iterator = modules_path.read_dir().await.unwrap(); + while let Some(path) = dir_iterator.next().await { + if let Some(module) = get_module_from_path(pid, path, &config.logs).await { + tracked_modules.push(module); + pid += 1; } - Some(tracked_modules) } - None => None, - }; - - keep_processes_alive(juno_process, config, tracked_modules).await; -} + } -pub async fn on_exit() { - *CLOSE_FLAG.lock().await = true; + tracked_modules } async fn get_module_from_path( @@ -186,218 +185,3 @@ async fn get_module_from_path( Some(runner) } - -async fn keep_processes_alive( - mut juno_process: ProcessRunner, - juno_config: GuillotineSpecificConfig, - mut processes: Option>, -) { - // Spawn juno before spawing any modules - while !juno_process.is_process_running() { - juno_process.respawn().await; - ensure_juno_initialized(juno_config.clone()).await; - } - // Initialize the guillotine juno module - let (mut sender, mut command_receiver) = unbounded::(); - let mut module = juno_module::setup_module(juno_config.clone(), sender).await; - - let mut timer_future = Delay::new(Duration::from_millis(100)); - let mut command_future = command_receiver.next(); - loop { - let selection = future::select(timer_future, command_future).await; - match selection { - Either::Left((_, next_command_future)) => { - if *CLOSE_FLAG.lock().await { - break; - } - - // Timer expired - command_future = next_command_future; - timer_future = Delay::new(Duration::from_millis(100)); - - // Make sure juno is running before checking any other modules - if !juno_process.is_process_running() { - module.close().await; - drop(module); - - juno_process.respawn().await; - ensure_juno_initialized(juno_config.clone()).await; - - let channel = unbounded::(); - sender = channel.0; - command_receiver = channel.1; - - command_future = command_receiver.next(); - module = juno_module::setup_module(juno_config.clone(), sender).await; - } - - if processes.is_none() { - continue; - } - let processes = processes.as_mut().unwrap(); - for module in processes.iter_mut() { - // If a module isn't running, respawn it. Simple. - if !module.is_process_running() { - module.respawn().await; - } - } - } - Either::Right((command_value, next_timer_future)) => { - // Got a command from juno - timer_future = next_timer_future; - command_future = command_receiver.next(); - - match command_value { - Some(cmd) => match cmd { - GuillotineMessage::ListProcesses(sender) => { - let mut runners = vec![juno_process.copy()]; - if processes.is_some() { - processes - .as_ref() - .unwrap() - .iter() - .for_each(|process| runners.push(process.copy())); - } - sender.send(runners).unwrap(); - } - GuillotineMessage::RestartProcess(pid, response_sender) => { - if processes.is_none() { - response_sender.send(false).unwrap(); - continue; - } - - if pid == 0 { - response_sender.send(true).unwrap(); - module.close().await; - drop(module); - - juno_process.respawn().await; - ensure_juno_initialized(juno_config.clone()).await; - - let channel = unbounded::(); - sender = channel.0; - command_receiver = channel.1; - - command_future = command_receiver.next(); - module = - juno_module::setup_module(juno_config.clone(), sender).await; - continue; - } - - let module = processes - .as_mut() - .unwrap() - .iter_mut() - .find(|process| process.module_id == pid); - if module.is_none() { - response_sender.send(false).unwrap(); - continue; - } - module.unwrap().respawn().await; - response_sender.send(true).unwrap(); - } - _ => {} - }, - None => { - println!("Got None as a command. Is the sender closed?"); - } - } - } - } - } - - // Execute exit actions - // Kill all modules first - if processes.is_some() { - processes.as_mut().unwrap().iter_mut().for_each(|module| { - logger::info(&format!("Quitting process: {}", module.config.name)); - module.send_quit_signal(); - }); - let quit_time = get_current_millis(); - loop { - // Give the processes some time to die. - task::sleep(Duration::from_millis(100)).await; - // If all of the processes are not running, then break - if processes - .as_mut() - .unwrap() - .iter_mut() - .all(|module| !module.is_process_running()) - { - break; - } - // If some of the processes are running, check if they've been given enough time. - if get_current_millis() > quit_time + 1000 { - // They've been trying to quit for more than 1 second. Kill them all and quit - processes.unwrap().iter_mut().for_each(|module| { - logger::info(&format!("Killing process: {}", module.config.name)); - module.kill(); - }); - break; - } - } - } - - // Now quit juno similarly - logger::info(&format!("Quitting process: {}", juno_process.config.name)); - juno_process.send_quit_signal(); - let quit_time = get_current_millis(); - loop { - // Give the process some time to die. - task::sleep(Duration::from_millis(100)).await; - - // If the process is not running, then break - if !juno_process.is_process_running() { - break; - } - // If the processes is running, check if it's been given enough time. - if get_current_millis() > quit_time + 1000 { - // It's been trying to quit for more than 1 second. Kill it and quit - logger::info(&format!("Killing process: {}", juno_process.config.name)); - juno_process.kill(); - break; - } - } -} - -async fn ensure_juno_initialized(config: GuillotineSpecificConfig) { - if config.juno.port.is_some() { - let port = config.juno.port.unwrap(); - // Keep attempting to connect to the port until you can connect - let port = format!("127.0.0.1:{}", port); - let mut connection = TcpStream::connect(&port).await; - while connection.is_err() { - // If connection failed, wait and try again - Delay::new(Duration::from_millis(250)).await; - connection = TcpStream::connect(&port).await; - } - } else { - let unix_socket = config - .juno - .socket_path - .unwrap_or_else(|| String::from("./juno.sock")); - let mut connection = connect_to_unix_socket(&unix_socket).await; - while connection.is_err() { - // If connection failed, wait and try again - Delay::new(Duration::from_millis(250)).await; - connection = connect_to_unix_socket(&unix_socket).await; - } - } -} - -#[cfg(target_family = "unix")] -async fn connect_to_unix_socket(socket_path: &str) -> Result<(), Error> { - async_std::os::unix::net::UnixStream::connect(socket_path).await?; - Ok(()) -} -#[cfg(target_family = "windows")] -async fn connect_to_unix_socket(_: &str) -> Result<(), Error> { - panic!("Unix sockets are not supported on Windows"); -} - -fn get_current_millis() -> u128 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards. Wtf?") - .as_millis() -} diff --git a/src/main.rs b/src/main.rs index e1d6782..00feae4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,7 @@ async fn main() { ) .subcommand( SubCommand::with_name("info") + .alias("i") .about("Get information about a process / module") .arg( Arg::with_name("pid") diff --git a/src/models/config_types.rs b/src/models/config_types.rs index e125498..5111e0c 100644 --- a/src/models/config_types.rs +++ b/src/models/config_types.rs @@ -2,34 +2,37 @@ use serde_derive::Deserialize; #[derive(Deserialize)] pub struct GuillotineConfig { - pub version: String, + pub version: u64, pub configs: Option>, - pub config: Option, + pub config: Option, } #[derive(Deserialize)] pub struct GuillotinePerEnvConfig { - pub env: EnvRequirements, - pub config: GuillotineSpecificConfig, + pub target: Option, + pub config: RunnerConfig, } #[derive(Deserialize)] -pub struct EnvRequirements { - pub target_family: Option, - pub target_os: Option, - pub target_arch: Option, - pub target_endian: Option, +pub struct ConfigTarget { + pub family: Option, + pub os: Option, + pub arch: Option, + pub endian: Option, } // Config specific to this environment #[derive(Deserialize, Clone)] -pub struct GuillotineSpecificConfig { - pub juno: JunoConfig, - pub modules: Option, +pub struct RunnerConfig { + pub name: String, + pub logs: Option, + pub host: Option, + pub node: Option, + pub modules: Option, } #[derive(Deserialize, Clone)] -pub struct JunoConfig { +pub struct HostConfig { pub path: String, pub connection_type: String, pub port: Option, @@ -38,9 +41,15 @@ pub struct JunoConfig { } #[derive(Deserialize, Clone)] -pub struct GuillotineModuleConfig { - pub path: String, - pub logs: Option, +pub struct NodeConfig { + pub connection_type: String, + pub port: Option, + pub ip: Option, + pub socket_path: Option, +} + +pub struct ModuleConfig { + pub directory: String } #[derive(Debug, Clone)] diff --git a/src/models/mod.rs b/src/models/mod.rs index fc65917..fdf37bc 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -5,6 +5,6 @@ pub mod parser; pub use cli_messages::GuillotineMessage; pub use config_types::{ - EnvRequirements, GuillotineConfig, GuillotineModuleConfig, GuillotinePerEnvConfig, - GuillotineSpecificConfig, ModuleRunnerConfig, ModuleRunningStatus, + ConfigTarget, GuillotineConfig, GuillotinePerEnvConfig, HostConfig, ModuleRunnerConfig, + ModuleRunningStatus, NodeConfig, RunnerConfig, ModuleConfig, }; diff --git a/src/models/parser.rs b/src/models/parser.rs index 8908c5a..d9277d2 100644 --- a/src/models/parser.rs +++ b/src/models/parser.rs @@ -1,31 +1,40 @@ -use super::{EnvRequirements, GuillotineConfig, GuillotineSpecificConfig}; +use super::{ConfigTarget, GuillotineConfig, RunnerConfig}; use async_std::{fs, path::Path}; use serde_json::{Error, Result}; -pub async fn select_config(input: String) -> Result { +pub async fn select_config(input: String) -> Result { let envs: GuillotineConfig = serde_json::from_str(&input)?; if envs.config.is_some() { parse_config(envs.config.unwrap()).await } else { + let mut default_config = None; for config in envs.configs.unwrap().into_iter() { - if parse_if_config(&config.env).await? { + if config.target.is_none() { + default_config = Some(config); + continue; + } + if parse_if_config(&config.target.unwrap()).await? { return parse_config(config.config).await; } } - throw_parse_error() + if default_config.is_none() { + throw_parse_error() + } else { + parse_config(default_config.unwrap().config).await + } } } -fn throw_parse_error() -> Result { +fn throw_parse_error() -> Result { Err(Error::io(std::io::Error::from( std::io::ErrorKind::InvalidData, ))) } -async fn parse_if_config(input: &EnvRequirements) -> Result { +async fn parse_if_config(input: &ConfigTarget) -> Result { let mut satisfied = true; - if let Some(required_cfg) = &input.target_family { + if let Some(required_cfg) = &input.family { if (required_cfg == "unix" && cfg!(target_family = "unix")) || (required_cfg == "windows" && cfg!(target_family = "windows")) { @@ -35,7 +44,7 @@ async fn parse_if_config(input: &EnvRequirements) -> Result { } } - if let Some(required_cfg) = &input.target_os { + if let Some(required_cfg) = &input.os { if (required_cfg == "windows" && cfg!(target_os = "windows")) || (required_cfg == "macos" && cfg!(target_os = "macos")) || (required_cfg == "ios" && cfg!(target_os = "ios")) @@ -52,7 +61,7 @@ async fn parse_if_config(input: &EnvRequirements) -> Result { } } - if let Some(required_cfg) = &input.target_arch { + if let Some(required_cfg) = &input.arch { if (required_cfg == "x86" && cfg!(target_arch = "x86")) || (required_cfg == "x86_64" && cfg!(target_arch = "x86_64")) || (required_cfg == "mips" && cfg!(target_arch = "mips")) @@ -67,7 +76,7 @@ async fn parse_if_config(input: &EnvRequirements) -> Result { } } - if let Some(required_cfg) = &input.target_endian { + if let Some(required_cfg) = &input.endian { if (required_cfg == "little" && cfg!(target_endian = "little")) || (required_cfg == "big" && cfg!(target_endian = "big")) { @@ -80,18 +89,18 @@ async fn parse_if_config(input: &EnvRequirements) -> Result { Ok(satisfied) } -async fn parse_config(mut input: GuillotineSpecificConfig) -> Result { +async fn parse_config(mut input: RunnerConfig) -> Result { if input.modules.is_some() { let mut modules = input.modules.unwrap(); - modules.path = fs::canonicalize(modules.path) + modules.directory = fs::canonicalize(modules.directory) .await .unwrap() .to_str() .unwrap() .to_string(); - if modules.logs.is_some() { - modules.logs = Some( - fs::canonicalize(modules.logs.unwrap()) + if input.logs.is_some() { + input.logs = Some( + fs::canonicalize(input.logs.unwrap()) .await .unwrap() .to_str() @@ -102,45 +111,86 @@ async fn parse_config(mut input: GuillotineSpecificConfig) -> Result Date: Tue, 19 May 2020 15:23:04 +0530 Subject: [PATCH 02/26] Added host loop and juno_module for host. TODO node loop and juno_module for node. TODO fix the cli that's broken also --- src/cli/list_modules.rs | 2 +- src/cli/list_processes.rs | 2 +- src/cli/mod.rs | 12 +- src/cli/restart_process.rs | 3 +- src/exec/host_runner.rs | 6 +- src/exec/juno_module.rs | 168 ------------- src/exec/mod.rs | 3 +- src/exec/process.rs | 6 +- src/exec/runner.rs | 10 +- src/host/juno_module.rs | 449 +++++++++++++++++++++++++++++++++ src/host/mod.rs | 5 + src/host/runner.rs | 493 +++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + src/models/cli_messages.rs | 50 +++- src/models/config_types.rs | 5 +- src/models/host_models.rs | 92 +++++++ src/models/mod.rs | 6 +- src/node/mod.rs | 1 + 18 files changed, 1110 insertions(+), 205 deletions(-) delete mode 100644 src/exec/juno_module.rs create mode 100644 src/host/juno_module.rs create mode 100644 src/host/mod.rs create mode 100644 src/host/runner.rs create mode 100644 src/models/host_models.rs create mode 100644 src/node/mod.rs diff --git a/src/cli/list_modules.rs b/src/cli/list_modules.rs index c1e387d..76e0444 100644 --- a/src/cli/list_modules.rs +++ b/src/cli/list_modules.rs @@ -93,4 +93,4 @@ pub async fn list_modules(config: RunnerConfig) { // Print it out table.unwrap().print_stdout().unwrap(); -} \ No newline at end of file +} diff --git a/src/cli/list_processes.rs b/src/cli/list_processes.rs index 7911945..4ae7342 100644 --- a/src/cli/list_processes.rs +++ b/src/cli/list_processes.rs @@ -165,4 +165,4 @@ pub async fn list_processes(config: RunnerConfig) { // Print it out table.unwrap().print_stdout().unwrap(); -} \ No newline at end of file +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index aaeb25e..97d4136 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,18 +1,16 @@ -mod list_processes; -mod list_modules; mod get_module_info; +mod list_modules; +mod list_processes; mod restart_process; -pub use list_processes::list_processes; -pub use list_modules::list_modules; pub use get_module_info::get_module_info; +pub use list_modules::list_modules; +pub use list_processes::list_processes; pub use restart_process::restart_process; use chrono::{prelude::*, Utc}; -pub async fn on_exit() { - -} +pub async fn on_exit() {} fn get_date_time(timestamp: i64) -> String { Utc.timestamp_millis(timestamp) diff --git a/src/cli/restart_process.rs b/src/cli/restart_process.rs index 09ec5fb..c5599db 100644 --- a/src/cli/restart_process.rs +++ b/src/cli/restart_process.rs @@ -155,7 +155,8 @@ pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { Cell::new( &format!( "{}", - if pid == process + if pid + == process .get("id") .unwrap() .as_number() diff --git a/src/exec/host_runner.rs b/src/exec/host_runner.rs index 6ec43af..dc18d3f 100644 --- a/src/exec/host_runner.rs +++ b/src/exec/host_runner.rs @@ -21,11 +21,7 @@ lazy_static! { static ref CLOSE_FLAG: Mutex = Mutex::new(false); } -pub async fn run( - mut juno_process: ProcessRunner, - juno_config: HostConfig, - mut processes: Vec, -) { +pub async fn run(mut juno_process: ProcessRunner, juno_config: HostConfig) { // Spawn juno before spawing any modules while !juno_process.is_process_running() { juno_process.respawn().await; diff --git a/src/exec/juno_module.rs b/src/exec/juno_module.rs deleted file mode 100644 index 50ced6c..0000000 --- a/src/exec/juno_module.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::{ - exec::process::ProcessRunner, - models::{GuillotineMessage, HostConfig, ModuleRunningStatus}, - utils::constants, -}; -use std::{collections::HashMap, sync::Mutex}; - -use async_std::task; -use futures::{ - channel::{mpsc::UnboundedSender, oneshot::channel}, - SinkExt, -}; -use juno::{ - models::{Number, Value}, - JunoModule, -}; - -lazy_static! { - static ref MESSAGE_SENDER: Mutex>> = Mutex::new(None); -} - -pub async fn setup_host_module( - config: &HostConfig, - sender: UnboundedSender, -) -> JunoModule { - MESSAGE_SENDER.lock().unwrap().replace(sender); - - let mut module = if config.connection_type == "unix_socket" { - let socket_path = config.socket_path.as_ref().unwrap(); - JunoModule::from_unix_socket(&socket_path) - } else { - let port = config.port.as_ref().unwrap(); - let bind_addr = config.bind_addr.as_ref().unwrap(); - - JunoModule::from_inet_socket(&bind_addr, *port) - }; - - module - .initialize(constants::APP_NAME, constants::APP_VERSION, HashMap::new()) - .await - .expect(&format!( - "Could not initialize {} Juno Module", - constants::APP_NAME - )); - - module - .declare_function("listProcesses", list_processes) - .await - .unwrap(); - - module - .declare_function("restartProcess", restart_process) - .await - .unwrap(); - - module -} - -fn list_processes(_: HashMap) -> Value { - let message_sender = MESSAGE_SENDER.lock().unwrap(); - let mut message_sender = message_sender.as_ref().unwrap(); - - let (sender, receiver) = channel::>(); - - task::block_on(message_sender.send(GuillotineMessage::ListProcesses(sender))).unwrap(); - - Value::Array( - task::block_on(receiver) - .unwrap() - .into_iter() - .map(|process| { - let mut map = HashMap::new(); - - map.insert( - String::from("id"), - Value::Number(Number::PosInt(process.get_module_id())), - ); - map.insert( - String::from("name"), - Value::String(process.get_name().to_string()), - ); - map.insert( - String::from("status"), - Value::String(String::from(match process.status { - ModuleRunningStatus::Running => "running", - ModuleRunningStatus::Offline => "offline", - })), - ); - map.insert( - String::from("restarts"), - Value::Number(Number::NegInt(process.restarts)), - ); - map.insert( - String::from("uptime"), - Value::Number(Number::PosInt(process.uptime)), - ); - map.insert( - String::from("crashes"), - Value::Number(Number::PosInt(process.crashes)), - ); - map.insert( - String::from("createdAt"), - Value::Number(Number::PosInt(process.created_at)), - ); - - Value::Object(map) - }) - .collect(), - ) -} - -fn restart_process(args: HashMap) -> Value { - let pid = args.get("processId"); - if pid.is_none() { - return Value::Object({ - let mut map = HashMap::new(); - map.insert(String::from("success"), Value::Bool(false)); - map.insert( - String::from("error"), - Value::String(String::from("No PID supplied")), - ); - map - }); - } - let pid = pid.unwrap().as_number(); - if pid.is_none() { - return Value::Object({ - let mut map = HashMap::new(); - map.insert(String::from("success"), Value::Bool(false)); - map.insert( - String::from("error"), - Value::String(String::from("PID supplied is not a number")), - ); - map - }); - } - let pid = match pid.unwrap() { - Number::Float(num) => *num as u64, - Number::NegInt(num) => *num as u64, - Number::PosInt(num) => *num, - }; - - let message_sender = MESSAGE_SENDER.lock().unwrap(); - let mut message_sender = message_sender.as_ref().unwrap(); - - let (sender, receiver) = channel::(); - - task::block_on(message_sender.send(GuillotineMessage::RestartProcess(pid, sender))).unwrap(); - let found_process = task::block_on(receiver).unwrap(); - - if found_process { - Value::Object({ - let mut map = HashMap::new(); - map.insert(String::from("success"), Value::Bool(true)); - map - }) - } else { - Value::Object({ - let mut map = HashMap::new(); - map.insert(String::from("success"), Value::Bool(false)); - map.insert( - String::from("error"), - Value::String(String::from("No process found with that PID")), - ); - map - }) - } -} diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 8a7cdaa..35f4b5c 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -1,4 +1,3 @@ -pub mod juno_module; +pub mod host_runner; pub mod process; pub mod runner; -pub mod host_runner; diff --git a/src/exec/process.rs b/src/exec/process.rs index 345f3e3..4a5db19 100644 --- a/src/exec/process.rs +++ b/src/exec/process.rs @@ -287,13 +287,13 @@ impl ProcessRunner { } async fn update_data(&mut self, module: &JunoModule) { - let result = module.call_function(&format!("guillotine-node-{}", self.runner), HashMap::new()).await; + let result = module + .call_function(&format!("guillotine-node-{}", self.runner), HashMap::new()) + .await; if result.is_err() { return; } let result = result.unwrap(); - - } } diff --git a/src/exec/runner.rs b/src/exec/runner.rs index b62c723..06e97c7 100644 --- a/src/exec/runner.rs +++ b/src/exec/runner.rs @@ -86,16 +86,8 @@ pub async fn run(config: RunnerConfig) { }; pid += 1; - let tracked_modules = get_all_available_modules(pid, &config).await; - - host_runner::run( - juno_process, - config.host.as_ref().unwrap().clone(), - tracked_modules, - ) - .await; + host_runner::run(juno_process, config.host.clone().unwrap()).await; } else if config.node.is_some() { - } else { return; } diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs new file mode 100644 index 0000000..a73d22e --- /dev/null +++ b/src/host/juno_module.rs @@ -0,0 +1,449 @@ +use crate::{ + models::{GuillotineMessage, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, ProcessData}, + utils::constants, +}; +use std::collections::HashMap; + +use async_std::{sync::RwLock, task}; +use futures::{ + channel::{mpsc::UnboundedSender, oneshot::channel}, + SinkExt, +}; +use juno::{ + models::{Number, Value}, + JunoModule, +}; + +lazy_static! { + static ref MESSAGE_SENDER: RwLock>> = + RwLock::new(None); +} + +pub async fn setup_host_module( + config: &HostConfig, + sender: UnboundedSender, +) -> JunoModule { + MESSAGE_SENDER.write().await.replace(sender); + + let mut module = if config.connection_type == "unix_socket" { + let socket_path = config.socket_path.as_ref().unwrap(); + JunoModule::from_unix_socket(&socket_path) + } else { + let port = config.port.as_ref().unwrap(); + let bind_addr = config.bind_addr.as_ref().unwrap(); + + JunoModule::from_inet_socket(&bind_addr, *port) + }; + + module + .initialize(constants::APP_NAME, constants::APP_VERSION, HashMap::new()) + .await + .expect(&format!( + "Could not initialize {} Juno Module", + constants::APP_NAME + )); + + module + .declare_function("registerNode", register_node) + .await + .unwrap(); + + module + .declare_function("registerProcess", register_process) + .await + .unwrap(); + + module + .declare_function("onProcessExited", process_exited) + .await + .unwrap(); + + module + .declare_function("onProcessRunning", process_running) + .await + .unwrap(); + + module + .register_hook("juno.moduleDeactivated", module_deactivated) + .await + .unwrap(); + + module +} + +fn register_node(mut args: HashMap) -> Value { + task::block_on(async { + let name = if let Some(Value::String(value)) = args.remove("name") { + value + } else { + return generate_error_response(Some("Name parameter is not a string")); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::RegisterNode { + node_name: name, + response: sender, + }) + .await; + + let response = receiver.await.unwrap(); + if response.is_ok() { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(Some(&response.unwrap_err())) + } + }) +} + +fn register_process(mut args: HashMap) -> Value { + task::block_on(async { + let node_name = if let Some(Value::String(value)) = args.remove("node") { + value + } else { + return generate_error_response(Some("Name parameter is not a string")); + }; + + let log_dir = if let Some(Value::String(dir)) = args.remove("log_dir") { + Some(dir) + } else { + None + }; + + let working_dir = if let Some(Value::String(dir)) = args.remove("working_dir") { + dir + } else { + return generate_error_response(Some("Working dir is not a string")); + }; + + let mut config = if let Some(Value::Object(config)) = args.remove("config") { + ModuleRunnerConfig { + name: if let Some(Value::String(value)) = config.remove("name") { + value + } else { + return generate_error_response(Some("Name is not present in config")); + }, + command: if let Some(Value::String(value)) = config.remove("command") { + value + } else { + return generate_error_response(Some("Command is not present in config")); + }, + interpreter: if let Some(Value::String(value)) = config.remove("interpreter") { + Some(value) + } else { + None + }, + args: if let Some(Value::Array(value)) = config.remove("args") { + Some( + value + .into_iter() + .filter_map(|value| { + if let Value::String(string) = value { + Some(string) + } else { + None + } + }) + .collect(), + ) + } else { + None + }, + envs: if let Some(Value::Object(value)) = config.remove("args") { + Some( + value + .into_iter() + .filter_map(|(key, value)| { + if let Value::String(string) = value { + Some((key, string)) + } else { + None + } + }) + .collect(), + ) + } else { + None + }, + } + } else { + return generate_error_response(Some("Config is not an object")); + }; + + let status = if let Some(Value::String(status)) = args.remove("status") { + match status.as_ref() { + "running" => ModuleRunningStatus::Running, + "stopped" => ModuleRunningStatus::Stopped, + "offline" => { + return generate_error_response(Some("Nodes can't declare a module as offline")) + } + _ => return generate_error_response(Some("Status is not a known value")), + } + } else { + return generate_error_response(Some("Status is not a known value")); + }; + + let last_started_at = if let Some(Value::Number(started_at)) = args.remove("lastStartedAt") + { + match started_at { + Number::PosInt(started_at) => started_at, + Number::NegInt(started_at) => started_at as u64, + Number::Float(started_at) => started_at as u64, + } + } else { + 0 + }; + + let created_at = if let Some(Value::Number(created_at)) = args.remove("createdAt") { + match created_at { + Number::PosInt(created_at) => created_at, + Number::NegInt(created_at) => created_at as u64, + Number::Float(created_at) => created_at as u64, + } + } else { + return generate_error_response(Some("Created at is not a number")); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::RegisterProcess { + node_name, + process_data: ProcessData::new( + log_dir, + working_dir, + config, + status, + last_started_at, + created_at, + ), + response: sender, + }) + .await; + + let response = receiver.await.unwrap(); + if let Ok(module_id) = response { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(module_id)), + ); + map + }) + } else { + generate_error_response(Some(&response.unwrap_err())) + } + }) +} + +fn process_exited(mut args: HashMap) -> Value { + task::block_on(async { + let node_name = if let Some(Value::String(value)) = args.remove("node") { + value + } else { + return generate_error_response(Some("Node parameter is not a string")); + }; + + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response(Some("Module ID is not a number")); + }; + + let crash = if let Some(Value::Bool(crash)) = args.remove("crash") { + crash + } else { + return generate_error_response(Some("Module ID is not a number")); + }; + + let last_spawned_at = + if let Some(Value::Number(last_spawned_at)) = args.remove("lastSpawnedAt") { + match last_spawned_at { + Number::PosInt(last_spawned_at) => last_spawned_at, + Number::NegInt(last_spawned_at) => last_spawned_at as u64, + Number::Float(last_spawned_at) => last_spawned_at as u64, + } + } else { + return generate_error_response(Some("Module ID is not a number")); + }; + + let (sender, receiver) = channel::<(bool, u64)>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::ProcessExited { + node_name, + module_id, + crash, + last_spawned_at, + response: sender, + }) + .await; + + let (should_restart, wait_duration_millis) = receiver.await.unwrap(); + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert(String::from("shouldRestart"), Value::Bool(should_restart)); + map.insert( + String::from("waitDuration"), + Value::Number(Number::PosInt(wait_duration_millis)), + ); + map + }) + }) +} + +fn process_running(mut args: HashMap) -> Value { + task::block_on(async { + let node_name = if let Some(Value::String(value)) = args.remove("node") { + value + } else { + return generate_error_response(Some("Node parameter is not a string")); + }; + + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response(Some("Module ID is not a number")); + }; + + let last_spawned_at = + if let Some(Value::Number(last_spawned_at)) = args.remove("lastSpawnedAt") { + match last_spawned_at { + Number::PosInt(last_spawned_at) => last_spawned_at, + Number::NegInt(last_spawned_at) => last_spawned_at as u64, + Number::Float(last_spawned_at) => last_spawned_at as u64, + } + } else { + return generate_error_response(Some("Module ID is not a number")); + }; + + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::ProcessRunning { + node_name, + module_id, + last_spawned_at, + }) + .await; + + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + }) +} + +fn module_deactivated(mut data: Value) { + task::block_on(async { + let args = if let Value::Object(args) = data { + args + } else { + return; + }; + + let module_id = if let Some(Value::String(module_id)) = args.remove("moduleId") { + module_id + } else { + return; + }; + + if !module_id.starts_with(&format!("{}-node-", constants::APP_NAME)) { + return; + } + let node_name = module_id + .chars() + .skip(constants::APP_NAME.len() + "-node-".len()) + .collect(); + + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::NodeDisconnected { node_name }) + .await; + }); +} + +fn get_node_module_from_object(mut object: HashMap) -> Option { + let mut module = ProcessData { + log_dir: None, + working_dir: String::default(), + module_id: -1, + config: ModuleRunnerConfig { + name: String::default(), + command: String::default(), + interpreter: None, + args: None, + envs: None, + }, + status: ModuleRunningStatus::Offline, + restarts: -1, + last_started_at: 0, + crashes: 0, + consequtive_crashes: 0, + created_at: 0, + }; + + if let Some(Value::String(log_dir)) = object.remove("log_dir") { + module.log_dir = log_dir; + } + + if let Some(Value::String(working_dir)) = object.remove("working_dir") { + module.working_dir = working_dir; + } else { + return None; + } + + if let Some(Value::Object(map)) = object.remove("config") {} +} + +fn generate_error_response(error_message: Option<&str>) -> Value { + Value::Object({ + let mut map = HashMap::new(); + + map.insert(String::from("success"), Value::Bool(false)); + if error_message.is_some() { + map.insert( + String::from("error"), + Value::String(String::from(error_message.unwrap())), + ); + } + + map + }) +} diff --git a/src/host/mod.rs b/src/host/mod.rs new file mode 100644 index 0000000..9ad4555 --- /dev/null +++ b/src/host/mod.rs @@ -0,0 +1,5 @@ +mod juno_module; +mod runner; + +pub use juno_module::setup_host_module; +pub use runner::run; diff --git a/src/host/runner.rs b/src/host/runner.rs new file mode 100644 index 0000000..403bfe5 --- /dev/null +++ b/src/host/runner.rs @@ -0,0 +1,493 @@ +use crate::{ + exec::process::ProcessRunner, + host::juno_module, + models::{ + GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, + RunnerConfig, + }, + utils::{constants, logger}, +}; +use std::{ + collections::HashMap, + io::Error, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use async_std::{fs, net::TcpStream, path::Path, sync::Mutex, task}; +use future::Either; +use futures::{channel::mpsc::unbounded, future, StreamExt}; +use futures_timer::Delay; +use juno::models::{Number, Value}; + +lazy_static! { + static ref CLOSE_FLAG: Mutex = Mutex::new(false); +} + +pub async fn run(mut config: RunnerConfig) { + let host = config.host.take().unwrap(); + + if try_connecting_to_juno(&host).await { + logger::error("An instance of Juno with the same configuration already seems to be running. Duplicate instances are not allowed!"); + return; + } + + let juno_process = if host.connection_type == constants::connection_type::UNIX_SOCKET { + let socket_path = host.socket_path.clone().unwrap(); + ProcessRunner::new( + 0, + ModuleRunnerConfig::juno_default( + host.path, + vec![String::from("--socket-location"), socket_path], + ), + match &config.logs { + Some(log_dir) => { + let sub_dir = Path::new(log_dir).join("Juno"); + if !sub_dir.exists().await { + fs::create_dir(&sub_dir).await.unwrap(); + } + Some(String::from(sub_dir.to_str().unwrap())) + } + None => None, + }, + Path::new(&host.path) + .parent() + .unwrap() + .to_str() + .unwrap() + .to_string(), + ) + } else { + let port = host.port.unwrap(); + let bind_addr = host.bind_addr.clone().unwrap(); + ProcessRunner::new( + 0, + ModuleRunnerConfig::juno_default( + host.path.clone(), + vec![ + String::from("--port"), + format!("{}", port), + String::from("--bind-addr"), + bind_addr, + ], + ), + match &config.logs { + Some(log_dir) => { + let sub_dir = Path::new(log_dir).join("Juno"); + if !sub_dir.exists().await { + fs::create_dir(&sub_dir).await.unwrap(); + } + Some(String::from(sub_dir.to_str().unwrap())) + } + None => None, + }, + Path::new(&host.path) + .parent() + .unwrap() + .to_str() + .unwrap() + .to_string(), + ) + }; + + keep_host_alive(juno_process, host).await; +} + +async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfig) { + // Spawn juno before spawing any modules + while !juno_process.is_process_running() { + juno_process.respawn().await; + try_connecting_to_juno(&juno_config).await; + } + + let mut node_runners = HashMap::new(); + let mut pid = 1; + + // Initialize the guillotine juno module + let (mut sender, mut command_receiver) = unbounded::(); + let mut juno_module = juno_module::setup_host_module(&juno_config, sender.clone()).await; + + let mut timer_future = Delay::new(Duration::from_millis(100)); + let mut command_future = command_receiver.next(); + loop { + let selection = future::select(timer_future, command_future).await; + match selection { + Either::Left((_, next_command_future)) => { + if *CLOSE_FLAG.lock().await { + break; + } + + // Timer expired + command_future = next_command_future; + timer_future = Delay::new(Duration::from_millis(100)); + + if juno_process.is_process_running() { + continue; + } + // juno_process isn't running. Restart it + + // TODO: when Juno process is restarted, all sub modules should be restarted too + // But how will you tell the nodes to restart if juno isn't running to tell them to restart? + // Fuck this shit, don't care (╯°□°)╯︵ ┻━┻ + + juno_module.close().await; + drop(juno_module); + juno_module = juno_module::setup_host_module(&juno_config, sender.clone()).await; + } + Either::Right((command_value, next_timer_future)) => { + // A command was received + command_future = command_receiver.next(); + timer_future = next_timer_future; + + if command_value.is_none() { + // Command is none. Are the senders closed? + break; + } + + match command_value.unwrap() { + // Node-host communication stuff + GuillotineMessage::RegisterNode { + node_name, + response, + } => { + if !node_runners.contains_key(&node_name) { + node_runners.insert( + node_name.clone(), + GuillotineNode { + name: node_name, + processes: vec![], + connected: true, + }, + ); + } else { + node_runners.get_mut(&node_name).unwrap().connected = true; + } + response.send(Ok(())).unwrap(); + } + GuillotineMessage::RegisterProcess { + node_name, + mut process_data, + response, + } => { + if !node_runners.contains_key(&node_name) { + response.send(Err(format!("Cannot register process. A runner with the name '{}' does not exists", node_name))).unwrap(); + continue; + } + + // Find a runner which runs a module of the same name + let runner = node_runners.values().find(|runner| { + runner + .get_process_by_name(&process_data.config.name) + .is_some() + }); + + // There's a runner which runs a module of the same name + if let Some(runner) = runner { + // That runner isn't what's registering the process + if node_name != runner.name { + response.send(Err(format!("Cannot register process. A process with the name '{}' is already registered under the runner '{}'", process_data.config.name, runner.name))).unwrap(); + continue; + } + let process = runner.get_process_by_name(&process_data.config.name); + // The process isn't offline. This isn't a stale process. + if process.unwrap().status != ModuleRunningStatus::Offline { + // The process being registered isn't offline. Duplicate module! + response.send(Err(format!("Cannot register process. A process with the name '{}' is already registered", process_data.config.name))).unwrap(); + continue; + } + + // There's a stale module re-registering itself. + process_data.module_id = process.unwrap().module_id; + runner.register_process(process_data); + + response.send(Ok(process_data.module_id)).unwrap(); + continue; + } + + // So far, no runner runs a process of the same name + // This is definitely a fresh registration + // Assign new pid and shit + let runner = node_runners.get_mut(&node_name); + if runner.is_none() { + response.send(Err(format!("Cannot register process. The runner with name '{}' doesn't exist", process_data.config.name))).unwrap(); + continue; + } + let runner = runner.unwrap(); + + let assigned_pid = pid; + pid += 1; + process_data.module_id = assigned_pid; + runner.register_process(process_data); + + response.send(Ok(assigned_pid)).unwrap(); + } + GuillotineMessage::ProcessExited { + node_name, + module_id, + crash, + last_spawned_at, + response, + } => { + let runner = node_runners.get_mut(&node_name); + if runner.is_none() { + response.send((false, 0)).unwrap(); + continue; + } + let runner = runner.unwrap(); + + let process = runner.get_process_by_id(module_id); + if process.is_none() { + response.send((false, 0)).unwrap(); + continue; + } + let process = process.as_mut().unwrap(); + + process.restarts += 1; + if crash { + process.crashes += 1; + process.consequtive_crashes += 1; + if process.consequtive_crashes > 10 { + // The process has crashed more than 10 times consequtively. + // Don't restart the process anymore + response.send((false, 0)).unwrap(); + process.status = ModuleRunningStatus::Stopped; + } else { + // The process has crashed less than 10 times consequtively. + // Wait for a while and restart the process + process.last_started_at = last_spawned_at; + response.send((true, 100)).unwrap(); + process.status = ModuleRunningStatus::Running; + } + } else { + process.last_started_at = last_spawned_at; + response.send((true, 0)).unwrap(); + process.status = ModuleRunningStatus::Running; + } + } + GuillotineMessage::ProcessRunning { + node_name, + module_id, + last_spawned_at, + } => { + let runner = node_runners.get_mut(&node_name); + if runner.is_none() { + continue; + } + let runner = runner.unwrap(); + + let process = runner.get_process_by_id(module_id); + if process.is_none() { + continue; + } + let process = process.as_mut().unwrap(); + + process.consequtive_crashes = 0; + process.last_started_at = last_spawned_at; + process.status = ModuleRunningStatus::Running; + } + GuillotineMessage::NodeDisconnected { node_name } => { + if let Some(runner) = node_runners.get_mut(&node_name) { + runner.connected = false; + runner + .processes + .iter_mut() + .for_each(|process| process.status = ModuleRunningStatus::Offline); + } + } + + // Cli stuff + GuillotineMessage::ListModules { response } => { + let result = juno_module + .call_function("juno.listModules", HashMap::new()) + .await; + if result.is_err() { + response.send(Err(format!( + "Error listing modules from Juno: {}", + result.unwrap_err() + ))); + continue; + } + let modules = result.unwrap(); + if !modules.is_array() { + response + .send(Err(format!("Expected array response. Got {:?}", modules))); + return; + } + let modules = modules + .as_array() + .unwrap() + .iter() + .map(|value| value.as_string().unwrap().clone()) + .collect(); + response.send(Ok(modules)).unwrap(); + } + GuillotineMessage::ListNodes { response } => { + response + .send(node_runners.iter().map(|(_, node)| node.clone()).collect()) + .unwrap(); + } + GuillotineMessage::ListAllProcesses { response } => { + let result = vec![]; + node_runners.iter().for_each(|(name, node)| { + node.processes + .iter() + .for_each(|process| result.push((name.clone(), process.clone()))); + }); + response.send(result).unwrap(); + } + GuillotineMessage::ListProcesses { + node_name, + response, + } => { + let runner = node_runners.get_mut(&node_name); + if runner.is_none() { + response + .send(Err(format!( + "Runner node with the name '{}' doesn't exist", + node_name + ))) + .unwrap(); + continue; + } + let runner = runner.unwrap(); + + response.send(Ok(runner.processes.clone())).unwrap(); + } + GuillotineMessage::RestartProcess { + module_id, + response, + } => { + let node = node_runners + .values() + .find(|node| node.get_process_by_id(module_id).is_some()); + if node.is_none() { + response + .send(Err(format!( + "No node found running the module with the ID {}", + module_id + ))) + .unwrap(); + continue; + } + let node = node.unwrap(); + if !node.connected { + response + .send(Err(format!( + "The node (with the name '{}') running the module {} is not connected", + node.name, + module_id + ))) + .unwrap(); + continue; + } + + // Now restart the process + let result = juno_module + .call_function( + &format!( + "{}-node-{}.respawnProcess", + constants::APP_NAME, + node.name + ), + { + let mut map = HashMap::new(); + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(module_id)), + ); + map + }, + ) + .await; + if result.is_err() { + response + .send(Err(format!( + "Error sending the restart command: {}", + result.unwrap_err() + ))) + .unwrap(); + continue; + } + let result = result.unwrap().as_object().unwrap(); + + if !*result.get("success").unwrap().as_bool().unwrap() { + response + .send(Err(format!( + "Error restarting process: {}", + result.get("error").unwrap().as_string().unwrap() + ))) + .unwrap(); + continue; + } + response.send(Ok(())).unwrap(); + } + msg => panic!("Unhandled guillotine message: {:#?}", msg), + } + } + } + } + + // TODO: Tell all nodes to quit their processes first + + logger::info(&format!("Quitting process: {}", juno_process.get_name())); + juno_process.send_quit_signal(); + let quit_time = get_current_millis(); + loop { + // Give the process some time to die. + task::sleep(Duration::from_millis(100)).await; + + // If the process is not running, then break + if !juno_process.is_process_running() { + break; + } + // If the processes is running, check if it's been given enough time. + if get_current_millis() > quit_time + 1000 { + // It's been trying to quit for more than 1 second. Kill it and quit + logger::info(&format!("Killing process: {}", juno_process.get_name())); + juno_process.kill(); + break; + } + } +} + +async fn try_connecting_to_juno(host: &HostConfig) -> bool { + if host.port.is_some() { + let port = host.port.unwrap(); + // Attempt to connect to the port until you can connect + let port = format!("127.0.0.1:{}", port); + let mut connection = TcpStream::connect(&port).await; + if connection.is_err() { + // If connection failed, wait and try again + Delay::new(Duration::from_millis(1000)).await; + connection = TcpStream::connect(&port).await; + return connection.is_ok(); + } + return true; + } else { + let unix_socket = host.socket_path.unwrap(); + let mut connection = connect_to_unix_socket(&unix_socket).await; + if connection.is_err() { + // If connection failed, wait and try again + Delay::new(Duration::from_millis(1000)).await; + connection = connect_to_unix_socket(&unix_socket).await; + return connection.is_ok(); + } + return true; + } +} + +#[cfg(target_family = "unix")] +async fn connect_to_unix_socket(socket_path: &str) -> Result<(), Error> { + async_std::os::unix::net::UnixStream::connect(socket_path).await?; + Ok(()) +} +#[cfg(target_family = "windows")] +async fn connect_to_unix_socket(_: &str) -> Result<(), Error> { + panic!("Unix sockets are not supported on Windows"); +} + +fn get_current_millis() -> u128 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards. Wtf?") + .as_millis() +} diff --git a/src/main.rs b/src/main.rs index 00feae4..12027fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,9 @@ extern crate winapi; mod cli; mod exec; +mod host; mod models; +mod node; mod utils; use exec::runner; diff --git a/src/models/cli_messages.rs b/src/models/cli_messages.rs index ecc6c65..1a03f80 100644 --- a/src/models/cli_messages.rs +++ b/src/models/cli_messages.rs @@ -1,12 +1,54 @@ -use crate::exec::process::ProcessRunner; +use crate::models::{GuillotineNode, ProcessData}; use futures::channel::oneshot::Sender; #[allow(dead_code)] #[derive(Debug)] pub enum GuillotineMessage { - ListModules(Sender>), - ListProcesses(Sender>), - RestartProcess(u64, Sender), + // Node-host communication stuff + RegisterNode { + node_name: String, + response: Sender>, + }, + RegisterProcess { + node_name: String, + process_data: ProcessData, + response: Sender>, + }, + ProcessExited { + node_name: String, + module_id: u64, + crash: bool, + last_spawned_at: u64, + response: Sender<(bool, u64)>, // (should_restart, wait_duration_millis) + }, + ProcessRunning { + node_name: String, + module_id: u64, + last_spawned_at: u64, + }, + NodeDisconnected { + node_name: String, + }, + + // Cli stuff + ListModules { + response: Sender, String>>, + }, + ListNodes { + response: Sender>, + }, + ListAllProcesses { + response: Sender>, // (node_name, process_data) + }, + ListProcesses { + node_name: String, + response: Sender, String>>, + }, + RestartProcess { + module_id: u64, + response: Sender>, + }, + AddProcess, StopProcess, StartProcess, DeleteProcess, diff --git a/src/models/config_types.rs b/src/models/config_types.rs index 5111e0c..4eac3e1 100644 --- a/src/models/config_types.rs +++ b/src/models/config_types.rs @@ -49,12 +49,13 @@ pub struct NodeConfig { } pub struct ModuleConfig { - pub directory: String + pub directory: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum ModuleRunningStatus { Running, + Stopped, Offline, } diff --git a/src/models/host_models.rs b/src/models/host_models.rs new file mode 100644 index 0000000..9695c1e --- /dev/null +++ b/src/models/host_models.rs @@ -0,0 +1,92 @@ +use super::{ModuleRunnerConfig, ModuleRunningStatus}; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(Debug, Clone)] +pub struct GuillotineNode { + pub name: String, + pub processes: Vec, + pub connected: bool, +} + +impl GuillotineNode { + pub fn get_process_by_name(&self, name: &str) -> Option<&ProcessData> { + return self + .processes + .iter() + .find(|process| process.config.name == name); + } + + pub fn get_process_by_id(&self, id: u64) -> Option<&ProcessData> { + return self + .processes + .iter() + .find(|process| process.module_id == id); + } + + pub fn register_process(&mut self, process_data: ProcessData) { + let position = self + .processes + .iter() + .position(|process| process.config.name == process_data.config.name); + // If a process with the same name exists, replace the existing value + if let Some(position) = position { + // Only update the values that can change. Retain the remaining values + self.processes[position].log_dir = process_data.log_dir; + self.processes[position].working_dir = process_data.working_dir; + self.processes[position].config = process_data.config; + self.processes[position].status = process_data.status; + } else { + self.processes.push(process_data); + } + } +} + +// Represents a process running under a node +#[derive(Debug, Clone)] +pub struct ProcessData { + pub log_dir: Option, + pub working_dir: String, + pub module_id: u64, + pub config: ModuleRunnerConfig, + pub status: ModuleRunningStatus, + pub restarts: i64, + pub last_started_at: u64, + pub crashes: u64, + pub consequtive_crashes: u64, + pub created_at: u64, +} + +impl ProcessData { + pub fn new( + log_dir: Option, + working_dir: String, + config: ModuleRunnerConfig, + status: ModuleRunningStatus, + last_started_at: u64, + created_at: u64, + ) -> Self { + ProcessData { + log_dir, + working_dir, + module_id: 0, + config, + status, + restarts: 0, + last_started_at, + crashes: 0, + consequtive_crashes: 0, + created_at, + } + } + + pub fn get_uptime(&self) -> u64 { + get_current_time() - self.last_started_at + } +} + +fn get_current_time() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards. Wtf?") + .as_millis() as u64 +} diff --git a/src/models/mod.rs b/src/models/mod.rs index fdf37bc..695350f 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,10 +1,12 @@ mod cli_messages; mod config_types; +mod host_models; pub mod parser; pub use cli_messages::GuillotineMessage; pub use config_types::{ - ConfigTarget, GuillotineConfig, GuillotinePerEnvConfig, HostConfig, ModuleRunnerConfig, - ModuleRunningStatus, NodeConfig, RunnerConfig, ModuleConfig, + ConfigTarget, GuillotineConfig, GuillotinePerEnvConfig, HostConfig, ModuleConfig, + ModuleRunnerConfig, ModuleRunningStatus, NodeConfig, RunnerConfig, }; +pub use host_models::{GuillotineNode, ProcessData}; diff --git a/src/node/mod.rs b/src/node/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/node/mod.rs @@ -0,0 +1 @@ + From 3ea60d069a198ba79e85a05d495ace1327cf816f Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Wed, 20 May 2020 12:31:48 +0530 Subject: [PATCH 03/26] Added all host configurations. Added a basic node runner. WIP node needs to handle messages from host. TODO make node keep a check on the processes. TODO fix up the cli issues --- src/cli/get_module_info.rs | 13 +- src/cli/list_modules.rs | 13 +- src/cli/list_processes.rs | 13 +- src/cli/restart_process.rs | 17 +- src/exec/host_runner.rs | 246 ----------- src/exec/mod.rs | 1 - src/exec/runner.rs | 6 +- src/host/juno_module.rs | 398 +++++++++++++++--- src/host/mod.rs | 2 +- src/host/runner.rs | 16 +- src/main.rs | 2 +- .../{config_types.rs => config_data.rs} | 2 +- ...{cli_messages.rs => guillotine_message.rs} | 0 .../{host_models.rs => guillotine_node.rs} | 53 +-- src/models/mod.rs | 25 +- src/models/parser.rs | 40 +- src/models/process_data.rs | 52 +++ src/node/juno_module.rs | 256 +++++++++++ src/node/mod.rs | 6 + src/node/module.rs | 56 +++ src/node/process.rs | 57 +++ src/node/runner.rs | 128 ++++++ src/runner.rs | 41 ++ 23 files changed, 1046 insertions(+), 397 deletions(-) delete mode 100644 src/exec/host_runner.rs rename src/models/{config_types.rs => config_data.rs} (98%) rename src/models/{cli_messages.rs => guillotine_message.rs} (100%) rename src/models/{host_models.rs => guillotine_node.rs} (51%) create mode 100644 src/models/process_data.rs create mode 100644 src/node/juno_module.rs create mode 100644 src/node/module.rs create mode 100644 src/node/process.rs create mode 100644 src/node/runner.rs create mode 100644 src/runner.rs diff --git a/src/cli/get_module_info.rs b/src/cli/get_module_info.rs index 5c386ab..1d00ee8 100644 --- a/src/cli/get_module_info.rs +++ b/src/cli/get_module_info.rs @@ -3,9 +3,18 @@ use crate::{logger, models::RunnerConfig, utils::constants}; use clap::ArgMatches; use cli_table::{ format::{ - Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + Align, + Border, + CellFormat, + Color, + HorizontalLine, + Separator, + TableFormat, + VerticalLine, }, - Cell, Row, Table, + Cell, + Row, + Table, }; use juno::{models::Value, JunoModule}; use std::collections::HashMap; diff --git a/src/cli/list_modules.rs b/src/cli/list_modules.rs index 76e0444..bd6d838 100644 --- a/src/cli/list_modules.rs +++ b/src/cli/list_modules.rs @@ -2,9 +2,18 @@ use crate::{logger, models::RunnerConfig, utils::constants}; use cli_table::{ format::{ - Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + Align, + Border, + CellFormat, + Color, + HorizontalLine, + Separator, + TableFormat, + VerticalLine, }, - Cell, Row, Table, + Cell, + Row, + Table, }; use juno::JunoModule; use std::collections::HashMap; diff --git a/src/cli/list_processes.rs b/src/cli/list_processes.rs index 4ae7342..57f3d41 100644 --- a/src/cli/list_processes.rs +++ b/src/cli/list_processes.rs @@ -2,9 +2,18 @@ use crate::{logger, models::RunnerConfig, utils::constants}; use cli_table::{ format::{ - Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + Align, + Border, + CellFormat, + Color, + HorizontalLine, + Separator, + TableFormat, + VerticalLine, }, - Cell, Row, Table, + Cell, + Row, + Table, }; use juno::JunoModule; use std::collections::HashMap; diff --git a/src/cli/restart_process.rs b/src/cli/restart_process.rs index c5599db..db20a94 100644 --- a/src/cli/restart_process.rs +++ b/src/cli/restart_process.rs @@ -3,9 +3,18 @@ use crate::{logger, models::RunnerConfig, utils::constants}; use clap::ArgMatches; use cli_table::{ format::{ - Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + Align, + Border, + CellFormat, + Color, + HorizontalLine, + Separator, + TableFormat, + VerticalLine, }, - Cell, Row, Table, + Cell, + Row, + Table, }; use juno::{ models::{Number, Value}, @@ -155,8 +164,8 @@ pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { Cell::new( &format!( "{}", - if pid - == process + if pid == + process .get("id") .unwrap() .as_number() diff --git a/src/exec/host_runner.rs b/src/exec/host_runner.rs deleted file mode 100644 index dc18d3f..0000000 --- a/src/exec/host_runner.rs +++ /dev/null @@ -1,246 +0,0 @@ -use crate::{ - exec::{juno_module, process::ProcessRunner}, - models::{GuillotineMessage, HostConfig}, - utils::logger, -}; -use std::{ - collections::HashMap, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; - -use async_std::{io::Error, net::TcpStream, sync::Mutex, task}; -use futures::{ - channel::mpsc::unbounded, - future::{self, Either}, - StreamExt, -}; -use futures_timer::Delay; -use juno::models::{Number, Value}; - -lazy_static! { - static ref CLOSE_FLAG: Mutex = Mutex::new(false); -} - -pub async fn run(mut juno_process: ProcessRunner, juno_config: HostConfig) { - // Spawn juno before spawing any modules - while !juno_process.is_process_running() { - juno_process.respawn().await; - ensure_juno_initialized(&juno_config).await; - } - - // Initialize the guillotine juno module - let (mut sender, mut command_receiver) = unbounded::(); - let mut juno_module = juno_module::setup_host_module(&juno_config, sender).await; - - let mut timer_future = Delay::new(Duration::from_millis(100)); - let mut command_future = command_receiver.next(); - loop { - let selection = future::select(timer_future, command_future).await; - match selection { - Either::Left((_, next_command_future)) => { - if *CLOSE_FLAG.lock().await { - break; - } - - // Timer expired - command_future = next_command_future; - timer_future = Delay::new(Duration::from_millis(100)); - - // Make sure juno is running before checking any other modules - if !juno_process.is_process_running() { - juno_module.close().await; - drop(juno_module); - juno_process.respawn().await; - ensure_juno_initialized(&juno_config).await; - - let channel = unbounded::(); - sender = channel.0; - command_receiver = channel.1; - - command_future = command_receiver.next(); - juno_module = juno_module::setup_host_module(&juno_config, sender).await; - } - - for module in processes.iter_mut() { - // If a module isn't running, respawn it. Simple. - if module.get_runner() == "host" { - if !module.is_process_running() { - module.respawn().await; - } - } else { - // The module belongs to another node. - let result = juno_module - .call_function( - &format!( - "guillotine-node-{}.isProcessRunning", - module.get_runner() - ), - HashMap::new(), - ) - .await; - if result.is_err() { - continue; - } - if result.unwrap() == Value::Bool(false) { - let _ = juno_module - .call_function( - &format!( - "guillotine-node-{}.respawnProcess", - module.get_runner() - ), - vec![( - String::from("pid"), - Value::Number(Number::PosInt(module.get_module_id())), - )] - .into_iter() - .collect(), - ) - .await; - } - } - } - } - Either::Right((command_value, next_timer_future)) => { - // Got a command from juno - timer_future = next_timer_future; - command_future = command_receiver.next(); - - match command_value { - Some(cmd) => match cmd { - GuillotineMessage::ListProcesses(sender) => { - let mut runners = vec![juno_process.copy()]; - processes - .iter() - .for_each(|process| runners.push(process.copy())); - sender.send(runners).unwrap(); - } - GuillotineMessage::RestartProcess(pid, response_sender) => { - if pid == 0 { - response_sender.send(true).unwrap(); - juno_module.close().await; - drop(juno_module); - juno_process.respawn().await; - ensure_juno_initialized(&juno_config).await; - - let channel = unbounded::(); - sender = channel.0; - command_receiver = channel.1; - - command_future = command_receiver.next(); - juno_module = - juno_module::setup_host_module(&juno_config, sender).await; - continue; - } - - let module = processes - .iter_mut() - .find(|process| process.get_module_id() == pid); - if module.is_none() { - response_sender.send(false).unwrap(); - continue; - } - module.unwrap().respawn().await; - response_sender.send(true).unwrap(); - } - _ => {} - }, - None => { - println!("Got None as a command. Is the sender closed?"); - } - } - } - } - } - - // Execute exit actions - // Kill all modules first - processes.iter_mut().for_each(|module| { - logger::info(&format!("Quitting process: {}", module.get_name())); - module.send_quit_signal(); - }); - let quit_time = get_current_millis(); - loop { - // Give the processes some time to die. - task::sleep(Duration::from_millis(100)).await; - // If all of the processes are not running, then break - if processes - .iter_mut() - .all(|module| !module.is_process_running()) - { - break; - } - // If some of the processes are running, check if they've been given enough time. - if get_current_millis() > quit_time + 1000 { - // They've been trying to quit for more than 1 second. Kill them all and quit - processes.iter_mut().for_each(|module| { - logger::info(&format!("Killing process: {}", module.get_name())); - module.kill(); - }); - break; - } - } - - // Now quit juno similarly - logger::info(&format!("Quitting process: {}", juno_process.get_name())); - juno_process.send_quit_signal(); - let quit_time = get_current_millis(); - loop { - // Give the process some time to die. - task::sleep(Duration::from_millis(100)).await; - - // If the process is not running, then break - if !juno_process.is_process_running() { - break; - } - // If the processes is running, check if it's been given enough time. - if get_current_millis() > quit_time + 1000 { - // It's been trying to quit for more than 1 second. Kill it and quit - logger::info(&format!("Killing process: {}", juno_process.get_name())); - juno_process.kill(); - break; - } - } -} - -pub async fn on_exit() { - *CLOSE_FLAG.lock().await = true; -} - -async fn ensure_juno_initialized(host: &HostConfig) { - if host.port.is_some() { - let port = host.port.unwrap(); - // Attempt to connect to the port until you can connect - let port = format!("127.0.0.1:{}", port); - let mut connection = TcpStream::connect(&port).await; - if connection.is_err() { - // If connection failed, wait and try again - Delay::new(Duration::from_millis(1000)).await; - connection = TcpStream::connect(&port).await; - } - } else { - let unix_socket = host.socket_path.unwrap(); - let mut connection = connect_to_unix_socket(&unix_socket).await; - if connection.is_err() { - // If connection failed, wait and try again - Delay::new(Duration::from_millis(1000)).await; - connection = connect_to_unix_socket(&unix_socket).await; - } - } -} - -#[cfg(target_family = "unix")] -async fn connect_to_unix_socket(socket_path: &str) -> Result<(), Error> { - async_std::os::unix::net::UnixStream::connect(socket_path).await?; - Ok(()) -} -#[cfg(target_family = "windows")] -async fn connect_to_unix_socket(_: &str) -> Result<(), Error> { - panic!("Unix sockets are not supported on Windows"); -} - -fn get_current_millis() -> u128 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards. Wtf?") - .as_millis() -} diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 35f4b5c..70b49cd 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -1,3 +1,2 @@ -pub mod host_runner; pub mod process; pub mod runner; diff --git a/src/exec/runner.rs b/src/exec/runner.rs index 06e97c7..48263b6 100644 --- a/src/exec/runner.rs +++ b/src/exec/runner.rs @@ -1,5 +1,5 @@ use crate::{ - exec::{host_runner, process::ProcessRunner}, + exec::process::ProcessRunner, models::{ModuleRunnerConfig, RunnerConfig}, utils::constants, }; @@ -86,7 +86,7 @@ pub async fn run(config: RunnerConfig) { }; pid += 1; - host_runner::run(juno_process, config.host.clone().unwrap()).await; + host::run(juno_process, config.host.clone().unwrap()).await; } else if config.node.is_some() { } else { return; @@ -94,7 +94,7 @@ pub async fn run(config: RunnerConfig) { } pub async fn on_exit() { - host_runner::on_exit().await; + host::on_exit().await; } async fn get_all_available_modules(starting_pid: u64, config: &RunnerConfig) -> Vec { diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index a73d22e..27999c8 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -1,5 +1,12 @@ use crate::{ - models::{GuillotineMessage, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, ProcessData}, + models::{ + GuillotineMessage, + GuillotineNode, + HostConfig, + ModuleRunnerConfig, + ModuleRunningStatus, + ProcessData, + }, utils::constants, }; use std::collections::HashMap; @@ -63,6 +70,31 @@ pub async fn setup_host_module( .await .unwrap(); + module + .declare_function("listModules", list_modules) + .await + .unwrap(); + + module + .declare_function("listNodes", list_nodes) + .await + .unwrap(); + + module + .declare_function("listAllProcesses", list_all_processes) + .await + .unwrap(); + + module + .declare_function("listProcesses", list_processes) + .await + .unwrap(); + + module + .declare_function("restartProcess", restart_process) + .await + .unwrap(); + module .register_hook("juno.moduleDeactivated", module_deactivated) .await @@ -76,7 +108,7 @@ fn register_node(mut args: HashMap) -> Value { let name = if let Some(Value::String(value)) = args.remove("name") { value } else { - return generate_error_response(Some("Name parameter is not a string")); + return generate_error_response("Name parameter is not a string"); }; let (sender, receiver) = channel::>(); @@ -100,7 +132,7 @@ fn register_node(mut args: HashMap) -> Value { map }) } else { - generate_error_response(Some(&response.unwrap_err())) + generate_error_response(&response.unwrap_err()) } }) } @@ -110,19 +142,19 @@ fn register_process(mut args: HashMap) -> Value { let node_name = if let Some(Value::String(value)) = args.remove("node") { value } else { - return generate_error_response(Some("Name parameter is not a string")); + return generate_error_response("Name parameter is not a string"); }; - let log_dir = if let Some(Value::String(dir)) = args.remove("log_dir") { + let log_dir = if let Some(Value::String(dir)) = args.remove("logDir") { Some(dir) } else { None }; - let working_dir = if let Some(Value::String(dir)) = args.remove("working_dir") { + let working_dir = if let Some(Value::String(dir)) = args.remove("workingDir") { dir } else { - return generate_error_response(Some("Working dir is not a string")); + return generate_error_response("Working dir is not a string"); }; let mut config = if let Some(Value::Object(config)) = args.remove("config") { @@ -130,12 +162,12 @@ fn register_process(mut args: HashMap) -> Value { name: if let Some(Value::String(value)) = config.remove("name") { value } else { - return generate_error_response(Some("Name is not present in config")); + return generate_error_response("Name is not present in config"); }, command: if let Some(Value::String(value)) = config.remove("command") { value } else { - return generate_error_response(Some("Command is not present in config")); + return generate_error_response("Command is not present in config"); }, interpreter: if let Some(Value::String(value)) = config.remove("interpreter") { Some(value) @@ -176,7 +208,7 @@ fn register_process(mut args: HashMap) -> Value { }, } } else { - return generate_error_response(Some("Config is not an object")); + return generate_error_response("Config is not an object"); }; let status = if let Some(Value::String(status)) = args.remove("status") { @@ -184,12 +216,12 @@ fn register_process(mut args: HashMap) -> Value { "running" => ModuleRunningStatus::Running, "stopped" => ModuleRunningStatus::Stopped, "offline" => { - return generate_error_response(Some("Nodes can't declare a module as offline")) + return generate_error_response("Nodes can't declare a module as offline") } - _ => return generate_error_response(Some("Status is not a known value")), + _ => return generate_error_response("Status is not a known value"), } } else { - return generate_error_response(Some("Status is not a known value")); + return generate_error_response("Status is not a known value"); }; let last_started_at = if let Some(Value::Number(started_at)) = args.remove("lastStartedAt") @@ -210,7 +242,7 @@ fn register_process(mut args: HashMap) -> Value { Number::Float(created_at) => created_at as u64, } } else { - return generate_error_response(Some("Created at is not a number")); + return generate_error_response("Created at is not a number"); }; let (sender, receiver) = channel::>(); @@ -246,7 +278,7 @@ fn register_process(mut args: HashMap) -> Value { map }) } else { - generate_error_response(Some(&response.unwrap_err())) + generate_error_response(&response.unwrap_err()) } }) } @@ -256,7 +288,7 @@ fn process_exited(mut args: HashMap) -> Value { let node_name = if let Some(Value::String(value)) = args.remove("node") { value } else { - return generate_error_response(Some("Node parameter is not a string")); + return generate_error_response("Node parameter is not a string"); }; let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { @@ -266,13 +298,13 @@ fn process_exited(mut args: HashMap) -> Value { Number::Float(module_id) => module_id as u64, } } else { - return generate_error_response(Some("Module ID is not a number")); + return generate_error_response("Module ID is not a number"); }; let crash = if let Some(Value::Bool(crash)) = args.remove("crash") { crash } else { - return generate_error_response(Some("Module ID is not a number")); + return generate_error_response("Module ID is not a number"); }; let last_spawned_at = @@ -283,7 +315,7 @@ fn process_exited(mut args: HashMap) -> Value { Number::Float(last_spawned_at) => last_spawned_at as u64, } } else { - return generate_error_response(Some("Module ID is not a number")); + return generate_error_response("Module ID is not a number"); }; let (sender, receiver) = channel::<(bool, u64)>(); @@ -321,7 +353,7 @@ fn process_running(mut args: HashMap) -> Value { let node_name = if let Some(Value::String(value)) = args.remove("node") { value } else { - return generate_error_response(Some("Node parameter is not a string")); + return generate_error_response("Node parameter is not a string"); }; let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { @@ -331,7 +363,7 @@ fn process_running(mut args: HashMap) -> Value { Number::Float(module_id) => module_id as u64, } } else { - return generate_error_response(Some("Module ID is not a number")); + return generate_error_response("Module ID is not a number"); }; let last_spawned_at = @@ -342,7 +374,7 @@ fn process_running(mut args: HashMap) -> Value { Number::Float(last_spawned_at) => last_spawned_at as u64, } } else { - return generate_error_response(Some("Module ID is not a number")); + return generate_error_response("Module ID is not a number"); }; MESSAGE_SENDER @@ -399,40 +431,304 @@ fn module_deactivated(mut data: Value) { }); } -fn get_node_module_from_object(mut object: HashMap) -> Option { - let mut module = ProcessData { - log_dir: None, - working_dir: String::default(), - module_id: -1, - config: ModuleRunnerConfig { - name: String::default(), - command: String::default(), - interpreter: None, - args: None, - envs: None, - }, - status: ModuleRunningStatus::Offline, - restarts: -1, - last_started_at: 0, - crashes: 0, - consequtive_crashes: 0, - created_at: 0, - }; +fn list_modules(mut args: HashMap) -> Value { + task::block_on(async { + let (sender, receiver) = channel::, String>>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::ListModules { response: sender }) + .await; - if let Some(Value::String(log_dir)) = object.remove("log_dir") { - module.log_dir = log_dir; - } + let result = receiver.await.unwrap(); + if let Ok(modules) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert( + String::from("modules"), + Value::Array( + modules + .into_iter() + .map(|module| Value::String(module)) + .collect(), + ), + ); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} - if let Some(Value::String(working_dir)) = object.remove("working_dir") { - module.working_dir = working_dir; - } else { - return None; - } +fn list_nodes(mut args: HashMap) -> Value { + task::block_on(async { + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::ListNodes { response: sender }) + .await; + + let nodes = receiver.await.unwrap(); + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert( + String::from("nodes"), + Value::Array( + nodes + .into_iter() + .map(|node| { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("name"), Value::String(node.name)); + map.insert(String::from("connected"), Value::Bool(node.connected)); + map.insert( + String::from("modules"), + Value::Number(Number::PosInt(node.processes.len() as u64)), + ); + map + }) + }) + .collect(), + ), + ); + map + }) + }) +} + +fn list_all_processes(mut args: HashMap) -> Value { + task::block_on(async { + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::ListAllProcesses { response: sender }) + .await; - if let Some(Value::Object(map)) = object.remove("config") {} + let processes = receiver.await.unwrap(); + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert( + String::from("processes"), + Value::Array( + processes + .into_iter() + .map(|(node, process)| { + Value::Object({ + let mut map = HashMap::new(); + + map.insert( + String::from("id"), + Value::Number(Number::PosInt(process.module_id)), + ); + map.insert( + String::from("name"), + Value::String(process.config.name), + ); + + map.insert( + String::from("logDir"), + if let Some(log_dir) = process.log_dir { + Value::String(log_dir) + } else { + Value::Null + }, + ); + map.insert( + String::from("workingDir"), + Value::String(process.working_dir), + ); + + map.insert( + String::from("status"), + Value::String(String::from(match process.status { + ModuleRunningStatus::Running => "running", + ModuleRunningStatus::Stopped => "stopped", + ModuleRunningStatus::Offline => "offline", + })), + ); + map.insert(String::from("node"), Value::String(node)); + map.insert( + String::from("restarts"), + Value::Number(Number::NegInt(process.restarts)), + ); + map.insert( + String::from("uptime"), + Value::Number(Number::PosInt(process.get_uptime())), + ); + map.insert( + String::from("crashes"), + Value::Number(Number::PosInt(process.crashes)), + ); + map.insert( + String::from("createdAt"), + Value::Number(Number::PosInt(process.created_at)), + ); + + map + }) + }) + .collect(), + ), + ); + map + }) + }) +} + +fn list_processes(mut args: HashMap) -> Value { + task::block_on(async { + let node_name = if let Some(Value::String(value)) = args.remove("node") { + value + } else { + return generate_error_response("Node parameter is not a string"); + }; + + let (sender, receiver) = channel::, String>>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::ListProcesses { + node_name: node_name.clone(), + response: sender, + }) + .await; + + let result = receiver.await.unwrap(); + if let Ok(processes) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert( + String::from("processes"), + Value::Array( + processes + .into_iter() + .map(|process| { + Value::Object({ + let mut map = HashMap::new(); + + map.insert( + String::from("id"), + Value::Number(Number::PosInt(process.module_id)), + ); + map.insert( + String::from("name"), + Value::String(process.config.name), + ); + + map.insert( + String::from("logDir"), + if let Some(log_dir) = process.log_dir { + Value::String(log_dir) + } else { + Value::Null + }, + ); + map.insert( + String::from("workingDir"), + Value::String(process.working_dir), + ); + + map.insert( + String::from("status"), + Value::String(String::from(match process.status { + ModuleRunningStatus::Running => "running", + ModuleRunningStatus::Stopped => "stopped", + ModuleRunningStatus::Offline => "offline", + })), + ); + map.insert( + String::from("node"), + Value::String(node_name.clone()), + ); + map.insert( + String::from("restarts"), + Value::Number(Number::NegInt(process.restarts)), + ); + map.insert( + String::from("uptime"), + Value::Number(Number::PosInt(process.get_uptime())), + ); + map.insert( + String::from("crashes"), + Value::Number(Number::PosInt(process.crashes)), + ); + map.insert( + String::from("createdAt"), + Value::Number(Number::PosInt(process.created_at)), + ); + + map + }) + }) + .collect(), + ), + ); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + +fn restart_process(mut args: HashMap) -> Value { + task::block_on(async { + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::RestartProcess { + module_id, + response: sender, + }) + .await; + + let result = receiver.await.unwrap(); + if result.is_ok() { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) } -fn generate_error_response(error_message: Option<&str>) -> Value { +fn generate_error_response(error_message: &str) -> Value { Value::Object({ let mut map = HashMap::new(); diff --git a/src/host/mod.rs b/src/host/mod.rs index 9ad4555..c858555 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -2,4 +2,4 @@ mod juno_module; mod runner; pub use juno_module::setup_host_module; -pub use runner::run; +pub use runner::{on_exit, run}; diff --git a/src/host/runner.rs b/src/host/runner.rs index 403bfe5..348a0a4 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -2,7 +2,11 @@ use crate::{ exec::process::ProcessRunner, host::juno_module, models::{ - GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, + GuillotineMessage, + GuillotineNode, + HostConfig, + ModuleRunnerConfig, + ModuleRunningStatus, RunnerConfig, }, utils::{constants, logger}, @@ -92,6 +96,10 @@ pub async fn run(mut config: RunnerConfig) { keep_host_alive(juno_process, host).await; } +pub async fn on_exit() { + *CLOSE_FLAG.lock().await = true; +} + async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfig) { // Spawn juno before spawing any modules while !juno_process.is_process_running() { @@ -450,7 +458,7 @@ async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfi } async fn try_connecting_to_juno(host: &HostConfig) -> bool { - if host.port.is_some() { + if host.connection_type == constants::connection_type::INET_SOCKET { let port = host.port.unwrap(); // Attempt to connect to the port until you can connect let port = format!("127.0.0.1:{}", port); @@ -462,7 +470,7 @@ async fn try_connecting_to_juno(host: &HostConfig) -> bool { return connection.is_ok(); } return true; - } else { + } else if host.connection_type == constants::connection_type::UNIX_SOCKET { let unix_socket = host.socket_path.unwrap(); let mut connection = connect_to_unix_socket(&unix_socket).await; if connection.is_err() { @@ -472,6 +480,8 @@ async fn try_connecting_to_juno(host: &HostConfig) -> bool { return connection.is_ok(); } return true; + } else { + panic!("Connection type is neither unix socket not inet socket. How did you get here?"); } } diff --git a/src/main.rs b/src/main.rs index 12027fe..b2a69ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,9 +26,9 @@ mod exec; mod host; mod models; mod node; +mod runner; mod utils; -use exec::runner; use models::parser; use utils::{constants, logger}; diff --git a/src/models/config_types.rs b/src/models/config_data.rs similarity index 98% rename from src/models/config_types.rs rename to src/models/config_data.rs index 4eac3e1..ea6bdc1 100644 --- a/src/models/config_types.rs +++ b/src/models/config_data.rs @@ -24,7 +24,7 @@ pub struct ConfigTarget { // Config specific to this environment #[derive(Deserialize, Clone)] pub struct RunnerConfig { - pub name: String, + pub name: Option, pub logs: Option, pub host: Option, pub node: Option, diff --git a/src/models/cli_messages.rs b/src/models/guillotine_message.rs similarity index 100% rename from src/models/cli_messages.rs rename to src/models/guillotine_message.rs diff --git a/src/models/host_models.rs b/src/models/guillotine_node.rs similarity index 51% rename from src/models/host_models.rs rename to src/models/guillotine_node.rs index 9695c1e..82a17db 100644 --- a/src/models/host_models.rs +++ b/src/models/guillotine_node.rs @@ -1,5 +1,4 @@ -use super::{ModuleRunnerConfig, ModuleRunningStatus}; -use std::time::{SystemTime, UNIX_EPOCH}; +use crate::models::ProcessData; #[derive(Debug, Clone)] pub struct GuillotineNode { @@ -40,53 +39,3 @@ impl GuillotineNode { } } } - -// Represents a process running under a node -#[derive(Debug, Clone)] -pub struct ProcessData { - pub log_dir: Option, - pub working_dir: String, - pub module_id: u64, - pub config: ModuleRunnerConfig, - pub status: ModuleRunningStatus, - pub restarts: i64, - pub last_started_at: u64, - pub crashes: u64, - pub consequtive_crashes: u64, - pub created_at: u64, -} - -impl ProcessData { - pub fn new( - log_dir: Option, - working_dir: String, - config: ModuleRunnerConfig, - status: ModuleRunningStatus, - last_started_at: u64, - created_at: u64, - ) -> Self { - ProcessData { - log_dir, - working_dir, - module_id: 0, - config, - status, - restarts: 0, - last_started_at, - crashes: 0, - consequtive_crashes: 0, - created_at, - } - } - - pub fn get_uptime(&self) -> u64 { - get_current_time() - self.last_started_at - } -} - -fn get_current_time() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards. Wtf?") - .as_millis() as u64 -} diff --git a/src/models/mod.rs b/src/models/mod.rs index 695350f..6636761 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,12 +1,21 @@ -mod cli_messages; -mod config_types; -mod host_models; +mod config_data; +mod guillotine_message; +mod guillotine_node; +mod process_data; pub mod parser; -pub use cli_messages::GuillotineMessage; -pub use config_types::{ - ConfigTarget, GuillotineConfig, GuillotinePerEnvConfig, HostConfig, ModuleConfig, - ModuleRunnerConfig, ModuleRunningStatus, NodeConfig, RunnerConfig, +pub use config_data::{ + ConfigTarget, + GuillotineConfig, + GuillotinePerEnvConfig, + HostConfig, + ModuleConfig, + ModuleRunnerConfig, + ModuleRunningStatus, + NodeConfig, + RunnerConfig, }; -pub use host_models::{GuillotineNode, ProcessData}; +pub use guillotine_message::GuillotineMessage; +pub use guillotine_node::GuillotineNode; +pub use process_data::ProcessData; diff --git a/src/models/parser.rs b/src/models/parser.rs index d9277d2..26ed3ba 100644 --- a/src/models/parser.rs +++ b/src/models/parser.rs @@ -35,8 +35,8 @@ async fn parse_if_config(input: &ConfigTarget) -> Result { let mut satisfied = true; if let Some(required_cfg) = &input.family { - if (required_cfg == "unix" && cfg!(target_family = "unix")) - || (required_cfg == "windows" && cfg!(target_family = "windows")) + if (required_cfg == "unix" && cfg!(target_family = "unix")) || + (required_cfg == "windows" && cfg!(target_family = "windows")) { satisfied &= true; } else { @@ -45,15 +45,15 @@ async fn parse_if_config(input: &ConfigTarget) -> Result { } if let Some(required_cfg) = &input.os { - if (required_cfg == "windows" && cfg!(target_os = "windows")) - || (required_cfg == "macos" && cfg!(target_os = "macos")) - || (required_cfg == "ios" && cfg!(target_os = "ios")) - || (required_cfg == "linux" && cfg!(target_os = "linux")) - || (required_cfg == "android" && cfg!(target_os = "android")) - || (required_cfg == "freebsd" && cfg!(target_os = "freebsd")) - || (required_cfg == "dragonfly" && cfg!(target_os = "dragonfly")) - || (required_cfg == "openbsd" && cfg!(target_os = "openbsd")) - || (required_cfg == "netbsd" && cfg!(target_os = "netbsd")) + if (required_cfg == "windows" && cfg!(target_os = "windows")) || + (required_cfg == "macos" && cfg!(target_os = "macos")) || + (required_cfg == "ios" && cfg!(target_os = "ios")) || + (required_cfg == "linux" && cfg!(target_os = "linux")) || + (required_cfg == "android" && cfg!(target_os = "android")) || + (required_cfg == "freebsd" && cfg!(target_os = "freebsd")) || + (required_cfg == "dragonfly" && cfg!(target_os = "dragonfly")) || + (required_cfg == "openbsd" && cfg!(target_os = "openbsd")) || + (required_cfg == "netbsd" && cfg!(target_os = "netbsd")) { satisfied &= true; } else { @@ -62,13 +62,13 @@ async fn parse_if_config(input: &ConfigTarget) -> Result { } if let Some(required_cfg) = &input.arch { - if (required_cfg == "x86" && cfg!(target_arch = "x86")) - || (required_cfg == "x86_64" && cfg!(target_arch = "x86_64")) - || (required_cfg == "mips" && cfg!(target_arch = "mips")) - || (required_cfg == "powerpc" && cfg!(target_arch = "powerpc")) - || (required_cfg == "powerpc64" && cfg!(target_arch = "powerpc64")) - || (required_cfg == "arm" && cfg!(target_arch = "arm")) - || (required_cfg == "aarch64" && cfg!(target_arch = "aarch64")) + if (required_cfg == "x86" && cfg!(target_arch = "x86")) || + (required_cfg == "x86_64" && cfg!(target_arch = "x86_64")) || + (required_cfg == "mips" && cfg!(target_arch = "mips")) || + (required_cfg == "powerpc" && cfg!(target_arch = "powerpc")) || + (required_cfg == "powerpc64" && cfg!(target_arch = "powerpc64")) || + (required_cfg == "arm" && cfg!(target_arch = "arm")) || + (required_cfg == "aarch64" && cfg!(target_arch = "aarch64")) { satisfied &= true; } else { @@ -77,8 +77,8 @@ async fn parse_if_config(input: &ConfigTarget) -> Result { } if let Some(required_cfg) = &input.endian { - if (required_cfg == "little" && cfg!(target_endian = "little")) - || (required_cfg == "big" && cfg!(target_endian = "big")) + if (required_cfg == "little" && cfg!(target_endian = "little")) || + (required_cfg == "big" && cfg!(target_endian = "big")) { satisfied &= true; } else { diff --git a/src/models/process_data.rs b/src/models/process_data.rs new file mode 100644 index 0000000..6f9b5e6 --- /dev/null +++ b/src/models/process_data.rs @@ -0,0 +1,52 @@ +use crate::models::{ModuleRunnerConfig, ModuleRunningStatus}; +use std::time::{SystemTime, UNIX_EPOCH}; + +// Represents a process running under a node +#[derive(Debug, Clone)] +pub struct ProcessData { + pub log_dir: Option, + pub working_dir: String, + pub module_id: u64, + pub config: ModuleRunnerConfig, + pub status: ModuleRunningStatus, + pub restarts: i64, + pub last_started_at: u64, + pub crashes: u64, + pub consequtive_crashes: u64, + pub created_at: u64, +} + +impl ProcessData { + pub fn new( + log_dir: Option, + working_dir: String, + config: ModuleRunnerConfig, + status: ModuleRunningStatus, + last_started_at: u64, + created_at: u64, + ) -> Self { + ProcessData { + log_dir, + working_dir, + module_id: 0, + config, + status, + restarts: 0, + last_started_at, + crashes: 0, + consequtive_crashes: 0, + created_at, + } + } + + pub fn get_uptime(&self) -> u64 { + get_current_time() - self.last_started_at + } +} + +fn get_current_time() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards. Wtf?") + .as_millis() as u64 +} diff --git a/src/node/juno_module.rs b/src/node/juno_module.rs new file mode 100644 index 0000000..d02d134 --- /dev/null +++ b/src/node/juno_module.rs @@ -0,0 +1,256 @@ +use crate::{ + models::{GuillotineMessage, NodeConfig}, + node::process::Process, + utils::constants, +}; + +use async_std::{sync::RwLock, task}; +use futures::{ + channel::{mpsc::UnboundedSender, oneshot::channel}, + SinkExt, +}; +use juno::{ + models::{Number, Value}, + JunoModule, +}; +use std::collections::HashMap; + +lazy_static! { + static ref MESSAGE_SENDER: RwLock>> = + RwLock::new(None); +} + +pub async fn setup_module( + node_name: &String, + node: &NodeConfig, + sender: UnboundedSender, +) -> Result { + MESSAGE_SENDER.write().await.replace(sender); + + let juno_module = if node.connection_type == constants::connection_type::UNIX_SOCKET { + JunoModule::from_unix_socket(&node.socket_path.unwrap()) + } else { + JunoModule::from_inet_socket(&node.ip.unwrap(), node.port.unwrap()) + }; + + juno_module + .initialize( + &format!("{}-node-{}", constants::APP_NAME, node_name), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + + juno_module + .declare_function("respawnProcess", respawn_process) + .await + .unwrap(); + + // Register node here + let response = juno_module + .call_function(&format!("{}.registerNode", constants::APP_NAME), { + let map = HashMap::new(); + map.insert(String::from("name"), Value::String(node_name.clone())); + map + }) + .await + .unwrap(); + + if let Value::Object(response) = response { + if response.remove("success").unwrap() == Value::Bool(true) { + Ok(juno_module) + } else if let Some(error) = response.remove("error") { + return Err(if let Value::String(error) = error { + error + } else { + return Err(format!( + "Expected a string response for error. Got: {:#?}", + error + )); + }); + } else { + return Err(format!( + "Expected a boolean success key in the response. Malformed object: {:#?}", + response + )); + } + } else { + return Err(format!( + "Expected an object response while registering process. Got: {:#?}", + response + )); + } +} + +pub async fn register_module( + node_name: &String, + juno_module: &JunoModule, + process: &Process, +) -> Result { + let args = HashMap::new(); + + if let Some(log_dir) = process.log_dir { + args.insert(String::from("logDir"), Value::String(log_dir)); + } + args.insert( + String::from("workingDir"), + Value::String(process.working_dir), + ); + args.insert( + String::from("config"), + Value::Object({ + let map = HashMap::new(); + map.insert( + String::from("name"), + Value::String(process.runner_config.name.clone()), + ); + map.insert( + String::from("command"), + Value::String(process.runner_config.command.clone()), + ); + if let Some(interpreter) = process.runner_config.interpreter { + map.insert(String::from("intepreter"), Value::String(interpreter)); + } + if let Some(args) = process.runner_config.args { + map.insert( + String::from("args"), + Value::Array(args.into_iter().map(Value::String).collect()), + ); + } + if let Some(envs) = process.runner_config.envs { + map.insert( + String::from("args"), + Value::Object( + envs.into_iter() + .map(|(key, value)| (key, Value::String(value))) + .collect(), + ), + ); + } + map + }), + ); + + args.insert( + String::from("status"), + Value::String(String::from(match process.is_process_running() { + (true, _) => "running", + (false, _) => "stopped", + })), + ); + args.insert( + String::from("lastStartedAt"), + Value::Number(Number::PosInt(process.last_started_at)), + ); + args.insert( + String::from("createdAt"), + Value::Number(Number::PosInt(process.created_at)), + ); + + let response = juno_module + .call_function(&format!("{}.registerProcess", constants::APP_NAME), args) + .await + .unwrap(); + + if let Value::Object(response) = response { + if !response.contains_key("success") { + return Err(String::from( + "Could not find success key in the response. Malformed object", + )); + } + if response.remove("success").unwrap() == Value::Bool(true) { + let module_id = response.remove("moduleId"); + if module_id.is_none() { + return Err(String::from( + "Could not find moduleId key in the response. Malformed object", + )); + } + let module_id = module_id.unwrap(); + if let Value::Number(module_id) = module_id { + Ok(match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + }) + } else { + return Err(format!( + "Expected a string response for moduleId. Got: {:#?}", + module_id + )); + } + } else if let Some(error) = response.remove("error") { + return Err(if let Value::String(error) = error { + error + } else { + return Err(format!( + "Expected a string response for error. Got: {:#?}", + error + )); + }); + } else { + return Err(String::from( + "Expected a boolean success key in the response. Malformed object", + )); + } + } else { + return Err(format!( + "Expected an object response while registering process. Got: {:#?}", + response + )); + } +} + +fn respawn_process(args: HashMap) -> Value { + task::block_on(async { + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::RestartProcess { + module_id, + response: sender, + }) + .await; + + let result = receiver.await.unwrap(); + if result.is_ok() { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + +fn generate_error_response(error_message: &str) -> Value { + Value::Object({ + let mut map = HashMap::new(); + + map.insert(String::from("success"), Value::Bool(false)); + if error_message.is_some() { + map.insert( + String::from("error"), + Value::String(String::from(error_message.unwrap())), + ); + } + + map + }) +} diff --git a/src/node/mod.rs b/src/node/mod.rs index 8b13789..e187bec 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1 +1,7 @@ +mod module; +mod process; +mod runner; +pub mod juno_module; + +pub use runner::{on_exit, run}; diff --git a/src/node/module.rs b/src/node/module.rs new file mode 100644 index 0000000..fc6960f --- /dev/null +++ b/src/node/module.rs @@ -0,0 +1,56 @@ +use super::process::Process; +use crate::models::ModuleRunnerConfig; +use async_std::{fs, path::Path}; + +pub async fn get_module_from_path(path: &str, log_dir: Option) -> Option { + let mut path = Path::new(path); + if !path.exists().await { + return None; + } + + // If path is a folder, get the module.json file + if path.is_dir().await { + path = path.join("module.json").as_path(); + if !path.exists().await { + return None; + } + } + + // If a random file was given, say fuck off + if !path.ends_with("module.json") { + return None; + } + + let module_json_contents = fs::read_to_string(path).await; + if module_json_contents.is_err() { + return None; + } + let module_json_contents = module_json_contents.unwrap(); + + let config: Result = + serde_json::from_str(&module_json_contents); + + if config.is_err() { + return None; + } + let config = config.unwrap(); + + let working_dir = path.parent().unwrap().to_str().unwrap().to_owned(); + + let runner = if let Some(log_dir) = log_dir { + let main_dir = Path::new(&log_dir); + if !main_dir.exists().await { + fs::create_dir(&main_dir).await.unwrap(); + } + + let sub_dir = main_dir.join(&config.name); + if !sub_dir.exists().await { + fs::create_dir(&sub_dir).await.unwrap(); + } + Process::new(config, Some(log_dir), working_dir) + } else { + Process::new(config, None, working_dir) + }; + + None +} diff --git a/src/node/process.rs b/src/node/process.rs new file mode 100644 index 0000000..910f004 --- /dev/null +++ b/src/node/process.rs @@ -0,0 +1,57 @@ +use crate::models::ModuleRunnerConfig; +use std::{ + process::Child, + time::{SystemTime, UNIX_EPOCH}, +}; + +pub struct Process { + pub process: Option, + pub runner_config: ModuleRunnerConfig, + pub log_dir: Option, + pub working_dir: String, + pub last_started_at: u64, + pub created_at: u64, +} + +impl Process { + pub fn new( + runner_config: ModuleRunnerConfig, + log_dir: Option, + working_dir: String, + ) -> Self { + Process { + process: None, + runner_config, + log_dir, + working_dir, + last_started_at: 0, + created_at: get_current_time(), + } + } + + // returns (is_running, is_crashed) + pub fn is_process_running(&mut self) -> (bool, bool) { + if self.process.is_none() { + return false; + } + + let process = self.process.as_mut().unwrap(); + match process.try_wait() { + Ok(Some(status)) => { + if !status.success() { + (false, true) + } + (false, false) + } // Process has already exited + Ok(None) => (true, false), + Err(error) => (false, true), + } + } +} + +fn get_current_time() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards. Wtf?") + .as_millis() as u64 +} diff --git a/src/node/runner.rs b/src/node/runner.rs new file mode 100644 index 0000000..5b3eaf0 --- /dev/null +++ b/src/node/runner.rs @@ -0,0 +1,128 @@ +use crate::{ + models::{GuillotineMessage, NodeConfig, RunnerConfig}, + node::{juno_module, module::get_module_from_path, process::Process}, + utils::{constants, logger}, +}; + +use async_std::{fs, net::TcpStream, path::Path, sync::Mutex}; +use futures::{channel::mpsc::unbounded, StreamExt}; +use futures_timer::Delay; +use std::{collections::HashMap, io::Error, time::Duration}; + +lazy_static! { + static ref CLOSE_FLAG: Mutex = Mutex::new(false); +} + +pub async fn run(config: RunnerConfig) { + if config.name.is_none() { + logger::error("Node name cannot be null"); + return; + } + + let node_name = config.name.unwrap(); + let node = config.node.take().unwrap(); + let log_dir = config.logs; + + if !try_connecting_to_host(&node).await { + logger::error(&format!( + "Could not connect to the host instance of {}. Please check your settings", + constants::APP_NAME + )); + return; + } + + // Populate any auto-start modules here + let mut auto_start_processes = vec![]; + if config.modules.is_some() { + let modules_dir = config.modules.unwrap().directory; + let modules_path = Path::new(&modules_dir); + + if modules_path.exists().await && modules_path.is_dir().await { + let mut dir_iterator = fs::read_dir(modules_dir).await.unwrap(); + while let Some(Ok(dir)) = dir_iterator.next().await { + if let Some(process) = + get_module_from_path(dir.path().to_str().unwrap(), log_dir.clone()).await + { + auto_start_processes.push(process); + } + } + } + } + + keep_node_alive(node_name, node, auto_start_processes).await; +} + +pub async fn on_exit() { + *CLOSE_FLAG.lock().await = true; +} + +async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_processes: Vec) { + // Initialize the guillotine juno module + let (sender, mut command_receiver) = unbounded::(); + let response = juno_module::setup_module(&node_name, &node, sender.clone()).await; + if let Err(error) = response { + logger::error(&format!("Error setting up Juno module: {}", error)); + return; + } + let juno_module = response.unwrap(); + + let ids_to_processes = HashMap::new(); + + // First, register all the auto_start_processes + for process in auto_start_processes { + let response = juno_module::register_module(&node_name, &juno_module, &process).await; + if let Ok(module_id) = response { + // Then, store their assigned moduleIds in a hashmap. + ids_to_processes.insert(module_id, process); + } else { + logger::error(&format!( + "Error while registering the module '{}': {}", + process.runner_config.name, + response.unwrap_err() + )); + } + } + + // Then iterate through the commands recieved by the juno module and do something about it + while let Some(command) = command_receiver.next().await { + + } +} + +async fn try_connecting_to_host(node: &NodeConfig) -> bool { + if node.connection_type == constants::connection_type::INET_SOCKET { + let port = node.port.unwrap(); + // Attempt to connect to the port until you can connect + let port = format!("127.0.0.1:{}", port); + let mut connection = TcpStream::connect(&port).await; + if connection.is_err() { + // If connection failed, wait and try again + Delay::new(Duration::from_millis(1000)).await; + connection = TcpStream::connect(&port).await; + return connection.is_ok(); + } + return true; + } else if node.connection_type == constants::connection_type::UNIX_SOCKET { + let unix_socket = node.socket_path.unwrap(); + let mut connection = connect_to_unix_socket(&unix_socket).await; + if connection.is_err() { + // If connection failed, wait and try again + Delay::new(Duration::from_millis(1000)).await; + connection = connect_to_unix_socket(&unix_socket).await; + return connection.is_ok(); + } + return true; + } else { + panic!("Connection type is neither unix socket not inet socket. How did you get here?"); + } +} + +#[cfg(target_family = "unix")] +async fn connect_to_unix_socket(socket_path: &str) -> Result<(), Error> { + async_std::os::unix::net::UnixStream::connect(socket_path).await?; + Ok(()) +} +#[cfg(target_family = "windows")] +async fn connect_to_unix_socket(_: &str) -> Result<(), Error> { + panic!("Unix sockets are not supported on Windows"); +} diff --git a/src/runner.rs b/src/runner.rs new file mode 100644 index 0000000..2779c5f --- /dev/null +++ b/src/runner.rs @@ -0,0 +1,41 @@ +use crate::{ + host, + models::{NodeConfig, RunnerConfig}, + node, +}; +use async_std::{fs, path::Path}; +use futures::future::join; + +pub async fn run(config: RunnerConfig) { + if config.logs.is_some() { + let log_dir = config.logs.as_ref().unwrap(); + let main_dir = Path::new(log_dir); + if !main_dir.exists().await { + fs::create_dir(&main_dir).await.unwrap(); + } + } + + if config.host.is_some() { + let host = config.host.as_ref().unwrap(); + if config.name.is_none() { + config.name = Some(String::from("host")); + } + // If the host doesn't have a node, create one + config.node = Some(NodeConfig { + connection_type: host.connection_type.clone(), + ip: Some(String::from("127.0.0.1")), + port: host.port, + socket_path: host.socket_path.clone(), + }); + + join(host::run(config), node::run(config)).await; + } else if config.node.is_some() { + node::run(config).await; + } else { + return; + } +} + +pub async fn on_exit() { + futures::join!(host::on_exit(), node::on_exit()); +} From a608371148ea9d397269811080d987e2f4af830d Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Wed, 20 May 2020 18:11:09 +0530 Subject: [PATCH 04/26] Node now handles messages from host. Node now keeps a check on processes and notifies the host accordingly. --- src/host/juno_module.rs | 12 -- src/host/runner.rs | 53 +++++--- src/models/guillotine_message.rs | 1 - src/node/juno_module.rs | 1 + src/node/process.rs | 8 ++ src/node/runner.rs | 224 ++++++++++++++++++++++++++++++- 6 files changed, 264 insertions(+), 35 deletions(-) diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 27999c8..90ca2e7 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -307,17 +307,6 @@ fn process_exited(mut args: HashMap) -> Value { return generate_error_response("Module ID is not a number"); }; - let last_spawned_at = - if let Some(Value::Number(last_spawned_at)) = args.remove("lastSpawnedAt") { - match last_spawned_at { - Number::PosInt(last_spawned_at) => last_spawned_at, - Number::NegInt(last_spawned_at) => last_spawned_at as u64, - Number::Float(last_spawned_at) => last_spawned_at as u64, - } - } else { - return generate_error_response("Module ID is not a number"); - }; - let (sender, receiver) = channel::<(bool, u64)>(); MESSAGE_SENDER .read() @@ -329,7 +318,6 @@ fn process_exited(mut args: HashMap) -> Value { node_name, module_id, crash, - last_spawned_at, response: sender, }) .await; diff --git a/src/host/runner.rs b/src/host/runner.rs index 348a0a4..3367135 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -2,11 +2,7 @@ use crate::{ exec::process::ProcessRunner, host::juno_module, models::{ - GuillotineMessage, - GuillotineNode, - HostConfig, - ModuleRunnerConfig, - ModuleRunningStatus, + GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, RunnerConfig, }, utils::{constants, logger}, @@ -232,7 +228,6 @@ async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfi node_name, module_id, crash, - last_spawned_at, response, } => { let runner = node_runners.get_mut(&node_name); @@ -261,12 +256,12 @@ async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfi } else { // The process has crashed less than 10 times consequtively. // Wait for a while and restart the process - process.last_started_at = last_spawned_at; + process.last_started_at = get_current_millis(); response.send((true, 100)).unwrap(); process.status = ModuleRunningStatus::Running; } } else { - process.last_started_at = last_spawned_at; + process.last_started_at = get_current_millis(); response.send((true, 0)).unwrap(); process.status = ModuleRunningStatus::Running; } @@ -406,25 +401,49 @@ async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfi }, ) .await; - if result.is_err() { + if let Err(error) = result { + response + .send(Err(format!("Error sending the restart command: {}", error))) + .unwrap(); + continue; + } + let result = if let Value::Object(args) = result.unwrap() { + args + } else { response .send(Err(format!( - "Error sending the restart command: {}", - result.unwrap_err() + "Response of restart command wasn't an object. Got: {:#?}", + result.unwrap() ))) .unwrap(); continue; - } - let result = result.unwrap().as_object().unwrap(); + }; - if !*result.get("success").unwrap().as_bool().unwrap() { + let success = if let Some(Value::Bool(success)) = result.remove("success") { + success + } else { response .send(Err(format!( - "Error restarting process: {}", - result.get("error").unwrap().as_string().unwrap() + "Success key of restart command wasn't a bool. Got: {:#?}", + result ))) .unwrap(); continue; + }; + if !success { + response + .send(Err( + if let Some(Value::String(error)) = result.remove("error") { + format!("Error restarting process: {}", error) + } else { + format!( + "Error key of restart command wasn't a string. Got: {:#?}", + result + ) + }, + )) + .unwrap(); + continue; } response.send(Ok(())).unwrap(); } @@ -495,7 +514,7 @@ async fn connect_to_unix_socket(_: &str) -> Result<(), Error> { panic!("Unix sockets are not supported on Windows"); } -fn get_current_millis() -> u128 { +fn get_current_millis() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards. Wtf?") diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index 1a03f80..f9c1608 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -18,7 +18,6 @@ pub enum GuillotineMessage { node_name: String, module_id: u64, crash: bool, - last_spawned_at: u64, response: Sender<(bool, u64)>, // (should_restart, wait_duration_millis) }, ProcessRunning { diff --git a/src/node/juno_module.rs b/src/node/juno_module.rs index d02d134..8e22834 100644 --- a/src/node/juno_module.rs +++ b/src/node/juno_module.rs @@ -90,6 +90,7 @@ pub async fn register_module( ) -> Result { let args = HashMap::new(); + args.insert(String::from("node"), Value::String(node_name.clone())); if let Some(log_dir) = process.log_dir { args.insert(String::from("logDir"), Value::String(log_dir)); } diff --git a/src/node/process.rs b/src/node/process.rs index 910f004..85f33e5 100644 --- a/src/node/process.rs +++ b/src/node/process.rs @@ -11,6 +11,8 @@ pub struct Process { pub working_dir: String, pub last_started_at: u64, pub created_at: u64, + pub start_scheduled_at: Option, + pub has_been_crashing: bool, } impl Process { @@ -26,6 +28,8 @@ impl Process { working_dir, last_started_at: 0, created_at: get_current_time(), + start_scheduled_at: None, + has_been_crashing: false, } } @@ -47,6 +51,10 @@ impl Process { Err(error) => (false, true), } } + + pub async fn respawn(&mut self) { + // TODO + } } fn get_current_time() -> u64 { diff --git a/src/node/runner.rs b/src/node/runner.rs index 5b3eaf0..5a911d1 100644 --- a/src/node/runner.rs +++ b/src/node/runner.rs @@ -5,9 +5,15 @@ use crate::{ }; use async_std::{fs, net::TcpStream, path::Path, sync::Mutex}; -use futures::{channel::mpsc::unbounded, StreamExt}; +use future::Either; +use futures::{channel::mpsc::unbounded, future, StreamExt}; use futures_timer::Delay; -use std::{collections::HashMap, io::Error, time::Duration}; +use juno::models::{Number, Value}; +use std::{ + collections::HashMap, + io::Error, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; lazy_static! { static ref CLOSE_FLAG: Mutex = Mutex::new(false); @@ -83,9 +89,210 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process } } - // Then iterate through the commands recieved by the juno module and do something about it - while let Some(command) = command_receiver.next().await { - + let mut timer_future = Delay::new(Duration::from_millis(100)); + let mut command_future = command_receiver.next(); + + loop { + let selection = future::select(timer_future, command_future).await; + match selection { + Either::Left((_, next_command_future)) => { + if *CLOSE_FLAG.lock().await { + break; + } + + // Timer expired + command_future = next_command_future; + timer_future = Delay::new(Duration::from_millis(100)); + + // Check if all the processes are doing okay + for (module_id, process) in ids_to_processes { + if let (false, crashed) = process.is_process_running() { + // Process ain't running. + + if process.start_scheduled_at.is_some() { + // The process is already scheduled to start at some point in the future. + // Don't bother notifying the host about this + if get_current_time() > process.start_scheduled_at.unwrap() { + // The current time is more than the time the process is schedued to start at. + // Start the process and set the scheduled time to none + process.respawn().await; + process.start_scheduled_at.take(); + } + continue; + } + + // The process just exited. Do something about it + if crashed { + process.has_been_crashing = true; + } + + let response = juno_module + .call_function(&format!("{}.onProcessExited", constants::APP_NAME), { + let map = HashMap::new(); + map.insert(String::from("node"), Value::String(node_name.clone())); + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(module_id)), + ); + map.insert(String::from("crash"), Value::Bool(crashed)); + map + }) + .await; + if let Err(error) = response { + logger::error(&format!("Error calling the exited function: {}", error)); + continue; + } + let response = response.unwrap(); + + let args = if let Value::Object(args) = response { + args + } else { + logger::error("Response is not an object. Malformed response"); + continue; + }; + let success = if let Some(Value::Bool(success)) = args.remove("success") { + success + } else { + logger::error( + "Could not find success key in the response. Malformed object", + ); + continue; + }; + + if !success { + logger::error( + if let Some(Value::String(error_msg)) = args.remove("error") { + &error_msg + } else { + "Could not find error key in false success response. Malformed object" + }, + ); + continue; + } + let should_restart = if let Some(Value::Bool(should_restart)) = + args.remove("shouldRestart") + { + should_restart + } else { + logger::error("Could not find shouldRestart key in true success response. Malformed object"); + continue; + }; + let wait_duration = if let Some(Value::Number(wait_duration)) = + args.remove("waitDuration") + { + match wait_duration { + Number::PosInt(wait_duration) => wait_duration, + Number::NegInt(wait_duration) => wait_duration as u64, + Number::Float(wait_duration) => wait_duration as u64, + } + } else { + logger::error("Could not find waitDuration key in true success response. Malformed object"); + continue; + }; + + if should_restart { + process.start_scheduled_at = Some(get_current_time() + wait_duration); + } + // Don't start the process yet. When the respawn is called at the scheduled time, + // the process will automatically be started + } else { + // Process is running + if process.has_been_crashing { + if get_current_time() - process.last_started_at > 1000 { + // The process has been crashing in the immediate past, + // and has now been running for more than a second. + // Notify the host that the process is now running, + // and remove the crashing flag to stop discriminating against this process + let response = juno_module + .call_function( + &format!("{}.onProcessRunning", constants::APP_NAME), + { + let map = HashMap::new(); + map.insert( + String::from("node"), + Value::String(node_name.clone()), + ); + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(module_id)), + ); + map.insert( + String::from("lastSpawnedAt"), + Value::Number(Number::PosInt( + process.last_started_at, + )), + ); + map + }, + ) + .await; + if let Err(error) = response { + logger::error(&format!( + "Error calling the running function: {}", + error + )); + continue; + } + let response = response.unwrap(); + + let args = if let Value::Object(args) = response { + args + } else { + logger::error("Response is not an object. Malformed response"); + continue; + }; + let success = + if let Some(Value::Bool(success)) = args.remove("success") { + success + } else { + logger::error( + "Could not find success key in the response. Malformed object", + ); + continue; + }; + + if !success { + logger::error( + if let Some(Value::String(error_msg)) = args.remove("error") + { + &error_msg + } else { + "Could not find error key in false success response. Malformed object" + }, + ); + continue; + } + process.has_been_crashing = false; + } + } + } + } + } + Either::Right((command_value, next_timer_future)) => { + // A command was received. Do something about it + command_future = command_receiver.next(); + timer_future = next_timer_future; + + if command_value.is_none() { + // Command is none. Are the senders closed? + break; + } + + match command_value.unwrap() { + GuillotineMessage::RestartProcess { + module_id, + response, + } => { + if !ids_to_processes.contains_key(&module_id) { + response.send(Err(String::from("Could not find any process with that moduleId in this runner. Is this stale data?"))).unwrap(); + } + ids_to_processes.get_mut(&module_id).unwrap().respawn(); + response.send(Ok(())).unwrap(); + } + msg => panic!("Unhandled guillotine message: {:#?}", msg), + } + } + } } } @@ -126,3 +333,10 @@ async fn connect_to_unix_socket(socket_path: &str) -> Result<(), Error> { async fn connect_to_unix_socket(_: &str) -> Result<(), Error> { panic!("Unix sockets are not supported on Windows"); } + +fn get_current_time() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards. Wtf?") + .as_millis() as u64 +} From 966602bb9aa3a507a389f10410334b148e9af7a8 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Wed, 20 May 2020 18:32:03 +0530 Subject: [PATCH 05/26] Fixed CLI issues. Moved juno_process in the host to the newly created Process struct. Removed the old exec module, since there's nothing in it we use anymore. Finally started throwing a bunch of borrow-checker issues. Hello borrow-checker my old friend. --- src/cli/get_module_info.rs | 32 ++-- src/cli/list_modules.rs | 18 ++- src/cli/list_processes.rs | 18 ++- src/cli/mod.rs | 26 ++++ src/cli/restart_process.rs | 18 ++- src/exec/mod.rs | 2 - src/exec/process.rs | 305 ------------------------------------- src/exec/runner.rs | 179 ---------------------- src/host/runner.rs | 21 ++- src/main.rs | 1 - src/node/mod.rs | 1 + src/node/process.rs | 8 + 12 files changed, 87 insertions(+), 542 deletions(-) delete mode 100644 src/exec/mod.rs delete mode 100644 src/exec/process.rs delete mode 100644 src/exec/runner.rs diff --git a/src/cli/get_module_info.rs b/src/cli/get_module_info.rs index 1d00ee8..4523932 100644 --- a/src/cli/get_module_info.rs +++ b/src/cli/get_module_info.rs @@ -1,32 +1,26 @@ -use crate::{logger, models::RunnerConfig, utils::constants}; +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; use clap::ArgMatches; use cli_table::{ format::{ - Align, - Border, - CellFormat, - Color, - HorizontalLine, - Separator, - TableFormat, - VerticalLine, + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, }, - Cell, - Row, - Table, + Cell, Row, Table, }; -use juno::{models::Value, JunoModule}; +use juno::models::Value; use std::collections::HashMap; pub async fn get_module_info(config: RunnerConfig, args: &ArgMatches<'_>) { - let mut module = if config.juno.connection_type == "unix_socket" { - let socket_path = config.juno.socket_path.as_ref().unwrap(); - JunoModule::from_unix_socket(&socket_path) + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module } else { - let port = config.juno.port.as_ref().unwrap(); - let bind_addr = config.juno.bind_addr.as_ref().unwrap(); - JunoModule::from_inet_socket(&bind_addr, *port) + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; }; let module_id = args.value_of("pid"); if module_id.is_none() { diff --git a/src/cli/list_modules.rs b/src/cli/list_modules.rs index bd6d838..bcb8a59 100644 --- a/src/cli/list_modules.rs +++ b/src/cli/list_modules.rs @@ -1,4 +1,4 @@ -use crate::{logger, models::RunnerConfig, utils::constants}; +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; use cli_table::{ format::{ @@ -15,17 +15,19 @@ use cli_table::{ Row, Table, }; -use juno::JunoModule; use std::collections::HashMap; pub async fn list_modules(config: RunnerConfig) { - let mut module = if config.juno.connection_type == "unix_socket" { - let socket_path = config.juno.socket_path.as_ref().unwrap(); - JunoModule::from_unix_socket(&socket_path) + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module } else { - let port = config.juno.port.as_ref().unwrap(); - let bind_addr = config.juno.bind_addr.as_ref().unwrap(); - JunoModule::from_inet_socket(&bind_addr, *port) + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; }; module diff --git a/src/cli/list_processes.rs b/src/cli/list_processes.rs index 57f3d41..5e0704b 100644 --- a/src/cli/list_processes.rs +++ b/src/cli/list_processes.rs @@ -1,4 +1,4 @@ -use crate::{logger, models::RunnerConfig, utils::constants}; +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; use cli_table::{ format::{ @@ -15,17 +15,19 @@ use cli_table::{ Row, Table, }; -use juno::JunoModule; use std::collections::HashMap; pub async fn list_processes(config: RunnerConfig) { - let mut module = if config.juno.connection_type == "unix_socket" { - let socket_path = config.juno.socket_path.as_ref().unwrap(); - JunoModule::from_unix_socket(&socket_path) + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module } else { - let port = config.juno.port.as_ref().unwrap(); - let bind_addr = config.juno.bind_addr.as_ref().unwrap(); - JunoModule::from_inet_socket(&bind_addr, *port) + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; }; module diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 97d4136..5e89aaa 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -9,9 +9,35 @@ pub use list_processes::list_processes; pub use restart_process::restart_process; use chrono::{prelude::*, Utc}; +use crate::models::RunnerConfig; +use juno::JunoModule; pub async fn on_exit() {} +fn get_juno_module_from_config(config: &RunnerConfig) -> Result { + if let Some(host) = config.host { + if host.connection_type == "unix_socket" { + let socket_path = host.socket_path.as_ref().unwrap(); + Ok(JunoModule::from_unix_socket(socket_path)) + } else { + let port = host.port.as_ref().unwrap(); + let bind_addr = host.bind_addr.as_ref().unwrap(); + Ok(JunoModule::from_inet_socket(&bind_addr, *port)) + } + } else if let Some(node) = config.node { + if node.connection_type == "unix_socket" { + let socket_path = node.socket_path.as_ref().unwrap(); + Ok(JunoModule::from_unix_socket(socket_path)) + } else { + let port = node.port.as_ref().unwrap(); + let ip = node.ip.as_ref().unwrap(); + Ok(JunoModule::from_inet_socket(&ip, *port)) + } + } else { + Err("Neither host, nor node are set. Invalid configuration found") + } +} + fn get_date_time(timestamp: i64) -> String { Utc.timestamp_millis(timestamp) .format("%a %b %e %T %Y") diff --git a/src/cli/restart_process.rs b/src/cli/restart_process.rs index db20a94..2c2a7a3 100644 --- a/src/cli/restart_process.rs +++ b/src/cli/restart_process.rs @@ -1,4 +1,4 @@ -use crate::{logger, models::RunnerConfig, utils::constants}; +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; use clap::ArgMatches; use cli_table::{ @@ -18,18 +18,20 @@ use cli_table::{ }; use juno::{ models::{Number, Value}, - JunoModule, }; use std::collections::HashMap; pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { - let mut module = if config.juno.connection_type == "unix_socket" { - let socket_path = config.juno.socket_path.as_ref().unwrap(); - JunoModule::from_unix_socket(&socket_path) + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module } else { - let port = config.juno.port.as_ref().unwrap(); - let bind_addr = config.juno.bind_addr.as_ref().unwrap(); - JunoModule::from_inet_socket(&bind_addr, *port) + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; }; let pid = args.value_of("pid"); if pid.is_none() { diff --git a/src/exec/mod.rs b/src/exec/mod.rs deleted file mode 100644 index 70b49cd..0000000 --- a/src/exec/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod process; -pub mod runner; diff --git a/src/exec/process.rs b/src/exec/process.rs deleted file mode 100644 index 4a5db19..0000000 --- a/src/exec/process.rs +++ /dev/null @@ -1,305 +0,0 @@ -use crate::{ - logger, - models::{ModuleRunnerConfig, ModuleRunningStatus}, -}; -use async_std::task; -use std::{ - collections::HashMap, - fs::OpenOptions, - path::Path, - process::{Child, Command, Stdio}, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; - -use juno::JunoModule; - -#[derive(Debug)] -pub struct ProcessRunner { - process: Option, - log_dir: Option, - runner: String, - working_dir: String, - module_id: u64, - config: ModuleRunnerConfig, - status: ModuleRunningStatus, - restarts: i64, - uptime: u64, - last_started_at: u64, - crashes: u64, - created_at: u64, -} - -impl ProcessRunner { - pub fn new( - module_id: u64, - config: ModuleRunnerConfig, - log_dir: Option, - working_dir: String, - ) -> Self { - ProcessRunner { - process: None, - log_dir, - runner: String::from("host"), - working_dir, - module_id, - config, - status: ModuleRunningStatus::Offline, - restarts: -1, - uptime: 0, - last_started_at: 0, - crashes: 0, - created_at: get_current_time(), - } - } - - pub fn get_name(&self) -> &str { - &self.config.name - } - - pub fn get_module_id(&self) -> u64 { - self.module_id - } - - pub fn set_module_id(&mut self, module_id: u64) { - self.module_id = module_id; - } - - pub fn get_runner(&self) -> &str { - &self.runner - } - - pub fn set_runner(&mut self, runner: String) { - self.runner = runner; - } - - pub fn is_process_running(&mut self) -> bool { - if self.process.is_none() { - return false; - } - - let process = self.process.as_mut().unwrap(); - match process.try_wait() { - Ok(Some(status)) => { - if !status.success() { - self.crashes += 1; - self.uptime = 0; - } - self.status = ModuleRunningStatus::Offline; - false - } // Process has already exited - Ok(None) => { - self.status = ModuleRunningStatus::Running; - self.uptime = get_current_time() - self.last_started_at; - true - } - Err(_) => { - self.status = ModuleRunningStatus::Offline; - self.uptime = 0; - false - } - } - } - - pub async fn respawn(&mut self) { - logger::info(&format!("Respawning '{}'", self.config.name)); - if self.process.is_some() && self.is_process_running() { - self.send_quit_signal(); - let quit_time = get_current_time(); - loop { - // Give the process some time to die. - task::sleep(Duration::from_millis(100)).await; - // If the process is not running, then break - if !self.is_process_running() { - break; - } - // If the processes is running, check if it's been given enough time. - if get_current_time() > quit_time + 1000 { - // It's been trying to quit for more than 1 second. Kill it and quit - logger::info(&format!("Killing process: {}", self.config.name)); - self.process.as_mut().unwrap().kill().unwrap_or(()); - break; - } - } - } - - let child = if self.config.interpreter.is_none() { - let mut command = Command::new(&self.config.command); - command - .current_dir(&self.working_dir) - .args(self.config.args.as_ref().unwrap_or(&vec![])) - .envs(self.config.envs.as_ref().unwrap_or(&vec![]).clone()); - - if self.log_dir.is_some() { - let log_dir = self.log_dir.as_ref().unwrap(); - - let output_location = Path::new(log_dir).join("output.log"); - let error_location = Path::new(log_dir).join("error.log"); - - let output = OpenOptions::new() - .create(true) - .append(true) - .open(output_location); - let error = OpenOptions::new() - .create(true) - .append(true) - .open(error_location); - - if output.is_ok() && error.is_ok() { - command - .stdin(Stdio::null()) - .stdout(Stdio::from(output.unwrap())) - .stderr(Stdio::from(error.unwrap())); - } else { - command - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - } - } - - command.spawn() - } else { - let mut command = Command::new(self.config.interpreter.as_ref().unwrap()); - command - .current_dir(&self.working_dir) - .arg(&self.config.command) - .args(self.config.args.as_ref().unwrap_or(&vec![])) - .envs(self.config.envs.as_ref().unwrap_or(&vec![]).clone()); - - if self.log_dir.is_some() { - let log_dir = self.log_dir.as_ref().unwrap(); - - let output_location = Path::new(log_dir).join("output.log"); - let error_location = Path::new(log_dir).join("error.log"); - - let output = OpenOptions::new() - .create(true) - .append(true) - .open(output_location); - let error = OpenOptions::new() - .create(true) - .append(true) - .open(error_location); - - if output.is_ok() && error.is_ok() { - command - .stdin(Stdio::null()) - .stdout(Stdio::from(output.unwrap())) - .stderr(Stdio::from(error.unwrap())); - } else { - command - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - } - } - - command.spawn() - }; - if let Err(err) = child { - logger::error(&format!( - "Error spawing child process '{}': {}", - self.config.name, err - )); - return; - } - self.process = Some(child.unwrap()); - self.restarts += 1; - self.uptime = 0; - self.status = ModuleRunningStatus::Running; - self.last_started_at = get_current_time(); - } - - #[cfg(target_family = "unix")] - pub fn send_quit_signal(&mut self) { - if self.process.is_none() { - return; - } - // Send SIGINT to a process in unix - use nix::{ - sys::signal::{self, Signal}, - unistd::Pid, - }; - - // send SIGINT to the child - let result = signal::kill( - Pid::from_raw(self.process.as_ref().unwrap().id() as i32), - Signal::SIGINT, - ); - if result.is_err() { - logger::error(&format!( - "Error sending SIGINT to child process '{}': {}", - self.config.name, - result.unwrap_err() - )); - } - } - - #[cfg(target_family = "windows")] - pub fn send_quit_signal(&mut self) { - if self.process.is_none() { - return; - } - // Send ctrl-c event to a process in windows - // Ref: https://blog.codetitans.pl/post/sending-ctrl-c-signal-to-another-application-on-windows/ - use winapi::um::{ - consoleapi::SetConsoleCtrlHandler, - wincon::{AttachConsole, FreeConsole, GenerateConsoleCtrlEvent}, - }; - - let pid = self.process.as_ref().unwrap().id(); - const CTRL_C_EVENT: u32 = 0; - - unsafe { - FreeConsole(); - if AttachConsole(pid) > 0 { - SetConsoleCtrlHandler(None, 1); - GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); - } - } - } - - pub fn kill(&mut self) { - if self.process.is_none() { - return; - } - let result = self.process.as_mut().unwrap().kill(); - if result.is_err() { - logger::error(&format!("Error killing process: {}", result.unwrap_err())); - } - } - - pub fn copy(&self) -> Self { - ProcessRunner { - process: None, - log_dir: self.log_dir.clone(), - runner: self.runner.clone(), - working_dir: self.working_dir.clone(), - module_id: self.module_id, - config: self.config.clone(), - status: self.status.clone(), - restarts: self.restarts, - uptime: self.uptime, - last_started_at: self.last_started_at, - crashes: self.crashes, - created_at: self.created_at, - } - } - - async fn update_data(&mut self, module: &JunoModule) { - let result = module - .call_function(&format!("guillotine-node-{}", self.runner), HashMap::new()) - .await; - if result.is_err() { - return; - } - let result = result.unwrap(); - } -} - -fn get_current_time() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards. Wtf?") - .as_millis() as u64 -} diff --git a/src/exec/runner.rs b/src/exec/runner.rs deleted file mode 100644 index 48263b6..0000000 --- a/src/exec/runner.rs +++ /dev/null @@ -1,179 +0,0 @@ -use crate::{ - exec::process::ProcessRunner, - models::{ModuleRunnerConfig, RunnerConfig}, - utils::constants, -}; - -use async_std::{ - fs::{self, DirEntry}, - io::Error, - path::Path, - prelude::*, -}; - -pub async fn run(config: RunnerConfig) { - if config.logs.is_some() { - let log_dir = config.logs.as_ref().unwrap(); - let main_dir = Path::new(log_dir); - if !main_dir.exists().await { - fs::create_dir(&main_dir).await.unwrap(); - } - } - - if config.host.is_some() { - let host = config.host.unwrap(); - let mut pid = 0; - - let juno_process = if host.connection_type == constants::connection_type::UNIX_SOCKET { - let socket_path = host.socket_path.as_ref().unwrap(); - ProcessRunner::new( - pid, - ModuleRunnerConfig::juno_default( - host.path.clone(), - vec![ - String::from("--socket-location"), - host.socket_path.as_ref().unwrap().clone(), - ], - ), - match &config.logs { - Some(log_dir) => { - let sub_dir = Path::new(log_dir).join("Juno"); - if !sub_dir.exists().await { - fs::create_dir(&sub_dir).await.unwrap(); - } - Some(String::from(sub_dir.to_str().unwrap())) - } - None => None, - }, - Path::new(&host.path) - .parent() - .unwrap() - .to_str() - .unwrap() - .to_string(), - ) - } else { - let port = host.port.as_ref().unwrap(); - let bind_addr = host.bind_addr.as_ref().unwrap(); - ProcessRunner::new( - pid, - ModuleRunnerConfig::juno_default( - host.path.clone(), - vec![ - "--port".to_string(), - format!("{}", port), - "--bind-addr".to_string(), - bind_addr.clone(), - ], - ), - match &config.logs { - Some(log_dir) => { - let sub_dir = Path::new(log_dir).join("Juno"); - if !sub_dir.exists().await { - fs::create_dir(&sub_dir).await.unwrap(); - } - Some(String::from(sub_dir.to_str().unwrap())) - } - None => None, - }, - Path::new(&host.path) - .parent() - .unwrap() - .to_str() - .unwrap() - .to_string(), - ) - }; - pid += 1; - - host::run(juno_process, config.host.clone().unwrap()).await; - } else if config.node.is_some() { - } else { - return; - } -} - -pub async fn on_exit() { - host::on_exit().await; -} - -async fn get_all_available_modules(starting_pid: u64, config: &RunnerConfig) -> Vec { - if config.modules.is_none() { - return vec![]; - } - let modules = config.modules.as_ref().unwrap(); - - let mut tracked_modules = vec![]; - let mut pid = starting_pid; - - let modules_path = Path::new(&modules.directory); - if modules_path.exists().await && modules_path.is_dir().await { - // Get all modules and add them to the list - let mut dir_iterator = modules_path.read_dir().await.unwrap(); - while let Some(path) = dir_iterator.next().await { - if let Some(module) = get_module_from_path(pid, path, &config.logs).await { - tracked_modules.push(module); - pid += 1; - } - } - } - - tracked_modules -} - -async fn get_module_from_path( - expected_pid: u64, - path: Result, - log_dir: &Option, -) -> Option { - if path.is_err() { - return None; - } - let root_path = path.unwrap().path(); - let module_json = root_path.join("module.json"); - - if !module_json.exists().await { - return None; - } - - let module_json_contents = fs::read_to_string(module_json).await; - if module_json_contents.is_err() { - return None; - } - let module_json_contents = module_json_contents.unwrap(); - - let config: Result = - serde_json::from_str(&module_json_contents); - - if config.is_err() { - return None; - } - let config = config.unwrap(); - - let runner = if let Some(log_dir) = log_dir { - let main_dir = Path::new(log_dir); - if !main_dir.exists().await { - fs::create_dir(&main_dir).await.unwrap(); - } - - let sub_dir = main_dir.join(&config.name); - if !sub_dir.exists().await { - fs::create_dir(&sub_dir).await.unwrap(); - } - ProcessRunner::new( - expected_pid, - config.clone(), - Some(String::from(sub_dir.to_str().unwrap())), - root_path.to_str().unwrap().to_string(), - ) - } else { - ProcessRunner::new( - expected_pid, - config.clone(), - None, - root_path.to_str().unwrap().to_string(), - ) - }; - - Some(runner) -} diff --git a/src/host/runner.rs b/src/host/runner.rs index 3367135..8c05b2d 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -1,11 +1,10 @@ use crate::{ - exec::process::ProcessRunner, host::juno_module, models::{ GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, RunnerConfig, }, - utils::{constants, logger}, + utils::{constants, logger}, node::Process, }; use std::{ collections::HashMap, @@ -33,8 +32,7 @@ pub async fn run(mut config: RunnerConfig) { let juno_process = if host.connection_type == constants::connection_type::UNIX_SOCKET { let socket_path = host.socket_path.clone().unwrap(); - ProcessRunner::new( - 0, + Process::new( ModuleRunnerConfig::juno_default( host.path, vec![String::from("--socket-location"), socket_path], @@ -59,8 +57,7 @@ pub async fn run(mut config: RunnerConfig) { } else { let port = host.port.unwrap(); let bind_addr = host.bind_addr.clone().unwrap(); - ProcessRunner::new( - 0, + Process::new( ModuleRunnerConfig::juno_default( host.path.clone(), vec![ @@ -96,9 +93,9 @@ pub async fn on_exit() { *CLOSE_FLAG.lock().await = true; } -async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfig) { +async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { // Spawn juno before spawing any modules - while !juno_process.is_process_running() { + while !juno_process.is_process_running().0 { juno_process.respawn().await; try_connecting_to_juno(&juno_config).await; } @@ -124,7 +121,7 @@ async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfi command_future = next_command_future; timer_future = Delay::new(Duration::from_millis(100)); - if juno_process.is_process_running() { + if juno_process.is_process_running().0 { continue; } // juno_process isn't running. Restart it @@ -455,7 +452,7 @@ async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfi // TODO: Tell all nodes to quit their processes first - logger::info(&format!("Quitting process: {}", juno_process.get_name())); + logger::info(&format!("Quitting process: {}", juno_process.runner_config.name)); juno_process.send_quit_signal(); let quit_time = get_current_millis(); loop { @@ -463,13 +460,13 @@ async fn keep_host_alive(mut juno_process: ProcessRunner, juno_config: HostConfi task::sleep(Duration::from_millis(100)).await; // If the process is not running, then break - if !juno_process.is_process_running() { + if !juno_process.is_process_running().0 { break; } // If the processes is running, check if it's been given enough time. if get_current_millis() > quit_time + 1000 { // It's been trying to quit for more than 1 second. Kill it and quit - logger::info(&format!("Killing process: {}", juno_process.get_name())); + logger::info(&format!("Killing process: {}", juno_process.runner_config.name)); juno_process.kill(); break; } diff --git a/src/main.rs b/src/main.rs index b2a69ec..1e77916 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,6 @@ extern crate nix; extern crate winapi; mod cli; -mod exec; mod host; mod models; mod node; diff --git a/src/node/mod.rs b/src/node/mod.rs index e187bec..b6c8fa9 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -5,3 +5,4 @@ mod runner; pub mod juno_module; pub use runner::{on_exit, run}; +pub use process::Process; diff --git a/src/node/process.rs b/src/node/process.rs index 85f33e5..6d92032 100644 --- a/src/node/process.rs +++ b/src/node/process.rs @@ -55,6 +55,14 @@ impl Process { pub async fn respawn(&mut self) { // TODO } + + pub async fn send_quit_signal(&mut self) { + // TODO + } + + pub async fn kill(&mut self) { + // TODO + } } fn get_current_time() -> u64 { From 14194c92d997e79a6bf9e048f0e27d3f87ee629f Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Wed, 20 May 2020 20:52:22 +0530 Subject: [PATCH 06/26] Finally fixed all borrow-checker issues. Phew! --- src/cli/mod.rs | 4 +- src/host/juno_module.rs | 128 ++++++++++++++++++++-------------- src/host/runner.rs | 57 +++++++++------ src/models/config_data.rs | 1 + src/models/guillotine_node.rs | 7 ++ src/models/parser.rs | 4 +- src/node/juno_module.rs | 45 ++++++------ src/node/module.rs | 17 ++--- src/node/process.rs | 13 ++-- src/node/runner.rs | 59 ++++++++-------- src/runner.rs | 4 +- 11 files changed, 193 insertions(+), 146 deletions(-) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 5e89aaa..fb6ef30 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -15,7 +15,7 @@ use juno::JunoModule; pub async fn on_exit() {} fn get_juno_module_from_config(config: &RunnerConfig) -> Result { - if let Some(host) = config.host { + if let Some(host) = &config.host { if host.connection_type == "unix_socket" { let socket_path = host.socket_path.as_ref().unwrap(); Ok(JunoModule::from_unix_socket(socket_path)) @@ -24,7 +24,7 @@ fn get_juno_module_from_config(config: &RunnerConfig) -> Result) -> Value { node_name: name, response: sender, }) - .await; + .await + .unwrap(); let response = receiver.await.unwrap(); if response.is_ok() { @@ -157,7 +154,7 @@ fn register_process(mut args: HashMap) -> Value { return generate_error_response("Working dir is not a string"); }; - let mut config = if let Some(Value::Object(config)) = args.remove("config") { + let config = if let Some(Value::Object(mut config)) = args.remove("config") { ModuleRunnerConfig { name: if let Some(Value::String(value)) = config.remove("name") { value @@ -264,7 +261,8 @@ fn register_process(mut args: HashMap) -> Value { ), response: sender, }) - .await; + .await + .unwrap(); let response = receiver.await.unwrap(); if let Ok(module_id) = response { @@ -320,7 +318,8 @@ fn process_exited(mut args: HashMap) -> Value { crash, response: sender, }) - .await; + .await + .unwrap(); let (should_restart, wait_duration_millis) = receiver.await.unwrap(); Value::Object({ @@ -376,7 +375,8 @@ fn process_running(mut args: HashMap) -> Value { module_id, last_spawned_at, }) - .await; + .await + .unwrap(); Value::Object({ let mut map = HashMap::new(); @@ -386,9 +386,9 @@ fn process_running(mut args: HashMap) -> Value { }) } -fn module_deactivated(mut data: Value) { +fn module_deactivated(data: Value) { task::block_on(async { - let args = if let Value::Object(args) = data { + let mut args = if let Value::Object(args) = data { args } else { return; @@ -415,11 +415,12 @@ fn module_deactivated(mut data: Value) { .unwrap() .clone() .send(GuillotineMessage::NodeDisconnected { node_name }) - .await; + .await + .unwrap(); }); } -fn list_modules(mut args: HashMap) -> Value { +fn list_modules(_: HashMap) -> Value { task::block_on(async { let (sender, receiver) = channel::, String>>(); MESSAGE_SENDER @@ -429,7 +430,8 @@ fn list_modules(mut args: HashMap) -> Value { .unwrap() .clone() .send(GuillotineMessage::ListModules { response: sender }) - .await; + .await + .unwrap(); let result = receiver.await.unwrap(); if let Ok(modules) = result { @@ -453,7 +455,7 @@ fn list_modules(mut args: HashMap) -> Value { }) } -fn list_nodes(mut args: HashMap) -> Value { +fn list_nodes(_: HashMap) -> Value { task::block_on(async { let (sender, receiver) = channel::>(); MESSAGE_SENDER @@ -463,7 +465,8 @@ fn list_nodes(mut args: HashMap) -> Value { .unwrap() .clone() .send(GuillotineMessage::ListNodes { response: sender }) - .await; + .await + .unwrap(); let nodes = receiver.await.unwrap(); Value::Object({ @@ -494,7 +497,7 @@ fn list_nodes(mut args: HashMap) -> Value { }) } -fn list_all_processes(mut args: HashMap) -> Value { +fn list_all_processes(_: HashMap) -> Value { task::block_on(async { let (sender, receiver) = channel::>(); MESSAGE_SENDER @@ -504,7 +507,8 @@ fn list_all_processes(mut args: HashMap) -> Value { .unwrap() .clone() .send(GuillotineMessage::ListAllProcesses { response: sender }) - .await; + .await + .unwrap(); let processes = receiver.await.unwrap(); Value::Object({ @@ -516,34 +520,42 @@ fn list_all_processes(mut args: HashMap) -> Value { processes .into_iter() .map(|(node, process)| { + let uptime = process.get_uptime(); + + let ProcessData { + module_id, + config, + log_dir, + working_dir, + status, + restarts, + crashes, + created_at, + .. + } = process; + Value::Object({ let mut map = HashMap::new(); map.insert( String::from("id"), - Value::Number(Number::PosInt(process.module_id)), - ); - map.insert( - String::from("name"), - Value::String(process.config.name), + Value::Number(Number::PosInt(module_id)), ); + map.insert(String::from("name"), Value::String(config.name)); map.insert( String::from("logDir"), - if let Some(log_dir) = process.log_dir { + if let Some(log_dir) = log_dir { Value::String(log_dir) } else { Value::Null }, ); - map.insert( - String::from("workingDir"), - Value::String(process.working_dir), - ); + map.insert(String::from("workingDir"), Value::String(working_dir)); map.insert( String::from("status"), - Value::String(String::from(match process.status { + Value::String(String::from(match status { ModuleRunningStatus::Running => "running", ModuleRunningStatus::Stopped => "stopped", ModuleRunningStatus::Offline => "offline", @@ -552,19 +564,19 @@ fn list_all_processes(mut args: HashMap) -> Value { map.insert(String::from("node"), Value::String(node)); map.insert( String::from("restarts"), - Value::Number(Number::NegInt(process.restarts)), + Value::Number(Number::NegInt(restarts)), ); map.insert( String::from("uptime"), - Value::Number(Number::PosInt(process.get_uptime())), + Value::Number(Number::PosInt(uptime)), ); map.insert( String::from("crashes"), - Value::Number(Number::PosInt(process.crashes)), + Value::Number(Number::PosInt(crashes)), ); map.insert( String::from("createdAt"), - Value::Number(Number::PosInt(process.created_at)), + Value::Number(Number::PosInt(created_at)), ); map @@ -597,7 +609,8 @@ fn list_processes(mut args: HashMap) -> Value { node_name: node_name.clone(), response: sender, }) - .await; + .await + .unwrap(); let result = receiver.await.unwrap(); if let Ok(processes) = result { @@ -610,21 +623,35 @@ fn list_processes(mut args: HashMap) -> Value { processes .into_iter() .map(|process| { + let uptime = process.get_uptime(); + + let ProcessData { + module_id, + config, + log_dir, + working_dir, + status, + restarts, + crashes, + created_at, + .. + } = process; + Value::Object({ let mut map = HashMap::new(); map.insert( String::from("id"), - Value::Number(Number::PosInt(process.module_id)), + Value::Number(Number::PosInt(module_id)), ); map.insert( String::from("name"), - Value::String(process.config.name), + Value::String(config.name.clone()), ); map.insert( String::from("logDir"), - if let Some(log_dir) = process.log_dir { + if let Some(log_dir) = log_dir { Value::String(log_dir) } else { Value::Null @@ -632,12 +659,12 @@ fn list_processes(mut args: HashMap) -> Value { ); map.insert( String::from("workingDir"), - Value::String(process.working_dir), + Value::String(working_dir), ); map.insert( String::from("status"), - Value::String(String::from(match process.status { + Value::String(String::from(match status { ModuleRunningStatus::Running => "running", ModuleRunningStatus::Stopped => "stopped", ModuleRunningStatus::Offline => "offline", @@ -649,19 +676,19 @@ fn list_processes(mut args: HashMap) -> Value { ); map.insert( String::from("restarts"), - Value::Number(Number::NegInt(process.restarts)), + Value::Number(Number::NegInt(restarts)), ); map.insert( String::from("uptime"), - Value::Number(Number::PosInt(process.get_uptime())), + Value::Number(Number::PosInt(uptime)), ); map.insert( String::from("crashes"), - Value::Number(Number::PosInt(process.crashes)), + Value::Number(Number::PosInt(crashes)), ); map.insert( String::from("createdAt"), - Value::Number(Number::PosInt(process.created_at)), + Value::Number(Number::PosInt(created_at)), ); map @@ -701,7 +728,8 @@ fn restart_process(mut args: HashMap) -> Value { module_id, response: sender, }) - .await; + .await + .unwrap(); let result = receiver.await.unwrap(); if result.is_ok() { @@ -721,12 +749,10 @@ fn generate_error_response(error_message: &str) -> Value { let mut map = HashMap::new(); map.insert(String::from("success"), Value::Bool(false)); - if error_message.is_some() { - map.insert( - String::from("error"), - Value::String(String::from(error_message.unwrap())), - ); - } + map.insert( + String::from("error"), + Value::String(String::from(error_message)), + ); map }) diff --git a/src/host/runner.rs b/src/host/runner.rs index 8c05b2d..358c938 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -4,7 +4,8 @@ use crate::{ GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, RunnerConfig, }, - utils::{constants, logger}, node::Process, + node::Process, + utils::{constants, logger}, }; use std::{ collections::HashMap, @@ -34,7 +35,7 @@ pub async fn run(mut config: RunnerConfig) { let socket_path = host.socket_path.clone().unwrap(); Process::new( ModuleRunnerConfig::juno_default( - host.path, + host.path.clone(), vec![String::from("--socket-location"), socket_path], ), match &config.logs { @@ -104,7 +105,7 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { let mut pid = 1; // Initialize the guillotine juno module - let (mut sender, mut command_receiver) = unbounded::(); + let (sender, mut command_receiver) = unbounded::(); let mut juno_module = juno_module::setup_host_module(&juno_config, sender.clone()).await; let mut timer_future = Delay::new(Duration::from_millis(100)); @@ -175,7 +176,7 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { } // Find a runner which runs a module of the same name - let runner = node_runners.values().find(|runner| { + let runner = node_runners.values_mut().find(|runner| { runner .get_process_by_name(&process_data.config.name) .is_some() @@ -198,7 +199,7 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { // There's a stale module re-registering itself. process_data.module_id = process.unwrap().module_id; - runner.register_process(process_data); + runner.register_process(process_data.clone()); response.send(Ok(process_data.module_id)).unwrap(); continue; @@ -234,12 +235,12 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { } let runner = runner.unwrap(); - let process = runner.get_process_by_id(module_id); + let process = runner.get_process_by_id_mut(module_id); if process.is_none() { response.send((false, 0)).unwrap(); continue; } - let process = process.as_mut().unwrap(); + let process = process.unwrap(); process.restarts += 1; if crash { @@ -274,11 +275,11 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { } let runner = runner.unwrap(); - let process = runner.get_process_by_id(module_id); + let process = runner.get_process_by_id_mut(module_id); if process.is_none() { continue; } - let process = process.as_mut().unwrap(); + let process = process.unwrap(); process.consequtive_crashes = 0; process.last_started_at = last_spawned_at; @@ -300,16 +301,19 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { .call_function("juno.listModules", HashMap::new()) .await; if result.is_err() { - response.send(Err(format!( - "Error listing modules from Juno: {}", - result.unwrap_err() - ))); + response + .send(Err(format!( + "Error listing modules from Juno: {}", + result.unwrap_err() + ))) + .unwrap(); continue; } let modules = result.unwrap(); if !modules.is_array() { response - .send(Err(format!("Expected array response. Got {:?}", modules))); + .send(Err(format!("Expected array response. Got {:?}", modules))) + .unwrap(); return; } let modules = modules @@ -326,7 +330,7 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { .unwrap(); } GuillotineMessage::ListAllProcesses { response } => { - let result = vec![]; + let mut result = vec![]; node_runners.iter().for_each(|(name, node)| { node.processes .iter() @@ -404,13 +408,14 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { .unwrap(); continue; } - let result = if let Value::Object(args) = result.unwrap() { + let result = result.unwrap(); + let mut result = if let Value::Object(args) = result { args } else { response .send(Err(format!( "Response of restart command wasn't an object. Got: {:#?}", - result.unwrap() + result ))) .unwrap(); continue; @@ -452,7 +457,10 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { // TODO: Tell all nodes to quit their processes first - logger::info(&format!("Quitting process: {}", juno_process.runner_config.name)); + logger::info(&format!( + "Quitting process: {}", + juno_process.runner_config.name + )); juno_process.send_quit_signal(); let quit_time = get_current_millis(); loop { @@ -466,7 +474,10 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { // If the processes is running, check if it's been given enough time. if get_current_millis() > quit_time + 1000 { // It's been trying to quit for more than 1 second. Kill it and quit - logger::info(&format!("Killing process: {}", juno_process.runner_config.name)); + logger::info(&format!( + "Killing process: {}", + juno_process.runner_config.name + )); juno_process.kill(); break; } @@ -487,12 +498,12 @@ async fn try_connecting_to_juno(host: &HostConfig) -> bool { } return true; } else if host.connection_type == constants::connection_type::UNIX_SOCKET { - let unix_socket = host.socket_path.unwrap(); - let mut connection = connect_to_unix_socket(&unix_socket).await; + let unix_socket = host.socket_path.as_ref().unwrap(); + let mut connection = connect_to_unix_socket(unix_socket).await; if connection.is_err() { // If connection failed, wait and try again Delay::new(Duration::from_millis(1000)).await; - connection = connect_to_unix_socket(&unix_socket).await; + connection = connect_to_unix_socket(unix_socket).await; return connection.is_ok(); } return true; @@ -515,5 +526,5 @@ fn get_current_millis() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards. Wtf?") - .as_millis() + .as_millis() as u64 } diff --git a/src/models/config_data.rs b/src/models/config_data.rs index ea6bdc1..936977f 100644 --- a/src/models/config_data.rs +++ b/src/models/config_data.rs @@ -48,6 +48,7 @@ pub struct NodeConfig { pub socket_path: Option, } +#[derive(Deserialize, Debug, Clone)] pub struct ModuleConfig { pub directory: String, } diff --git a/src/models/guillotine_node.rs b/src/models/guillotine_node.rs index 82a17db..d426652 100644 --- a/src/models/guillotine_node.rs +++ b/src/models/guillotine_node.rs @@ -22,6 +22,13 @@ impl GuillotineNode { .find(|process| process.module_id == id); } + pub fn get_process_by_id_mut(&mut self, id: u64) -> Option<&mut ProcessData> { + return self + .processes + .iter_mut() + .find(|process| process.module_id == id); + } + pub fn register_process(&mut self, process_data: ProcessData) { let position = self .processes diff --git a/src/models/parser.rs b/src/models/parser.rs index 26ed3ba..1e4a8c1 100644 --- a/src/models/parser.rs +++ b/src/models/parser.rs @@ -112,7 +112,7 @@ async fn parse_config(mut input: RunnerConfig) -> Result { } if input.host.is_some() { - let host = input.host.unwrap(); + let mut host = input.host.unwrap(); host.path = fs::canonicalize(host.path) .await .unwrap() @@ -154,7 +154,7 @@ async fn parse_config(mut input: RunnerConfig) -> Result { input.host = Some(host); } else if input.node.is_some() { - let node = input.node.unwrap(); + let mut node = input.node.unwrap(); if node.connection_type == "unix_socket" { if node.socket_path.is_none() { return throw_parse_error(); diff --git a/src/node/juno_module.rs b/src/node/juno_module.rs index 8e22834..bbb5335 100644 --- a/src/node/juno_module.rs +++ b/src/node/juno_module.rs @@ -27,10 +27,10 @@ pub async fn setup_module( ) -> Result { MESSAGE_SENDER.write().await.replace(sender); - let juno_module = if node.connection_type == constants::connection_type::UNIX_SOCKET { - JunoModule::from_unix_socket(&node.socket_path.unwrap()) + let mut juno_module = if node.connection_type == constants::connection_type::UNIX_SOCKET { + JunoModule::from_unix_socket(node.socket_path.as_ref().unwrap()) } else { - JunoModule::from_inet_socket(&node.ip.unwrap(), node.port.unwrap()) + JunoModule::from_inet_socket(node.ip.as_ref().unwrap(), node.port.unwrap()) }; juno_module @@ -50,14 +50,14 @@ pub async fn setup_module( // Register node here let response = juno_module .call_function(&format!("{}.registerNode", constants::APP_NAME), { - let map = HashMap::new(); + let mut map = HashMap::new(); map.insert(String::from("name"), Value::String(node_name.clone())); map }) .await .unwrap(); - if let Value::Object(response) = response { + if let Value::Object(mut response) = response { if response.remove("success").unwrap() == Value::Bool(true) { Ok(juno_module) } else if let Some(error) = response.remove("error") { @@ -85,23 +85,23 @@ pub async fn setup_module( pub async fn register_module( node_name: &String, - juno_module: &JunoModule, - process: &Process, + juno_module: &mut JunoModule, + process: &mut Process, ) -> Result { - let args = HashMap::new(); + let mut args = HashMap::new(); args.insert(String::from("node"), Value::String(node_name.clone())); - if let Some(log_dir) = process.log_dir { + if let Some(log_dir) = process.log_dir.clone() { args.insert(String::from("logDir"), Value::String(log_dir)); } args.insert( String::from("workingDir"), - Value::String(process.working_dir), + Value::String(process.working_dir.clone()), ); args.insert( String::from("config"), Value::Object({ - let map = HashMap::new(); + let mut map = HashMap::new(); map.insert( String::from("name"), Value::String(process.runner_config.name.clone()), @@ -110,16 +110,16 @@ pub async fn register_module( String::from("command"), Value::String(process.runner_config.command.clone()), ); - if let Some(interpreter) = process.runner_config.interpreter { + if let Some(interpreter) = process.runner_config.interpreter.clone() { map.insert(String::from("intepreter"), Value::String(interpreter)); } - if let Some(args) = process.runner_config.args { + if let Some(args) = process.runner_config.args.clone() { map.insert( String::from("args"), Value::Array(args.into_iter().map(Value::String).collect()), ); } - if let Some(envs) = process.runner_config.envs { + if let Some(envs) = process.runner_config.envs.clone() { map.insert( String::from("args"), Value::Object( @@ -154,7 +154,7 @@ pub async fn register_module( .await .unwrap(); - if let Value::Object(response) = response { + if let Value::Object(mut response) = response { if !response.contains_key("success") { return Err(String::from( "Could not find success key in the response. Malformed object", @@ -202,7 +202,7 @@ pub async fn register_module( } } -fn respawn_process(args: HashMap) -> Value { +fn respawn_process(mut args: HashMap) -> Value { task::block_on(async { let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { match module_id { @@ -225,7 +225,8 @@ fn respawn_process(args: HashMap) -> Value { module_id, response: sender, }) - .await; + .await + .unwrap(); let result = receiver.await.unwrap(); if result.is_ok() { @@ -245,12 +246,10 @@ fn generate_error_response(error_message: &str) -> Value { let mut map = HashMap::new(); map.insert(String::from("success"), Value::Bool(false)); - if error_message.is_some() { - map.insert( - String::from("error"), - Value::String(String::from(error_message.unwrap())), - ); - } + map.insert( + String::from("error"), + Value::String(String::from(error_message)), + ); map }) diff --git a/src/node/module.rs b/src/node/module.rs index fc6960f..06ef2ad 100644 --- a/src/node/module.rs +++ b/src/node/module.rs @@ -1,16 +1,19 @@ use super::process::Process; use crate::models::ModuleRunnerConfig; -use async_std::{fs, path::Path}; +use async_std::{ + fs, + path::{Path, PathBuf}, +}; pub async fn get_module_from_path(path: &str, log_dir: Option) -> Option { - let mut path = Path::new(path); + let mut path = PathBuf::from(path); if !path.exists().await { return None; } // If path is a folder, get the module.json file if path.is_dir().await { - path = path.join("module.json").as_path(); + path.push("module.json"); if !path.exists().await { return None; } @@ -21,7 +24,7 @@ pub async fn get_module_from_path(path: &str, log_dir: Option) -> Option return None; } - let module_json_contents = fs::read_to_string(path).await; + let module_json_contents = fs::read_to_string(&path).await; if module_json_contents.is_err() { return None; } @@ -37,7 +40,7 @@ pub async fn get_module_from_path(path: &str, log_dir: Option) -> Option let working_dir = path.parent().unwrap().to_str().unwrap().to_owned(); - let runner = if let Some(log_dir) = log_dir { + Some(if let Some(log_dir) = log_dir { let main_dir = Path::new(&log_dir); if !main_dir.exists().await { fs::create_dir(&main_dir).await.unwrap(); @@ -50,7 +53,5 @@ pub async fn get_module_from_path(path: &str, log_dir: Option) -> Option Process::new(config, Some(log_dir), working_dir) } else { Process::new(config, None, working_dir) - }; - - None + }) } diff --git a/src/node/process.rs b/src/node/process.rs index 6d92032..561e4d6 100644 --- a/src/node/process.rs +++ b/src/node/process.rs @@ -36,19 +36,20 @@ impl Process { // returns (is_running, is_crashed) pub fn is_process_running(&mut self) -> (bool, bool) { if self.process.is_none() { - return false; + return (false, false); } let process = self.process.as_mut().unwrap(); match process.try_wait() { Ok(Some(status)) => { - if !status.success() { + if status.success() { + (false, false) + } else { (false, true) } - (false, false) } // Process has already exited Ok(None) => (true, false), - Err(error) => (false, true), + Err(_) => (false, true), } } @@ -56,11 +57,11 @@ impl Process { // TODO } - pub async fn send_quit_signal(&mut self) { + pub fn send_quit_signal(&mut self) { // TODO } - pub async fn kill(&mut self) { + pub fn kill(&mut self) { // TODO } } diff --git a/src/node/runner.rs b/src/node/runner.rs index 5a911d1..6114e8b 100644 --- a/src/node/runner.rs +++ b/src/node/runner.rs @@ -19,7 +19,7 @@ lazy_static! { static ref CLOSE_FLAG: Mutex = Mutex::new(false); } -pub async fn run(config: RunnerConfig) { +pub async fn run(mut config: RunnerConfig) { if config.name.is_none() { logger::error("Node name cannot be null"); return; @@ -70,13 +70,14 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process logger::error(&format!("Error setting up Juno module: {}", error)); return; } - let juno_module = response.unwrap(); + let mut juno_module = response.unwrap(); - let ids_to_processes = HashMap::new(); + let mut ids_to_processes = HashMap::new(); // First, register all the auto_start_processes - for process in auto_start_processes { - let response = juno_module::register_module(&node_name, &juno_module, &process).await; + for mut process in auto_start_processes { + let response = + juno_module::register_module(&node_name, &mut juno_module, &mut process).await; if let Ok(module_id) = response { // Then, store their assigned moduleIds in a hashmap. ids_to_processes.insert(module_id, process); @@ -105,7 +106,7 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process timer_future = Delay::new(Duration::from_millis(100)); // Check if all the processes are doing okay - for (module_id, process) in ids_to_processes { + for (module_id, process) in ids_to_processes.iter_mut() { if let (false, crashed) = process.is_process_running() { // Process ain't running. @@ -128,11 +129,11 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process let response = juno_module .call_function(&format!("{}.onProcessExited", constants::APP_NAME), { - let map = HashMap::new(); + let mut map = HashMap::new(); map.insert(String::from("node"), Value::String(node_name.clone())); map.insert( String::from("moduleId"), - Value::Number(Number::PosInt(module_id)), + Value::Number(Number::PosInt(*module_id)), ); map.insert(String::from("crash"), Value::Bool(crashed)); map @@ -144,7 +145,7 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process } let response = response.unwrap(); - let args = if let Value::Object(args) = response { + let mut args = if let Value::Object(args) = response { args } else { logger::error("Response is not an object. Malformed response"); @@ -160,13 +161,13 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process }; if !success { - logger::error( - if let Some(Value::String(error_msg)) = args.remove("error") { - &error_msg - } else { - "Could not find error key in false success response. Malformed object" - }, - ); + logger::error(&if let Some(Value::String(error_msg)) = + args.remove("error") + { + error_msg + } else { + String::from("Could not find error key in false success response. Malformed object") + }); continue; } let should_restart = if let Some(Value::Bool(should_restart)) = @@ -207,14 +208,14 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process .call_function( &format!("{}.onProcessRunning", constants::APP_NAME), { - let map = HashMap::new(); + let mut map = HashMap::new(); map.insert( String::from("node"), Value::String(node_name.clone()), ); map.insert( String::from("moduleId"), - Value::Number(Number::PosInt(module_id)), + Value::Number(Number::PosInt(*module_id)), ); map.insert( String::from("lastSpawnedAt"), @@ -235,7 +236,7 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process } let response = response.unwrap(); - let args = if let Value::Object(args) = response { + let mut args = if let Value::Object(args) = response { args } else { logger::error("Response is not an object. Malformed response"); @@ -252,14 +253,13 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process }; if !success { - logger::error( - if let Some(Value::String(error_msg)) = args.remove("error") - { - &error_msg - } else { - "Could not find error key in false success response. Malformed object" - }, - ); + logger::error(&if let Some(Value::String(error_msg)) = + args.remove("error") + { + error_msg + } else { + String::from("Could not find error key in false success response. Malformed object") + }); continue; } process.has_been_crashing = false; @@ -285,8 +285,9 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process } => { if !ids_to_processes.contains_key(&module_id) { response.send(Err(String::from("Could not find any process with that moduleId in this runner. Is this stale data?"))).unwrap(); + continue; } - ids_to_processes.get_mut(&module_id).unwrap().respawn(); + ids_to_processes.get_mut(&module_id).unwrap().respawn().await; response.send(Ok(())).unwrap(); } msg => panic!("Unhandled guillotine message: {:#?}", msg), @@ -310,7 +311,7 @@ async fn try_connecting_to_host(node: &NodeConfig) -> bool { } return true; } else if node.connection_type == constants::connection_type::UNIX_SOCKET { - let unix_socket = node.socket_path.unwrap(); + let unix_socket = node.socket_path.clone().unwrap(); let mut connection = connect_to_unix_socket(&unix_socket).await; if connection.is_err() { // If connection failed, wait and try again diff --git a/src/runner.rs b/src/runner.rs index 2779c5f..28c9398 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -6,7 +6,7 @@ use crate::{ use async_std::{fs, path::Path}; use futures::future::join; -pub async fn run(config: RunnerConfig) { +pub async fn run(mut config: RunnerConfig) { if config.logs.is_some() { let log_dir = config.logs.as_ref().unwrap(); let main_dir = Path::new(log_dir); @@ -28,7 +28,7 @@ pub async fn run(config: RunnerConfig) { socket_path: host.socket_path.clone(), }); - join(host::run(config), node::run(config)).await; + join(host::run(config.clone()), node::run(config)).await; } else if config.node.is_some() { node::run(config).await; } else { From d89e94a3296b805cda398ae14dfec5c199259c66 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Wed, 20 May 2020 22:10:19 +0530 Subject: [PATCH 07/26] Added format --- src/cli/list_modules.rs | 13 ++----------- src/cli/list_processes.rs | 13 ++----------- src/cli/mod.rs | 2 +- src/cli/restart_process.rs | 21 +++++--------------- src/models/mod.rs | 11 ++--------- src/models/parser.rs | 40 +++++++++++++++++++------------------- src/node/mod.rs | 2 +- src/node/runner.rs | 6 +++++- 8 files changed, 38 insertions(+), 70 deletions(-) diff --git a/src/cli/list_modules.rs b/src/cli/list_modules.rs index bcb8a59..bf9dc86 100644 --- a/src/cli/list_modules.rs +++ b/src/cli/list_modules.rs @@ -2,18 +2,9 @@ use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, util use cli_table::{ format::{ - Align, - Border, - CellFormat, - Color, - HorizontalLine, - Separator, - TableFormat, - VerticalLine, + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, }, - Cell, - Row, - Table, + Cell, Row, Table, }; use std::collections::HashMap; diff --git a/src/cli/list_processes.rs b/src/cli/list_processes.rs index 5e0704b..c2051e6 100644 --- a/src/cli/list_processes.rs +++ b/src/cli/list_processes.rs @@ -2,18 +2,9 @@ use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, util use cli_table::{ format::{ - Align, - Border, - CellFormat, - Color, - HorizontalLine, - Separator, - TableFormat, - VerticalLine, + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, }, - Cell, - Row, - Table, + Cell, Row, Table, }; use std::collections::HashMap; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index fb6ef30..07ffef3 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -8,8 +8,8 @@ pub use list_modules::list_modules; pub use list_processes::list_processes; pub use restart_process::restart_process; -use chrono::{prelude::*, Utc}; use crate::models::RunnerConfig; +use chrono::{prelude::*, Utc}; use juno::JunoModule; pub async fn on_exit() {} diff --git a/src/cli/restart_process.rs b/src/cli/restart_process.rs index 2c2a7a3..2470170 100644 --- a/src/cli/restart_process.rs +++ b/src/cli/restart_process.rs @@ -3,22 +3,11 @@ use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, util use clap::ArgMatches; use cli_table::{ format::{ - Align, - Border, - CellFormat, - Color, - HorizontalLine, - Separator, - TableFormat, - VerticalLine, + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, }, - Cell, - Row, - Table, -}; -use juno::{ - models::{Number, Value}, + Cell, Row, Table, }; +use juno::models::{Number, Value}; use std::collections::HashMap; pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { @@ -166,8 +155,8 @@ pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { Cell::new( &format!( "{}", - if pid == - process + if pid + == process .get("id") .unwrap() .as_number() diff --git a/src/models/mod.rs b/src/models/mod.rs index 6636761..a0f93f5 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -6,15 +6,8 @@ mod process_data; pub mod parser; pub use config_data::{ - ConfigTarget, - GuillotineConfig, - GuillotinePerEnvConfig, - HostConfig, - ModuleConfig, - ModuleRunnerConfig, - ModuleRunningStatus, - NodeConfig, - RunnerConfig, + ConfigTarget, GuillotineConfig, GuillotinePerEnvConfig, HostConfig, ModuleConfig, + ModuleRunnerConfig, ModuleRunningStatus, NodeConfig, RunnerConfig, }; pub use guillotine_message::GuillotineMessage; pub use guillotine_node::GuillotineNode; diff --git a/src/models/parser.rs b/src/models/parser.rs index 1e4a8c1..41d266d 100644 --- a/src/models/parser.rs +++ b/src/models/parser.rs @@ -35,8 +35,8 @@ async fn parse_if_config(input: &ConfigTarget) -> Result { let mut satisfied = true; if let Some(required_cfg) = &input.family { - if (required_cfg == "unix" && cfg!(target_family = "unix")) || - (required_cfg == "windows" && cfg!(target_family = "windows")) + if (required_cfg == "unix" && cfg!(target_family = "unix")) + || (required_cfg == "windows" && cfg!(target_family = "windows")) { satisfied &= true; } else { @@ -45,15 +45,15 @@ async fn parse_if_config(input: &ConfigTarget) -> Result { } if let Some(required_cfg) = &input.os { - if (required_cfg == "windows" && cfg!(target_os = "windows")) || - (required_cfg == "macos" && cfg!(target_os = "macos")) || - (required_cfg == "ios" && cfg!(target_os = "ios")) || - (required_cfg == "linux" && cfg!(target_os = "linux")) || - (required_cfg == "android" && cfg!(target_os = "android")) || - (required_cfg == "freebsd" && cfg!(target_os = "freebsd")) || - (required_cfg == "dragonfly" && cfg!(target_os = "dragonfly")) || - (required_cfg == "openbsd" && cfg!(target_os = "openbsd")) || - (required_cfg == "netbsd" && cfg!(target_os = "netbsd")) + if (required_cfg == "windows" && cfg!(target_os = "windows")) + || (required_cfg == "macos" && cfg!(target_os = "macos")) + || (required_cfg == "ios" && cfg!(target_os = "ios")) + || (required_cfg == "linux" && cfg!(target_os = "linux")) + || (required_cfg == "android" && cfg!(target_os = "android")) + || (required_cfg == "freebsd" && cfg!(target_os = "freebsd")) + || (required_cfg == "dragonfly" && cfg!(target_os = "dragonfly")) + || (required_cfg == "openbsd" && cfg!(target_os = "openbsd")) + || (required_cfg == "netbsd" && cfg!(target_os = "netbsd")) { satisfied &= true; } else { @@ -62,13 +62,13 @@ async fn parse_if_config(input: &ConfigTarget) -> Result { } if let Some(required_cfg) = &input.arch { - if (required_cfg == "x86" && cfg!(target_arch = "x86")) || - (required_cfg == "x86_64" && cfg!(target_arch = "x86_64")) || - (required_cfg == "mips" && cfg!(target_arch = "mips")) || - (required_cfg == "powerpc" && cfg!(target_arch = "powerpc")) || - (required_cfg == "powerpc64" && cfg!(target_arch = "powerpc64")) || - (required_cfg == "arm" && cfg!(target_arch = "arm")) || - (required_cfg == "aarch64" && cfg!(target_arch = "aarch64")) + if (required_cfg == "x86" && cfg!(target_arch = "x86")) + || (required_cfg == "x86_64" && cfg!(target_arch = "x86_64")) + || (required_cfg == "mips" && cfg!(target_arch = "mips")) + || (required_cfg == "powerpc" && cfg!(target_arch = "powerpc")) + || (required_cfg == "powerpc64" && cfg!(target_arch = "powerpc64")) + || (required_cfg == "arm" && cfg!(target_arch = "arm")) + || (required_cfg == "aarch64" && cfg!(target_arch = "aarch64")) { satisfied &= true; } else { @@ -77,8 +77,8 @@ async fn parse_if_config(input: &ConfigTarget) -> Result { } if let Some(required_cfg) = &input.endian { - if (required_cfg == "little" && cfg!(target_endian = "little")) || - (required_cfg == "big" && cfg!(target_endian = "big")) + if (required_cfg == "little" && cfg!(target_endian = "little")) + || (required_cfg == "big" && cfg!(target_endian = "big")) { satisfied &= true; } else { diff --git a/src/node/mod.rs b/src/node/mod.rs index b6c8fa9..d22d3eb 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -4,5 +4,5 @@ mod runner; pub mod juno_module; -pub use runner::{on_exit, run}; pub use process::Process; +pub use runner::{on_exit, run}; diff --git a/src/node/runner.rs b/src/node/runner.rs index 6114e8b..4418d6f 100644 --- a/src/node/runner.rs +++ b/src/node/runner.rs @@ -287,7 +287,11 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process response.send(Err(String::from("Could not find any process with that moduleId in this runner. Is this stale data?"))).unwrap(); continue; } - ids_to_processes.get_mut(&module_id).unwrap().respawn().await; + ids_to_processes + .get_mut(&module_id) + .unwrap() + .respawn() + .await; response.send(Ok(())).unwrap(); } msg => panic!("Unhandled guillotine message: {:#?}", msg), From bff3d68ceb7b9d62cc55c68cb257b5a6c1f4c478 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Thu, 21 May 2020 00:20:29 +0530 Subject: [PATCH 08/26] Added process runner for quitting, killing and respawning --- src/node/process.rs | 169 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 163 insertions(+), 6 deletions(-) diff --git a/src/node/process.rs b/src/node/process.rs index 561e4d6..acd452b 100644 --- a/src/node/process.rs +++ b/src/node/process.rs @@ -1,9 +1,12 @@ -use crate::models::ModuleRunnerConfig; +use crate::{models::ModuleRunnerConfig, utils::logger}; use std::{ - process::Child, - time::{SystemTime, UNIX_EPOCH}, + fs::OpenOptions, + process::{Child, Command, Stdio}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; +use async_std::{path::Path, task}; + pub struct Process { pub process: Option, pub runner_config: ModuleRunnerConfig, @@ -54,15 +57,169 @@ impl Process { } pub async fn respawn(&mut self) { - // TODO + logger::info(&format!("Respawning '{}'", self.runner_config.name)); + if self.process.is_some() && self.is_process_running().0 { + self.send_quit_signal(); + let quit_time = get_current_time(); + loop { + // Give the process some time to die. + task::sleep(Duration::from_millis(100)).await; + // If the process is not running, then break + if !self.is_process_running().0 { + break; + } + // If the processes is running, check if it's been given enough time. + if get_current_time() > quit_time + 1000 { + // It's been trying to quit for more than 1 second. Kill it and quit + logger::info(&format!("Killing process: {}", self.runner_config.name)); + self.process.as_mut().unwrap().kill().unwrap_or(()); + break; + } + } + } + + let child = if self.runner_config.interpreter.is_none() { + let mut command = Command::new(&self.runner_config.command); + command + .current_dir(&self.working_dir) + .args(self.runner_config.args.as_ref().unwrap_or(&vec![])) + .envs(self.runner_config.envs.as_ref().unwrap_or(&vec![]).clone()); + + if self.log_dir.is_some() { + let log_dir = self.log_dir.as_ref().unwrap(); + + let output_location = Path::new(log_dir).join("output.log"); + let error_location = Path::new(log_dir).join("error.log"); + + let output = OpenOptions::new() + .create(true) + .append(true) + .open(output_location); + let error = OpenOptions::new() + .create(true) + .append(true) + .open(error_location); + + if output.is_ok() && error.is_ok() { + command + .stdin(Stdio::null()) + .stdout(Stdio::from(output.unwrap())) + .stderr(Stdio::from(error.unwrap())); + } else { + command + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + } + } + + command.spawn() + } else { + let mut command = Command::new(self.runner_config.interpreter.as_ref().unwrap()); + command + .current_dir(&self.working_dir) + .arg(&self.runner_config.command) + .args(self.runner_config.args.as_ref().unwrap_or(&vec![])) + .envs(self.runner_config.envs.as_ref().unwrap_or(&vec![]).clone()); + + if self.log_dir.is_some() { + let log_dir = self.log_dir.as_ref().unwrap(); + + let output_location = Path::new(log_dir).join("output.log"); + let error_location = Path::new(log_dir).join("error.log"); + + let output = OpenOptions::new() + .create(true) + .append(true) + .open(output_location); + let error = OpenOptions::new() + .create(true) + .append(true) + .open(error_location); + + if output.is_ok() && error.is_ok() { + command + .stdin(Stdio::null()) + .stdout(Stdio::from(output.unwrap())) + .stderr(Stdio::from(error.unwrap())); + } else { + command + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + } + } + + command.spawn() + }; + if let Err(err) = child { + logger::error(&format!( + "Error spawing child process '{}': {}", + self.runner_config.name, err + )); + return; + } + self.process = Some(child.unwrap()); + self.last_started_at = get_current_time(); } + #[cfg(target_family = "unix")] pub fn send_quit_signal(&mut self) { - // TODO + if self.process.is_none() { + return; + } + // Send SIGINT to a process in unix + use nix::{ + sys::signal::{self, Signal}, + unistd::Pid, + }; + + // send SIGINT to the child + let result = signal::kill( + Pid::from_raw(self.process.as_ref().unwrap().id() as i32), + Signal::SIGINT, + ); + if result.is_err() { + logger::error(&format!( + "Error sending SIGINT to child process '{}': {}", + self.runner_config.name, + result.unwrap_err() + )); + } + } + + #[cfg(target_family = "windows")] + pub fn send_quit_signal(&mut self) { + if self.process.is_none() { + return; + } + // Send ctrl-c event to a process in windows + // Ref: https://blog.codetitans.pl/post/sending-ctrl-c-signal-to-another-application-on-windows/ + use winapi::um::{ + consoleapi::SetConsoleCtrlHandler, + wincon::{AttachConsole, FreeConsole, GenerateConsoleCtrlEvent}, + }; + + let pid = self.process.as_ref().unwrap().id(); + const CTRL_C_EVENT: u32 = 0; + + unsafe { + FreeConsole(); + if AttachConsole(pid) > 0 { + SetConsoleCtrlHandler(None, 1); + GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); + } + } } pub fn kill(&mut self) { - // TODO + if self.process.is_none() { + return; + } + let result = self.process.as_mut().unwrap().kill(); + if result.is_err() { + logger::error(&format!("Error killing process: {}", result.unwrap_err())); + } } } From a24291310d4654289f07fe244b1741a315580883 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Thu, 21 May 2020 00:36:44 +0530 Subject: [PATCH 09/26] Added cargo suggestions --- src/cli/get_module_info.rs | 4 +- src/host/juno_module.rs | 25 ++---- src/host/runner.rs | 8 +- src/models/guillotine_message.rs | 2 +- src/models/guillotine_node.rs | 15 ++-- src/node/juno_module.rs | 38 ++++----- src/node/runner.rs | 132 +++++++++++++++---------------- src/runner.rs | 2 - 8 files changed, 104 insertions(+), 122 deletions(-) diff --git a/src/cli/get_module_info.rs b/src/cli/get_module_info.rs index 4523932..fef0f13 100644 --- a/src/cli/get_module_info.rs +++ b/src/cli/get_module_info.rs @@ -173,9 +173,9 @@ fn constrain_string_to(array: Vec, max_length: usize) -> String { let total_string = array.join(", "); if total_string.len() > max_length { let mut total_string: String = total_string.chars().take(max_length - 3).collect(); - total_string.extend("...".chars()); + total_string.push_str("..."); total_string - } else if total_string.len() == 0 { + } else if total_string.is_empty() { String::from("-") } else { total_string diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 89a8807..e39e956 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -41,10 +41,7 @@ pub async fn setup_host_module( module .initialize(constants::APP_NAME, constants::APP_VERSION, HashMap::new()) .await - .expect(&format!( - "Could not initialize {} Juno Module", - constants::APP_NAME - )); + .unwrap_or_else(|_| panic!("Could not initialize {} Juno Module", constants::APP_NAME)); module .declare_function("registerNode", register_node) @@ -122,7 +119,7 @@ fn register_node(mut args: HashMap) -> Value { .unwrap(); let response = receiver.await.unwrap(); - if response.is_ok() { + if let Ok(()) = response { Value::Object({ let mut map = HashMap::new(); map.insert(String::from("success"), Value::Bool(true)); @@ -251,14 +248,14 @@ fn register_process(mut args: HashMap) -> Value { .clone() .send(GuillotineMessage::RegisterProcess { node_name, - process_data: ProcessData::new( + process_data: Box::new(ProcessData::new( log_dir, working_dir, config, status, last_started_at, created_at, - ), + )), response: sender, }) .await @@ -440,12 +437,7 @@ fn list_modules(_: HashMap) -> Value { map.insert(String::from("success"), Value::Bool(true)); map.insert( String::from("modules"), - Value::Array( - modules - .into_iter() - .map(|module| Value::String(module)) - .collect(), - ), + Value::Array(modules.into_iter().map(Value::String).collect()), ); map }) @@ -644,10 +636,7 @@ fn list_processes(mut args: HashMap) -> Value { String::from("id"), Value::Number(Number::PosInt(module_id)), ); - map.insert( - String::from("name"), - Value::String(config.name.clone()), - ); + map.insert(String::from("name"), Value::String(config.name)); map.insert( String::from("logDir"), @@ -732,7 +721,7 @@ fn restart_process(mut args: HashMap) -> Value { .unwrap(); let result = receiver.await.unwrap(); - if result.is_ok() { + if let Ok(()) = result { Value::Object({ let mut map = HashMap::new(); map.insert(String::from("success"), Value::Bool(true)); diff --git a/src/host/runner.rs b/src/host/runner.rs index 358c938..e37cad6 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -199,7 +199,7 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { // There's a stale module re-registering itself. process_data.module_id = process.unwrap().module_id; - runner.register_process(process_data.clone()); + runner.register_process(process_data.as_ref().clone()); response.send(Ok(process_data.module_id)).unwrap(); continue; @@ -218,7 +218,7 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { let assigned_pid = pid; pid += 1; process_data.module_id = assigned_pid; - runner.register_process(process_data); + runner.register_process(process_data.as_ref().clone()); response.send(Ok(assigned_pid)).unwrap(); } @@ -496,7 +496,7 @@ async fn try_connecting_to_juno(host: &HostConfig) -> bool { connection = TcpStream::connect(&port).await; return connection.is_ok(); } - return true; + true } else if host.connection_type == constants::connection_type::UNIX_SOCKET { let unix_socket = host.socket_path.as_ref().unwrap(); let mut connection = connect_to_unix_socket(unix_socket).await; @@ -506,7 +506,7 @@ async fn try_connecting_to_juno(host: &HostConfig) -> bool { connection = connect_to_unix_socket(unix_socket).await; return connection.is_ok(); } - return true; + true } else { panic!("Connection type is neither unix socket not inet socket. How did you get here?"); } diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index f9c1608..b2bdfaa 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -11,7 +11,7 @@ pub enum GuillotineMessage { }, RegisterProcess { node_name: String, - process_data: ProcessData, + process_data: Box, response: Sender>, }, ProcessExited { diff --git a/src/models/guillotine_node.rs b/src/models/guillotine_node.rs index d426652..7a05157 100644 --- a/src/models/guillotine_node.rs +++ b/src/models/guillotine_node.rs @@ -9,24 +9,21 @@ pub struct GuillotineNode { impl GuillotineNode { pub fn get_process_by_name(&self, name: &str) -> Option<&ProcessData> { - return self - .processes + self.processes .iter() - .find(|process| process.config.name == name); + .find(|process| process.config.name == name) } pub fn get_process_by_id(&self, id: u64) -> Option<&ProcessData> { - return self - .processes + self.processes .iter() - .find(|process| process.module_id == id); + .find(|process| process.module_id == id) } pub fn get_process_by_id_mut(&mut self, id: u64) -> Option<&mut ProcessData> { - return self - .processes + self.processes .iter_mut() - .find(|process| process.module_id == id); + .find(|process| process.module_id == id) } pub fn register_process(&mut self, process_data: ProcessData) { diff --git a/src/node/juno_module.rs b/src/node/juno_module.rs index bbb5335..a2c9bdb 100644 --- a/src/node/juno_module.rs +++ b/src/node/juno_module.rs @@ -21,7 +21,7 @@ lazy_static! { } pub async fn setup_module( - node_name: &String, + node_name: String, node: &NodeConfig, sender: UnboundedSender, ) -> Result { @@ -51,7 +51,7 @@ pub async fn setup_module( let response = juno_module .call_function(&format!("{}.registerNode", constants::APP_NAME), { let mut map = HashMap::new(); - map.insert(String::from("name"), Value::String(node_name.clone())); + map.insert(String::from("name"), Value::String(node_name)); map }) .await @@ -61,36 +61,36 @@ pub async fn setup_module( if response.remove("success").unwrap() == Value::Bool(true) { Ok(juno_module) } else if let Some(error) = response.remove("error") { - return Err(if let Value::String(error) = error { + Err(if let Value::String(error) = error { error } else { return Err(format!( "Expected a string response for error. Got: {:#?}", error )); - }); + }) } else { - return Err(format!( + Err(format!( "Expected a boolean success key in the response. Malformed object: {:#?}", response - )); + )) } } else { - return Err(format!( + Err(format!( "Expected an object response while registering process. Got: {:#?}", response - )); + )) } } pub async fn register_module( - node_name: &String, + node_name: String, juno_module: &mut JunoModule, process: &mut Process, ) -> Result { let mut args = HashMap::new(); - args.insert(String::from("node"), Value::String(node_name.clone())); + args.insert(String::from("node"), Value::String(node_name)); if let Some(log_dir) = process.log_dir.clone() { args.insert(String::from("logDir"), Value::String(log_dir)); } @@ -175,30 +175,30 @@ pub async fn register_module( Number::Float(module_id) => module_id as u64, }) } else { - return Err(format!( + Err(format!( "Expected a string response for moduleId. Got: {:#?}", module_id - )); + )) } } else if let Some(error) = response.remove("error") { - return Err(if let Value::String(error) = error { + Err(if let Value::String(error) = error { error } else { return Err(format!( "Expected a string response for error. Got: {:#?}", error )); - }); + }) } else { - return Err(String::from( + Err(String::from( "Expected a boolean success key in the response. Malformed object", - )); + )) } } else { - return Err(format!( + Err(format!( "Expected an object response while registering process. Got: {:#?}", response - )); + )) } } @@ -229,7 +229,7 @@ fn respawn_process(mut args: HashMap) -> Value { .unwrap(); let result = receiver.await.unwrap(); - if result.is_ok() { + if let Ok(()) = result { Value::Object({ let mut map = HashMap::new(); map.insert(String::from("success"), Value::Bool(true)); diff --git a/src/node/runner.rs b/src/node/runner.rs index 4418d6f..68d7174 100644 --- a/src/node/runner.rs +++ b/src/node/runner.rs @@ -65,7 +65,7 @@ pub async fn on_exit() { async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_processes: Vec) { // Initialize the guillotine juno module let (sender, mut command_receiver) = unbounded::(); - let response = juno_module::setup_module(&node_name, &node, sender.clone()).await; + let response = juno_module::setup_module(node_name.clone(), &node, sender.clone()).await; if let Err(error) = response { logger::error(&format!("Error setting up Juno module: {}", error)); return; @@ -77,7 +77,7 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process // First, register all the auto_start_processes for mut process in auto_start_processes { let response = - juno_module::register_module(&node_name, &mut juno_module, &mut process).await; + juno_module::register_module(node_name.clone(), &mut juno_module, &mut process).await; if let Ok(module_id) = response { // Then, store their assigned moduleIds in a hashmap. ids_to_processes.insert(module_id, process); @@ -198,72 +198,70 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process // the process will automatically be started } else { // Process is running - if process.has_been_crashing { - if get_current_time() - process.last_started_at > 1000 { - // The process has been crashing in the immediate past, - // and has now been running for more than a second. - // Notify the host that the process is now running, - // and remove the crashing flag to stop discriminating against this process - let response = juno_module - .call_function( - &format!("{}.onProcessRunning", constants::APP_NAME), - { - let mut map = HashMap::new(); - map.insert( - String::from("node"), - Value::String(node_name.clone()), - ); - map.insert( - String::from("moduleId"), - Value::Number(Number::PosInt(*module_id)), - ); - map.insert( - String::from("lastSpawnedAt"), - Value::Number(Number::PosInt( - process.last_started_at, - )), - ); - map - }, - ) - .await; - if let Err(error) = response { - logger::error(&format!( - "Error calling the running function: {}", - error - )); - continue; - } - let response = response.unwrap(); - - let mut args = if let Value::Object(args) = response { - args - } else { - logger::error("Response is not an object. Malformed response"); - continue; - }; - let success = - if let Some(Value::Bool(success)) = args.remove("success") { - success - } else { - logger::error( - "Could not find success key in the response. Malformed object", + if process.has_been_crashing + && get_current_time() - process.last_started_at > 1000 + { + // The process has been crashing in the immediate past, + // and has now been running for more than a second. + // Notify the host that the process is now running, + // and remove the crashing flag to stop discriminating against this process + let response = juno_module + .call_function( + &format!("{}.onProcessRunning", constants::APP_NAME), + { + let mut map = HashMap::new(); + map.insert( + String::from("node"), + Value::String(node_name.clone()), ); - continue; - }; + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(*module_id)), + ); + map.insert( + String::from("lastSpawnedAt"), + Value::Number(Number::PosInt(process.last_started_at)), + ); + map + }, + ) + .await; + if let Err(error) = response { + logger::error(&format!( + "Error calling the running function: {}", + error + )); + continue; + } + let response = response.unwrap(); - if !success { - logger::error(&if let Some(Value::String(error_msg)) = - args.remove("error") - { - error_msg - } else { - String::from("Could not find error key in false success response. Malformed object") - }); - continue; - } - process.has_been_crashing = false; + let mut args = if let Value::Object(args) = response { + args + } else { + logger::error("Response is not an object. Malformed response"); + continue; + }; + let success = if let Some(Value::Bool(success)) = args.remove("success") + { + success + } else { + logger::error( + "Could not find success key in the response. Malformed object", + ); + continue; + }; + + if !success { + logger::error(&if let Some(Value::String(error_msg)) = + args.remove("error") + { + error_msg + } else { + String::from("Could not find error key in false success response. Malformed object") + }); + continue; } + process.has_been_crashing = false; } } } @@ -313,7 +311,7 @@ async fn try_connecting_to_host(node: &NodeConfig) -> bool { connection = TcpStream::connect(&port).await; return connection.is_ok(); } - return true; + true } else if node.connection_type == constants::connection_type::UNIX_SOCKET { let unix_socket = node.socket_path.clone().unwrap(); let mut connection = connect_to_unix_socket(&unix_socket).await; @@ -323,7 +321,7 @@ async fn try_connecting_to_host(node: &NodeConfig) -> bool { connection = connect_to_unix_socket(&unix_socket).await; return connection.is_ok(); } - return true; + true } else { panic!("Connection type is neither unix socket not inet socket. How did you get here?"); } diff --git a/src/runner.rs b/src/runner.rs index 28c9398..b3f6b53 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -31,8 +31,6 @@ pub async fn run(mut config: RunnerConfig) { join(host::run(config.clone()), node::run(config)).await; } else if config.node.is_some() { node::run(config).await; - } else { - return; } } From 9942029d26292a83f67409c573308b7a0d55ff6f Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Thu, 21 May 2020 13:00:07 +0530 Subject: [PATCH 10/26] Host and nodes all work well together! Finally!!!!! --- src/cli/list_processes.rs | 2 +- src/host/juno_module.rs | 114 +++++++++++++++++++++++++++----------- src/host/runner.rs | 33 +++++++++-- src/main.rs | 2 +- src/models/config_data.rs | 6 +- src/node/juno_module.rs | 9 ++- src/node/runner.rs | 24 ++++---- src/runner.rs | 16 +++++- 8 files changed, 150 insertions(+), 56 deletions(-) diff --git a/src/cli/list_processes.rs b/src/cli/list_processes.rs index c2051e6..0a05891 100644 --- a/src/cli/list_processes.rs +++ b/src/cli/list_processes.rs @@ -31,7 +31,7 @@ pub async fn list_processes(config: RunnerConfig) { .unwrap(); let processes = module .call_function( - &format!("{}.listProcesses", constants::APP_NAME), + &format!("{}.listAllProcesses", constants::APP_NAME), HashMap::new(), ) .await diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index e39e956..b6282ae 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -26,8 +26,6 @@ pub async fn setup_host_module( config: &HostConfig, sender: UnboundedSender, ) -> JunoModule { - MESSAGE_SENDER.write().await.replace(sender); - let mut module = if config.connection_type == "unix_socket" { let socket_path = config.socket_path.as_ref().unwrap(); JunoModule::from_unix_socket(&socket_path) @@ -92,12 +90,20 @@ pub async fn setup_host_module( .register_hook("juno.moduleDeactivated", module_deactivated) .await .unwrap(); + + MESSAGE_SENDER.write().await.replace(sender); module } fn register_node(mut args: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let name = if let Some(Value::String(value)) = args.remove("name") { value } else { @@ -105,9 +111,7 @@ fn register_node(mut args: HashMap) -> Value { }; let (sender, receiver) = channel::>(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() @@ -117,6 +121,7 @@ fn register_node(mut args: HashMap) -> Value { }) .await .unwrap(); + drop(message_sender); let response = receiver.await.unwrap(); if let Ok(()) = response { @@ -133,6 +138,12 @@ fn register_node(mut args: HashMap) -> Value { fn register_process(mut args: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let node_name = if let Some(Value::String(value)) = args.remove("node") { value } else { @@ -240,9 +251,7 @@ fn register_process(mut args: HashMap) -> Value { }; let (sender, receiver) = channel::>(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() @@ -260,6 +269,7 @@ fn register_process(mut args: HashMap) -> Value { }) .await .unwrap(); + drop(message_sender); let response = receiver.await.unwrap(); if let Ok(module_id) = response { @@ -280,6 +290,12 @@ fn register_process(mut args: HashMap) -> Value { fn process_exited(mut args: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let node_name = if let Some(Value::String(value)) = args.remove("node") { value } else { @@ -303,9 +319,7 @@ fn process_exited(mut args: HashMap) -> Value { }; let (sender, receiver) = channel::<(bool, u64)>(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() @@ -317,6 +331,7 @@ fn process_exited(mut args: HashMap) -> Value { }) .await .unwrap(); + drop(message_sender); let (should_restart, wait_duration_millis) = receiver.await.unwrap(); Value::Object({ @@ -334,6 +349,12 @@ fn process_exited(mut args: HashMap) -> Value { fn process_running(mut args: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let node_name = if let Some(Value::String(value)) = args.remove("node") { value } else { @@ -361,9 +382,7 @@ fn process_running(mut args: HashMap) -> Value { return generate_error_response("Module ID is not a number"); }; - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() @@ -374,6 +393,7 @@ fn process_running(mut args: HashMap) -> Value { }) .await .unwrap(); + drop(message_sender); Value::Object({ let mut map = HashMap::new(); @@ -385,6 +405,12 @@ fn process_running(mut args: HashMap) -> Value { fn module_deactivated(data: Value) { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return; + } + let mut args = if let Value::Object(args) = data { args } else { @@ -405,30 +431,34 @@ fn module_deactivated(data: Value) { .skip(constants::APP_NAME.len() + "-node-".len()) .collect(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() .send(GuillotineMessage::NodeDisconnected { node_name }) .await .unwrap(); + drop(message_sender); }); } fn list_modules(_: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let (sender, receiver) = channel::, String>>(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() .send(GuillotineMessage::ListModules { response: sender }) .await .unwrap(); + drop(message_sender); let result = receiver.await.unwrap(); if let Ok(modules) = result { @@ -449,16 +479,21 @@ fn list_modules(_: HashMap) -> Value { fn list_nodes(_: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let (sender, receiver) = channel::>(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() .send(GuillotineMessage::ListNodes { response: sender }) .await .unwrap(); + drop(message_sender); let nodes = receiver.await.unwrap(); Value::Object({ @@ -491,16 +526,21 @@ fn list_nodes(_: HashMap) -> Value { fn list_all_processes(_: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let (sender, receiver) = channel::>(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() .send(GuillotineMessage::ListAllProcesses { response: sender }) .await .unwrap(); + drop(message_sender); let processes = receiver.await.unwrap(); Value::Object({ @@ -584,6 +624,12 @@ fn list_all_processes(_: HashMap) -> Value { fn list_processes(mut args: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let node_name = if let Some(Value::String(value)) = args.remove("node") { value } else { @@ -591,9 +637,7 @@ fn list_processes(mut args: HashMap) -> Value { }; let (sender, receiver) = channel::, String>>(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() @@ -603,6 +647,7 @@ fn list_processes(mut args: HashMap) -> Value { }) .await .unwrap(); + drop(message_sender); let result = receiver.await.unwrap(); if let Ok(processes) = result { @@ -696,6 +741,12 @@ fn list_processes(mut args: HashMap) -> Value { fn restart_process(mut args: HashMap) -> Value { task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { match module_id { Number::PosInt(module_id) => module_id, @@ -707,9 +758,7 @@ fn restart_process(mut args: HashMap) -> Value { }; let (sender, receiver) = channel::>(); - MESSAGE_SENDER - .read() - .await + message_sender .as_ref() .unwrap() .clone() @@ -719,6 +768,7 @@ fn restart_process(mut args: HashMap) -> Value { }) .await .unwrap(); + drop(message_sender); let result = receiver.await.unwrap(); if let Ok(()) = result { diff --git a/src/host/runner.rs b/src/host/runner.rs index e37cad6..072d721 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -15,7 +15,10 @@ use std::{ use async_std::{fs, net::TcpStream, path::Path, sync::Mutex, task}; use future::Either; -use futures::{channel::mpsc::unbounded, future, StreamExt}; +use futures::{ + channel::{mpsc::unbounded, oneshot::Sender}, + future, StreamExt, +}; use futures_timer::Delay; use juno::models::{Number, Value}; @@ -23,7 +26,7 @@ lazy_static! { static ref CLOSE_FLAG: Mutex = Mutex::new(false); } -pub async fn run(mut config: RunnerConfig) { +pub async fn run(mut config: RunnerConfig, initialized_sender: Sender>) { let host = config.host.take().unwrap(); if try_connecting_to_juno(&host).await { @@ -36,7 +39,11 @@ pub async fn run(mut config: RunnerConfig) { Process::new( ModuleRunnerConfig::juno_default( host.path.clone(), - vec![String::from("--socket-location"), socket_path], + vec![ + String::from("--socket-location"), + socket_path, + String::from("-VVV"), + ], ), match &config.logs { Some(log_dir) => { @@ -87,16 +94,27 @@ pub async fn run(mut config: RunnerConfig) { ) }; - keep_host_alive(juno_process, host).await; + keep_host_alive(juno_process, host, initialized_sender).await; } pub async fn on_exit() { *CLOSE_FLAG.lock().await = true; } -async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { +async fn keep_host_alive( + mut juno_process: Process, + juno_config: HostConfig, + initialized_sender: Sender>, +) { // Spawn juno before spawing any modules while !juno_process.is_process_running().0 { + if *CLOSE_FLAG.lock().await { + logger::info("Exit command received. Exiting"); + initialized_sender + .send(Err(String::from("Exiting"))) + .unwrap(); + return; + } juno_process.respawn().await; try_connecting_to_juno(&juno_config).await; } @@ -108,13 +126,18 @@ async fn keep_host_alive(mut juno_process: Process, juno_config: HostConfig) { let (sender, mut command_receiver) = unbounded::(); let mut juno_module = juno_module::setup_host_module(&juno_config, sender.clone()).await; + logger::info("Host initialized. Waiting for nodes to connect"); + initialized_sender.send(Ok(())).unwrap(); + let mut timer_future = Delay::new(Duration::from_millis(100)); let mut command_future = command_receiver.next(); loop { let selection = future::select(timer_future, command_future).await; + match selection { Either::Left((_, next_command_future)) => { if *CLOSE_FLAG.lock().await { + logger::info("Exit command received. Exiting"); break; } diff --git a/src/main.rs b/src/main.rs index 1e77916..624e12e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,6 +121,6 @@ async fn main() { } async fn on_exit() { - logger::info("Recieved exit code. Closing all modules"); + logger::info("Received exit code. Closing all modules"); future::join(runner::on_exit(), cli::on_exit()).await; } diff --git a/src/models/config_data.rs b/src/models/config_data.rs index 936977f..ac5a349 100644 --- a/src/models/config_data.rs +++ b/src/models/config_data.rs @@ -22,7 +22,7 @@ pub struct ConfigTarget { } // Config specific to this environment -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Clone, Debug)] pub struct RunnerConfig { pub name: Option, pub logs: Option, @@ -31,7 +31,7 @@ pub struct RunnerConfig { pub modules: Option, } -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Clone, Debug)] pub struct HostConfig { pub path: String, pub connection_type: String, @@ -40,7 +40,7 @@ pub struct HostConfig { pub socket_path: Option, } -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Clone, Debug)] pub struct NodeConfig { pub connection_type: String, pub port: Option, diff --git a/src/node/juno_module.rs b/src/node/juno_module.rs index a2c9bdb..072633f 100644 --- a/src/node/juno_module.rs +++ b/src/node/juno_module.rs @@ -37,7 +37,14 @@ pub async fn setup_module( .initialize( &format!("{}-node-{}", constants::APP_NAME, node_name), constants::APP_VERSION, - HashMap::new(), + { + let mut map = HashMap::new(); + map.insert( + String::from(constants::APP_NAME), + String::from(constants::APP_VERSION), + ); + map + }, ) .await .unwrap(); diff --git a/src/node/runner.rs b/src/node/runner.rs index 68d7174..235bd47 100644 --- a/src/node/runner.rs +++ b/src/node/runner.rs @@ -3,18 +3,18 @@ use crate::{ node::{juno_module, module::get_module_from_path, process::Process}, utils::{constants, logger}, }; - -use async_std::{fs, net::TcpStream, path::Path, sync::Mutex}; -use future::Either; -use futures::{channel::mpsc::unbounded, future, StreamExt}; -use futures_timer::Delay; -use juno::models::{Number, Value}; use std::{ collections::HashMap, io::Error, time::{Duration, SystemTime, UNIX_EPOCH}, }; +use async_std::{fs, net::TcpStream, path::Path, sync::Mutex, task}; +use future::Either; +use futures::{channel::mpsc::unbounded, future, StreamExt}; +use futures_timer::Delay; +use juno::models::{Number, Value}; + lazy_static! { static ref CLOSE_FLAG: Mutex = Mutex::new(false); } @@ -29,12 +29,16 @@ pub async fn run(mut config: RunnerConfig) { let node = config.node.take().unwrap(); let log_dir = config.logs; - if !try_connecting_to_host(&node).await { + while !try_connecting_to_host(&node).await { logger::error(&format!( - "Could not connect to the host instance of {}. Please check your settings", - constants::APP_NAME + "Could not connect to the host instance of {}. Will try again in {} ms", + constants::APP_NAME, + 1000 )); - return; + task::sleep(Duration::from_millis(1000)).await; + if *CLOSE_FLAG.lock().await { + return; + } } // Populate any auto-start modules here diff --git a/src/runner.rs b/src/runner.rs index b3f6b53..92f8f40 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -2,9 +2,11 @@ use crate::{ host, models::{NodeConfig, RunnerConfig}, node, + utils::logger, }; + use async_std::{fs, path::Path}; -use futures::future::join; +use futures::{channel::oneshot::channel, future}; pub async fn run(mut config: RunnerConfig) { if config.logs.is_some() { @@ -28,12 +30,20 @@ pub async fn run(mut config: RunnerConfig) { socket_path: host.socket_path.clone(), }); - join(host::run(config.clone()), node::run(config)).await; + let (sender, receiver) = channel::>(); + future::join(host::run(config.clone(), sender), async { + if let Err(msg) = receiver.await.unwrap() { + logger::error(&msg); + return; + } + node::run(config).await; + }) + .await; } else if config.node.is_some() { node::run(config).await; } } pub async fn on_exit() { - futures::join!(host::on_exit(), node::on_exit()); + future::join(host::on_exit(), node::on_exit()).await; } From ef55aa0ccc764ae81aba8dcc9fbf0ccb2016e389 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Thu, 21 May 2020 13:00:43 +0530 Subject: [PATCH 11/26] Formatted code --- src/host/juno_module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index b6282ae..5fb2d02 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -90,7 +90,7 @@ pub async fn setup_host_module( .register_hook("juno.moduleDeactivated", module_deactivated) .await .unwrap(); - + MESSAGE_SENDER.write().await.replace(sender); module From 98194bd877ddd824dc6ca0db2eddc1c0bceabe3f Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Thu, 21 May 2020 16:41:11 +0530 Subject: [PATCH 12/26] Added cli commands. Juno-rust doesn't seem to be able to handle nested function calls. The cli seems to hang unless that's implemented --- Cargo.lock | 24 ++-- src/cli/list_all_processes.rs | 192 +++++++++++++++++++++++++++++++ src/cli/list_nodes.rs | 130 +++++++++++++++++++++ src/cli/list_processes.rs | 59 ++++++++-- src/cli/mod.rs | 4 + src/cli/restart_process.rs | 170 +-------------------------- src/host/juno_module.rs | 4 +- src/host/runner.rs | 29 +++-- src/main.rs | 47 ++++++-- src/models/guillotine_message.rs | 4 +- src/models/parser.rs | 7 +- src/node/module.rs | 3 +- src/runner.rs | 11 +- 13 files changed, 461 insertions(+), 223 deletions(-) create mode 100644 src/cli/list_all_processes.rs create mode 100644 src/cli/list_nodes.rs diff --git a/Cargo.lock b/Cargo.lock index 4d84380..c158d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,9 +91,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "cc" -version = "1.0.53" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfg-if" @@ -351,9 +351,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" dependencies = [ "libc", ] @@ -550,18 +550,18 @@ checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" [[package]] name = "pin-project" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d480cb4e89522ccda96d0eed9af94180b7a5f93fb28f66e1fd7d68431663d1" +checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82996f11efccb19b685b14b5df818de31c1edcee3daa256ab5775dd98e72feb" +checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ "proc-macro2", "quote", @@ -603,9 +603,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42934bc9c8ab0d3b273a16d8551c8f0fcff46be73276ca083ec2414c15c4ba5e" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ "proc-macro2", ] @@ -664,9 +664,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" +checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" dependencies = [ "proc-macro2", "quote", diff --git a/src/cli/list_all_processes.rs b/src/cli/list_all_processes.rs new file mode 100644 index 0000000..76b42f1 --- /dev/null +++ b/src/cli/list_all_processes.rs @@ -0,0 +1,192 @@ +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; + +use cli_table::{ + format::{ + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + }, + Cell, Row, Table, +}; +use juno::models::Value; +use std::collections::HashMap; + +pub async fn list_all_processes(config: RunnerConfig) { + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module + } else { + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; + }; + + module + .initialize( + &format!("{}-cli", constants::APP_NAME), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + let response = module + .call_function( + &format!("{}.listAllProcesses", constants::APP_NAME), + HashMap::new(), + ) + .await + .unwrap(); + let processes = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(processes)) = map.remove("processes") { + processes + } else { + logger::error("Invalid processes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); + return; + }; + + // Make the looks first + let header_format = CellFormat::builder() + .align(Align::Center) + .bold(true) + .underline(true) + .build(); + let table_format = TableFormat::new( + Border::builder() + .top(HorizontalLine::new('┌', '┐', '┬', '─')) + .bottom(HorizontalLine::new('└', '┘', '┴', '─')) + .right(VerticalLine::new('│')) + .left(VerticalLine::new('│')) + .build(), + Separator::builder() + .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) + .column(Some(VerticalLine::new('│'))) + .build(), + ); + + // Now make the data + let mut table_data = vec![Row::new(vec![ + Cell::new("ID", header_format), + Cell::new("Name", header_format), + Cell::new("Node", header_format), + Cell::new("Status", header_format), + Cell::new("Restarts", header_format), + Cell::new("Uptime", header_format), + Cell::new("Crashes", header_format), + Cell::new("Created at", header_format), + ])]; + for process in processes.into_iter() { + let process = process.as_object().unwrap(); + table_data.push(Row::new(vec![ + Cell::new( + &format!( + "{}", + process + .get("id") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + process.get("name").unwrap().as_string().unwrap(), + Default::default(), + ), + Cell::new( + process.get("node").unwrap().as_string().unwrap(), + Default::default(), + ), + match process.get("status").unwrap().as_string().unwrap().as_ref() { + "running" => Cell::new( + "running", + CellFormat::builder() + .foreground_color(Some(Color::Green)) + .build(), + ), + "offline" => Cell::new( + "offline", + CellFormat::builder() + .foreground_color(Some(Color::Red)) + .build(), + ), + _ => Cell::new( + "unknown", + CellFormat::builder() + .foreground_color(Some(Color::Cyan)) + .build(), + ), + }, + Cell::new( + &format!( + "{}", + process + .get("restarts") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_duration( + process + .get("uptime") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + Cell::new( + &format!( + "{}", + process + .get("crashes") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_date_time( + process + .get("createdAt") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + ])); + } + let table = Table::new(table_data, table_format); + + // Print it out + table.unwrap().print_stdout().unwrap(); +} diff --git a/src/cli/list_nodes.rs b/src/cli/list_nodes.rs new file mode 100644 index 0000000..cf872fb --- /dev/null +++ b/src/cli/list_nodes.rs @@ -0,0 +1,130 @@ +use crate::{ + cli::get_juno_module_from_config, + models::RunnerConfig, + utils::{constants, logger}, +}; +use cli_table::{ + format::{ + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + }, + Cell, Row, Table, +}; +use juno::models::Value; +use std::collections::HashMap; + +pub async fn list_nodes(config: RunnerConfig) { + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module + } else { + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; + }; + + module + .initialize( + &format!("{}-cli", constants::APP_NAME), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + let response = module + .call_function( + &format!("{}.listNodes", constants::APP_NAME), + HashMap::new(), + ) + .await + .unwrap(); + + let nodes = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(nodes)) = map.remove("nodes") { + nodes + } else { + logger::error("Invalid nodes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); + return; + }; + + // Make the looks first + let header_format = CellFormat::builder() + .align(Align::Center) + .bold(true) + .underline(true) + .build(); + let table_format = TableFormat::new( + Border::builder() + .top(HorizontalLine::new('┌', '┐', '┬', '─')) + .bottom(HorizontalLine::new('└', '┘', '┴', '─')) + .right(VerticalLine::new('│')) + .left(VerticalLine::new('│')) + .build(), + Separator::builder() + .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) + .column(Some(VerticalLine::new('│'))) + .build(), + ); + + // Now make the data + let mut table_data = vec![Row::new(vec![ + Cell::new("Name", header_format), + Cell::new("Connected", header_format), + Cell::new("Modules", header_format), + ])]; + for node in nodes.into_iter() { + let node = node.as_object().unwrap(); + table_data.push(Row::new(vec![ + Cell::new( + node.get("name").unwrap().as_string().unwrap(), + Default::default(), + ), + match node.get("connected").unwrap().as_bool().unwrap() { + true => Cell::new( + "online", + CellFormat::builder() + .foreground_color(Some(Color::Green)) + .build(), + ), + false => Cell::new( + "offline", + CellFormat::builder() + .foreground_color(Some(Color::Red)) + .build(), + ), + }, + Cell::new( + &format!( + "{}", + node.get("modules") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + ])); + } + let table = Table::new(table_data, table_format); + + // Print it out + table.unwrap().print_stdout().unwrap(); +} diff --git a/src/cli/list_processes.rs b/src/cli/list_processes.rs index 0a05891..c2942d7 100644 --- a/src/cli/list_processes.rs +++ b/src/cli/list_processes.rs @@ -1,14 +1,16 @@ use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; +use clap::ArgMatches; use cli_table::{ format::{ Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, }, Cell, Row, Table, }; +use juno::models::Value; use std::collections::HashMap; -pub async fn list_processes(config: RunnerConfig) { +pub async fn list_processes(config: RunnerConfig, args: &ArgMatches<'_>) { let result = get_juno_module_from_config(&config); let mut module = if let Ok(module) = result { module @@ -21,6 +23,13 @@ pub async fn list_processes(config: RunnerConfig) { return; }; + let node = args.value_of("node"); + if node.is_none() { + logger::error("No node supplied!"); + return; + } + let node = node.unwrap(); + module .initialize( &format!("{}-cli", constants::APP_NAME), @@ -29,18 +38,35 @@ pub async fn list_processes(config: RunnerConfig) { ) .await .unwrap(); - let processes = module - .call_function( - &format!("{}.listAllProcesses", constants::APP_NAME), - HashMap::new(), - ) + let response = module + .call_function(&format!("{}.listProcesses", constants::APP_NAME), { + let mut map = HashMap::new(); + map.insert(String::from("node"), Value::String(String::from(node))); + map + }) .await .unwrap(); - if !processes.is_array() { - logger::error(&format!("Expected array response. Got {:?}", processes)); + let processes = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(processes)) = map.remove("processes") { + processes + } else { + logger::error("Invalid processes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); return; - } - let processes = processes.as_array().unwrap(); + }; // Make the looks first let header_format = CellFormat::builder() @@ -65,13 +91,14 @@ pub async fn list_processes(config: RunnerConfig) { let mut table_data = vec![Row::new(vec![ Cell::new("ID", header_format), Cell::new("Name", header_format), + Cell::new("Node", header_format), Cell::new("Status", header_format), Cell::new("Restarts", header_format), Cell::new("Uptime", header_format), Cell::new("Crashes", header_format), Cell::new("Created at", header_format), ])]; - for process in processes.iter() { + for process in processes.into_iter() { let process = process.as_object().unwrap(); table_data.push(Row::new(vec![ Cell::new( @@ -91,6 +118,10 @@ pub async fn list_processes(config: RunnerConfig) { process.get("name").unwrap().as_string().unwrap(), Default::default(), ), + Cell::new( + process.get("node").unwrap().as_string().unwrap(), + Default::default(), + ), match process.get("status").unwrap().as_string().unwrap().as_ref() { "running" => Cell::new( "running", @@ -100,6 +131,12 @@ pub async fn list_processes(config: RunnerConfig) { ), "offline" => Cell::new( "offline", + CellFormat::builder() + .foreground_color(Some(Color::Blue)) + .build(), + ), + "stopped" => Cell::new( + "stopped", CellFormat::builder() .foreground_color(Some(Color::Red)) .build(), diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 07ffef3..b2014ea 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,10 +1,14 @@ mod get_module_info; +mod list_all_processes; mod list_modules; +mod list_nodes; mod list_processes; mod restart_process; pub use get_module_info::get_module_info; +pub use list_all_processes::list_all_processes; pub use list_modules::list_modules; +pub use list_nodes::list_nodes; pub use list_processes::list_processes; pub use restart_process::restart_process; diff --git a/src/cli/restart_process.rs b/src/cli/restart_process.rs index 2470170..82b7168 100644 --- a/src/cli/restart_process.rs +++ b/src/cli/restart_process.rs @@ -1,12 +1,6 @@ use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; use clap::ArgMatches; -use cli_table::{ - format::{ - Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, - }, - Cell, Row, Table, -}; use juno::models::{Number, Value}; use std::collections::HashMap; @@ -43,27 +37,14 @@ pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { .await .unwrap(); - let processes = module - .call_function( - &format!("{}.listProcesses", constants::APP_NAME), - HashMap::new(), - ) - .await - .unwrap(); - let response = module .call_function(&format!("{}.restartProcess", constants::APP_NAME), { let mut map = HashMap::new(); - map.insert( - String::from("processId"), - Value::Number(Number::PosInt(pid)), - ); + map.insert(String::from("moduleId"), Value::Number(Number::PosInt(pid))); map }) .await .unwrap(); - module.close().await; - drop(module); if !response.is_object() { logger::error(&format!("Expected object response. Got {:?}", response)); @@ -77,153 +58,6 @@ pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { logger::error(&format!("Error restarting process: {}", error)); return; } - if !processes.is_array() { - logger::error(&format!("Expected array response. Got {:?}", processes)); - return; - } - let processes = processes.as_array().unwrap(); - - // Make the looks first - let header_format = CellFormat::builder() - .align(Align::Center) - .bold(true) - .underline(true) - .build(); - let table_format = TableFormat::new( - Border::builder() - .top(HorizontalLine::new('┌', '┐', '┬', '─')) - .bottom(HorizontalLine::new('└', '┘', '┴', '─')) - .right(VerticalLine::new('│')) - .left(VerticalLine::new('│')) - .build(), - Separator::builder() - .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) - .column(Some(VerticalLine::new('│'))) - .build(), - ); - - // Now make the data - let mut table_data = vec![Row::new(vec![ - Cell::new("ID", header_format), - Cell::new("Name", header_format), - Cell::new("Status", header_format), - Cell::new("Restarts", header_format), - Cell::new("Uptime", header_format), - Cell::new("Crashes", header_format), - Cell::new("Created at", header_format), - ])]; - for process in processes.iter() { - let process = process.as_object().unwrap(); - table_data.push(Row::new(vec![ - Cell::new( - &format!( - "{}", - process - .get("id") - .unwrap() - .as_number() - .unwrap() - .as_i64() - .unwrap() - ), - Default::default(), - ), - Cell::new( - process.get("name").unwrap().as_string().unwrap(), - Default::default(), - ), - match process.get("status").unwrap().as_string().unwrap().as_ref() { - "running" => Cell::new( - "running", - CellFormat::builder() - .foreground_color(Some(Color::Green)) - .build(), - ), - "offline" => Cell::new( - "offline", - CellFormat::builder() - .foreground_color(Some(Color::Red)) - .build(), - ), - _ => Cell::new( - "unknown", - CellFormat::builder() - .foreground_color(Some(Color::Cyan)) - .build(), - ), - }, - Cell::new( - &format!( - "{}", - if pid - == process - .get("id") - .unwrap() - .as_number() - .unwrap() - .as_i64() - .unwrap() as u64 - { - process - .get("restarts") - .unwrap() - .as_number() - .unwrap() - .as_i64() - .unwrap() + 1 - } else { - process - .get("restarts") - .unwrap() - .as_number() - .unwrap() - .as_i64() - .unwrap() - } - ), - Default::default(), - ), - Cell::new( - &super::get_duration( - process - .get("uptime") - .unwrap() - .as_number() - .unwrap() - .as_i64() - .unwrap(), - ), - Default::default(), - ), - Cell::new( - &format!( - "{}", - process - .get("crashes") - .unwrap() - .as_number() - .unwrap() - .as_i64() - .unwrap() - ), - Default::default(), - ), - Cell::new( - &super::get_date_time( - process - .get("createdAt") - .unwrap() - .as_number() - .unwrap() - .as_i64() - .unwrap(), - ), - Default::default(), - ), - ])); - } - let table = Table::new(table_data, table_format); - // Print it out - table.unwrap().print_stdout().unwrap(); + super::list_all_processes(config).await; } diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 5fb2d02..4c8e481 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -450,7 +450,7 @@ fn list_modules(_: HashMap) -> Value { return generate_error_response("Host module is not initialized yet"); } - let (sender, receiver) = channel::, String>>(); + let (sender, receiver) = channel::>, String>>(); message_sender .as_ref() .unwrap() @@ -467,7 +467,7 @@ fn list_modules(_: HashMap) -> Value { map.insert(String::from("success"), Value::Bool(true)); map.insert( String::from("modules"), - Value::Array(modules.into_iter().map(Value::String).collect()), + Value::Array(modules.into_iter().map(Value::Object).collect()), ); map }) diff --git a/src/host/runner.rs b/src/host/runner.rs index 072d721..6a9e3af 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -26,11 +26,12 @@ lazy_static! { static ref CLOSE_FLAG: Mutex = Mutex::new(false); } -pub async fn run(mut config: RunnerConfig, initialized_sender: Sender>) { +pub async fn run(mut config: RunnerConfig, initialized_sender: Sender>) { let host = config.host.take().unwrap(); if try_connecting_to_juno(&host).await { logger::error("An instance of Juno with the same configuration already seems to be running. Duplicate instances are not allowed!"); + initialized_sender.send(None).unwrap(); return; } @@ -104,15 +105,13 @@ pub async fn on_exit() { async fn keep_host_alive( mut juno_process: Process, juno_config: HostConfig, - initialized_sender: Sender>, + initialized_sender: Sender>, ) { // Spawn juno before spawing any modules while !juno_process.is_process_running().0 { if *CLOSE_FLAG.lock().await { logger::info("Exit command received. Exiting"); - initialized_sender - .send(Err(String::from("Exiting"))) - .unwrap(); + initialized_sender.send(None).unwrap(); return; } juno_process.respawn().await; @@ -127,7 +126,7 @@ async fn keep_host_alive( let mut juno_module = juno_module::setup_host_module(&juno_config, sender.clone()).await; logger::info("Host initialized. Waiting for nodes to connect"); - initialized_sender.send(Ok(())).unwrap(); + initialized_sender.send(Some(())).unwrap(); let mut timer_future = Delay::new(Duration::from_millis(100)); let mut command_future = command_receiver.next(); @@ -333,17 +332,23 @@ async fn keep_host_alive( continue; } let modules = result.unwrap(); - if !modules.is_array() { + let modules = if let Value::Array(modules) = modules { + modules + } else { response .send(Err(format!("Expected array response. Got {:?}", modules))) .unwrap(); return; - } + }; let modules = modules - .as_array() - .unwrap() - .iter() - .map(|value| value.as_string().unwrap().clone()) + .into_iter() + .filter_map(|value| { + if let Value::Object(map) = value { + Some(map) + } else { + None + } + }) .collect(); response.send(Ok(modules)).unwrap(); } diff --git a/src/main.rs b/src/main.rs index 624e12e..664400d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,20 +44,36 @@ async fn main() { .subcommand( SubCommand::with_name("run").about("Run the application with a given config file"), ) - .subcommand( - SubCommand::with_name("list-processes") - .alias("lp") - .about("List the running processes and their statuses"), - ) .subcommand( SubCommand::with_name("list-modules") .alias("lm") .about("List the modules connected and their statuses"), ) .subcommand( - SubCommand::with_name("info") - .alias("i") - .about("Get information about a process / module") + SubCommand::with_name("list-nodes") + .alias("ln") + .about("List all the nodes registered with this host and their details"), + ) + .subcommand( + SubCommand::with_name("list-all-processes") + .alias("lap") + .about("List all running processes and their states across all nodes"), + ) + .subcommand( + SubCommand::with_name("list-processes") + .alias("lp") + .about("List the running processes and their statuses for a given node") + .arg( + Arg::with_name("node") + .short("n") + .long("node") + .takes_value(true) + .required(false), + ), + ) + .subcommand( + SubCommand::with_name("restart") + .about("Restarts a process with a processId") .arg( Arg::with_name("pid") .takes_value(true) @@ -66,8 +82,9 @@ async fn main() { ), ) .subcommand( - SubCommand::with_name("restart") - .about("Restarts a process with a processId") + SubCommand::with_name("info") + .alias("i") + .about("Get information about a process / module") .arg( Arg::with_name("pid") .takes_value(true) @@ -111,11 +128,17 @@ async fn main() { let config = config_result.unwrap(); match args.subcommand() { - ("run", Some(_)) => runner::run(config).await, - ("list-processes", Some(_)) => cli::list_processes(config).await, + // Host or node stuff + ("run", Some(_)) | ("", _) => runner::run(config).await, + + // Cli stuff ("list-modules", Some(_)) => cli::list_modules(config).await, + ("list-nodes", Some(_)) => cli::list_nodes(config).await, + ("list-all-processes", Some(_)) => cli::list_all_processes(config).await, + ("list-processes", Some(args)) => cli::list_processes(config, args).await, ("info", Some(args)) => cli::get_module_info(config, args).await, ("restart", Some(args)) => cli::restart_process(config, args).await, + (cmd, _) => println!("Unknown command '{}'", cmd), } } diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index b2bdfaa..7dab02a 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -1,5 +1,7 @@ use crate::models::{GuillotineNode, ProcessData}; use futures::channel::oneshot::Sender; +use juno::models::Value; +use std::collections::HashMap; #[allow(dead_code)] #[derive(Debug)] @@ -31,7 +33,7 @@ pub enum GuillotineMessage { // Cli stuff ListModules { - response: Sender, String>>, + response: Sender>, String>>, }, ListNodes { response: Sender>, diff --git a/src/models/parser.rs b/src/models/parser.rs index 41d266d..414f2a5 100644 --- a/src/models/parser.rs +++ b/src/models/parser.rs @@ -99,8 +99,13 @@ async fn parse_config(mut input: RunnerConfig) -> Result { .unwrap() .to_string(); if input.logs.is_some() { + let logs = input.logs.unwrap(); + let log_path = Path::new(&logs); + if !log_path.exists().await { + fs::create_dir(log_path).await.unwrap(); + } input.logs = Some( - fs::canonicalize(input.logs.unwrap()) + fs::canonicalize(logs) .await .unwrap() .to_str() diff --git a/src/node/module.rs b/src/node/module.rs index 06ef2ad..cb55973 100644 --- a/src/node/module.rs +++ b/src/node/module.rs @@ -50,7 +50,8 @@ pub async fn get_module_from_path(path: &str, log_dir: Option) -> Option if !sub_dir.exists().await { fs::create_dir(&sub_dir).await.unwrap(); } - Process::new(config, Some(log_dir), working_dir) + let log_sub_dir = sub_dir.to_str().unwrap().to_owned(); + Process::new(config, Some(log_sub_dir), working_dir) } else { Process::new(config, None, working_dir) }) diff --git a/src/runner.rs b/src/runner.rs index 92f8f40..36ac7d9 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -30,10 +30,15 @@ pub async fn run(mut config: RunnerConfig) { socket_path: host.socket_path.clone(), }); - let (sender, receiver) = channel::>(); + let (sender, receiver) = channel::>(); future::join(host::run(config.clone(), sender), async { - if let Err(msg) = receiver.await.unwrap() { - logger::error(&msg); + let response = receiver.await; + if response.is_err() { + logger::error("Host didn't start properly yet"); + return; + } + + if response.unwrap().is_none() { return; } node::run(config).await; From c375a8f6b6805142b093839a9ad4faa50da884b8 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Thu, 21 May 2020 23:25:01 +0530 Subject: [PATCH 13/26] Restarts seem to work upon changing juno-rust to the new, half-non-mpsc half-thread-based connections --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/cli/restart_process.rs | 166 ++++++++++++++++++++++++++++++++++++- src/host/runner.rs | 12 ++- 4 files changed, 177 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c158d83..fb7e1a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,9 +375,7 @@ checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "juno" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e3ce61bde449eae0e4f3aff3aec02e06b03b9bfe2acb6bc96a6e2fe89afcc6" +version = "0.1.3-4" dependencies = [ "async-std", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 40fc904..fff6241 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ clap = "2" serde_json = "1" serde = "1" serde_derive = "1" -juno = "0.1.1" +juno = { path = "../juno-rust" } futures = "0.3.4" futures-timer = "3.0.2" lazy_static = "1.4.0" diff --git a/src/cli/restart_process.rs b/src/cli/restart_process.rs index 82b7168..d1d55dc 100644 --- a/src/cli/restart_process.rs +++ b/src/cli/restart_process.rs @@ -1,6 +1,12 @@ use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; use clap::ArgMatches; +use cli_table::{ + format::{ + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + }, + Cell, Row, Table, +}; use juno::models::{Number, Value}; use std::collections::HashMap; @@ -59,5 +65,163 @@ pub async fn restart_process(config: RunnerConfig, args: &ArgMatches<'_>) { return; } - super::list_all_processes(config).await; + let response = module + .call_function( + &format!("{}.listAllProcesses", constants::APP_NAME), + HashMap::new(), + ) + .await + .unwrap(); + let processes = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(processes)) = map.remove("processes") { + processes + } else { + logger::error("Invalid processes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); + return; + }; + + // Make the looks first + let header_format = CellFormat::builder() + .align(Align::Center) + .bold(true) + .underline(true) + .build(); + let table_format = TableFormat::new( + Border::builder() + .top(HorizontalLine::new('┌', '┐', '┬', '─')) + .bottom(HorizontalLine::new('└', '┘', '┴', '─')) + .right(VerticalLine::new('│')) + .left(VerticalLine::new('│')) + .build(), + Separator::builder() + .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) + .column(Some(VerticalLine::new('│'))) + .build(), + ); + + // Now make the data + let mut table_data = vec![Row::new(vec![ + Cell::new("ID", header_format), + Cell::new("Name", header_format), + Cell::new("Node", header_format), + Cell::new("Status", header_format), + Cell::new("Restarts", header_format), + Cell::new("Uptime", header_format), + Cell::new("Crashes", header_format), + Cell::new("Created at", header_format), + ])]; + for process in processes.into_iter() { + let process = process.as_object().unwrap(); + table_data.push(Row::new(vec![ + Cell::new( + &format!( + "{}", + process + .get("id") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + process.get("name").unwrap().as_string().unwrap(), + Default::default(), + ), + Cell::new( + process.get("node").unwrap().as_string().unwrap(), + Default::default(), + ), + match process.get("status").unwrap().as_string().unwrap().as_ref() { + "running" => Cell::new( + "running", + CellFormat::builder() + .foreground_color(Some(Color::Green)) + .build(), + ), + "offline" => Cell::new( + "offline", + CellFormat::builder() + .foreground_color(Some(Color::Red)) + .build(), + ), + _ => Cell::new( + "unknown", + CellFormat::builder() + .foreground_color(Some(Color::Cyan)) + .build(), + ), + }, + Cell::new( + &format!( + "{}", + process + .get("restarts") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_duration( + process + .get("uptime") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + Cell::new( + &format!( + "{}", + process + .get("crashes") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_date_time( + process + .get("createdAt") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + ])); + } + let table = Table::new(table_data, table_format); + + // Print it out + table.unwrap().print_stdout().unwrap(); } diff --git a/src/host/runner.rs b/src/host/runner.rs index 6a9e3af..2032d4d 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -153,7 +153,7 @@ async fn keep_host_alive( // But how will you tell the nodes to restart if juno isn't running to tell them to restart? // Fuck this shit, don't care (╯°□°)╯︵ ┻━┻ - juno_module.close().await; + juno_module.close().await.unwrap(); drop(juno_module); juno_module = juno_module::setup_host_module(&juno_config, sender.clone()).await; } @@ -389,7 +389,7 @@ async fn keep_host_alive( response, } => { let node = node_runners - .values() + .values_mut() .find(|node| node.get_process_by_id(module_id).is_some()); if node.is_none() { response @@ -475,6 +475,14 @@ async fn keep_host_alive( .unwrap(); continue; } + let process = node.get_process_by_id_mut(module_id); + if process.is_none() { + response.send(Err(String::from("Node notified successful process restart, but couldn't find the process in host's memory. Data may be stale"))).unwrap(); + continue; + } + let process = process.unwrap(); + process.restarts += 1; + process.last_started_at = get_current_millis(); response.send(Ok(())).unwrap(); } msg => panic!("Unhandled guillotine message: {:#?}", msg), From ac68473217f20baeb87d68a0e6272b5167e06634 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Tue, 26 May 2020 09:47:05 +0530 Subject: [PATCH 14/26] Removed box for registering process. --- src/host/juno_module.rs | 14 ++++++-------- src/host/runner.rs | 29 ++++++++++++++++++++++------- src/models/guillotine_message.rs | 8 +++++++- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 4c8e481..1755635 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -257,14 +257,12 @@ fn register_process(mut args: HashMap) -> Value { .clone() .send(GuillotineMessage::RegisterProcess { node_name, - process_data: Box::new(ProcessData::new( - log_dir, - working_dir, - config, - status, - last_started_at, - created_at, - )), + process_log_dir: log_dir, + process_working_dir: working_dir, + process_config: config, + process_status: status, + process_last_started_at: last_started_at, + process_created_at: created_at, response: sender, }) .await diff --git a/src/host/runner.rs b/src/host/runner.rs index 2032d4d..91e72f1 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -2,7 +2,7 @@ use crate::{ host::juno_module, models::{ GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, - RunnerConfig, + RunnerConfig, ProcessData, }, node::Process, utils::{constants, logger}, @@ -189,7 +189,12 @@ async fn keep_host_alive( } GuillotineMessage::RegisterProcess { node_name, - mut process_data, + process_log_dir, + process_working_dir, + process_config, + process_status, + process_last_started_at, + process_created_at, response, } => { if !node_runners.contains_key(&node_name) { @@ -200,10 +205,19 @@ async fn keep_host_alive( // Find a runner which runs a module of the same name let runner = node_runners.values_mut().find(|runner| { runner - .get_process_by_name(&process_data.config.name) + .get_process_by_name(&process_config.name) .is_some() }); + let mut process_data = ProcessData::new( + process_log_dir, + process_working_dir, + process_config, + process_status, + process_last_started_at, + process_created_at, + ); + // There's a runner which runs a module of the same name if let Some(runner) = runner { // That runner isn't what's registering the process @@ -220,10 +234,11 @@ async fn keep_host_alive( } // There's a stale module re-registering itself. - process_data.module_id = process.unwrap().module_id; - runner.register_process(process_data.as_ref().clone()); + let module_id = process.unwrap().module_id; + process_data.module_id = module_id; + runner.register_process(process_data); - response.send(Ok(process_data.module_id)).unwrap(); + response.send(Ok(module_id)).unwrap(); continue; } @@ -240,7 +255,7 @@ async fn keep_host_alive( let assigned_pid = pid; pid += 1; process_data.module_id = assigned_pid; - runner.register_process(process_data.as_ref().clone()); + runner.register_process(process_data); response.send(Ok(assigned_pid)).unwrap(); } diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index 7dab02a..9c58913 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -2,6 +2,7 @@ use crate::models::{GuillotineNode, ProcessData}; use futures::channel::oneshot::Sender; use juno::models::Value; use std::collections::HashMap; +use super::{ModuleRunningStatus, ModuleRunnerConfig}; #[allow(dead_code)] #[derive(Debug)] @@ -13,7 +14,12 @@ pub enum GuillotineMessage { }, RegisterProcess { node_name: String, - process_data: Box, + process_log_dir: Option, + process_working_dir: String, + process_config: ModuleRunnerConfig, + process_status: ModuleRunningStatus, + process_last_started_at: u64, + process_created_at: u64, response: Sender>, }, ProcessExited { From e68872f1456fc65c55982a3bf0e3582fd22cf5b0 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Tue, 26 May 2020 13:30:28 +0530 Subject: [PATCH 15/26] Added separation of get_process_info and get_module_info, all under the same get_info module --- src/cli/{ => get_info}/get_module_info.rs | 14 +- src/cli/get_info/get_process_info.rs | 298 ++++++++++++++++++++++ src/cli/get_info/mod.rs | 19 ++ src/cli/mod.rs | 4 +- src/host/juno_module.rs | 174 +++++++++++-- src/host/runner.rs | 42 ++- src/main.rs | 2 +- src/models/guillotine_message.rs | 7 +- 8 files changed, 513 insertions(+), 47 deletions(-) rename src/cli/{ => get_info}/get_module_info.rs (92%) create mode 100644 src/cli/get_info/get_process_info.rs create mode 100644 src/cli/get_info/mod.rs diff --git a/src/cli/get_module_info.rs b/src/cli/get_info/get_module_info.rs similarity index 92% rename from src/cli/get_module_info.rs rename to src/cli/get_info/get_module_info.rs index fef0f13..f074091 100644 --- a/src/cli/get_module_info.rs +++ b/src/cli/get_info/get_module_info.rs @@ -1,6 +1,5 @@ use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; -use clap::ArgMatches; use cli_table::{ format::{ Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, @@ -10,7 +9,7 @@ use cli_table::{ use juno::models::Value; use std::collections::HashMap; -pub async fn get_module_info(config: RunnerConfig, args: &ArgMatches<'_>) { +pub async fn get_module_info(config: RunnerConfig, module_id: &str) { let result = get_juno_module_from_config(&config); let mut module = if let Ok(module) = result { module @@ -22,12 +21,6 @@ pub async fn get_module_info(config: RunnerConfig, args: &ArgMatches<'_>) { }); return; }; - let module_id = args.value_of("pid"); - if module_id.is_none() { - logger::error("No pid supplied!"); - return; - } - let module_id = module_id.unwrap(); module .initialize( @@ -78,8 +71,7 @@ pub async fn get_module_info(config: RunnerConfig, args: &ArgMatches<'_>) { ); // Now make the data - let mut table_data = vec![]; - table_data.extend(vec![ + let table_data = vec![ Row::new(vec![ Cell::new("Module ID", header_format), Cell::new( @@ -162,7 +154,7 @@ pub async fn get_module_info(config: RunnerConfig, args: &ArgMatches<'_>) { Default::default(), ), ]), - ]); + ]; let table = Table::new(table_data, table_format); // Print it out diff --git a/src/cli/get_info/get_process_info.rs b/src/cli/get_info/get_process_info.rs new file mode 100644 index 0000000..c2ef864 --- /dev/null +++ b/src/cli/get_info/get_process_info.rs @@ -0,0 +1,298 @@ +use crate::{ + cli::{get_date_time, get_juno_module_from_config, get_duration}, + models::RunnerConfig, + utils::{constants, logger}, +}; +use cli_table::{ + format::{ + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + }, + Cell, Row, Table, +}; +use juno::models::{Number, Value}; +use std::collections::HashMap; + +pub async fn get_process_info(config: RunnerConfig, pid: u64) { + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module + } else { + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; + }; + + module + .initialize( + &format!("{}-cli", constants::APP_NAME), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + let response = module + .call_function( + &format!("{}.getProcessInfo", constants::APP_NAME), + [(String::from("moduleId"), Value::Number(Number::PosInt(pid)))] + .iter() + .cloned() + .collect(), + ) + .await + .unwrap(); + let process = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Object(process)) = map.remove("process") { + process + } else { + logger::error("Invalid process key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); + return; + }; + let config = if let Some(Value::Object(config)) = process.get("config") { + config + } else { + logger::error("Invalid config key in response"); + return; + }; + + // Make the looks first + let header_format = CellFormat::builder() + .align(Align::Center) + .bold(true) + .build(); + let table_format = TableFormat::new( + Border::builder() + .top(HorizontalLine::new('┌', '┐', '┬', '─')) + .bottom(HorizontalLine::new('└', '┘', '┴', '─')) + .right(VerticalLine::new('│')) + .left(VerticalLine::new('│')) + .build(), + Separator::builder() + .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) + .column(Some(VerticalLine::new('│'))) + .build(), + ); + + // Now make the data + let mut table_data = vec![]; + table_data.extend(vec![ + Row::new(vec![ + Cell::new("Module ID", header_format), + Cell::new( + &format!( + "{}", + process + .get("id") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Name", header_format), + Cell::new( + process.get("name").unwrap().as_string().unwrap(), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Command", header_format), + Cell::new( + config.get("command").unwrap().as_string().unwrap(), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Intepreter", header_format), + Cell::new( + if let Some(Value::String(intepreter)) = config.get("intepreter") { + intepreter.as_ref() + } else { + "-" + }, + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Arguments", header_format), + Cell::new( + &constrain_string_to( + config + .get("args") + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|arg| arg.as_string().unwrap().clone()) + .collect(), + 55, + ), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Environment", header_format), + Cell::new( + &constrain_string_to( + config + .get("envs") + .unwrap() + .as_object() + .unwrap() + .iter() + .map(|(key, value)| format!("{}={}", key, value.as_string().unwrap())) + .collect(), + 55, + ), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Log Dir", header_format), + Cell::new( + if let Some(Value::String(log_dir)) = process.get("logDir") { + log_dir.as_ref() + } else { + "-" + }, + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Working Dir", header_format), + Cell::new( + process.get("workingDir").unwrap().as_string().unwrap(), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Status", header_format), + match process.get("status").unwrap().as_string().unwrap().as_ref() { + "running" => Cell::new( + "running", + CellFormat::builder() + .foreground_color(Some(Color::Green)) + .build(), + ), + "offline" => Cell::new( + "offline", + CellFormat::builder() + .foreground_color(Some(Color::Red)) + .build(), + ), + _ => Cell::new( + "unknown", + CellFormat::builder() + .foreground_color(Some(Color::Cyan)) + .build(), + ), + }, + ]), + Row::new(vec![ + Cell::new("Node", header_format), + Cell::new( + process.get("node").unwrap().as_string().unwrap(), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Uptime", header_format), + Cell::new( + &get_duration( + process + .get("uptime") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Restarts", header_format), + Cell::new( + &format!( + "{}", + process + .get("restarts") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Crashes", header_format), + Cell::new( + &format!( + "{}", + process + .get("crashes") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + ]), + Row::new(vec![ + Cell::new("Created At", header_format), + Cell::new( + &get_date_time( + process + .get("createdAt") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + ]), + ]); + let table = Table::new(table_data, table_format); + + // Print it out + table.unwrap().print_stdout().unwrap(); +} + +fn constrain_string_to(array: Vec, max_length: usize) -> String { + let total_string = array.join(", "); + if total_string.len() > max_length { + let mut total_string: String = total_string.chars().take(max_length - 3).collect(); + total_string.push_str("..."); + total_string + } else if total_string.is_empty() { + String::from("-") + } else { + total_string + } +} diff --git a/src/cli/get_info/mod.rs b/src/cli/get_info/mod.rs new file mode 100644 index 0000000..76b6599 --- /dev/null +++ b/src/cli/get_info/mod.rs @@ -0,0 +1,19 @@ +mod get_module_info; +mod get_process_info; + +use crate::{models::RunnerConfig, utils::logger}; +use clap::ArgMatches; + +pub async fn get_info(config: RunnerConfig, args: &ArgMatches<'_>) { + let module_id = args.value_of("pid"); + if module_id.is_none() { + logger::error("No pid supplied!"); + return; + } + let module_id = module_id.unwrap(); + if let Ok(pid) = module_id.parse::() { + get_process_info::get_process_info(config, pid).await; + } else { + get_module_info::get_module_info(config, module_id).await; + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index b2014ea..575a6f9 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,11 +1,11 @@ -mod get_module_info; +mod get_info; mod list_all_processes; mod list_modules; mod list_nodes; mod list_processes; mod restart_process; -pub use get_module_info::get_module_info; +pub use get_info::get_info; pub use list_all_processes::list_all_processes; pub use list_modules::list_modules; pub use list_nodes::list_nodes; diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 1755635..18c58fd 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -81,6 +81,11 @@ pub async fn setup_host_module( .await .unwrap(); + module + .declare_function("getProcessInfo", get_process_info) + .await + .unwrap(); + module .declare_function("restartProcess", restart_process) .await @@ -555,8 +560,6 @@ fn list_all_processes(_: HashMap) -> Value { let ProcessData { module_id, config, - log_dir, - working_dir, status, restarts, crashes, @@ -573,16 +576,6 @@ fn list_all_processes(_: HashMap) -> Value { ); map.insert(String::from("name"), Value::String(config.name)); - map.insert( - String::from("logDir"), - if let Some(log_dir) = log_dir { - Value::String(log_dir) - } else { - Value::Null - }, - ); - map.insert(String::from("workingDir"), Value::String(working_dir)); - map.insert( String::from("status"), Value::String(String::from(match status { @@ -663,8 +656,6 @@ fn list_processes(mut args: HashMap) -> Value { let ProcessData { module_id, config, - log_dir, - working_dir, status, restarts, crashes, @@ -681,19 +672,6 @@ fn list_processes(mut args: HashMap) -> Value { ); map.insert(String::from("name"), Value::String(config.name)); - map.insert( - String::from("logDir"), - if let Some(log_dir) = log_dir { - Value::String(log_dir) - } else { - Value::Null - }, - ); - map.insert( - String::from("workingDir"), - Value::String(working_dir), - ); - map.insert( String::from("status"), Value::String(String::from(match status { @@ -737,6 +715,148 @@ fn list_processes(mut args: HashMap) -> Value { }) } +fn get_process_info(mut args: HashMap) -> Value { + task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + message_sender + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::GetProcessInfo { + module_id, + response: sender, + }) + .await + .unwrap(); + drop(message_sender); + + let result = receiver.await.unwrap(); + if let Ok((node_name, process)) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert( + String::from("process"), + Value::Object({ + let uptime = process.get_uptime(); + + let ProcessData { + log_dir, + working_dir, + module_id, + config, + status, + restarts, + crashes, + created_at, + .. + } = process; + let mut map = HashMap::new(); + + map.insert(String::from("id"), Value::Number(Number::PosInt(module_id))); + map.insert(String::from("name"), Value::String(config.name)); + + map.insert( + String::from("config"), + Value::Object({ + let mut map = HashMap::new(); + + map.insert(String::from("command"), Value::String(config.command)); + + if let Some(interpreter) = config.interpreter { + map.insert( + String::from("interpreter"), + Value::String(interpreter), + ); + } + + map.insert( + String::from("args"), + Value::Array(if let Some(args) = config.args { + args.into_iter().map(Value::String).collect() + } else { + vec![] + }), + ); + + map.insert( + String::from("envs"), + Value::Object(if let Some(envs) = config.envs { + envs.into_iter() + .map(|(key, value)| (key, Value::String(value))) + .collect() + } else { + HashMap::new() + }), + ); + + map + }), + ); + + map.insert( + String::from("logDir"), + if let Some(log_dir) = log_dir { + Value::String(log_dir) + } else { + Value::Null + }, + ); + map.insert(String::from("workingDir"), Value::String(working_dir)); + + map.insert( + String::from("status"), + Value::String(String::from(match status { + ModuleRunningStatus::Running => "running", + ModuleRunningStatus::Stopped => "stopped", + ModuleRunningStatus::Offline => "offline", + })), + ); + map.insert(String::from("node"), Value::String(node_name.clone())); + map.insert( + String::from("restarts"), + Value::Number(Number::NegInt(restarts)), + ); + map.insert( + String::from("uptime"), + Value::Number(Number::PosInt(uptime)), + ); + map.insert( + String::from("crashes"), + Value::Number(Number::PosInt(crashes)), + ); + map.insert( + String::from("createdAt"), + Value::Number(Number::PosInt(created_at)), + ); + + map + }), + ); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + fn restart_process(mut args: HashMap) -> Value { task::block_on(async { let message_sender = MESSAGE_SENDER.read().await; diff --git a/src/host/runner.rs b/src/host/runner.rs index 91e72f1..efbbff4 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -2,7 +2,7 @@ use crate::{ host::juno_module, models::{ GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, - RunnerConfig, ProcessData, + ProcessData, RunnerConfig, }, node::Process, utils::{constants, logger}, @@ -204,9 +204,7 @@ async fn keep_host_alive( // Find a runner which runs a module of the same name let runner = node_runners.values_mut().find(|runner| { - runner - .get_process_by_name(&process_config.name) - .is_some() + runner.get_process_by_name(&process_config.name).is_some() }); let mut process_data = ProcessData::new( @@ -500,6 +498,42 @@ async fn keep_host_alive( process.last_started_at = get_current_millis(); response.send(Ok(())).unwrap(); } + GuillotineMessage::GetProcessInfo { + module_id, + response, + } => { + let node = node_runners.values().find_map(|node| { + if let Some(process) = node.get_process_by_id(module_id) { + Some((node, process)) + } else { + None + } + }); + if node.is_none() { + response + .send(Err(format!( + "No node found running the module with the ID {}", + module_id + ))) + .unwrap(); + continue; + } + let node = node.unwrap(); + if !node.0.connected { + response + .send(Err(format!( + "The node (with the name '{}') running the module {} is not connected", + node.0.name, + module_id + ))) + .unwrap(); + continue; + } + + response + .send(Ok((node.0.name.clone(), node.1.clone()))) + .unwrap(); + } msg => panic!("Unhandled guillotine message: {:#?}", msg), } } diff --git a/src/main.rs b/src/main.rs index 664400d..1728ddd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -136,7 +136,7 @@ async fn main() { ("list-nodes", Some(_)) => cli::list_nodes(config).await, ("list-all-processes", Some(_)) => cli::list_all_processes(config).await, ("list-processes", Some(args)) => cli::list_processes(config, args).await, - ("info", Some(args)) => cli::get_module_info(config, args).await, + ("info", Some(args)) => cli::get_info(config, args).await, ("restart", Some(args)) => cli::restart_process(config, args).await, (cmd, _) => println!("Unknown command '{}'", cmd), diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index 9c58913..e6051a7 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -1,8 +1,8 @@ +use super::{ModuleRunnerConfig, ModuleRunningStatus}; use crate::models::{GuillotineNode, ProcessData}; use futures::channel::oneshot::Sender; use juno::models::Value; use std::collections::HashMap; -use super::{ModuleRunningStatus, ModuleRunnerConfig}; #[allow(dead_code)] #[derive(Debug)] @@ -55,11 +55,14 @@ pub enum GuillotineMessage { module_id: u64, response: Sender>, }, + GetProcessInfo { + module_id: u64, + response: Sender>, // (node_name, process_data) + }, AddProcess, StopProcess, StartProcess, DeleteProcess, - Info, } // TODO ADD: // ReloadConfig, From 2508b146d75a3d17093d6a9f9c1363ef68324d9d Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Tue, 26 May 2020 15:41:25 +0530 Subject: [PATCH 16/26] Updated juno to 0.1.4 --- Cargo.lock | 383 +++++++++++++++++++++++++++++++++-------------------- Cargo.toml | 2 +- 2 files changed, 239 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb7e1a6..840b066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.8", + "winapi", ] [[package]] @@ -21,39 +21,34 @@ dependencies = [ [[package]] name = "async-std" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +checksum = "a45cee2749d880d7066e328a7e161c7470ced883b2fd000ca4643e9f1dd5083a" dependencies = [ "async-attributes", "async-task", - "crossbeam-channel", - "crossbeam-deque", "crossbeam-utils", + "futures-channel", "futures-core", "futures-io", - "futures-timer 2.0.2", + "futures-timer", "kv-log-macro", "log", "memchr", - "mio", - "mio-uds", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", + "smol", + "wasm-bindgen-futures", ] [[package]] name = "async-task" -version = "1.3.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" -dependencies = [ - "libc", - "winapi 0.3.8", -] +checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" [[package]] name = "async-trait" @@ -74,7 +69,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -89,6 +84,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bumpalo" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" + [[package]] name = "cc" version = "1.0.54" @@ -129,9 +130,9 @@ dependencies = [ [[package]] name = "cli-table" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c33fefd3d47608151daee022309798b78d25bb3320b378258822ce2edc2abe7" +checksum = "bd782cbfda62468ed8f94f2c00496ff909ad4916f4411ab9ec7bdced5414a699" dependencies = [ "termcolor", "unicode-width", @@ -145,7 +146,21 @@ checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" dependencies = [ "atty", "lazy_static", - "winapi 0.3.8", + "winapi", +] + +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", ] [[package]] @@ -184,6 +199,16 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "crossbeam-queue" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -202,25 +227,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a4ba686dff9fa4c1c9636ce1010b0cf98ceb421361b0bb3d6faeec43bd217a7" dependencies = [ "nix", - "winapi 0.3.8", + "winapi", ] -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures" version = "0.3.5" @@ -296,17 +305,15 @@ dependencies = [ "once_cell", ] -[[package]] -name = "futures-timer" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" - [[package]] name = "futures-timer" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +dependencies = [ + "gloo-timers", + "send_wrapper", +] [[package]] name = "futures-util" @@ -328,6 +335,19 @@ dependencies = [ "slab", ] +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "guillotine" version = "0.1.0" @@ -339,14 +359,14 @@ dependencies = [ "colored", "ctrlc", "futures", - "futures-timer 3.0.2", + "futures-timer", "juno", "lazy_static", "nix", "serde", "serde_derive", "serde_json", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -358,24 +378,26 @@ dependencies = [ "libc", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +[[package]] +name = "js-sys" +version = "0.3.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "juno" -version = "0.1.3-4" +version = "0.1.4-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "837e835ffcf014e672d708848e11e000bd9d64608005d21333ec437a3331b35a" dependencies = [ "async-std", "async-trait", @@ -384,21 +406,11 @@ dependencies = [ "serde_json", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "kv-log-macro" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" +checksum = "4ff57d6d215f7ca7eb35a9a64d656ba4d9d2bef114d741dc08048e75e2f5d418" dependencies = [ "log", ] @@ -445,59 +457,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mio" -version = "0.6.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" -dependencies = [ - "cfg-if", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio", -] - -[[package]] -name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - -[[package]] -name = "net2" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" -dependencies = [ - "cfg-if", - "libc", - "winapi 0.3.8", -] - [[package]] name = "nix" version = "0.17.0" @@ -578,11 +537,23 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0deb65f46e873ba8aa7c6a8dbe3f23cb1bf59c339a81a1d56361dde4d66ac8" +dependencies = [ + "crossbeam-utils", + "futures-io", + "futures-sink", + "futures-util", +] + [[package]] name = "proc-macro-hack" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro-nested" @@ -592,9 +563,9 @@ checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.13" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" +checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" dependencies = [ "unicode-xid", ] @@ -608,18 +579,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + [[package]] name = "ryu" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +[[package]] +name = "scoped-tls-hkt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + [[package]] name = "serde" version = "1.0.110" @@ -654,6 +643,37 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "smol" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686c634ad1873fffef6aed20f180eede424fbf3bb31802394c90fd7335a661b7" +dependencies = [ + "async-task", + "crossbeam", + "futures-io", + "futures-util", + "nix", + "once_cell", + "piper", + "scoped-tls-hkt", + "slab", + "socket2", + "wepoll-binding", +] + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "strsim" version = "0.8.0" @@ -662,9 +682,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" +checksum = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f" dependencies = [ "proc-macro2", "quote", @@ -696,7 +716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -724,10 +744,99 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] -name = "winapi" -version = "0.2.8" +name = "wasm-bindgen" +version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" + +[[package]] +name = "web-sys" +version = "0.3.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-binding" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7" +dependencies = [ + "bitflags", + "wepoll-sys", +] + +[[package]] +name = "wepoll-sys" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" +dependencies = [ + "cc", +] [[package]] name = "winapi" @@ -739,12 +848,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -757,7 +860,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.8", + "winapi", ] [[package]] @@ -765,13 +868,3 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] diff --git a/Cargo.toml b/Cargo.toml index fff6241..249bedd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ clap = "2" serde_json = "1" serde = "1" serde_derive = "1" -juno = { path = "../juno-rust" } +juno = "0.1.4-beta" futures = "0.3.4" futures-timer = "3.0.2" lazy_static = "1.4.0" From 478e7fb9267ac1f786c303d6bbacd13906a1bc9a Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Tue, 26 May 2020 15:48:58 +0530 Subject: [PATCH 17/26] Formatted code and added clippy suggestions --- src/cli/get_info/get_process_info.rs | 4 ++-- src/host/juno_module.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/get_info/get_process_info.rs b/src/cli/get_info/get_process_info.rs index c2ef864..52809b5 100644 --- a/src/cli/get_info/get_process_info.rs +++ b/src/cli/get_info/get_process_info.rs @@ -1,5 +1,5 @@ use crate::{ - cli::{get_date_time, get_juno_module_from_config, get_duration}, + cli::{get_date_time, get_duration, get_juno_module_from_config}, models::RunnerConfig, utils::{constants, logger}, }; @@ -225,7 +225,7 @@ pub async fn get_process_info(config: RunnerConfig, pid: u64) { .as_number() .unwrap() .as_i64() - .unwrap() + .unwrap(), ), Default::default(), ), diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 18c58fd..4ee2130 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -828,7 +828,7 @@ fn get_process_info(mut args: HashMap) -> Value { ModuleRunningStatus::Offline => "offline", })), ); - map.insert(String::from("node"), Value::String(node_name.clone())); + map.insert(String::from("node"), Value::String(node_name)); map.insert( String::from("restarts"), Value::Number(Number::NegInt(restarts)), From 1ee9cadbb87d66cabcf04951a134a320968d8a89 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Tue, 26 May 2020 17:59:03 +0530 Subject: [PATCH 18/26] Boxed enum values to conserve space --- src/host/juno_module.rs | 14 ++++++++------ src/host/runner.rs | 26 +++++++------------------- src/models/guillotine_message.rs | 8 +------- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 4ee2130..25fc222 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -262,12 +262,14 @@ fn register_process(mut args: HashMap) -> Value { .clone() .send(GuillotineMessage::RegisterProcess { node_name, - process_log_dir: log_dir, - process_working_dir: working_dir, - process_config: config, - process_status: status, - process_last_started_at: last_started_at, - process_created_at: created_at, + process_data: Box::new(ProcessData::new( + log_dir, + working_dir, + config, + status, + last_started_at, + created_at, + )), response: sender, }) .await diff --git a/src/host/runner.rs b/src/host/runner.rs index efbbff4..e449a21 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -2,7 +2,7 @@ use crate::{ host::juno_module, models::{ GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, - ProcessData, RunnerConfig, + RunnerConfig, }, node::Process, utils::{constants, logger}, @@ -189,12 +189,7 @@ async fn keep_host_alive( } GuillotineMessage::RegisterProcess { node_name, - process_log_dir, - process_working_dir, - process_config, - process_status, - process_last_started_at, - process_created_at, + mut process_data, response, } => { if !node_runners.contains_key(&node_name) { @@ -204,18 +199,11 @@ async fn keep_host_alive( // Find a runner which runs a module of the same name let runner = node_runners.values_mut().find(|runner| { - runner.get_process_by_name(&process_config.name).is_some() + runner + .get_process_by_name(&process_data.config.name) + .is_some() }); - let mut process_data = ProcessData::new( - process_log_dir, - process_working_dir, - process_config, - process_status, - process_last_started_at, - process_created_at, - ); - // There's a runner which runs a module of the same name if let Some(runner) = runner { // That runner isn't what's registering the process @@ -234,7 +222,7 @@ async fn keep_host_alive( // There's a stale module re-registering itself. let module_id = process.unwrap().module_id; process_data.module_id = module_id; - runner.register_process(process_data); + runner.register_process(process_data.as_ref().clone()); response.send(Ok(module_id)).unwrap(); continue; @@ -253,7 +241,7 @@ async fn keep_host_alive( let assigned_pid = pid; pid += 1; process_data.module_id = assigned_pid; - runner.register_process(process_data); + runner.register_process(process_data.as_ref().clone()); response.send(Ok(assigned_pid)).unwrap(); } diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index e6051a7..2e3510d 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -1,4 +1,3 @@ -use super::{ModuleRunnerConfig, ModuleRunningStatus}; use crate::models::{GuillotineNode, ProcessData}; use futures::channel::oneshot::Sender; use juno::models::Value; @@ -14,12 +13,7 @@ pub enum GuillotineMessage { }, RegisterProcess { node_name: String, - process_log_dir: Option, - process_working_dir: String, - process_config: ModuleRunnerConfig, - process_status: ModuleRunningStatus, - process_last_started_at: u64, - process_created_at: u64, + process_data: Box, response: Sender>, }, ProcessExited { From d69f49df74d8eebd037fcdb304a17b0447e011ca Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Thu, 28 May 2020 14:17:44 +0530 Subject: [PATCH 19/26] Added add_process and delete_process commands --- Cargo.lock | 8 +- Cargo.toml | 5 +- src/cli/add_process.rs | 231 ++++++++++ src/cli/delete_process.rs | 227 ++++++++++ src/cli/mod.rs | 8 + src/cli/start_process.rs | 227 ++++++++++ src/cli/stop_process.rs | 227 ++++++++++ src/host/juno_module.rs | 205 ++++++++- src/host/mod.rs | 4 + .../guillotine_node.rs => host/node.rs} | 14 +- .../process_data.rs => host/process.rs} | 6 +- src/host/runner.rs | 394 ++++++++++++++++-- src/main.rs | 79 +++- src/models/guillotine_message.rs | 23 +- src/models/mod.rs | 4 - src/node/juno_module.rs | 173 ++++++++ src/node/module.rs | 4 +- src/node/process.rs | 16 +- src/node/runner.rs | 143 ++++++- 19 files changed, 1916 insertions(+), 82 deletions(-) create mode 100644 src/cli/add_process.rs create mode 100644 src/cli/delete_process.rs create mode 100644 src/cli/start_process.rs create mode 100644 src/cli/stop_process.rs rename src/{models/guillotine_node.rs => host/node.rs} (75%) rename src/{models/process_data.rs => host/process.rs} (89%) diff --git a/Cargo.lock b/Cargo.lock index 840b066..80a7ee5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -423,9 +423,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "log" @@ -682,9 +682,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.25" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f" +checksum = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 249bedd..a683cbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,4 @@ chrono = "0.4.11" ctrlc = "3.1.4" nix = "0.17.0" winapi = "0.3.8" - -[dependencies.async-std] -version = "1.5.0" -features = ["attributes"] +async-std = { version = "1", features = ["attributes"] } diff --git a/src/cli/add_process.rs b/src/cli/add_process.rs new file mode 100644 index 0000000..1cf5753 --- /dev/null +++ b/src/cli/add_process.rs @@ -0,0 +1,231 @@ +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; + +use clap::ArgMatches; +use cli_table::{ + format::{ + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + }, + Cell, Row, Table, +}; +use juno::models::Value; +use std::collections::HashMap; + +pub async fn add_process(config: RunnerConfig, args: &ArgMatches<'_>) { + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module + } else { + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; + }; + + let node = args.value_of("node"); + if node.is_none() { + logger::error("No node supplied!"); + return; + } + let node = node.unwrap().to_string(); + + let path = args.value_of("path"); + if path.is_none() { + logger::error("No path supplied!"); + return; + } + let path = path.unwrap().to_string(); + + module + .initialize( + &format!("{}-cli", constants::APP_NAME), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + + let response = module + .call_function(&format!("{}.addProcess", constants::APP_NAME), { + let mut map = HashMap::new(); + map.insert(String::from("node"), Value::String(node.clone())); + map.insert(String::from("path"), Value::String(path)); + map + }) + .await + .unwrap(); + + if !response.is_object() { + logger::error(&format!("Expected object response. Got {:?}", response)); + return; + } + let response = response.as_object().unwrap(); + + let success = response.get("success").unwrap(); + if !success.as_bool().unwrap() { + let error = response.get("error").unwrap().as_string().unwrap(); + logger::error(&format!("Error adding process: {}", error)); + return; + } + + let response = module + .call_function( + &format!("{}.listAllProcesses", constants::APP_NAME), + HashMap::new(), + ) + .await + .unwrap(); + let processes = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(processes)) = map.remove("processes") { + processes + } else { + logger::error("Invalid processes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); + return; + }; + + // Make the looks first + let header_format = CellFormat::builder() + .align(Align::Center) + .bold(true) + .underline(true) + .build(); + let table_format = TableFormat::new( + Border::builder() + .top(HorizontalLine::new('┌', '┐', '┬', '─')) + .bottom(HorizontalLine::new('└', '┘', '┴', '─')) + .right(VerticalLine::new('│')) + .left(VerticalLine::new('│')) + .build(), + Separator::builder() + .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) + .column(Some(VerticalLine::new('│'))) + .build(), + ); + + // Now make the data + let mut table_data = vec![Row::new(vec![ + Cell::new("ID", header_format), + Cell::new("Name", header_format), + Cell::new("Node", header_format), + Cell::new("Status", header_format), + Cell::new("Restarts", header_format), + Cell::new("Uptime", header_format), + Cell::new("Crashes", header_format), + Cell::new("Created at", header_format), + ])]; + for process in processes.into_iter() { + let process = process.as_object().unwrap(); + table_data.push(Row::new(vec![ + Cell::new( + &format!( + "{}", + process + .get("id") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + process.get("name").unwrap().as_string().unwrap(), + Default::default(), + ), + Cell::new( + process.get("node").unwrap().as_string().unwrap(), + Default::default(), + ), + match process.get("status").unwrap().as_string().unwrap().as_ref() { + "running" => Cell::new( + "running", + CellFormat::builder() + .foreground_color(Some(Color::Green)) + .build(), + ), + "offline" => Cell::new( + "offline", + CellFormat::builder() + .foreground_color(Some(Color::Red)) + .build(), + ), + _ => Cell::new( + "unknown", + CellFormat::builder() + .foreground_color(Some(Color::Cyan)) + .build(), + ), + }, + Cell::new( + &format!( + "{}", + process + .get("restarts") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_duration( + process + .get("uptime") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + Cell::new( + &format!( + "{}", + process + .get("crashes") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_date_time( + process + .get("createdAt") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + ])); + } + let table = Table::new(table_data, table_format); + + // Print it out + table.unwrap().print_stdout().unwrap(); +} diff --git a/src/cli/delete_process.rs b/src/cli/delete_process.rs new file mode 100644 index 0000000..8159c22 --- /dev/null +++ b/src/cli/delete_process.rs @@ -0,0 +1,227 @@ +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; + +use clap::ArgMatches; +use cli_table::{ + format::{ + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + }, + Cell, Row, Table, +}; +use juno::models::{Number, Value}; +use std::collections::HashMap; + +pub async fn delete_process(config: RunnerConfig, args: &ArgMatches<'_>) { + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module + } else { + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; + }; + let pid = args.value_of("pid"); + if pid.is_none() { + logger::error("No pid supplied!"); + return; + } + let pid = pid.unwrap().parse::(); + if pid.is_err() { + logger::error("Pid supplied is not a number!"); + return; + } + let pid = pid.unwrap(); + + module + .initialize( + &format!("{}-cli", constants::APP_NAME), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + + let response = module + .call_function(&format!("{}.deleteProcess", constants::APP_NAME), { + let mut map = HashMap::new(); + map.insert(String::from("moduleId"), Value::Number(Number::PosInt(pid))); + map + }) + .await + .unwrap(); + + if !response.is_object() { + logger::error(&format!("Expected object response. Got {:?}", response)); + return; + } + let response = response.as_object().unwrap(); + + let success = response.get("success").unwrap(); + if !success.as_bool().unwrap() { + let error = response.get("error").unwrap().as_string().unwrap(); + logger::error(&format!("Error deleting process: {}", error)); + return; + } + + let response = module + .call_function( + &format!("{}.listAllProcesses", constants::APP_NAME), + HashMap::new(), + ) + .await + .unwrap(); + let processes = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(processes)) = map.remove("processes") { + processes + } else { + logger::error("Invalid processes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); + return; + }; + + // Make the looks first + let header_format = CellFormat::builder() + .align(Align::Center) + .bold(true) + .underline(true) + .build(); + let table_format = TableFormat::new( + Border::builder() + .top(HorizontalLine::new('┌', '┐', '┬', '─')) + .bottom(HorizontalLine::new('└', '┘', '┴', '─')) + .right(VerticalLine::new('│')) + .left(VerticalLine::new('│')) + .build(), + Separator::builder() + .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) + .column(Some(VerticalLine::new('│'))) + .build(), + ); + + // Now make the data + let mut table_data = vec![Row::new(vec![ + Cell::new("ID", header_format), + Cell::new("Name", header_format), + Cell::new("Node", header_format), + Cell::new("Status", header_format), + Cell::new("Restarts", header_format), + Cell::new("Uptime", header_format), + Cell::new("Crashes", header_format), + Cell::new("Created at", header_format), + ])]; + for process in processes.into_iter() { + let process = process.as_object().unwrap(); + table_data.push(Row::new(vec![ + Cell::new( + &format!( + "{}", + process + .get("id") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + process.get("name").unwrap().as_string().unwrap(), + Default::default(), + ), + Cell::new( + process.get("node").unwrap().as_string().unwrap(), + Default::default(), + ), + match process.get("status").unwrap().as_string().unwrap().as_ref() { + "running" => Cell::new( + "running", + CellFormat::builder() + .foreground_color(Some(Color::Green)) + .build(), + ), + "offline" => Cell::new( + "offline", + CellFormat::builder() + .foreground_color(Some(Color::Red)) + .build(), + ), + _ => Cell::new( + "unknown", + CellFormat::builder() + .foreground_color(Some(Color::Cyan)) + .build(), + ), + }, + Cell::new( + &format!( + "{}", + process + .get("restarts") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_duration( + process + .get("uptime") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + Cell::new( + &format!( + "{}", + process + .get("crashes") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_date_time( + process + .get("createdAt") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + ])); + } + let table = Table::new(table_data, table_format); + + // Print it out + table.unwrap().print_stdout().unwrap(); +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 575a6f9..430b96f 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,16 +1,24 @@ +mod add_process; +mod delete_process; mod get_info; mod list_all_processes; mod list_modules; mod list_nodes; mod list_processes; mod restart_process; +mod start_process; +mod stop_process; +pub use add_process::add_process; +pub use delete_process::delete_process; pub use get_info::get_info; pub use list_all_processes::list_all_processes; pub use list_modules::list_modules; pub use list_nodes::list_nodes; pub use list_processes::list_processes; pub use restart_process::restart_process; +pub use start_process::start_process; +pub use stop_process::stop_process; use crate::models::RunnerConfig; use chrono::{prelude::*, Utc}; diff --git a/src/cli/start_process.rs b/src/cli/start_process.rs new file mode 100644 index 0000000..7ba6047 --- /dev/null +++ b/src/cli/start_process.rs @@ -0,0 +1,227 @@ +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; + +use clap::ArgMatches; +use cli_table::{ + format::{ + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + }, + Cell, Row, Table, +}; +use juno::models::{Number, Value}; +use std::collections::HashMap; + +pub async fn start_process(config: RunnerConfig, args: &ArgMatches<'_>) { + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module + } else { + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; + }; + let pid = args.value_of("pid"); + if pid.is_none() { + logger::error("No pid supplied!"); + return; + } + let pid = pid.unwrap().parse::(); + if pid.is_err() { + logger::error("Pid supplied is not a number!"); + return; + } + let pid = pid.unwrap(); + + module + .initialize( + &format!("{}-cli", constants::APP_NAME), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + + let response = module + .call_function(&format!("{}.startProcess", constants::APP_NAME), { + let mut map = HashMap::new(); + map.insert(String::from("moduleId"), Value::Number(Number::PosInt(pid))); + map + }) + .await + .unwrap(); + + if !response.is_object() { + logger::error(&format!("Expected object response. Got {:?}", response)); + return; + } + let response = response.as_object().unwrap(); + + let success = response.get("success").unwrap(); + if !success.as_bool().unwrap() { + let error = response.get("error").unwrap().as_string().unwrap(); + logger::error(&format!("Error restarting process: {}", error)); + return; + } + + let response = module + .call_function( + &format!("{}.listAllProcesses", constants::APP_NAME), + HashMap::new(), + ) + .await + .unwrap(); + let processes = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(processes)) = map.remove("processes") { + processes + } else { + logger::error("Invalid processes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); + return; + }; + + // Make the looks first + let header_format = CellFormat::builder() + .align(Align::Center) + .bold(true) + .underline(true) + .build(); + let table_format = TableFormat::new( + Border::builder() + .top(HorizontalLine::new('┌', '┐', '┬', '─')) + .bottom(HorizontalLine::new('└', '┘', '┴', '─')) + .right(VerticalLine::new('│')) + .left(VerticalLine::new('│')) + .build(), + Separator::builder() + .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) + .column(Some(VerticalLine::new('│'))) + .build(), + ); + + // Now make the data + let mut table_data = vec![Row::new(vec![ + Cell::new("ID", header_format), + Cell::new("Name", header_format), + Cell::new("Node", header_format), + Cell::new("Status", header_format), + Cell::new("Restarts", header_format), + Cell::new("Uptime", header_format), + Cell::new("Crashes", header_format), + Cell::new("Created at", header_format), + ])]; + for process in processes.into_iter() { + let process = process.as_object().unwrap(); + table_data.push(Row::new(vec![ + Cell::new( + &format!( + "{}", + process + .get("id") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + process.get("name").unwrap().as_string().unwrap(), + Default::default(), + ), + Cell::new( + process.get("node").unwrap().as_string().unwrap(), + Default::default(), + ), + match process.get("status").unwrap().as_string().unwrap().as_ref() { + "running" => Cell::new( + "running", + CellFormat::builder() + .foreground_color(Some(Color::Green)) + .build(), + ), + "offline" => Cell::new( + "offline", + CellFormat::builder() + .foreground_color(Some(Color::Red)) + .build(), + ), + _ => Cell::new( + "unknown", + CellFormat::builder() + .foreground_color(Some(Color::Cyan)) + .build(), + ), + }, + Cell::new( + &format!( + "{}", + process + .get("restarts") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_duration( + process + .get("uptime") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + Cell::new( + &format!( + "{}", + process + .get("crashes") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_date_time( + process + .get("createdAt") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + ])); + } + let table = Table::new(table_data, table_format); + + // Print it out + table.unwrap().print_stdout().unwrap(); +} diff --git a/src/cli/stop_process.rs b/src/cli/stop_process.rs new file mode 100644 index 0000000..9d06d20 --- /dev/null +++ b/src/cli/stop_process.rs @@ -0,0 +1,227 @@ +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; + +use clap::ArgMatches; +use cli_table::{ + format::{ + Align, Border, CellFormat, Color, HorizontalLine, Separator, TableFormat, VerticalLine, + }, + Cell, Row, Table, +}; +use juno::models::{Number, Value}; +use std::collections::HashMap; + +pub async fn stop_process(config: RunnerConfig, args: &ArgMatches<'_>) { + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module + } else { + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; + }; + let pid = args.value_of("pid"); + if pid.is_none() { + logger::error("No pid supplied!"); + return; + } + let pid = pid.unwrap().parse::(); + if pid.is_err() { + logger::error("Pid supplied is not a number!"); + return; + } + let pid = pid.unwrap(); + + module + .initialize( + &format!("{}-cli", constants::APP_NAME), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + + let response = module + .call_function(&format!("{}.startProcess", constants::APP_NAME), { + let mut map = HashMap::new(); + map.insert(String::from("moduleId"), Value::Number(Number::PosInt(pid))); + map + }) + .await + .unwrap(); + + if !response.is_object() { + logger::error(&format!("Expected object response. Got {:?}", response)); + return; + } + let response = response.as_object().unwrap(); + + let success = response.get("success").unwrap(); + if !success.as_bool().unwrap() { + let error = response.get("error").unwrap().as_string().unwrap(); + logger::error(&format!("Error restarting process: {}", error)); + return; + } + + let response = module + .call_function( + &format!("{}.listAllProcesses", constants::APP_NAME), + HashMap::new(), + ) + .await + .unwrap(); + let processes = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(processes)) = map.remove("processes") { + processes + } else { + logger::error("Invalid processes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); + return; + }; + + // Make the looks first + let header_format = CellFormat::builder() + .align(Align::Center) + .bold(true) + .underline(true) + .build(); + let table_format = TableFormat::new( + Border::builder() + .top(HorizontalLine::new('┌', '┐', '┬', '─')) + .bottom(HorizontalLine::new('└', '┘', '┴', '─')) + .right(VerticalLine::new('│')) + .left(VerticalLine::new('│')) + .build(), + Separator::builder() + .row(None) //Use this for a line: Some(HorizontalLine::new('├', '┤', '┼', '─'))) + .column(Some(VerticalLine::new('│'))) + .build(), + ); + + // Now make the data + let mut table_data = vec![Row::new(vec![ + Cell::new("ID", header_format), + Cell::new("Name", header_format), + Cell::new("Node", header_format), + Cell::new("Status", header_format), + Cell::new("Restarts", header_format), + Cell::new("Uptime", header_format), + Cell::new("Crashes", header_format), + Cell::new("Created at", header_format), + ])]; + for process in processes.into_iter() { + let process = process.as_object().unwrap(); + table_data.push(Row::new(vec![ + Cell::new( + &format!( + "{}", + process + .get("id") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + process.get("name").unwrap().as_string().unwrap(), + Default::default(), + ), + Cell::new( + process.get("node").unwrap().as_string().unwrap(), + Default::default(), + ), + match process.get("status").unwrap().as_string().unwrap().as_ref() { + "running" => Cell::new( + "running", + CellFormat::builder() + .foreground_color(Some(Color::Green)) + .build(), + ), + "offline" => Cell::new( + "offline", + CellFormat::builder() + .foreground_color(Some(Color::Red)) + .build(), + ), + _ => Cell::new( + "unknown", + CellFormat::builder() + .foreground_color(Some(Color::Cyan)) + .build(), + ), + }, + Cell::new( + &format!( + "{}", + process + .get("restarts") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_duration( + process + .get("uptime") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + Cell::new( + &format!( + "{}", + process + .get("crashes") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap() + ), + Default::default(), + ), + Cell::new( + &super::get_date_time( + process + .get("createdAt") + .unwrap() + .as_number() + .unwrap() + .as_i64() + .unwrap(), + ), + Default::default(), + ), + ])); + } + let table = Table::new(table_data, table_format); + + // Print it out + table.unwrap().print_stdout().unwrap(); +} diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 25fc222..4f58609 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -1,8 +1,6 @@ use crate::{ - models::{ - GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, - ProcessData, - }, + host::{GuillotineNode, ProcessData}, + models::{GuillotineMessage, HostConfig, ModuleRunnerConfig, ModuleRunningStatus}, utils::constants, }; use std::collections::HashMap; @@ -91,6 +89,26 @@ pub async fn setup_host_module( .await .unwrap(); + module + .declare_function("addProcess", add_process) + .await + .unwrap(); + + module + .declare_function("startProcess", start_process) + .await + .unwrap(); + + module + .declare_function("stopProcess", stop_process) + .await + .unwrap(); + + module + .declare_function("deleteProcess", delete_process) + .await + .unwrap(); + module .register_hook("juno.moduleDeactivated", module_deactivated) .await @@ -903,6 +921,185 @@ fn restart_process(mut args: HashMap) -> Value { }) } +fn add_process(mut args: HashMap) -> Value { + task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + + let node_name = if let Some(Value::String(value)) = args.remove("node") { + value + } else { + return generate_error_response("Node parameter is not a string"); + }; + + let path = if let Some(Value::String(path)) = args.remove("path") { + path + } else { + return generate_error_response("Path parameter is not a String"); + }; + + let (sender, receiver) = channel::>(); + message_sender + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::AddProcess { + node_name, + path, + response: sender, + }) + .await + .unwrap(); + drop(message_sender); + + let result = receiver.await.unwrap(); + if let Ok(()) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + +fn start_process(mut args: HashMap) -> Value { + task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + message_sender + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::StartProcess { + module_id, + response: sender, + }) + .await + .unwrap(); + drop(message_sender); + + let result = receiver.await.unwrap(); + if let Ok(()) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + +fn stop_process(mut args: HashMap) -> Value { + task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + message_sender + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::StartProcess { + module_id, + response: sender, + }) + .await + .unwrap(); + drop(message_sender); + + let result = receiver.await.unwrap(); + if let Ok(()) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + +fn delete_process(mut args: HashMap) -> Value { + task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + message_sender + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::DeleteProcess { + module_id, + response: sender, + }) + .await + .unwrap(); + drop(message_sender); + + let result = receiver.await.unwrap(); + if let Ok(()) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + fn generate_error_response(error_message: &str) -> Value { Value::Object({ let mut map = HashMap::new(); diff --git a/src/host/mod.rs b/src/host/mod.rs index c858555..6c80d66 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -1,5 +1,9 @@ mod juno_module; +mod node; +mod process; mod runner; pub use juno_module::setup_host_module; +pub use node::GuillotineNode; +pub use process::ProcessData; pub use runner::{on_exit, run}; diff --git a/src/models/guillotine_node.rs b/src/host/node.rs similarity index 75% rename from src/models/guillotine_node.rs rename to src/host/node.rs index 7a05157..6e1784f 100644 --- a/src/models/guillotine_node.rs +++ b/src/host/node.rs @@ -1,4 +1,4 @@ -use crate::models::ProcessData; +use super::ProcessData; #[derive(Debug, Clone)] pub struct GuillotineNode { @@ -42,4 +42,16 @@ impl GuillotineNode { self.processes.push(process_data); } } + + pub fn delete_process_by_id(&mut self, module_id: u64) { + let position = self + .processes + .iter() + .position(|process| process.module_id == module_id); + // If a process with the same name exists, replace the existing value + if let Some(position) = position { + // Only update the values that can change. Retain the remaining values + self.processes.remove(position); + } + } } diff --git a/src/models/process_data.rs b/src/host/process.rs similarity index 89% rename from src/models/process_data.rs rename to src/host/process.rs index 6f9b5e6..7478dfb 100644 --- a/src/models/process_data.rs +++ b/src/host/process.rs @@ -40,7 +40,11 @@ impl ProcessData { } pub fn get_uptime(&self) -> u64 { - get_current_time() - self.last_started_at + if self.status == ModuleRunningStatus::Running { + get_current_time() - self.last_started_at + } else { + 0 + } } } diff --git a/src/host/runner.rs b/src/host/runner.rs index e449a21..01dfcd9 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -1,8 +1,7 @@ use crate::{ - host::juno_module, + host::{juno_module, GuillotineNode}, models::{ - GuillotineMessage, GuillotineNode, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, - RunnerConfig, + GuillotineMessage, HostConfig, ModuleRunnerConfig, ModuleRunningStatus, RunnerConfig, }, node::Process, utils::{constants, logger}, @@ -13,7 +12,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -use async_std::{fs, net::TcpStream, path::Path, sync::Mutex, task}; +use async_std::{fs, net::TcpStream, path::Path, sync::Mutex}; use future::Either; use futures::{ channel::{mpsc::unbounded, oneshot::Sender}, @@ -522,7 +521,370 @@ async fn keep_host_alive( .send(Ok((node.0.name.clone(), node.1.clone()))) .unwrap(); } - msg => panic!("Unhandled guillotine message: {:#?}", msg), + GuillotineMessage::AddProcess { + node_name, + path, + response, + } => { + let node = node_runners.get_mut(&node_name); + if node.is_none() { + response + .send(Err(format!( + "Runner node with the name '{}' doesn't exist", + node_name + ))) + .unwrap(); + continue; + } + + let result = juno_module + .call_function( + &format!( + "{}-node-{}.addProcess", + constants::APP_AUTHORS, + node_name + ), + { + let mut map = HashMap::new(); + map.insert(String::from("path"), Value::String(path)); + map + }, + ) + .await; + if let Err(error) = result { + response + .send(Err(format!("Error sending the restart command: {}", error))) + .unwrap(); + continue; + } + let result = result.unwrap(); + let mut result = if let Value::Object(args) = result { + args + } else { + response + .send(Err(format!( + "Response of restart command wasn't an object. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + + let success = if let Some(Value::Bool(success)) = result.remove("success") { + success + } else { + response + .send(Err(format!( + "Success key of restart command wasn't a bool. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + if !success { + response + .send(Err( + if let Some(Value::String(error)) = result.remove("error") { + format!("Error restarting process: {}", error) + } else { + format!( + "Error key of restart command wasn't a string. Got: {:#?}", + result + ) + }, + )) + .unwrap(); + continue; + } + response.send(Ok(())).unwrap(); + } + GuillotineMessage::StartProcess { + module_id, + response, + } => { + let node = node_runners + .values_mut() + .find(|node| node.get_process_by_id(module_id).is_some()); + if node.is_none() { + response + .send(Err(format!( + "No node found running the module with the ID {}", + module_id + ))) + .unwrap(); + continue; + } + let node = node.unwrap(); + if !node.connected { + response + .send(Err(format!( + "The node (with the name '{}') running the module {} is not connected", + node.name, + module_id + ))) + .unwrap(); + continue; + } + + // Now start the process + let result = juno_module + .call_function( + &format!("{}-node-{}.startProcess", constants::APP_NAME, node.name), + { + let mut map = HashMap::new(); + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(module_id)), + ); + map + }, + ) + .await; + if let Err(error) = result { + response + .send(Err(format!("Error sending the start command: {}", error))) + .unwrap(); + continue; + } + let result = result.unwrap(); + let mut result = if let Value::Object(args) = result { + args + } else { + response + .send(Err(format!( + "Response of start command wasn't an object. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + + let success = if let Some(Value::Bool(success)) = result.remove("success") { + success + } else { + response + .send(Err(format!( + "Success key of start command wasn't a bool. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + if !success { + response + .send(Err( + if let Some(Value::String(error)) = result.remove("error") { + format!("Error starting process: {}", error) + } else { + format!( + "Error key of start command wasn't a string. Got: {:#?}", + result + ) + }, + )) + .unwrap(); + continue; + } + let process = node.get_process_by_id_mut(module_id); + if process.is_none() { + response.send(Err(String::from("Node notified successful process start, but couldn't find the process in host's memory. Data may be stale"))).unwrap(); + continue; + } + let process = process.unwrap(); + process.status = ModuleRunningStatus::Running; + process.last_started_at = get_current_millis(); + response.send(Ok(())).unwrap(); + } + GuillotineMessage::StopProcess { + module_id, + response, + } => { + let node = node_runners + .values_mut() + .find(|node| node.get_process_by_id(module_id).is_some()); + if node.is_none() { + response + .send(Err(format!( + "No node found running the module with the ID {}", + module_id + ))) + .unwrap(); + continue; + } + let node = node.unwrap(); + if !node.connected { + response + .send(Err(format!( + "The node (with the name '{}') running the module {} is not connected", + node.name, + module_id + ))) + .unwrap(); + continue; + } + + // Now stop the process + let result = juno_module + .call_function( + &format!("{}-node-{}.stopProcess", constants::APP_NAME, node.name), + { + let mut map = HashMap::new(); + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(module_id)), + ); + map + }, + ) + .await; + if let Err(error) = result { + response + .send(Err(format!("Error sending the stop command: {}", error))) + .unwrap(); + continue; + } + let result = result.unwrap(); + let mut result = if let Value::Object(args) = result { + args + } else { + response + .send(Err(format!( + "Response of stop command wasn't an object. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + + let success = if let Some(Value::Bool(success)) = result.remove("success") { + success + } else { + response + .send(Err(format!( + "Success key of stop command wasn't a bool. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + if !success { + response + .send(Err( + if let Some(Value::String(error)) = result.remove("error") { + format!("Error stopping process: {}", error) + } else { + format!( + "Error key of stop command wasn't a string. Got: {:#?}", + result + ) + }, + )) + .unwrap(); + continue; + } + let process = node.get_process_by_id_mut(module_id); + if process.is_none() { + response.send(Err(String::from("Node notified successful process stop, but couldn't find the process in host's memory. Data may be stale"))).unwrap(); + continue; + } + let process = process.unwrap(); + process.status = ModuleRunningStatus::Stopped; + response.send(Ok(())).unwrap(); + } + GuillotineMessage::DeleteProcess { + module_id, + response, + } => { + let node = node_runners + .values_mut() + .find(|node| node.get_process_by_id(module_id).is_some()); + if node.is_none() { + response + .send(Err(format!( + "No node found running the module with the ID {}", + module_id + ))) + .unwrap(); + continue; + } + let node = node.unwrap(); + if !node.connected { + response + .send(Err(format!( + "The node (with the name '{}') running the module {} is not connected", + node.name, + module_id + ))) + .unwrap(); + continue; + } + + // Now delete the process + let result = juno_module + .call_function( + &format!( + "{}-node-{}.deleteProcess", + constants::APP_NAME, + node.name + ), + { + let mut map = HashMap::new(); + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(module_id)), + ); + map + }, + ) + .await; + if let Err(error) = result { + response + .send(Err(format!("Error sending the delete command: {}", error))) + .unwrap(); + continue; + } + let result = result.unwrap(); + let mut result = if let Value::Object(args) = result { + args + } else { + response + .send(Err(format!( + "Response of delete command wasn't an object. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + + let success = if let Some(Value::Bool(success)) = result.remove("success") { + success + } else { + response + .send(Err(format!( + "Success key of delete command wasn't a bool. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + if !success { + response + .send(Err( + if let Some(Value::String(error)) = result.remove("error") { + format!("Error deleting process: {}", error) + } else { + format!( + "Error key of delete command wasn't a string. Got: {:#?}", + result + ) + }, + )) + .unwrap(); + continue; + } + node.delete_process_by_id(module_id); + response.send(Ok(())).unwrap(); + } } } } @@ -534,27 +896,7 @@ async fn keep_host_alive( "Quitting process: {}", juno_process.runner_config.name )); - juno_process.send_quit_signal(); - let quit_time = get_current_millis(); - loop { - // Give the process some time to die. - task::sleep(Duration::from_millis(100)).await; - - // If the process is not running, then break - if !juno_process.is_process_running().0 { - break; - } - // If the processes is running, check if it's been given enough time. - if get_current_millis() > quit_time + 1000 { - // It's been trying to quit for more than 1 second. Kill it and quit - logger::info(&format!( - "Killing process: {}", - juno_process.runner_config.name - )); - juno_process.kill(); - break; - } - } + juno_process.wait_for_quit_or_kill_within(1000).await; } async fn try_connecting_to_juno(host: &HostConfig) -> bool { diff --git a/src/main.rs b/src/main.rs index 1728ddd..2b30ba0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,53 @@ async fn main() { .author(constants::APP_AUTHORS) .about(constants::APP_ABOUT) .subcommand( - SubCommand::with_name("run").about("Run the application with a given config file"), + SubCommand::with_name("add") + .alias("a") + .about("Adds a process to the node from a module.json") + .arg( + Arg::with_name("path") + .takes_value(true) + .required(true) + .allow_hyphen_values(false), + ) + .arg( + Arg::with_name("node") + .takes_value(true) + .required(true) + .allow_hyphen_values(true), + ) + .arg( + Arg::with_name("dont-start") + .takes_value(false) + .required(false), + ), + ) + .subcommand( + SubCommand::with_name("delete") + .alias("d") + .about("Stops a module and removes it from the list") + .arg( + Arg::with_name("pid") + .takes_value(true) + .required(true) + .allow_hyphen_values(false), + ), + ) + .subcommand( + SubCommand::with_name("info") + .alias("i") + .about("Get information about a process / module") + .arg( + Arg::with_name("pid") + .takes_value(true) + .required(true) + .allow_hyphen_values(false), + ), + ) + .subcommand( + SubCommand::with_name("list-all-processes") + .alias("lap") + .about("List all running processes and their states across all nodes"), ) .subcommand( SubCommand::with_name("list-modules") @@ -54,11 +100,6 @@ async fn main() { .alias("ln") .about("List all the nodes registered with this host and their details"), ) - .subcommand( - SubCommand::with_name("list-all-processes") - .alias("lap") - .about("List all running processes and their states across all nodes"), - ) .subcommand( SubCommand::with_name("list-processes") .alias("lp") @@ -82,9 +123,8 @@ async fn main() { ), ) .subcommand( - SubCommand::with_name("info") - .alias("i") - .about("Get information about a process / module") + SubCommand::with_name("start") + .about("Starts a process with a processId, if the process isn't running already") .arg( Arg::with_name("pid") .takes_value(true) @@ -92,6 +132,19 @@ async fn main() { .allow_hyphen_values(false), ), ) + .subcommand( + SubCommand::with_name("stop") + .about("Stops a process with a processId, if it's running") + .arg( + Arg::with_name("pid") + .takes_value(true) + .required(true) + .allow_hyphen_values(false), + ), + ) + .subcommand( + SubCommand::with_name("run").about("Run the application with a given config file"), + ) .arg( Arg::with_name("config") .short("c") @@ -132,12 +185,16 @@ async fn main() { ("run", Some(_)) | ("", _) => runner::run(config).await, // Cli stuff + ("add", Some(args)) => cli::add_process(config, args).await, + ("delete", Some(args)) => cli::delete_process(config, args).await, + ("info", Some(args)) => cli::get_info(config, args).await, + ("list-all-processes", Some(_)) => cli::list_all_processes(config).await, ("list-modules", Some(_)) => cli::list_modules(config).await, ("list-nodes", Some(_)) => cli::list_nodes(config).await, - ("list-all-processes", Some(_)) => cli::list_all_processes(config).await, ("list-processes", Some(args)) => cli::list_processes(config, args).await, - ("info", Some(args)) => cli::get_info(config, args).await, ("restart", Some(args)) => cli::restart_process(config, args).await, + ("start", Some(args)) => cli::start_process(config, args).await, + ("stop", Some(args)) => cli::stop_process(config, args).await, (cmd, _) => println!("Unknown command '{}'", cmd), } diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index 2e3510d..56dd0d5 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -1,4 +1,4 @@ -use crate::models::{GuillotineNode, ProcessData}; +use crate::host::{GuillotineNode, ProcessData}; use futures::channel::oneshot::Sender; use juno::models::Value; use std::collections::HashMap; @@ -53,10 +53,23 @@ pub enum GuillotineMessage { module_id: u64, response: Sender>, // (node_name, process_data) }, - AddProcess, - StopProcess, - StartProcess, - DeleteProcess, + AddProcess { + node_name: String, + path: String, + response: Sender>, + }, + StartProcess { + module_id: u64, + response: Sender>, + }, + StopProcess { + module_id: u64, + response: Sender>, + }, + DeleteProcess { + module_id: u64, + response: Sender>, + }, } // TODO ADD: // ReloadConfig, diff --git a/src/models/mod.rs b/src/models/mod.rs index a0f93f5..bc9c66c 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,7 +1,5 @@ mod config_data; mod guillotine_message; -mod guillotine_node; -mod process_data; pub mod parser; @@ -10,5 +8,3 @@ pub use config_data::{ ModuleRunnerConfig, ModuleRunningStatus, NodeConfig, RunnerConfig, }; pub use guillotine_message::GuillotineMessage; -pub use guillotine_node::GuillotineNode; -pub use process_data::ProcessData; diff --git a/src/node/juno_module.rs b/src/node/juno_module.rs index 072633f..b54f20b 100644 --- a/src/node/juno_module.rs +++ b/src/node/juno_module.rs @@ -54,6 +54,26 @@ pub async fn setup_module( .await .unwrap(); + juno_module + .declare_function("addProcess", add_process) + .await + .unwrap(); + + juno_module + .declare_function("startProcess", start_process) + .await + .unwrap(); + + juno_module + .declare_function("stopProcess", stop_process) + .await + .unwrap(); + + juno_module + .declare_function("deleteProcess", delete_process) + .await + .unwrap(); + // Register node here let response = juno_module .call_function(&format!("{}.registerNode", constants::APP_NAME), { @@ -248,6 +268,159 @@ fn respawn_process(mut args: HashMap) -> Value { }) } +fn add_process(mut args: HashMap) -> Value { + task::block_on(async { + let path = if let Some(Value::String(path)) = args.remove("path") { + path + } else { + return generate_error_response("Path parameter is not a String"); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::AddProcess { + node_name: String::new(), + path, + response: sender, + }) + .await + .unwrap(); + + let result = receiver.await.unwrap(); + if let Ok(()) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + +fn start_process(mut args: HashMap) -> Value { + task::block_on(async { + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::StartProcess { + module_id, + response: sender, + }) + .await + .unwrap(); + + let result = receiver.await.unwrap(); + if let Ok(()) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + +fn stop_process(mut args: HashMap) -> Value { + task::block_on(async { + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::StopProcess { + module_id, + response: sender, + }) + .await + .unwrap(); + + let result = receiver.await.unwrap(); + if let Ok(()) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + +fn delete_process(mut args: HashMap) -> Value { + task::block_on(async { + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::DeleteProcess { + module_id, + response: sender, + }) + .await + .unwrap(); + + let result = receiver.await.unwrap(); + if let Ok(()) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + fn generate_error_response(error_message: &str) -> Value { Value::Object({ let mut map = HashMap::new(); diff --git a/src/node/module.rs b/src/node/module.rs index cb55973..f2aae7a 100644 --- a/src/node/module.rs +++ b/src/node/module.rs @@ -5,7 +5,7 @@ use async_std::{ path::{Path, PathBuf}, }; -pub async fn get_module_from_path(path: &str, log_dir: Option) -> Option { +pub async fn get_module_from_path(path: &str, log_dir: &Option) -> Option { let mut path = PathBuf::from(path); if !path.exists().await { return None; @@ -41,7 +41,7 @@ pub async fn get_module_from_path(path: &str, log_dir: Option) -> Option let working_dir = path.parent().unwrap().to_str().unwrap().to_owned(); Some(if let Some(log_dir) = log_dir { - let main_dir = Path::new(&log_dir); + let main_dir = Path::new(log_dir); if !main_dir.exists().await { fs::create_dir(&main_dir).await.unwrap(); } diff --git a/src/node/process.rs b/src/node/process.rs index acd452b..a1d8f96 100644 --- a/src/node/process.rs +++ b/src/node/process.rs @@ -16,6 +16,7 @@ pub struct Process { pub created_at: u64, pub start_scheduled_at: Option, pub has_been_crashing: bool, + pub should_be_running: bool, } impl Process { @@ -33,6 +34,7 @@ impl Process { created_at: get_current_time(), start_scheduled_at: None, has_been_crashing: false, + should_be_running: true, } } @@ -56,8 +58,7 @@ impl Process { } } - pub async fn respawn(&mut self) { - logger::info(&format!("Respawning '{}'", self.runner_config.name)); + pub async fn wait_for_quit_or_kill_within(&mut self, wait_ms: u64) { if self.process.is_some() && self.is_process_running().0 { self.send_quit_signal(); let quit_time = get_current_time(); @@ -69,14 +70,19 @@ impl Process { break; } // If the processes is running, check if it's been given enough time. - if get_current_time() > quit_time + 1000 { - // It's been trying to quit for more than 1 second. Kill it and quit + if get_current_time() > quit_time + wait_ms { + // It's been trying to quit for more than the given duration. Kill it and quit logger::info(&format!("Killing process: {}", self.runner_config.name)); - self.process.as_mut().unwrap().kill().unwrap_or(()); + self.kill(); break; } } } + } + + pub async fn respawn(&mut self) { + logger::info(&format!("Respawning '{}'", self.runner_config.name)); + self.wait_for_quit_or_kill_within(1000).await; let child = if self.runner_config.interpreter.is_none() { let mut command = Command::new(&self.runner_config.command); diff --git a/src/node/runner.rs b/src/node/runner.rs index 235bd47..ebd2ba7 100644 --- a/src/node/runner.rs +++ b/src/node/runner.rs @@ -19,17 +19,15 @@ lazy_static! { static ref CLOSE_FLAG: Mutex = Mutex::new(false); } -pub async fn run(mut config: RunnerConfig) { +pub async fn run(config: RunnerConfig) { if config.name.is_none() { logger::error("Node name cannot be null"); return; } - let node_name = config.name.unwrap(); - let node = config.node.take().unwrap(); - let log_dir = config.logs; + let log_dir = &config.logs; - while !try_connecting_to_host(&node).await { + while !try_connecting_to_host(config.node.as_ref().unwrap()).await { logger::error(&format!( "Could not connect to the host instance of {}. Will try again in {} ms", constants::APP_NAME, @@ -44,14 +42,14 @@ pub async fn run(mut config: RunnerConfig) { // Populate any auto-start modules here let mut auto_start_processes = vec![]; if config.modules.is_some() { - let modules_dir = config.modules.unwrap().directory; - let modules_path = Path::new(&modules_dir); + let modules_dir = &config.modules.as_ref().unwrap().directory; + let modules_path = Path::new(modules_dir); if modules_path.exists().await && modules_path.is_dir().await { let mut dir_iterator = fs::read_dir(modules_dir).await.unwrap(); while let Some(Ok(dir)) = dir_iterator.next().await { if let Some(process) = - get_module_from_path(dir.path().to_str().unwrap(), log_dir.clone()).await + get_module_from_path(dir.path().to_str().unwrap(), log_dir).await { auto_start_processes.push(process); } @@ -59,17 +57,22 @@ pub async fn run(mut config: RunnerConfig) { } } - keep_node_alive(node_name, node, auto_start_processes).await; + keep_node_alive(config, auto_start_processes).await; } pub async fn on_exit() { *CLOSE_FLAG.lock().await = true; } -async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_processes: Vec) { +async fn keep_node_alive(config: RunnerConfig, auto_start_processes: Vec) { // Initialize the guillotine juno module let (sender, mut command_receiver) = unbounded::(); - let response = juno_module::setup_module(node_name.clone(), &node, sender.clone()).await; + let response = juno_module::setup_module( + config.name.as_ref().unwrap().clone(), + &config.node.unwrap(), + sender.clone(), + ) + .await; if let Err(error) = response { logger::error(&format!("Error setting up Juno module: {}", error)); return; @@ -80,8 +83,12 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process // First, register all the auto_start_processes for mut process in auto_start_processes { - let response = - juno_module::register_module(node_name.clone(), &mut juno_module, &mut process).await; + let response = juno_module::register_module( + config.name.as_ref().unwrap().clone(), + &mut juno_module, + &mut process, + ) + .await; if let Ok(module_id) = response { // Then, store their assigned moduleIds in a hashmap. ids_to_processes.insert(module_id, process); @@ -114,6 +121,13 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process if let (false, crashed) = process.is_process_running() { // Process ain't running. + if !process.should_be_running { + // The process isn't expected to be running. + // Either it has been stopped or it just isn't started yet + // Don't bother processing this module. Let it stay dead. + continue; + } + if process.start_scheduled_at.is_some() { // The process is already scheduled to start at some point in the future. // Don't bother notifying the host about this @@ -134,7 +148,10 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process let response = juno_module .call_function(&format!("{}.onProcessExited", constants::APP_NAME), { let mut map = HashMap::new(); - map.insert(String::from("node"), Value::String(node_name.clone())); + map.insert( + String::from("node"), + Value::String(config.name.as_ref().unwrap().clone()), + ); map.insert( String::from("moduleId"), Value::Number(Number::PosInt(*module_id)), @@ -216,7 +233,7 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process let mut map = HashMap::new(); map.insert( String::from("node"), - Value::String(node_name.clone()), + Value::String(config.name.as_ref().unwrap().clone()), ); map.insert( String::from("moduleId"), @@ -296,11 +313,107 @@ async fn keep_node_alive(node_name: String, node: NodeConfig, auto_start_process .await; response.send(Ok(())).unwrap(); } + GuillotineMessage::AddProcess { + node_name: _, + path, + response, + } => { + let mut process = if let Some(process) = + get_module_from_path(&path, &config.logs).await + { + process + } else { + response + .send(Err(format!( + "{}{}{}", + "The path provided is not a valid path to a module. ", + "Please ensure that the path points to a module.json file ", + "or a folder containing the file" + ))) + .unwrap(); + continue; + }; + + let result = juno_module::register_module( + config.name.as_ref().unwrap().clone(), + &mut juno_module, + &mut process, + ) + .await; + let module_id = if let Ok(module_id) = result { + module_id + } else { + response + .send(Err(format!( + "Error while registering the module '{}': {}", + process.runner_config.name, + result.unwrap_err() + ))) + .unwrap(); + continue; + }; + + // Then, store their assigned moduleIds in a hashmap. + ids_to_processes.insert(module_id, process); + response.send(Ok(())).unwrap(); + } + GuillotineMessage::StartProcess { + module_id, + response, + } => { + if !ids_to_processes.contains_key(&module_id) { + response.send(Err(String::from("Could not find any process with that moduleId in this runner. Is this stale data?"))).unwrap(); + continue; + } + + let process = ids_to_processes.get_mut(&module_id).unwrap(); + if !process.is_process_running().0 { + process.respawn().await; + process.should_be_running = true; + } + response.send(Ok(())).unwrap(); + } + GuillotineMessage::StopProcess { + module_id, + response, + } => { + if !ids_to_processes.contains_key(&module_id) { + response.send(Err(String::from("Could not find any process with that moduleId in this runner. Is this stale data?"))).unwrap(); + continue; + } + + let process = ids_to_processes.get_mut(&module_id).unwrap(); + if process.is_process_running().0 { + process.wait_for_quit_or_kill_within(1000).await; + process.should_be_running = false; + } + response.send(Ok(())).unwrap(); + } + GuillotineMessage::DeleteProcess { + module_id, + response, + } => { + if !ids_to_processes.contains_key(&module_id) { + response.send(Err(String::from("Could not find any process with that moduleId in this runner. Is this stale data?"))).unwrap(); + continue; + } + + let process = ids_to_processes.get_mut(&module_id).unwrap(); + if process.is_process_running().0 { + process.wait_for_quit_or_kill_within(1000).await; + } + ids_to_processes.remove(&module_id); + response.send(Ok(())).unwrap(); + } msg => panic!("Unhandled guillotine message: {:#?}", msg), } } } } + + for (_, mut process) in ids_to_processes { + process.wait_for_quit_or_kill_within(1000).await; + } } async fn try_connecting_to_host(node: &NodeConfig) -> bool { From a6128fd7af880fd3177d307a452c0269afed906a Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Fri, 29 May 2020 11:23:15 +0530 Subject: [PATCH 20/26] Updated dependencies --- Cargo.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80a7ee5..ed79b73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -386,9 +386,9 @@ checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "js-sys" -version = "0.3.39" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" +checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" dependencies = [ "wasm-bindgen", ] @@ -745,9 +745,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "wasm-bindgen" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" +checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -755,9 +755,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" +checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" dependencies = [ "bumpalo", "lazy_static", @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04" +checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6" dependencies = [ "cfg-if", "js-sys", @@ -782,9 +782,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" +checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -792,9 +792,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" +checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ "proc-macro2", "quote", @@ -805,15 +805,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" +checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" [[package]] name = "web-sys" -version = "0.3.39" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" +checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" dependencies = [ "js-sys", "wasm-bindgen", From 10cff0fa7ba98153fc3835f76585a216b46c0ada Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Fri, 29 May 2020 12:37:46 +0530 Subject: [PATCH 21/26] Added proxying of listing modules through guillotine instead of directly to juno to allow for filtering, if required --- src/cli/list_modules.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli/list_modules.rs b/src/cli/list_modules.rs index bf9dc86..5b51129 100644 --- a/src/cli/list_modules.rs +++ b/src/cli/list_modules.rs @@ -30,7 +30,10 @@ pub async fn list_modules(config: RunnerConfig) { .await .unwrap(); let modules = module - .call_function("juno.listModules", HashMap::new()) + .call_function( + &format!("{}.listModules", constants::APP_NAME), + HashMap::new(), + ) .await .unwrap(); if !modules.is_array() { From 6df42aaffe673f8715cd9473807fb7261717c499 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Sat, 30 May 2020 12:11:51 +0530 Subject: [PATCH 22/26] Added arg names --- src/main.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2b30ba0..04bff8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,19 +48,29 @@ async fn main() { .arg( Arg::with_name("path") .takes_value(true) + .value_name("PATH") .required(true) .allow_hyphen_values(false), ) .arg( Arg::with_name("node") + .short("n") + .long("node") .takes_value(true) + .value_name("NODE-NAME") .required(true) + .multiple(false) .allow_hyphen_values(true), ) .arg( - Arg::with_name("dont-start") - .takes_value(false) - .required(false), + Arg::with_name("autostart") + .short("a") + .long("autostart") + .takes_value(true) + .value_name("true / false") + .required(false) + .multiple(false) + .allow_hyphen_values(true), ), ) .subcommand( @@ -70,6 +80,7 @@ async fn main() { .arg( Arg::with_name("pid") .takes_value(true) + .value_name("PID") .required(true) .allow_hyphen_values(false), ), @@ -81,6 +92,7 @@ async fn main() { .arg( Arg::with_name("pid") .takes_value(true) + .value_name("PID") .required(true) .allow_hyphen_values(false), ), @@ -109,6 +121,7 @@ async fn main() { .short("n") .long("node") .takes_value(true) + .value_name("NODE-NAME") .required(false), ), ) @@ -118,6 +131,7 @@ async fn main() { .arg( Arg::with_name("pid") .takes_value(true) + .value_name("PID") .required(true) .allow_hyphen_values(false), ), @@ -128,6 +142,7 @@ async fn main() { .arg( Arg::with_name("pid") .takes_value(true) + .value_name("PID") .required(true) .allow_hyphen_values(false), ), @@ -138,6 +153,7 @@ async fn main() { .arg( Arg::with_name("pid") .takes_value(true) + .value_name("PID") .required(true) .allow_hyphen_values(false), ), @@ -159,7 +175,7 @@ async fn main() { ctrlc::set_handler(|| task::block_on(on_exit())).expect("Error setting the CtrlC handler"); - let config_path = Path::new(args.value_of("config").unwrap_or("./config.json")); + let config_path = Path::new(args.value_of("config").unwrap()); if !config_path.exists().await { println!( From bb541b4396be68069bef817d9240ade0a18fabcb Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Sat, 30 May 2020 13:15:58 +0530 Subject: [PATCH 23/26] Added buffered logs. Need to make it chuncked, once juno supports chuncked data --- src/cli/get_process_logs.rs | 87 +++++++++++++++++++++++ src/cli/mod.rs | 2 + src/host/juno_module.rs | 51 ++++++++++++++ src/host/runner.rs | 115 +++++++++++++++++++++++++++++++ src/main.rs | 1 + src/models/guillotine_message.rs | 4 ++ src/node/juno_module.rs | 46 +++++++++++++ src/node/runner.rs | 38 ++++++++++ 8 files changed, 344 insertions(+) create mode 100644 src/cli/get_process_logs.rs diff --git a/src/cli/get_process_logs.rs b/src/cli/get_process_logs.rs new file mode 100644 index 0000000..7ea675f --- /dev/null +++ b/src/cli/get_process_logs.rs @@ -0,0 +1,87 @@ +use crate::{cli::get_juno_module_from_config, logger, models::RunnerConfig, utils::constants}; + +use clap::ArgMatches; +use colored::Colorize; +use juno::models::{Number, Value}; +use std::collections::HashMap; + +pub async fn get_process_logs(config: RunnerConfig, args: &ArgMatches<'_>) { + let result = get_juno_module_from_config(&config); + let mut module = if let Ok(module) = result { + module + } else { + logger::error(if let Err(err) = result { + err + } else { + return; + }); + return; + }; + let pid = args.value_of("pid"); + if pid.is_none() { + logger::error("No pid supplied!"); + return; + } + let pid = pid.unwrap().parse::(); + if pid.is_err() { + logger::error("Pid supplied is not a number!"); + return; + } + let pid = pid.unwrap(); + + module + .initialize( + &format!("{}-cli", constants::APP_NAME), + constants::APP_VERSION, + HashMap::new(), + ) + .await + .unwrap(); + + let response = module + .call_function(&format!("{}.getProcessLogs", constants::APP_NAME), { + let mut map = HashMap::new(); + map.insert(String::from("moduleId"), Value::Number(Number::PosInt(pid))); + map + }) + .await + .unwrap(); + + if !response.is_object() { + logger::error(&format!("Expected object response. Got {:?}", response)); + return; + } + let response = response.as_object().unwrap(); + + let success = response.get("success").unwrap(); + if !success.as_bool().unwrap() { + let error = response.get("error").unwrap().as_string().unwrap(); + logger::error(&format!("Error getting logs of process: {}", error)); + return; + } + + let stdout_header = format!("|{}-stdout|", pid).green(); + let stderr_header = format!("|{}-stderr|", pid).red(); + + println!(); + response + .get("stdout") + .unwrap() + .as_string() + .unwrap() + .split('\n') + .for_each(|line| { + println!("{}: {}", stdout_header, line); + }); + println!(); + response + .get("stderr") + .unwrap() + .as_string() + .unwrap() + .split('\n') + .for_each(|line| { + println!("{}: {}", stderr_header, line); + }); + println!(); +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 430b96f..70f1153 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,6 +1,7 @@ mod add_process; mod delete_process; mod get_info; +mod get_process_logs; mod list_all_processes; mod list_modules; mod list_nodes; @@ -12,6 +13,7 @@ mod stop_process; pub use add_process::add_process; pub use delete_process::delete_process; pub use get_info::get_info; +pub use get_process_logs::get_process_logs; pub use list_all_processes::list_all_processes; pub use list_modules::list_modules; pub use list_nodes::list_nodes; diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 4f58609..4db902a 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -109,6 +109,11 @@ pub async fn setup_host_module( .await .unwrap(); + module + .declare_function("getProcessLogs", get_process_logs) + .await + .unwrap(); + module .register_hook("juno.moduleDeactivated", module_deactivated) .await @@ -1100,6 +1105,52 @@ fn delete_process(mut args: HashMap) -> Value { }) } +fn get_process_logs(mut args: HashMap) -> Value { + task::block_on(async { + let message_sender = MESSAGE_SENDER.read().await; + if message_sender.is_none() { + drop(message_sender); + return generate_error_response("Host module is not initialized yet"); + } + + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + message_sender + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::GetProcessLogs { + module_id, + response: sender, + }) + .await + .unwrap(); + drop(message_sender); + + let result = receiver.await.unwrap(); + if let Ok((stdout, stderr)) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert(String::from("stdout"), Value::String(stdout)); + map.insert(String::from("stderr"), Value::String(stderr)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + fn generate_error_response(error_message: &str) -> Value { Value::Object({ let mut map = HashMap::new(); diff --git a/src/host/runner.rs b/src/host/runner.rs index 01dfcd9..8a7aa35 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -885,6 +885,121 @@ async fn keep_host_alive( node.delete_process_by_id(module_id); response.send(Ok(())).unwrap(); } + GuillotineMessage::GetProcessLogs { + module_id, + response, + } => { + let node = node_runners + .values() + .find(|node| node.get_process_by_id(module_id).is_some()); + if node.is_none() { + response + .send(Err(format!( + "No node found running the module with the ID {}", + module_id + ))) + .unwrap(); + continue; + } + let node = node.unwrap(); + if !node.connected { + response + .send(Err(format!( + "The node (with the name '{}') running the module {} is not connected", + node.name, + module_id + ))) + .unwrap(); + continue; + } + + // Now stop the process + let result = juno_module + .call_function( + &format!( + "{}-node-{}.getProcessLogs", + constants::APP_NAME, + node.name + ), + { + let mut map = HashMap::new(); + map.insert( + String::from("moduleId"), + Value::Number(Number::PosInt(module_id)), + ); + map + }, + ) + .await; + if let Err(error) = result { + response + .send(Err(format!("Error sending the log command: {}", error))) + .unwrap(); + continue; + } + let result = result.unwrap(); + let mut result = if let Value::Object(args) = result { + args + } else { + response + .send(Err(format!( + "Response of log command wasn't an object. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + + let success = if let Some(Value::Bool(success)) = result.remove("success") { + success + } else { + response + .send(Err(format!( + "Success key of log command wasn't a bool. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + if !success { + response + .send(Err( + if let Some(Value::String(error)) = result.remove("error") { + format!("Error getting logs of process: {}", error) + } else { + format!( + "Error key of log command wasn't a string. Got: {:#?}", + result + ) + }, + )) + .unwrap(); + continue; + } + let stdout = if let Some(Value::String(stdout)) = result.remove("stdout") { + stdout + } else { + response + .send(Err(format!( + "Stdout key of log command wasn't a string. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + let stderr = if let Some(Value::String(stderr)) = result.remove("stderr") { + stderr + } else { + response + .send(Err(format!( + "Stderr key of log command wasn't a string. Got: {:#?}", + result + ))) + .unwrap(); + continue; + }; + response.send(Ok((stdout, stderr))).unwrap(); + } } } } diff --git a/src/main.rs b/src/main.rs index 04bff8f..9a44bae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -203,6 +203,7 @@ async fn main() { // Cli stuff ("add", Some(args)) => cli::add_process(config, args).await, ("delete", Some(args)) => cli::delete_process(config, args).await, + ("logs", Some(args)) => cli::get_process_logs(config, args).await, ("info", Some(args)) => cli::get_info(config, args).await, ("list-all-processes", Some(_)) => cli::list_all_processes(config).await, ("list-modules", Some(_)) => cli::list_modules(config).await, diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index 56dd0d5..4002627 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -70,6 +70,10 @@ pub enum GuillotineMessage { module_id: u64, response: Sender>, }, + GetProcessLogs { + module_id: u64, + response: Sender>, // (stdout, stderr) + } } // TODO ADD: // ReloadConfig, diff --git a/src/node/juno_module.rs b/src/node/juno_module.rs index b54f20b..8b1ccf8 100644 --- a/src/node/juno_module.rs +++ b/src/node/juno_module.rs @@ -74,6 +74,11 @@ pub async fn setup_module( .await .unwrap(); + juno_module + .declare_function("getLogs", get_logs) + .await + .unwrap(); + // Register node here let response = juno_module .call_function(&format!("{}.registerNode", constants::APP_NAME), { @@ -421,6 +426,47 @@ fn delete_process(mut args: HashMap) -> Value { }) } +fn get_logs(mut args: HashMap) -> Value { + task::block_on(async { + let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { + match module_id { + Number::PosInt(module_id) => module_id, + Number::NegInt(module_id) => module_id as u64, + Number::Float(module_id) => module_id as u64, + } + } else { + return generate_error_response("Module ID is not a number"); + }; + + let (sender, receiver) = channel::>(); + MESSAGE_SENDER + .read() + .await + .as_ref() + .unwrap() + .clone() + .send(GuillotineMessage::GetProcessLogs { + module_id, + response: sender, + }) + .await + .unwrap(); + + let result = receiver.await.unwrap(); + if let Ok((stdout, stderr)) = result { + Value::Object({ + let mut map = HashMap::new(); + map.insert(String::from("success"), Value::Bool(true)); + map.insert(String::from("stdout"), Value::String(stdout)); + map.insert(String::from("stderr"), Value::String(stderr)); + map + }) + } else { + generate_error_response(&result.unwrap_err()) + } + }) +} + fn generate_error_response(error_message: &str) -> Value { Value::Object({ let mut map = HashMap::new(); diff --git a/src/node/runner.rs b/src/node/runner.rs index ebd2ba7..2ae41aa 100644 --- a/src/node/runner.rs +++ b/src/node/runner.rs @@ -405,6 +405,44 @@ async fn keep_node_alive(config: RunnerConfig, auto_start_processes: Vec { + if !ids_to_processes.contains_key(&module_id) { + response.send(Err(String::from("Could not find any process with that moduleId in this runner. Is this stale data?"))).unwrap(); + continue; + } + + let process = ids_to_processes.get(&module_id).unwrap(); + if process.log_dir.is_none() { + response.send(Err(String::from("The process's output isn't being logged. Is the log directory mentioned in the runner config?"))).unwrap(); + continue; + } + let stdout = fs::read_to_string( + Path::new(process.log_dir.as_ref().unwrap()).join("output.log"), + ) + .await; + if let Err(err) = stdout { + response + .send(Err(format!("Error while reading stdout logs: {}", err))) + .unwrap(); + continue; + } + let stderr = fs::read_to_string( + Path::new(process.log_dir.as_ref().unwrap()).join("error.log"), + ) + .await; + if let Err(err) = stderr { + response + .send(Err(format!("Error while reading stderr logs: {}", err))) + .unwrap(); + continue; + } + response + .send(Ok((stdout.unwrap(), stderr.unwrap()))) + .unwrap(); + } msg => panic!("Unhandled guillotine message: {:#?}", msg), } } From 4d65e757f613696dff07d954b76e22170451a501 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Sat, 30 May 2020 13:24:11 +0530 Subject: [PATCH 24/26] Added rustfmt formatting --- src/models/guillotine_message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/guillotine_message.rs b/src/models/guillotine_message.rs index 4002627..ed4920a 100644 --- a/src/models/guillotine_message.rs +++ b/src/models/guillotine_message.rs @@ -73,7 +73,7 @@ pub enum GuillotineMessage { GetProcessLogs { module_id: u64, response: Sender>, // (stdout, stderr) - } + }, } // TODO ADD: // ReloadConfig, From 8f09501ae75a737d7efabc2cfff43cedf5e31c88 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Tue, 23 Jun 2020 11:08:52 +0530 Subject: [PATCH 25/26] Fixed but with logs not displaying --- Cargo.lock | 423 ++++++++++++++++------------------------ Cargo.toml | 4 +- src/cli/add_process.rs | 15 +- src/cli/list_modules.rs | 28 ++- src/host/juno_module.rs | 8 +- src/host/runner.rs | 22 +-- src/main.rs | 12 ++ src/node/juno_module.rs | 4 +- 8 files changed, 231 insertions(+), 285 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed79b73..22df138 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi", + "winapi 0.3.8", ] [[package]] @@ -21,40 +21,45 @@ dependencies = [ [[package]] name = "async-std" -version = "1.6.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45cee2749d880d7066e328a7e161c7470ced883b2fd000ca4643e9f1dd5083a" +checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" dependencies = [ "async-attributes", "async-task", + "crossbeam-channel", + "crossbeam-deque", "crossbeam-utils", - "futures-channel", "futures-core", "futures-io", - "futures-timer", + "futures-timer 2.0.2", "kv-log-macro", "log", "memchr", + "mio", + "mio-uds", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", - "smol", - "wasm-bindgen-futures", ] [[package]] name = "async-task" -version = "3.0.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" +checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" +dependencies = [ + "libc", + "winapi 0.3.8", +] [[package]] name = "async-trait" -version = "0.1.31" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" +checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92" dependencies = [ "proc-macro2", "quote", @@ -69,7 +74,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.8", ] [[package]] @@ -84,12 +89,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "bumpalo" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" - [[package]] name = "cc" version = "1.0.54" @@ -146,21 +145,7 @@ checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" dependencies = [ "atty", "lazy_static", - "winapi", -] - -[[package]] -name = "crossbeam" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" -dependencies = [ - "cfg-if", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", + "winapi 0.3.8", ] [[package]] @@ -199,16 +184,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "crossbeam-queue" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -227,9 +202,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a4ba686dff9fa4c1c9636ce1010b0cf98ceb421361b0bb3d6faeec43bd217a7" dependencies = [ "nix", - "winapi", + "winapi 0.3.8", ] +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + [[package]] name = "futures" version = "0.3.5" @@ -305,15 +296,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "futures-timer" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" + [[package]] name = "futures-timer" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" -dependencies = [ - "gloo-timers", - "send_wrapper", -] [[package]] name = "futures-util" @@ -335,19 +328,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gloo-timers" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "guillotine" version = "0.1.0" @@ -359,45 +339,43 @@ dependencies = [ "colored", "ctrlc", "futures", - "futures-timer", + "futures-timer 3.0.2", "juno", "lazy_static", "nix", "serde", "serde_derive", "serde_json", - "winapi", + "winapi 0.3.8", ] [[package]] name = "hermit-abi" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" dependencies = [ "libc", ] [[package]] -name = "itoa" -version = "0.4.5" +name = "iovec" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] [[package]] -name = "js-sys" -version = "0.3.40" +name = "itoa" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" -dependencies = [ - "wasm-bindgen", -] +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "juno" -version = "0.1.4-beta" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "837e835ffcf014e672d708848e11e000bd9d64608005d21333ec437a3331b35a" +version = "0.1.6" dependencies = [ "async-std", "async-trait", @@ -406,6 +384,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "kv-log-macro" version = "1.0.6" @@ -457,6 +445,59 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.8", +] + [[package]] name = "nix" version = "0.17.0" @@ -472,9 +513,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" dependencies = [ "autocfg", "num-traits", @@ -482,9 +523,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ "autocfg", ] @@ -507,18 +548,18 @@ checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" [[package]] name = "pin-project" -version = "0.4.17" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" +checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.17" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" dependencies = [ "proc-macro2", "quote", @@ -527,9 +568,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" [[package]] name = "pin-utils" @@ -537,18 +578,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0deb65f46e873ba8aa7c6a8dbe3f23cb1bf59c339a81a1d56361dde4d66ac8" -dependencies = [ - "crossbeam-utils", - "futures-io", - "futures-sink", - "futures-util", -] - [[package]] name = "proc-macro-hack" version = "0.5.16" @@ -557,45 +586,33 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro-nested" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] name = "proc-macro2" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" - [[package]] name = "ryu" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" - -[[package]] -name = "scoped-tls-hkt" -version = "0.1.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "scopeguard" @@ -603,23 +620,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - [[package]] name = "serde" -version = "1.0.110" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +checksum = "6135c78461981c79497158ef777264c51d9d0f4f3fc3a4d22b915900e42dac6a" [[package]] name = "serde_derive" -version = "1.0.110" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +checksum = "93c5eaa17d0954cb481cdcfffe9d84fcfa7a1a9f2349271e678677be4c26ae31" dependencies = [ "proc-macro2", "quote", @@ -628,9 +639,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.53" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" +checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" dependencies = [ "itoa", "ryu", @@ -643,37 +654,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "smol" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686c634ad1873fffef6aed20f180eede424fbf3bb31802394c90fd7335a661b7" -dependencies = [ - "async-task", - "crossbeam", - "futures-io", - "futures-util", - "nix", - "once_cell", - "piper", - "scoped-tls-hkt", - "slab", - "socket2", - "wepoll-binding", -] - -[[package]] -name = "socket2" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "winapi", -] - [[package]] name = "strsim" version = "0.8.0" @@ -682,9 +662,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.27" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de" +checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" dependencies = [ "proc-macro2", "quote", @@ -716,7 +696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "winapi", + "winapi 0.3.8", ] [[package]] @@ -744,99 +724,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] -name = "wasm-bindgen" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" - -[[package]] -name = "web-sys" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wepoll-binding" -version = "2.0.2" +name = "winapi" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7" -dependencies = [ - "bitflags", - "wepoll-sys", -] - -[[package]] -name = "wepoll-sys" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" -dependencies = [ - "cc", -] +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" [[package]] name = "winapi" @@ -848,6 +739,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -860,7 +757,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.8", ] [[package]] @@ -868,3 +765,13 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml index a683cbc..e9a0430 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ clap = "2" serde_json = "1" serde = "1" serde_derive = "1" -juno = "0.1.4-beta" +juno = { path = "../juno-rust/" } futures = "0.3.4" futures-timer = "3.0.2" lazy_static = "1.4.0" @@ -22,4 +22,4 @@ chrono = "0.4.11" ctrlc = "3.1.4" nix = "0.17.0" winapi = "0.3.8" -async-std = { version = "1", features = ["attributes"] } +async-std = { version = "=1.5.0", features = ["attributes"] } diff --git a/src/cli/add_process.rs b/src/cli/add_process.rs index 1cf5753..727e84a 100644 --- a/src/cli/add_process.rs +++ b/src/cli/add_process.rs @@ -47,12 +47,15 @@ pub async fn add_process(config: RunnerConfig, args: &ArgMatches<'_>) { .unwrap(); let response = module - .call_function(&format!("{}.addProcess", constants::APP_NAME), { - let mut map = HashMap::new(); - map.insert(String::from("node"), Value::String(node.clone())); - map.insert(String::from("path"), Value::String(path)); - map - }) + .call_function( + &format!("{}-node-{}.addProcess", constants::APP_NAME, node), + { + let mut map = HashMap::new(); + //map.insert(String::from("node"), Value::String(node.clone())); + map.insert(String::from("path"), Value::String(path)); + map + }, + ) .await .unwrap(); diff --git a/src/cli/list_modules.rs b/src/cli/list_modules.rs index 5b51129..01d29cf 100644 --- a/src/cli/list_modules.rs +++ b/src/cli/list_modules.rs @@ -6,6 +6,7 @@ use cli_table::{ }, Cell, Row, Table, }; +use juno::models::Value; use std::collections::HashMap; pub async fn list_modules(config: RunnerConfig) { @@ -29,18 +30,35 @@ pub async fn list_modules(config: RunnerConfig) { ) .await .unwrap(); - let modules = module + let response = module .call_function( &format!("{}.listModules", constants::APP_NAME), HashMap::new(), ) .await .unwrap(); - if !modules.is_array() { - logger::error(&format!("Expected array response. Got {:?}", modules)); + + let modules = if let Value::Object(mut map) = response { + if let Some(Value::Bool(success)) = map.remove("success") { + if success { + if let Some(Value::Array(modules)) = map.remove("modules") { + modules + } else { + logger::error("Invalid nodes key in response"); + return; + } + } else { + logger::error(map.remove("error").unwrap().as_string().unwrap()); + return; + } + } else { + logger::error("Invalid success key in response"); + return; + } + } else { + logger::error(&format!("Expected object response. Got: {:#?}", response)); return; - } - let modules = modules.as_array().unwrap(); + }; // Make the looks first let header_format = CellFormat::builder() diff --git a/src/host/juno_module.rs b/src/host/juno_module.rs index 4db902a..e801273 100644 --- a/src/host/juno_module.rs +++ b/src/host/juno_module.rs @@ -37,7 +37,13 @@ pub async fn setup_host_module( module .initialize(constants::APP_NAME, constants::APP_VERSION, HashMap::new()) .await - .unwrap_or_else(|_| panic!("Could not initialize {} Juno Module", constants::APP_NAME)); + .unwrap_or_else(|err| { + panic!( + "Could not initialize {} Juno Module: {}", + constants::APP_NAME, + err + ) + }); module .declare_function("registerNode", register_node) diff --git a/src/host/runner.rs b/src/host/runner.rs index 8a7aa35..f9aa87c 100644 --- a/src/host/runner.rs +++ b/src/host/runner.rs @@ -12,7 +12,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -use async_std::{fs, net::TcpStream, path::Path, sync::Mutex}; +use async_std::{fs, net::TcpStream, path::Path, sync::RwLock}; use future::Either; use futures::{ channel::{mpsc::unbounded, oneshot::Sender}, @@ -22,7 +22,7 @@ use futures_timer::Delay; use juno::models::{Number, Value}; lazy_static! { - static ref CLOSE_FLAG: Mutex = Mutex::new(false); + static ref CLOSE_FLAG: RwLock = RwLock::new(false); } pub async fn run(mut config: RunnerConfig, initialized_sender: Sender>) { @@ -98,7 +98,7 @@ pub async fn run(mut config: RunnerConfig, initialized_sender: Sender } pub async fn on_exit() { - *CLOSE_FLAG.lock().await = true; + *CLOSE_FLAG.write().await = true; } async fn keep_host_alive( @@ -108,7 +108,7 @@ async fn keep_host_alive( ) { // Spawn juno before spawing any modules while !juno_process.is_process_running().0 { - if *CLOSE_FLAG.lock().await { + if *CLOSE_FLAG.read().await { logger::info("Exit command received. Exiting"); initialized_sender.send(None).unwrap(); return; @@ -130,11 +130,15 @@ async fn keep_host_alive( let mut timer_future = Delay::new(Duration::from_millis(100)); let mut command_future = command_receiver.next(); loop { + if *CLOSE_FLAG.read().await { + logger::info("Exit command received. Exiting"); + break; + } let selection = future::select(timer_future, command_future).await; match selection { Either::Left((_, next_command_future)) => { - if *CLOSE_FLAG.lock().await { + if *CLOSE_FLAG.read().await { logger::info("Exit command received. Exiting"); break; } @@ -338,7 +342,7 @@ async fn keep_host_alive( response .send(Err(format!("Expected array response. Got {:?}", modules))) .unwrap(); - return; + continue; }; let modules = modules .into_iter() @@ -539,11 +543,7 @@ async fn keep_host_alive( let result = juno_module .call_function( - &format!( - "{}-node-{}.addProcess", - constants::APP_AUTHORS, - node_name - ), + &format!("{}-node-{}.addProcess", constants::APP_NAME, node_name), { let mut map = HashMap::new(); map.insert(String::from("path"), Value::String(path)); diff --git a/src/main.rs b/src/main.rs index 9a44bae..6291671 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,6 +85,18 @@ async fn main() { .allow_hyphen_values(false), ), ) + .subcommand( + SubCommand::with_name("logs") + .alias("l") + .about("Get the logs for a module") + .arg( + Arg::with_name("pid") + .takes_value(true) + .value_name("PID") + .required(true) + .allow_hyphen_values(false), + ), + ) .subcommand( SubCommand::with_name("info") .alias("i") diff --git a/src/node/juno_module.rs b/src/node/juno_module.rs index 8b1ccf8..8d381e9 100644 --- a/src/node/juno_module.rs +++ b/src/node/juno_module.rs @@ -75,7 +75,7 @@ pub async fn setup_module( .unwrap(); juno_module - .declare_function("getLogs", get_logs) + .declare_function("getProcessLogs", get_process_logs) .await .unwrap(); @@ -426,7 +426,7 @@ fn delete_process(mut args: HashMap) -> Value { }) } -fn get_logs(mut args: HashMap) -> Value { +fn get_process_logs(mut args: HashMap) -> Value { task::block_on(async { let module_id = if let Some(Value::Number(module_id)) = args.remove("moduleId") { match module_id { From 2af66e5c3bb7a9b37e20e40c2b156fd26fa4d05a Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Tue, 23 Jun 2020 16:33:20 +0530 Subject: [PATCH 26/26] Added exitable cli futures --- Cargo.lock | 13 +++++++------ Cargo.toml | 1 + src/cli/mod.rs | 2 -- src/main.rs | 40 +++++++++++++++++++++++++--------------- src/utils/exitable.rs | 27 +++++++++++++++++++++++++++ src/utils/mod.rs | 1 + 6 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 src/utils/exitable.rs diff --git a/Cargo.lock b/Cargo.lock index 22df138..109baff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,6 +333,7 @@ name = "guillotine" version = "0.1.0" dependencies = [ "async-std", + "async-trait", "chrono", "clap", "cli-table", @@ -622,15 +623,15 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6135c78461981c79497158ef777264c51d9d0f4f3fc3a4d22b915900e42dac6a" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" [[package]] name = "serde_derive" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93c5eaa17d0954cb481cdcfffe9d84fcfa7a1a9f2349271e678677be4c26ae31" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" dependencies = [ "proc-macro2", "quote", @@ -662,9 +663,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" +checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e9a0430..ab80e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ ctrlc = "3.1.4" nix = "0.17.0" winapi = "0.3.8" async-std = { version = "=1.5.0", features = ["attributes"] } +async-trait = "0.1.36" diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 70f1153..35ad0c8 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -26,8 +26,6 @@ use crate::models::RunnerConfig; use chrono::{prelude::*, Utc}; use juno::JunoModule; -pub async fn on_exit() {} - fn get_juno_module_from_config(config: &RunnerConfig) -> Result { if let Some(host) = &config.host { if host.connection_type == "unix_socket" { diff --git a/src/main.rs b/src/main.rs index 6291671..ed84c77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,8 @@ extern crate futures_timer; extern crate juno; extern crate serde; extern crate serde_json; +#[macro_use] +extern crate async_trait; #[cfg(target_family = "unix")] extern crate nix; @@ -29,11 +31,16 @@ mod runner; mod utils; use models::parser; -use utils::{constants, logger}; +use utils::{constants, exitable::Exitable, logger}; -use async_std::{fs, path::Path, task}; +use async_std::{fs, path::Path, sync::Mutex, task}; use clap::{App, Arg, SubCommand}; -use futures::future; +use futures::channel::oneshot::{channel, Receiver, Sender}; + +lazy_static! { + static ref CLOSE_SENDER: Mutex>> = Mutex::new(None); + static ref CLOSE_RECEIVER: Mutex>> = Mutex::new(None); +} #[async_std::main] async fn main() { @@ -185,6 +192,9 @@ async fn main() { ) .get_matches(); + let (sender, receiver) = channel::<()>(); + CLOSE_SENDER.lock().await.replace(sender); + CLOSE_RECEIVER.lock().await.replace(receiver); ctrlc::set_handler(|| task::block_on(on_exit())).expect("Error setting the CtrlC handler"); let config_path = Path::new(args.value_of("config").unwrap()); @@ -213,17 +223,17 @@ async fn main() { ("run", Some(_)) | ("", _) => runner::run(config).await, // Cli stuff - ("add", Some(args)) => cli::add_process(config, args).await, - ("delete", Some(args)) => cli::delete_process(config, args).await, - ("logs", Some(args)) => cli::get_process_logs(config, args).await, - ("info", Some(args)) => cli::get_info(config, args).await, - ("list-all-processes", Some(_)) => cli::list_all_processes(config).await, - ("list-modules", Some(_)) => cli::list_modules(config).await, - ("list-nodes", Some(_)) => cli::list_nodes(config).await, - ("list-processes", Some(args)) => cli::list_processes(config, args).await, - ("restart", Some(args)) => cli::restart_process(config, args).await, - ("start", Some(args)) => cli::start_process(config, args).await, - ("stop", Some(args)) => cli::stop_process(config, args).await, + ("add", Some(args)) => cli::add_process(config, args).exitable().await, + ("delete", Some(args)) => cli::delete_process(config, args).exitable().await, + ("logs", Some(args)) => cli::get_process_logs(config, args).exitable().await, + ("info", Some(args)) => cli::get_info(config, args).exitable().await, + ("list-all-processes", Some(_)) => cli::list_all_processes(config).exitable().await, + ("list-modules", Some(_)) => cli::list_modules(config).exitable().await, + ("list-nodes", Some(_)) => cli::list_nodes(config).exitable().await, + ("list-processes", Some(args)) => cli::list_processes(config, args).exitable().await, + ("restart", Some(args)) => cli::restart_process(config, args).exitable().await, + ("start", Some(args)) => cli::start_process(config, args).exitable().await, + ("stop", Some(args)) => cli::stop_process(config, args).exitable().await, (cmd, _) => println!("Unknown command '{}'", cmd), } @@ -231,5 +241,5 @@ async fn main() { async fn on_exit() { logger::info("Received exit code. Closing all modules"); - future::join(runner::on_exit(), cli::on_exit()).await; + runner::on_exit().await; } diff --git a/src/utils/exitable.rs b/src/utils/exitable.rs new file mode 100644 index 0000000..1518ddb --- /dev/null +++ b/src/utils/exitable.rs @@ -0,0 +1,27 @@ + +use futures::{future, Future}; +use future::Either; +use crate::utils::logger; + +#[async_trait] +pub trait Exitable { + async fn exitable(self) -> O; +} + +#[async_trait] +impl Exitable for F +where + F: Future + Send, +{ + async fn exitable(self) -> O { + let mut close_receiver = crate::CLOSE_RECEIVER.lock().await; + let receiver = close_receiver.as_mut().unwrap(); + match future::select(Box::pin(self), receiver).await { + Either::Left((result, _)) => result, + Either::Right((..)) => { + logger::warn("Recieved exit code. Quitting..."); + std::process::exit(0); + }, + } + } +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c5d4373..73f88b1 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod constants; pub mod logger; +pub mod exitable;