diff --git a/proto/vmm.proto b/proto/vmm.proto index e954584..5acfbd4 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 c35bc95..dc703ba 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/Cargo.toml b/src/vmm/Cargo.toml index 6f5aa30..b5397df 100644 --- a/src/vmm/Cargo.toml +++ b/src/vmm/Cargo.toml @@ -35,6 +35,7 @@ vm-memory = { version = "0.14.1", features = ["backend-mmap"] } vm-superio = "0.7.0" vmm-sys-util = "0.12.1" tokio-stream = "0.1.15" +hyper = "1.3.1" [build-dependencies] tonic-build = "0.9" 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 facac57..583e7a2 100644 --- a/src/vmm/src/grpc/server.rs +++ b/src/vmm/src/grpc/server.rs @@ -1,4 +1,4 @@ -use self::vmmorchestrator::{vmm_service_server::VmmService as VmmServiceTrait, RunVmmRequest}; +use self::vmmorchestrator::{vmm_service_server::VmmService as VmmServiceTrait, RunVmmRequest, ShutdownVmRequest, ShutdownVmResponse}; use crate::grpc::client::agent::ExecuteRequest; use crate::VmmErrors; use crate::{core::vmm::VMM, grpc::client::WorkloadClient}; @@ -43,6 +43,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);