diff --git a/proto/vmm.proto b/proto/vmm.proto index f9c61a6..8244446 100644 --- a/proto/vmm.proto +++ b/proto/vmm.proto @@ -32,6 +32,7 @@ message ExecuteResponse { } service VmmService { + rpc Shutdown (ShutdownVmRequest) returns (ShutdownVmResponse) {}; rpc Run (RunVmmRequest) returns (stream ExecuteResponse) {}; } @@ -45,3 +46,10 @@ message RunVmmRequest { message RunVmmResponse { } + +message ShutdownVmRequest { +} + +message ShutdownVmResponse { + bool success = 1; +} \ No newline at end of file diff --git a/src/api/src/client.rs b/src/api/src/client.rs index 28b772e..9f24618 100644 --- a/src/api/src/client.rs +++ b/src/api/src/client.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use tonic::{transport::Channel, Streaming}; use vmmorchestrator::vmm_service_client::VmmServiceClient; @@ -27,4 +29,17 @@ impl VmmClient { Ok(response_stream) } + + pub async fn shutdown_vm( + &mut self, + request: vmmorchestrator::ShutdownVmRequest, + ) -> Result { + let mut request = tonic::Request::new(request); + request.set_timeout(Duration::from_secs(5)); + let response = self.client.shutdown(request).await?.into_inner(); + + println!("shutdown response: {:?}", response); + + Ok(response) + } } diff --git a/src/api/src/service.rs b/src/api/src/service.rs index 6155437..398828f 100644 --- a/src/api/src/service.rs +++ b/src/api/src/service.rs @@ -1,8 +1,8 @@ use crate::client::{ - vmmorchestrator::{ExecuteResponse, RunVmmRequest}, + vmmorchestrator::{ExecuteResponse, RunVmmRequest, ShutdownVmRequest, ShutdownVmResponse}, VmmClient, }; -use actix_web::{post, web, HttpResponse, Responder}; +use actix_web::{post, web, HttpRequest, HttpResponse, Responder}; use actix_web_lab::sse; use async_stream::stream; use serde::Serialize; @@ -65,7 +65,40 @@ impl From for ExecuteJsonResponse { } #[post("/shutdown")] -pub async fn shutdown(req_body: String) -> impl Responder { - // TODO: Get the id from the body and shutdown the vm - HttpResponse::Ok().body(req_body) +pub async fn shutdown(request: HttpRequest) -> impl Responder { + let req = request; + + let mut client = VmmClient::new().await.unwrap(); + + println!("Request: {:?}", req); + + let shutdown_request = ShutdownVmRequest{}; + let response_result = client.shutdown_vm(shutdown_request).await; + + match response_result { + Ok(response) => { + let json_response: ShutdownJsonResponse = response.into(); + return HttpResponse::Ok().body(serde_json::to_string(&json_response).unwrap()); + } + Err(_) => { + let json_response: ShutdownJsonResponse = ShutdownJsonResponse { + success: false + }; + return HttpResponse::Ok().body(serde_json::to_string(&json_response).unwrap()); + } + } + +} + +#[derive(Debug, Serialize)] +pub struct ShutdownJsonResponse { + pub success: bool, +} + +impl From for ShutdownJsonResponse { + fn from(value: ShutdownVmResponse) -> Self { + Self { + success: value.success + } + } } diff --git a/src/cli/src/args.rs b/src/cli/src/args.rs index c78fa13..3c5b252 100644 --- a/src/cli/src/args.rs +++ b/src/cli/src/args.rs @@ -14,4 +14,5 @@ pub enum Commands { #[arg(short, long)] config_path: PathBuf, }, + Shutdown {}, } diff --git a/src/cli/src/main.rs b/src/cli/src/main.rs index 0e1b0e3..1e80599 100644 --- a/src/cli/src/main.rs +++ b/src/cli/src/main.rs @@ -29,6 +29,16 @@ async fn main() -> io::Result<()> { Ok(_) => println!("Request successful {:?}", response), Err(e) => eprintln!("Error while making the request: {}", e), } + }, + Commands::Shutdown {} => { + let response = CloudletClient::shutdown().await; + match response { + Ok(bool) => { + if bool { println!("Shutdown Request successful !")} + else { println!("Shutdown Request Failed")} + }, + Err(()) => println!("Cannot send shutdown Request"), + } } } diff --git a/src/cli/src/services.rs b/src/cli/src/services.rs index 2ec38ba..6a38332 100644 --- a/src/cli/src/services.rs +++ b/src/cli/src/services.rs @@ -1,7 +1,7 @@ use crate::utils::ConfigFileHandler; use reqwest::Client; use serde::Deserialize; -use shared_models::{BuildConfig, CloudletDtoRequest, Language, ServerConfig}; +use shared_models::{BuildConfig, CloudletDtoRequest, Language, ServerConfig, CloudletShutdownResponse}; use std::error::Error; #[derive(Deserialize, Debug)] @@ -50,4 +50,15 @@ impl CloudletClient { println!("Response: {:?}", res.text().await?); Ok(()) } -} + + pub async fn shutdown() -> Result { + let client = Client::new(); + let response = client.post("http://127.0.0.1:3000/shutdown") + .send() + .await; + + let shutdown_response: CloudletShutdownResponse = response.unwrap().json::().await.unwrap(); + + Ok(shutdown_response.success) + } +} \ No newline at end of file diff --git a/src/shared-models/src/lib.rs b/src/shared-models/src/lib.rs index 2b0b673..2a54e87 100644 --- a/src/shared-models/src/lib.rs +++ b/src/shared-models/src/lib.rs @@ -39,6 +39,11 @@ pub struct CloudletDtoRequest { pub build: BuildConfig, } +#[derive(Debug, Deserialize)] +pub struct CloudletShutdownResponse { + pub success: bool +} + #[derive(Serialize, Deserialize, Debug)] pub struct ServerConfig { pub address: String, diff --git a/src/vmm/src/grpc/client.rs b/src/vmm/src/grpc/client.rs index c9a93cc..0b076d7 100644 --- a/src/vmm/src/grpc/client.rs +++ b/src/vmm/src/grpc/client.rs @@ -1,7 +1,8 @@ -use self::agent::{workload_runner_client::WorkloadRunnerClient, ExecuteRequest}; +use self::agent::{workload_runner_client::WorkloadRunnerClient, ExecuteRequest, SignalRequest}; use log::error; -use std::{net::Ipv4Addr, time::Duration}; -use tonic::{transport::Channel, Streaming}; +use std::{error::Error, net::Ipv4Addr, time::Duration}; +use tonic::{transport::Channel, IntoRequest, Streaming}; +use super::server::vmmorchestrator::{ShutdownVmRequest, ShutdownVmResponse}; pub mod agent { tonic::include_proto!("cloudlet.agent"); @@ -34,7 +35,30 @@ impl WorkloadClient { ) -> Result, tonic::Status> { let request = tonic::Request::new(request); let response_stream = self.client.execute(request).await?.into_inner(); - + Ok(response_stream) } + + pub async fn shutdown( + &mut self, + _request: ShutdownVmRequest, + ) -> Result { + const BROKEN_PIPE_ERROR: &str = "stream closed because of a broken pipe"; + + let signal_request = SignalRequest::default(); + let response = self.client.signal(signal_request).await; + + if let Err(status) = response { + let error = status.source().unwrap().source().unwrap().source().unwrap(); + if error.to_string().as_str().eq(BROKEN_PIPE_ERROR) { + return Ok(ShutdownVmResponse { + success: true + }); + } + } + + Ok(ShutdownVmResponse { + success: false + }) + } } diff --git a/src/vmm/src/grpc/server.rs b/src/vmm/src/grpc/server.rs index e5c6919..59e55b7 100644 --- a/src/vmm/src/grpc/server.rs +++ b/src/vmm/src/grpc/server.rs @@ -1,6 +1,4 @@ -use self::vmmorchestrator::{ - vmm_service_server::VmmService as VmmServiceTrait, Language, RunVmmRequest, -}; +use self::vmmorchestrator::{vmm_service_server::VmmService as VmmServiceTrait, Language, RunVmmRequest, ShutdownVmRequest, ShutdownVmResponse}; use crate::grpc::client::agent::ExecuteRequest; use crate::VmmErrors; use crate::{core::vmm::VMM, grpc::client::WorkloadClient}; @@ -46,6 +44,31 @@ impl VmmServiceTrait for VmmService { type RunStream = ReceiverStream>; + async fn shutdown(&self, request: Request) -> Result { + const GUEST_IP: Ipv4Addr = Ipv4Addr::new(172, 29, 0, 2); + + let grpc_client = tokio::spawn(async move { + // Wait 2 seconds + tokio::time::sleep(Duration::from_secs(2)).await; + println!("Connecting to Agent service"); + + WorkloadClient::new(GUEST_IP, 50051).await + }) + .await + .unwrap(); + + if let Ok(mut client) = grpc_client { + info!("Attempting to shutdown the VM..."); + + let response = client.shutdown(request.into_inner()).await.unwrap(); + + return Ok(Response::new(response)); + }else if let Err(e) = grpc_client { + error!("ERROR {:?}", e); + } + return Err(Status::internal("Failed to shutdown the VM")); + } + async fn run(&self, request: Request) -> Result { let (tx, rx) = tokio::sync::mpsc::channel(4); @@ -53,6 +76,15 @@ impl VmmServiceTrait for VmmService { const HOST_NETMASK: Ipv4Addr = Ipv4Addr::new(255, 255, 0, 0); const GUEST_IP: Ipv4Addr = Ipv4Addr::new(172, 29, 0, 2); + // get current directory + let mut curr_dir = + current_dir().expect("Need to be able to access current directory path."); + + // define kernel path + let mut kernel_entire_path = curr_dir.as_os_str().to_owned(); + kernel_entire_path + .push("/tools/kernel/linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin"); + // get current directory let mut curr_dir = current_dir().expect("Need to be able to access current directory path."); @@ -67,6 +99,11 @@ impl VmmServiceTrait for VmmService { .try_exists() .unwrap_or_else(|_| panic!("Could not access folder {:?}", &kernel_entire_path)); + if !kernel_exists { + let kernel_exists = Path::new(&kernel_entire_path) + .try_exists() + .unwrap_or_else(|_| panic!("Could not access folder {:?}", &kernel_entire_path)); + if !kernel_exists { info!("Kernel not found, building kernel"); // Execute the script using sh and capture output and error streams @@ -79,6 +116,7 @@ impl VmmServiceTrait for VmmService { // Print output and error streams info!("Script output: {}", String::from_utf8_lossy(&output.stdout)); + info!("Script output: {}", String::from_utf8_lossy(&output.stdout)); error!("Script errors: {}", String::from_utf8_lossy(&output.stderr)); }; let kernel_path = Path::new(&kernel_entire_path); @@ -157,9 +195,86 @@ impl VmmServiceTrait for VmmService { } let initramfs_path = PathBuf::from(&initramfs_entire_file_path); + let kernel_path = Path::new(&kernel_entire_path); + + // define initramfs file placement + let mut initramfs_entire_file_path = curr_dir.as_os_str().to_owned(); + initramfs_entire_file_path.push("/tools/rootfs/"); + + // get request with the language + let vmm_request = request.into_inner(); + let language: Language = + Language::from_i32(vmm_request.language).expect("Unknown language"); + + let image = match language { + Language::Rust => { + initramfs_entire_file_path.push("rust.img"); + "rust:alpine" + } + Language::Python => { + initramfs_entire_file_path.push("python.img"); + "python:alpine" + } + Language::Node => { + initramfs_entire_file_path.push("node.img"); + "node:alpine" + } + }; + + let rootfs_exists = Path::new(&initramfs_entire_file_path) + .try_exists() + .unwrap_or_else(|_| { + panic!("Could not access folder {:?}", &initramfs_entire_file_path) + }); + if !rootfs_exists { + // check if agent binary exists + let agent_file_name = curr_dir.as_mut_os_string(); + agent_file_name.push("/target/x86_64-unknown-linux-musl/release/agent"); + + // if agent hasn't been build, build it + let agent_exists = Path::new(&agent_file_name) + .try_exists() + .unwrap_or_else(|_| panic!("Could not access folder {:?}", &agent_file_name)); + if !agent_exists { + //build agent + info!("Building agent binary"); + // Execute the script using sh and capture output and error streams + let output = Command::new("just") + .arg("build-musl-agent") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute the just build script for the agent"); + + // Print output and error streams + info!("Script output: {}", String::from_utf8_lossy(&output.stdout)); + error!("Script errors: {}", String::from_utf8_lossy(&output.stderr)); + info!("Agent binary successfully built.") + } + + info!("Building initramfs"); + // Execute the script using sh and capture output and error streams + let output = Command::new("sh") + .arg("./tools/rootfs/mkrootfs.sh") + .arg(image) + .arg(&agent_file_name) + .arg(&initramfs_entire_file_path) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute the initramfs build script"); + + // Print output and error streams + info!("Script output: {}", String::from_utf8_lossy(&output.stdout)); + error!("Script errors: {}", String::from_utf8_lossy(&output.stderr)); + info!("Initramfs successfully built.") + } + let initramfs_path = PathBuf::from(&initramfs_entire_file_path); + let mut vmm = VMM::new(HOST_IP, HOST_NETMASK, GUEST_IP).map_err(VmmErrors::VmmNew)?; // Configure the VMM parameters might need to be calculated rather than hardcoded + vmm.configure(1, 4000, kernel_path, &Some(initramfs_path)) vmm.configure(1, 4000, kernel_path, &Some(initramfs_path)) .map_err(VmmErrors::VmmConfigure)?; @@ -185,6 +300,9 @@ impl VmmServiceTrait for VmmService { let agent_request = ExecuteRequest { workload_name: vmm_request.workload_name, language: match vmm_request.language { + 0 => "rust".to_string(), + 1 => "python".to_string(), + 2 => "node".to_string(), 0 => "rust".to_string(), 1 => "python".to_string(), 2 => "node".to_string(),