Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shutdown VM #40

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions proto/vmm.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ message ExecuteResponse {
}

service VmmService {
rpc Shutdown (ShutdownVmRequest) returns (ShutdownVmResponse) {};
rpc Run (RunVmmRequest) returns (stream ExecuteResponse) {};
}

Expand All @@ -45,3 +46,10 @@ message RunVmmRequest {

message RunVmmResponse {
}

message ShutdownVmRequest {
}

message ShutdownVmResponse {
bool success = 1;
}
15 changes: 15 additions & 0 deletions src/api/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::Duration;

use tonic::{transport::Channel, Streaming};
use vmmorchestrator::vmm_service_client::VmmServiceClient;

Expand Down Expand Up @@ -27,4 +29,17 @@ impl VmmClient {

Ok(response_stream)
}

pub async fn shutdown_vm(
&mut self,
request: vmmorchestrator::ShutdownVmRequest,
) -> Result<vmmorchestrator::ShutdownVmResponse, tonic::Status> {
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)
}
}
43 changes: 38 additions & 5 deletions src/api/src/service.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -65,7 +65,40 @@ impl From<ExecuteResponse> 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<ShutdownVmResponse> for ShutdownJsonResponse {
fn from(value: ShutdownVmResponse) -> Self {
Self {
success: value.success
}
}
}
1 change: 1 addition & 0 deletions src/cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ pub enum Commands {
#[arg(short, long)]
config_path: PathBuf,
},
Shutdown {},
}
10 changes: 10 additions & 0 deletions src/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}
}
}

Expand Down
15 changes: 13 additions & 2 deletions src/cli/src/services.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -50,4 +50,15 @@ impl CloudletClient {
println!("Response: {:?}", res.text().await?);
Ok(())
}
}

pub async fn shutdown() -> Result<bool, ()> {
let client = Client::new();
let response = client.post("http://127.0.0.1:3000/shutdown")
.send()
.await;

let shutdown_response: CloudletShutdownResponse = response.unwrap().json::<CloudletShutdownResponse>().await.unwrap();

Ok(shutdown_response.success)
}
}
5 changes: 5 additions & 0 deletions src/shared-models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
32 changes: 28 additions & 4 deletions src/vmm/src/grpc/client.rs
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -34,7 +35,30 @@ impl WorkloadClient {
) -> Result<Streaming<agent::ExecuteResponse>, 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<ShutdownVmResponse, tonic::Status> {
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
})
}
}
124 changes: 121 additions & 3 deletions src/vmm/src/grpc/server.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -46,13 +44,47 @@ impl VmmServiceTrait for VmmService {
type RunStream =
ReceiverStream<std::result::Result<vmmorchestrator::ExecuteResponse, tonic::Status>>;

async fn shutdown(&self, request: Request<ShutdownVmRequest>) -> Result<ShutdownVmResponse> {
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<RunVmmRequest>) -> Result<Self::RunStream> {
let (tx, rx) = tokio::sync::mpsc::channel(4);

const HOST_IP: Ipv4Addr = Ipv4Addr::new(172, 29, 0, 1);
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.");
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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)?;

Expand All @@ -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(),
Expand Down
Loading