From 642f39bc5db0906a0e6a8f534e109407428cf027 Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Mon, 30 Oct 2023 14:09:40 +0100 Subject: [PATCH 1/8] test: add more tests on process spawn Signed-off-by: WoodenMaiden --- agent/lib/src/runner_engine/service.rs | 40 ++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/agent/lib/src/runner_engine/service.rs b/agent/lib/src/runner_engine/service.rs index 64eaf55..b4ec073 100644 --- a/agent/lib/src/runner_engine/service.rs +++ b/agent/lib/src/runner_engine/service.rs @@ -223,16 +223,38 @@ mod tests { string } + #[test] + fn run_one_works_with_ouputs_and_code() { + let res = RunnerEngine::new(ExecuteRequest { + id: "".to_string(), + files: vec![], + steps: vec![] + }).run_one("echo -n 'This is stdout' && echo -n 'This is stderr' >&2 && exit 1"); + + assert!(res.is_ok()); + + let code_return = res.unwrap(); + + assert_eq!(code_return.stdout, "This is stdout"); + assert_eq!(code_return.stderr, "This is stderr"); + assert_eq!(code_return.exit_code, 1); + } + /// Test the creation of a file #[test] fn workload_runs_correctly() { let files: Vec = Vec::new(); - let mut steps: Vec = Vec::new(); - let step = ExecuteRequestStep { - command: "echo 'This is stdout' && echo 'This is stderr' >&2".to_string(), - enable_output: true, - }; - steps.push(step); + let steps: Vec = vec![ + ExecuteRequestStep { + command: "echo 'This is stdout' && echo 'This is stderr' >&2".to_string(), + enable_output: true, + }, + ExecuteRequestStep { + command: "echo 'This is stdout' && echo 'This is stderr' >&2 && exit 1".to_string(), + enable_output: false, + }, + ]; + let request_data = ExecuteRequest { id: "4bf68974-c315-4c41-aee2-3dc2920e76e9".to_string(), files, @@ -246,6 +268,12 @@ mod tests { assert_eq!(res.steps[0].exit_code, 0); assert_eq!(res.steps[0].stderr, "This is stderr\n"); assert_eq!(res.steps[0].stdout, "This is stdout\n"); + + println!("{:?}", res.steps[1]); + assert_eq!(res.steps[1].exit_code, 1); + assert_eq!(res.steps[1].stderr, "This is stderr\n"); + assert!(res.steps[1].stdout.is_empty()); + assert_eq!(res.id, "4bf68974-c315-4c41-aee2-3dc2920e76e9"); } From 9ea2ba747aede43ededdc259459fc271ed4a4381 Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Mon, 30 Oct 2023 15:44:37 +0100 Subject: [PATCH 2/8] test: mock fs using tempfile crate Signed-off-by: WoodenMaiden --- agent/Cargo.toml | 2 +- agent/lib/src/runner_engine/model.rs | 37 +++++++++- agent/lib/src/runner_engine/service.rs | 93 +++++++++++++------------- 3 files changed, 82 insertions(+), 50 deletions(-) diff --git a/agent/Cargo.toml b/agent/Cargo.toml index df8a98f..13ab045 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -31,4 +31,4 @@ name = "agent_lib" path = "lib/src/lib.rs" [dev-dependencies] -rand = "0.8.5" +tempfile = "3.8.1" diff --git a/agent/lib/src/runner_engine/model.rs b/agent/lib/src/runner_engine/model.rs index 47358be..84d3037 100644 --- a/agent/lib/src/runner_engine/model.rs +++ b/agent/lib/src/runner_engine/model.rs @@ -1,4 +1,10 @@ -use std::path::PathBuf; +use anyhow::Result; +use std::{ + fs::File, + io::{Read, Write}, + path::Path, + path::PathBuf, +}; use serde::{Deserialize, Serialize}; @@ -49,3 +55,32 @@ impl CodeReturn { } } } + +/// A trait responsible for CRU operations on files and dir +/// Mainly used in tests to mock file operations +trait FileHandler: Read + Write { + fn create>(path: P) -> Result + where + Self: Sized; + fn read>(path: P) -> Result + where + Self: Sized; +} + +impl FileHandler for File { + fn create>(path: P) -> Result + where + Self: Sized, + { + let file = File::create(path)?; + Ok(file) + } + + fn read>(path: P) -> Result + where + Self: Sized, + { + let file = File::open(path)?; + Ok(file) + } +} diff --git a/agent/lib/src/runner_engine/service.rs b/agent/lib/src/runner_engine/service.rs index b4ec073..6592fe7 100644 --- a/agent/lib/src/runner_engine/service.rs +++ b/agent/lib/src/runner_engine/service.rs @@ -10,16 +10,17 @@ use std::{ process::Command, }; -/// The path where the workspace will be created -const WORKSPACE_PATH: &str = "/tmp"; - -/// The RunnerEngine API +/// The RunnerEngine API, responsible for running a request message +#[derive(Clone, Debug)] pub struct RunnerEngine { + /// the request message pub request_message: ExecuteRequest, + /// The root path of the workspace + pub root_path: PathBuf, } impl RunnerEngine { - /// Create a new instance of RunnerEngine + /// Create a new instance of RunnerEngine and set the root path to your system's temp folder/directory /// /// # Arguments /// @@ -29,7 +30,27 @@ impl RunnerEngine { /// /// * `Self` - The new instance of RunnerEngine pub fn new(request_message: ExecuteRequest) -> Self { - Self { request_message } + Self { + request_message, + root_path: std::env::temp_dir(), + } + } + + /// Create a new instance of RunnerEngine that uses another root path than /tmp + /// + /// # Arguments + /// + /// * `request_message` - The request message + /// * `root_path` - The root path of the workspace + /// + /// # Returns + /// + /// * `Self` - The new instance of RunnerEngine + pub fn new_with_path(request_message: ExecuteRequest, root_path: &str) -> Self { + Self { + request_message, + root_path: PathBuf::from(root_path), + } } /// Create the workspace for the code execution @@ -42,14 +63,13 @@ impl RunnerEngine { // Create a vector of FileModel and a root path let mut file_models: Vec = Vec::new(); - let root_path = PathBuf::from(WORKSPACE_PATH); self.request_message.files.iter().for_each(|file| { let mut file_path = PathBuf::from(&file.filename); file_path.pop(); // Add `/tmp` before each path - file_path = root_path.join(file_path); + file_path = self.root_path.join(file_path); // Take the file name and add it to the vector of files let file_name = Path::file_name(Path::new(&file.filename)); @@ -169,7 +189,7 @@ impl RunnerEngine { let child_process = Command::new("/bin/sh") .args(["-c", command]) - .current_dir(WORKSPACE_PATH) + .current_dir(self.root_path.clone()) .output() .map_err(|e| anyhow!("Failed to spawn command : {}", e))?; @@ -194,42 +214,17 @@ mod tests { use crate::api::grpc_definitions::{ExecuteRequestStep, FileModel}; use super::*; - use rand::random; use std::fs::File; use std::io::Read; - /// Generate a random string - /// - /// # Arguments - /// - /// * `len` - The length of the string - /// - /// # Returns - /// - /// * `String` - The random string - fn native_rand_string(len: usize) -> String { - let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; - let mut string = String::new(); - - for _ in 0..len { - string.push( - chars - .chars() - .nth(random::() % (chars.len() - 1)) - .unwrap(), - ); - } - - string - } - #[test] fn run_one_works_with_ouputs_and_code() { - let res = RunnerEngine::new(ExecuteRequest { + let res = RunnerEngine::new(ExecuteRequest { id: "".to_string(), - files: vec![], - steps: vec![] - }).run_one("echo -n 'This is stdout' && echo -n 'This is stderr' >&2 && exit 1"); + files: vec![], + steps: vec![], + }) + .run_one("echo -n 'This is stdout' && echo -n 'This is stderr' >&2 && exit 1"); assert!(res.is_ok()); @@ -269,24 +264,24 @@ mod tests { assert_eq!(res.steps[0].stderr, "This is stderr\n"); assert_eq!(res.steps[0].stdout, "This is stdout\n"); - println!("{:?}", res.steps[1]); assert_eq!(res.steps[1].exit_code, 1); assert_eq!(res.steps[1].stderr, "This is stderr\n"); assert!(res.steps[1].stdout.is_empty()); - + assert_eq!(res.id, "4bf68974-c315-4c41-aee2-3dc2920e76e9"); } /// Test the execution of a command with a workspace #[test] fn workspace_created_sucessfully() { - let mut base_dir = PathBuf::from(WORKSPACE_PATH); - base_dir.push(native_rand_string(20)); - base_dir.push("main.sh"); - let path = base_dir.into_os_string().into_string().unwrap(); + let tempdir = tempfile::tempdir(); + let temp_dir = &tempdir.unwrap(); + let path = temp_dir.path(); + + let filename = path.join("main.sh").to_str().unwrap().to_string(); let files: Vec = vec![FileModel { - filename: path.clone(), + filename: filename.clone(), content: "Hello World!".to_string(), }]; let steps: Vec = Vec::new(); @@ -296,12 +291,14 @@ mod tests { steps, }; - RunnerEngine::new(request_data).create_workspace().unwrap(); + RunnerEngine::new_with_path(request_data, path.as_os_str().to_str().unwrap()) + .create_workspace() + .unwrap(); assert!(Path::new(&path).exists()); //Check that the file contains the specified content - let mut file = File::open(&path).unwrap(); + let mut file = File::open(filename).unwrap(); let mut buffer = [0; 12]; file.read_exact(&mut buffer[..]).unwrap(); From 8001149dba408dbbd06e699792ae41929239a9fa Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Tue, 21 Nov 2023 16:46:04 +0100 Subject: [PATCH 3/8] revert: remove unnecessary FileHandler Signed-off-by: WoodenMaiden --- agent/lib/src/runner_engine/model.rs | 37 +--------------------------- 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/agent/lib/src/runner_engine/model.rs b/agent/lib/src/runner_engine/model.rs index 84d3037..47358be 100644 --- a/agent/lib/src/runner_engine/model.rs +++ b/agent/lib/src/runner_engine/model.rs @@ -1,10 +1,4 @@ -use anyhow::Result; -use std::{ - fs::File, - io::{Read, Write}, - path::Path, - path::PathBuf, -}; +use std::path::PathBuf; use serde::{Deserialize, Serialize}; @@ -55,32 +49,3 @@ impl CodeReturn { } } } - -/// A trait responsible for CRU operations on files and dir -/// Mainly used in tests to mock file operations -trait FileHandler: Read + Write { - fn create>(path: P) -> Result - where - Self: Sized; - fn read>(path: P) -> Result - where - Self: Sized; -} - -impl FileHandler for File { - fn create>(path: P) -> Result - where - Self: Sized, - { - let file = File::create(path)?; - Ok(file) - } - - fn read>(path: P) -> Result - where - Self: Sized, - { - let file = File::open(path)?; - Ok(file) - } -} From a728af68eafb025a890e753ca71745235f2aa335 Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Tue, 21 Nov 2023 18:20:05 +0100 Subject: [PATCH 4/8] feat: add workspace_path opt so tests runs w/ tmpfile Signed-off-by: WoodenMaiden --- agent/lib/src/api/server.rs | 5 ++++- agent/lib/src/config.rs | 7 +++++++ agent/lib/src/runner_engine/service.rs | 26 ++++++-------------------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/agent/lib/src/api/server.rs b/agent/lib/src/api/server.rs index 7b2c6f8..d7380e5 100644 --- a/agent/lib/src/api/server.rs +++ b/agent/lib/src/api/server.rs @@ -84,7 +84,10 @@ impl LambdoAgentService for LambdoAgentServer { let request = request.into_inner(); debug!("Received request: {:?}", request); - let mut runner_engine = runner_engine::service::RunnerEngine::new(request); + let mut runner_engine = runner_engine::service::RunnerEngine::new( + request, + &self.config.workspace_path + ); let mut self_client = self.client.lock().await; if let Err(e) = runner_engine.create_workspace() { diff --git a/agent/lib/src/config.rs b/agent/lib/src/config.rs index f3054dc..c92a4a1 100644 --- a/agent/lib/src/config.rs +++ b/agent/lib/src/config.rs @@ -15,6 +15,10 @@ const fn default_local_port() -> u16 { 0 } +fn default_workspace_path() -> String { + std::env::temp_dir().to_str().unwrap().to_string() +} + #[derive(Error, Debug)] pub enum AgentConfigError { #[error("cannot load config file")] @@ -37,6 +41,9 @@ pub struct AgentConfig { /// The gRPC configuration #[serde(default = "default_grpc")] pub grpc: GRPCConfig, + /// The workspace where the agent will store the files of Requests and their resulting files + #[serde(default = "default_workspace_path")] + pub workspace_path: String, } #[derive(Serialize, Deserialize, PartialEq, Debug)] diff --git a/agent/lib/src/runner_engine/service.rs b/agent/lib/src/runner_engine/service.rs index 6592fe7..065ef3e 100644 --- a/agent/lib/src/runner_engine/service.rs +++ b/agent/lib/src/runner_engine/service.rs @@ -25,28 +25,12 @@ impl RunnerEngine { /// # Arguments /// /// * `request_message` - The request message - /// - /// # Returns - /// - /// * `Self` - The new instance of RunnerEngine - pub fn new(request_message: ExecuteRequest) -> Self { - Self { - request_message, - root_path: std::env::temp_dir(), - } - } - - /// Create a new instance of RunnerEngine that uses another root path than /tmp - /// - /// # Arguments - /// - /// * `request_message` - The request message /// * `root_path` - The root path of the workspace /// /// # Returns /// /// * `Self` - The new instance of RunnerEngine - pub fn new_with_path(request_message: ExecuteRequest, root_path: &str) -> Self { + pub fn new(request_message: ExecuteRequest, root_path: &str) -> Self { Self { request_message, root_path: PathBuf::from(root_path), @@ -217,13 +201,15 @@ mod tests { use std::fs::File; use std::io::Read; + const DEFAULT_WORKSPACE_PATH: &str = "/tmp"; + #[test] fn run_one_works_with_ouputs_and_code() { let res = RunnerEngine::new(ExecuteRequest { id: "".to_string(), files: vec![], steps: vec![], - }) + }, DEFAULT_WORKSPACE_PATH) .run_one("echo -n 'This is stdout' && echo -n 'This is stderr' >&2 && exit 1"); assert!(res.is_ok()); @@ -256,7 +242,7 @@ mod tests { steps, }; - let mut api = RunnerEngine::new(request_data); + let mut api = RunnerEngine::new(request_data, DEFAULT_WORKSPACE_PATH); let res = api.run().unwrap(); @@ -291,7 +277,7 @@ mod tests { steps, }; - RunnerEngine::new_with_path(request_data, path.as_os_str().to_str().unwrap()) + RunnerEngine::new(request_data, path.as_os_str().to_str().unwrap()) .create_workspace() .unwrap(); From edff1b37f4f62880e921e55401d5e0fb00c83858 Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Mon, 27 Nov 2023 16:36:30 +0100 Subject: [PATCH 5/8] feat: use tmpfiles to mock runner_engine files Signed-off-by: WoodenMaiden --- agent/lib/src/api/server.rs | 6 ++---- agent/lib/src/runner_engine/service.rs | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/agent/lib/src/api/server.rs b/agent/lib/src/api/server.rs index d7380e5..35f21f5 100644 --- a/agent/lib/src/api/server.rs +++ b/agent/lib/src/api/server.rs @@ -84,10 +84,8 @@ impl LambdoAgentService for LambdoAgentServer { let request = request.into_inner(); debug!("Received request: {:?}", request); - let mut runner_engine = runner_engine::service::RunnerEngine::new( - request, - &self.config.workspace_path - ); + let mut runner_engine = + runner_engine::service::RunnerEngine::new(request, &self.config.workspace_path); let mut self_client = self.client.lock().await; if let Err(e) = runner_engine.create_workspace() { diff --git a/agent/lib/src/runner_engine/service.rs b/agent/lib/src/runner_engine/service.rs index 065ef3e..1723a40 100644 --- a/agent/lib/src/runner_engine/service.rs +++ b/agent/lib/src/runner_engine/service.rs @@ -201,15 +201,18 @@ mod tests { use std::fs::File; use std::io::Read; - const DEFAULT_WORKSPACE_PATH: &str = "/tmp"; + use tempfile::tempdir; #[test] fn run_one_works_with_ouputs_and_code() { - let res = RunnerEngine::new(ExecuteRequest { - id: "".to_string(), - files: vec![], - steps: vec![], - }, DEFAULT_WORKSPACE_PATH) + let res = RunnerEngine::new( + ExecuteRequest { + id: "".to_string(), + files: vec![], + steps: vec![], + }, + tempdir().unwrap().path().to_str().unwrap(), + ) .run_one("echo -n 'This is stdout' && echo -n 'This is stderr' >&2 && exit 1"); assert!(res.is_ok()); @@ -224,6 +227,10 @@ mod tests { /// Test the creation of a file #[test] fn workload_runs_correctly() { + let tempdir = tempfile::tempdir(); + let temp_dir = &tempdir.unwrap(); + let path = temp_dir.path(); + let files: Vec = Vec::new(); let steps: Vec = vec![ ExecuteRequestStep { @@ -242,7 +249,7 @@ mod tests { steps, }; - let mut api = RunnerEngine::new(request_data, DEFAULT_WORKSPACE_PATH); + let mut api = RunnerEngine::new(request_data, path.as_os_str().to_str().unwrap()); let res = api.run().unwrap(); From a8b3903d4ada2c31a744b552620269dd38da1ce9 Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Mon, 27 Nov 2023 21:47:10 +0100 Subject: [PATCH 6/8] feat: add ClientTrait Signed-off-by: WoodenMaiden --- agent/lib/src/api/client.rs | 11 +++++++---- agent/lib/src/api/mod.rs | 9 +++++++++ agent/lib/src/api/server.rs | 4 ++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/agent/lib/src/api/client.rs b/agent/lib/src/api/client.rs index f775024..b1a6fac 100644 --- a/agent/lib/src/api/client.rs +++ b/agent/lib/src/api/client.rs @@ -5,9 +5,9 @@ use log::{error, info, trace}; use crate::api::grpc_definitions::{register_response::Response, RegisterRequest}; -use super::grpc_definitions::{ +use super::{grpc_definitions::{ lambdo_api_service_client::LambdoApiServiceClient, Code, StatusMessage, -}; +}, ClientTrait}; pub struct Client { client: LambdoApiServiceClient, @@ -34,8 +34,11 @@ impl Client { panic!("Failed to connect to gRPC server"); } +} - pub async fn register(&mut self, port: u16) -> Result { +#[tonic::async_trait] +impl ClientTrait for Client { + async fn register(&mut self, port: u16) -> Result { info!("Registering to lambdo.."); let register_response = self .client @@ -49,7 +52,7 @@ impl Client { } } - pub async fn status(&mut self, id: String, code: Code) -> Result<()> { + async fn status(&mut self, id: String, code: Code) -> Result<()> { self.client .status(StatusMessage { id, diff --git a/agent/lib/src/api/mod.rs b/agent/lib/src/api/mod.rs index 35cb39d..0449ae0 100644 --- a/agent/lib/src/api/mod.rs +++ b/agent/lib/src/api/mod.rs @@ -1,4 +1,13 @@ +use anyhow::Result; +use grpc_definitions::Code; + pub mod client; #[rustfmt::skip] pub mod grpc_definitions; pub mod server; + +#[tonic::async_trait] +pub trait ClientTrait { + async fn status(&mut self, id: String, code: Code) -> Result<()>; + async fn register(&mut self, port: u16) -> Result; +} \ No newline at end of file diff --git a/agent/lib/src/api/server.rs b/agent/lib/src/api/server.rs index 35f21f5..ae0f68e 100644 --- a/agent/lib/src/api/server.rs +++ b/agent/lib/src/api/server.rs @@ -4,7 +4,7 @@ use log::{debug, error, info, trace}; use tokio::sync::Mutex; use tonic::{Request, Response, Status}; -use crate::{config::AgentConfig, runner_engine}; +use crate::{config::AgentConfig, runner_engine, api::ClientTrait}; use super::{ client::Client, @@ -38,7 +38,7 @@ impl LambdoAgentServer { match client.register(config.grpc.local_port).await { Ok(id) => break id, Err(e) => { - error!("Failed to register to gRPC server, {} try: {}", counter, e); + error!("Failed to rese provide us with your discord handle, after joining our servergister to gRPC server, {} try: {}", counter, e); counter += 1; if counter >= 10 { panic!("Failed to register to gRPC server"); From 50e4aa11d392c4402f8e4c4905c9e4fb237dc8f4 Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Tue, 28 Nov 2023 00:10:06 +0100 Subject: [PATCH 7/8] feat: pass ClientTrait to LambdoAgentServer Signed-off-by: WoodenMaiden --- agent/lib/src/api/client.rs | 7 ++++--- agent/lib/src/api/mod.rs | 9 ++++++++- agent/lib/src/api/server.rs | 38 +++++++++++++++++++++++++++++++------ agent/src/main.rs | 4 ++-- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/agent/lib/src/api/client.rs b/agent/lib/src/api/client.rs index b1a6fac..5bfe0bf 100644 --- a/agent/lib/src/api/client.rs +++ b/agent/lib/src/api/client.rs @@ -7,14 +7,15 @@ use crate::api::grpc_definitions::{register_response::Response, RegisterRequest} use super::{grpc_definitions::{ lambdo_api_service_client::LambdoApiServiceClient, Code, StatusMessage, -}, ClientTrait}; +}, ClientTrait, SelfCreatingClientTrait}; pub struct Client { client: LambdoApiServiceClient, } -impl Client { - pub async fn new(gprc_host: IpAddr, port: u16) -> Self { +#[tonic::async_trait] +impl SelfCreatingClientTrait for Client { + async fn new(gprc_host: IpAddr, port: u16) -> Self { info!("Connecting to gRPC server at {}:{}", gprc_host, port); let mut counter = 0; diff --git a/agent/lib/src/api/mod.rs b/agent/lib/src/api/mod.rs index 0449ae0..af3044a 100644 --- a/agent/lib/src/api/mod.rs +++ b/agent/lib/src/api/mod.rs @@ -1,3 +1,5 @@ +use std::net::IpAddr; + use anyhow::Result; use grpc_definitions::Code; @@ -7,7 +9,12 @@ pub mod grpc_definitions; pub mod server; #[tonic::async_trait] -pub trait ClientTrait { +pub trait ClientTrait: Send + Sync { async fn status(&mut self, id: String, code: Code) -> Result<()>; async fn register(&mut self, port: u16) -> Result; +} + +#[tonic::async_trait] +pub trait SelfCreatingClientTrait: ClientTrait { + async fn new(grpc_host: IpAddr, port: u16) -> Self; } \ No newline at end of file diff --git a/agent/lib/src/api/server.rs b/agent/lib/src/api/server.rs index ae0f68e..10851e0 100644 --- a/agent/lib/src/api/server.rs +++ b/agent/lib/src/api/server.rs @@ -7,21 +7,20 @@ use tonic::{Request, Response, Status}; use crate::{config::AgentConfig, runner_engine, api::ClientTrait}; use super::{ - client::Client, grpc_definitions::{ lambdo_agent_service_server::LambdoAgentService, Empty, ExecuteRequest, ExecuteResponse, StatusMessage, - }, + }, SelfCreatingClientTrait, }; pub struct LambdoAgentServer { pub config: AgentConfig, - pub client: Arc>, + pub client: Arc>>, pub id: String, } impl LambdoAgentServer { - pub async fn new(config: AgentConfig) -> Self { + pub async fn new(config: AgentConfig) -> Self { let grpc_remote_host = IpAddr::from_str(&config.grpc.remote_host).unwrap_or_else(|e| { error!("Invalid IP address: {}", config.grpc.remote_host); panic!("{}", e.to_string()) @@ -29,7 +28,7 @@ impl LambdoAgentServer { trace!("gRPC remote host: {}", grpc_remote_host); trace!("Creating gRPC client.."); - let mut client = Client::new(grpc_remote_host, config.grpc.remote_port).await; + let mut client = C::new(grpc_remote_host, config.grpc.remote_port).await; trace!("Registering to gRPC server.."); let id = { @@ -63,7 +62,7 @@ impl LambdoAgentServer { Self { config, - client: Arc::new(Mutex::new(client)), + client: Arc::new(Mutex::new(Box::new(client))), id, } } @@ -123,3 +122,30 @@ impl LambdoAgentService for LambdoAgentServer { } } } + +#[cfg(test)] +mod test { + use crate::api::{ClientTrait, SelfCreatingClientTrait}; + use anyhow::Result; + use super::super::grpc_definitions::Code; + + struct MockClient; + + #[tonic::async_trait] + impl ClientTrait for MockClient { + async fn register(&mut self, _local_port: u16) -> Result { + Ok("test".to_string()) + } + + async fn status(&mut self, _id: String, _code: Code) -> Result<()>{ + Ok(()) + } + } + + #[tonic::async_trait] + impl SelfCreatingClientTrait for MockClient { + async fn new(_grpc_host: std::net::IpAddr, _port: u16) -> Self { + MockClient + } + } +} \ No newline at end of file diff --git a/agent/src/main.rs b/agent/src/main.rs index 0d5514f..7f21ea0 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -1,7 +1,7 @@ use agent_lib::{ api::{ grpc_definitions::lambdo_agent_service_server::LambdoAgentServiceServer, - server::LambdoAgentServer, + server::LambdoAgentServer, client::Client, }, config::AgentConfig, }; @@ -57,7 +57,7 @@ async fn main() -> Result<()> { tonic::transport::Server::builder() .add_service(LambdoAgentServiceServer::new( - LambdoAgentServer::new(config).await, + LambdoAgentServer::new::(config).await, )) .serve_with_incoming(tcp_stream) .await From bfcc8193a5780dffb9cb1540e156f967503cd904 Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Tue, 28 Nov 2023 00:22:13 +0100 Subject: [PATCH 8/8] fix: test status and execute Signed-off-by: WoodenMaiden --- agent/lib/src/api/client.rs | 7 +-- agent/lib/src/api/mod.rs | 6 ++- agent/lib/src/api/server.rs | 93 ++++++++++++++++++++++++++++++++++--- agent/src/main.rs | 4 +- 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/agent/lib/src/api/client.rs b/agent/lib/src/api/client.rs index 5bfe0bf..9da06b7 100644 --- a/agent/lib/src/api/client.rs +++ b/agent/lib/src/api/client.rs @@ -5,9 +5,10 @@ use log::{error, info, trace}; use crate::api::grpc_definitions::{register_response::Response, RegisterRequest}; -use super::{grpc_definitions::{ - lambdo_api_service_client::LambdoApiServiceClient, Code, StatusMessage, -}, ClientTrait, SelfCreatingClientTrait}; +use super::{ + grpc_definitions::{lambdo_api_service_client::LambdoApiServiceClient, Code, StatusMessage}, + ClientTrait, SelfCreatingClientTrait, +}; pub struct Client { client: LambdoApiServiceClient, diff --git a/agent/lib/src/api/mod.rs b/agent/lib/src/api/mod.rs index af3044a..0ac390e 100644 --- a/agent/lib/src/api/mod.rs +++ b/agent/lib/src/api/mod.rs @@ -8,13 +8,17 @@ pub mod client; pub mod grpc_definitions; pub mod server; +/// Client trait +/// This trait is used to abstract the gRPC client #[tonic::async_trait] pub trait ClientTrait: Send + Sync { async fn status(&mut self, id: String, code: Code) -> Result<()>; async fn register(&mut self, port: u16) -> Result; } +/// SelfCreatingClientTrait trait +/// This trait is used to abstract the gRPC client creation #[tonic::async_trait] pub trait SelfCreatingClientTrait: ClientTrait { async fn new(grpc_host: IpAddr, port: u16) -> Self; -} \ No newline at end of file +} diff --git a/agent/lib/src/api/server.rs b/agent/lib/src/api/server.rs index 10851e0..0a751df 100644 --- a/agent/lib/src/api/server.rs +++ b/agent/lib/src/api/server.rs @@ -4,13 +4,14 @@ use log::{debug, error, info, trace}; use tokio::sync::Mutex; use tonic::{Request, Response, Status}; -use crate::{config::AgentConfig, runner_engine, api::ClientTrait}; +use crate::{api::ClientTrait, config::AgentConfig, runner_engine}; use super::{ grpc_definitions::{ lambdo_agent_service_server::LambdoAgentService, Empty, ExecuteRequest, ExecuteResponse, StatusMessage, - }, SelfCreatingClientTrait, + }, + SelfCreatingClientTrait, }; pub struct LambdoAgentServer { @@ -20,7 +21,9 @@ pub struct LambdoAgentServer { } impl LambdoAgentServer { - pub async fn new(config: AgentConfig) -> Self { + pub async fn new( + config: AgentConfig, + ) -> Self { let grpc_remote_host = IpAddr::from_str(&config.grpc.remote_host).unwrap_or_else(|e| { error!("Invalid IP address: {}", config.grpc.remote_host); panic!("{}", e.to_string()) @@ -125,9 +128,20 @@ impl LambdoAgentService for LambdoAgentServer { #[cfg(test)] mod test { - use crate::api::{ClientTrait, SelfCreatingClientTrait}; - use anyhow::Result; use super::super::grpc_definitions::Code; + use crate::{ + api::{ + grpc_definitions::{ + lambdo_agent_service_server::LambdoAgentService, Empty, ExecuteRequest, + ExecuteRequestStep, + }, + server::LambdoAgentServer, + ClientTrait, SelfCreatingClientTrait, + }, + config::{AgentConfig, GRPCConfig}, + }; + use anyhow::Result; + use tonic::Request; struct MockClient; @@ -137,7 +151,7 @@ mod test { Ok("test".to_string()) } - async fn status(&mut self, _id: String, _code: Code) -> Result<()>{ + async fn status(&mut self, _id: String, _code: Code) -> Result<()> { Ok(()) } } @@ -148,4 +162,69 @@ mod test { MockClient } } -} \ No newline at end of file + + #[tokio::test] + async fn status_unimplemented() { + let config = AgentConfig { + apiVersion: "lambdo.io/v1alpha1".to_string(), + kind: "AgentConfig".to_string(), + grpc: GRPCConfig { + remote_port: 50051, + remote_host: "127.0.0.1".to_string(), + local_host: "127.0.0.1".to_string(), + local_port: 50051, + }, + workspace_path: tempfile::tempdir() + .unwrap() + .into_path() + .to_str() + .unwrap() + .to_string(), + }; + + let server = LambdoAgentServer::new::(config).await; + let status = server.status(Request::new(Empty {})).await; + + assert!(status.is_err()); + } + + #[tokio::test] + async fn execute_works() { + let config = AgentConfig { + apiVersion: "lambdo.io/v1alpha1".to_string(), + kind: "AgentConfig".to_string(), + grpc: GRPCConfig { + remote_port: 50051, + remote_host: "127.0.0.1".to_string(), + local_host: "127.0.0.1".to_string(), + local_port: 50051, + }, + workspace_path: tempfile::tempdir() + .unwrap() + .into_path() + .to_str() + .unwrap() + .to_string(), + }; + + let server = LambdoAgentServer::new::(config).await; + let execute = server + .execute(Request::new(ExecuteRequest { + id: "test".to_string(), + files: vec![], + steps: vec![ExecuteRequestStep { + command: "echo -n 'This is stdout' && echo -n 'This is stderr' >&2 && exit 1" + .to_string(), + enable_output: true, + }], + })) + .await; + + assert!(execute.is_ok()); + + let execution_recap = execute.unwrap().into_inner(); + + assert_eq!(execution_recap.clone().steps[0].stdout, "This is stdout"); + assert_eq!(execution_recap.steps[0].stderr, "This is stderr"); + } +} diff --git a/agent/src/main.rs b/agent/src/main.rs index 7f21ea0..2cfb853 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -1,7 +1,7 @@ use agent_lib::{ api::{ - grpc_definitions::lambdo_agent_service_server::LambdoAgentServiceServer, - server::LambdoAgentServer, client::Client, + client::Client, grpc_definitions::lambdo_agent_service_server::LambdoAgentServiceServer, + server::LambdoAgentServer, }, config::AgentConfig, };