diff --git a/build.rs b/build.rs index 0de789b..4ae9761 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,13 @@ fn main() -> Result<(), Box> { tonic_build::configure() .protoc_arg("--experimental_allow_proto3_optional") - .compile_protos(&["proto/feos.proto", "proto/container.proto"], &["proto"])?; + .compile_protos( + &[ + "proto/feos.proto", + "proto/container.proto", + "proto/isolated_container.proto", + ], + &["proto"], + )?; Ok(()) } diff --git a/client/build.rs b/client/build.rs index 1efbc37..d46a779 100644 --- a/client/build.rs +++ b/client/build.rs @@ -2,7 +2,11 @@ fn main() { tonic_build::configure() .build_server(false) .compile( - &["../proto/feos.proto", "../proto/container.proto"], + &[ + "../proto/feos.proto", + "../proto/container.proto", + "../proto/isolated_container.proto", + ], &["../proto"], ) .unwrap(); diff --git a/client/src/client.rs b/client/src/client.rs index 0e1870e..08225f0 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -7,6 +7,7 @@ use tonic::transport::Endpoint; use tonic::Request; use crate::client_container::ContainerCommand; +use crate::client_isolated_container::IsolatedContainerCommand; use feos_grpc::feos_grpc_client::FeosGrpcClient; use feos_grpc::*; @@ -63,6 +64,7 @@ pub enum Command { uuid: String, }, Container(ContainerCommand), + IsolatedContainer(IsolatedContainerCommand), } fn format_address(ip: &str, port: u16) -> String { @@ -89,6 +91,14 @@ pub async fn run_client(opt: Opt) -> Result<(), Box> { crate::client_container::run_container_client(opt.server_ip, opt.port, container_cmd) .await?; } + Command::IsolatedContainer(container_cmd) => { + crate::client_isolated_container::run_isolated_container_client( + opt.server_ip, + opt.port, + container_cmd, + ) + .await?; + } Command::Reboot => { let request = Request::new(RebootRequest {}); let response = client.reboot(request).await?; diff --git a/client/src/client_isolated_container.rs b/client/src/client_isolated_container.rs new file mode 100644 index 0000000..5801b98 --- /dev/null +++ b/client/src/client_isolated_container.rs @@ -0,0 +1,76 @@ +use std::time::Duration; +use structopt::StructOpt; +use tokio::time::timeout; +use tonic::transport::Endpoint; +use tonic::Request; + +use isolated_container_grpc::isolated_container_service_client::IsolatedContainerServiceClient; +use isolated_container_grpc::*; + +pub mod isolated_container_grpc { + tonic::include_proto!("isolated_container"); +} + +#[derive(StructOpt, Debug)] +pub enum IsolatedContainerCommand { + Create { + image: String, + #[structopt(name = "COMMAND", required = true, min_values = 1)] + command: Vec, + }, + Run { + uuid: String, + }, + Stop { + uuid: String, + }, + State { + uuid: String, + }, +} + +pub async fn run_isolated_container_client( + server_ip: String, + port: u16, + cmd: IsolatedContainerCommand, +) -> Result<(), Box> { + let address = format!("http://{}:{}", server_ip, port); + let channel = Endpoint::from_shared(address)?.connect().await?; + let mut client = IsolatedContainerServiceClient::new(channel); + + match cmd { + IsolatedContainerCommand::Create { image, command } => { + let request = Request::new(CreateContainerRequest { image, command }); + match timeout(Duration::from_secs(30), client.create_container(request)).await { + Ok(response) => match response { + Ok(response) => println!("CREATE ISOLATED CONTAINER RESPONSE={:?}", response), + Err(e) => eprintln!("Error creating isolated container: {:?}", e), + }, + Err(_) => eprintln!("Request timed out after 30 seconds"), + } + } + IsolatedContainerCommand::Run { uuid } => { + let request = Request::new(RunContainerRequest { uuid }); + match client.run_container(request).await { + Ok(response) => println!("RUN ISOLATED CONTAINER RESPONSE={:?}", response), + Err(e) => eprintln!("Error running isolated container: {:?}", e), + } + } + IsolatedContainerCommand::Stop { uuid } => { + let request = Request::new(StopContainerRequest { uuid }); + match client.stop_container(request).await { + Ok(response) => println!("STOP ISOLATED CONTAINER RESPONSE={:?}", response), + Err(e) => eprintln!("Error stopping isolated container: {:?}", e), + } + } + IsolatedContainerCommand::State { uuid } => { + let request = Request::new(StateContainerRequest { uuid }); + match client.state_container(request).await { + Ok(response) => println!("STATE ISOLATED CONTAINER RESPONSE={:?}", response), + Err(e) => eprintln!("Error getting isolated container state: {:?}", e), + } + } + } + + Ok(()) +} diff --git a/client/src/main.rs b/client/src/main.rs index 61322c0..377b3b1 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,5 +1,6 @@ mod client; mod client_container; +mod client_isolated_container; use client::Opt; use structopt::StructOpt; diff --git a/proto/isolated_container.proto b/proto/isolated_container.proto new file mode 100644 index 0000000..bbad95a --- /dev/null +++ b/proto/isolated_container.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; +package isolated_container; + +service IsolatedContainerService { + rpc CreateContainer (CreateContainerRequest) returns (CreateContainerResponse); + rpc RunContainer (RunContainerRequest) returns (RunContainerResponse); + rpc StopContainer (StopContainerRequest) returns (StopContainerResponse); + rpc StateContainer (StateContainerRequest) returns (StateContainerResponse); +} + +message CreateContainerRequest { + string image = 1; + repeated string command = 2; +} +message CreateContainerResponse { + string uuid = 1; +} + +message RunContainerRequest { + string uuid = 1; +} +message RunContainerResponse {} + +message StopContainerRequest { + string uuid = 1; +} +message StopContainerResponse {} + +message StateContainerRequest { + string uuid = 1; +} +message StateContainerResponse { + string state = 1; + optional int32 pid = 2; +} diff --git a/src/daemon.rs b/src/daemon.rs index 27c2b22..7e1fcfb 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,11 +1,9 @@ -use log::{debug, error, info, warn}; +use log::{error, info, warn}; use std::net::Ipv6Addr; use std::path::PathBuf; use std::{env, io}; use tonic::{transport::Server, Request, Response, Status}; -use crate::container; -use crate::dhcpv6::{add_ipv6_route, add_to_ipv6, adjust_base_ip, run_dhcpv6_server, IpRange}; use crate::feos_grpc; use crate::feos_grpc::feos_grpc_server::*; use crate::feos_grpc::{ @@ -17,20 +15,18 @@ use crate::feos_grpc::{ ShutdownVmRequest, ShutdownVmResponse, }; use crate::host; -use crate::radv::start_radv_server; use crate::ringbuffer::*; use crate::vm::{self}; use crate::vm::{image, Manager}; +use crate::{container, network}; use hyper_util::rt::TokioIo; use nix::libc::VMADDR_CID_ANY; use nix::unistd::Uid; -use rtnetlink::new_connection; use std::sync::Arc; use tokio::fs::File; use tokio::io::AsyncReadExt; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::UnixStream; -use tokio::spawn; use tokio::sync::{mpsc, Mutex}; use tokio::time::{sleep, Duration}; use tokio_stream::wrappers::ReceiverStream; @@ -40,6 +36,7 @@ use tower::service_fn; use uuid::Uuid; use crate::filesystem::mount_virtual_filesystems; +use crate::isolated_container::{isolated_container_service, IsolatedContainerAPI}; use crate::network::{configure_network_devices, configure_sriov}; #[derive(Debug)] @@ -51,83 +48,204 @@ pub struct FeOSAPI { impl FeOSAPI { pub fn new( - vmm: vm::Manager, + vmm: Arc, buffer: Arc, log_receiver: Arc>>, ) -> Self { FeOSAPI { - vmm: Arc::new(vmm), + vmm, buffer, log_receiver, } } -} -fn handle_error(e: vm::Error) -> tonic::Status { - match e { - vm::Error::AlreadyExists => Status::new(tonic::Code::AlreadyExists, "vm already exists"), - vm::Error::NotFound => Status::new(tonic::Code::NotFound, "vm not found"), - vm::Error::SocketFailure(e) => { - info!("socket error: {:?}", e); - Status::new(tonic::Code::Internal, "failed to ") - } - vm::Error::InvalidInput(e) => { - info!("invalid input error: {:?}", e); - Status::new(tonic::Code::Internal, "invalid input") - } - vm::Error::CHCommandFailure(e) => { - info!("failed to connect to cloud hypervisor: {:?}", e); - Status::new( - tonic::Code::Internal, - "failed to connect to cloud hypervisor", - ) - } - vm::Error::CHApiFailure(e) => { - info!("failed to connect to cloud hypervisor api: {:?}", e); - Status::new( - tonic::Code::Internal, - "failed to connect to cloud hypervisor api", - ) - } - vm::Error::ExtractionFailure(e) => { - info!("extraction error: {:?}", e); - Status::new(tonic::Code::Internal, "failed to extract UKI image") + fn handle_error(&self, e: vm::Error) -> Status { + match e { + vm::Error::AlreadyExists => { + Status::new(tonic::Code::AlreadyExists, "vm already exists") + } + vm::Error::NotFound => Status::new(tonic::Code::NotFound, "vm not found"), + vm::Error::SocketFailure(e) => { + info!("socket error: {:?}", e); + Status::new(tonic::Code::Internal, "failed to ") + } + vm::Error::InvalidInput(e) => { + info!("invalid input error: {:?}", e); + Status::new(tonic::Code::Internal, "invalid input") + } + vm::Error::CHCommandFailure(e) => { + info!("failed to connect to cloud hypervisor: {:?}", e); + Status::new( + tonic::Code::Internal, + "failed to connect to cloud hypervisor", + ) + } + vm::Error::CHApiFailure(e) => { + info!("failed to connect to cloud hypervisor api: {:?}", e); + Status::new( + tonic::Code::Internal, + "failed to connect to cloud hypervisor api", + ) + } } - vm::Error::Failed => Status::new(tonic::Code::AlreadyExists, "vm already exists"), } } #[tonic::async_trait] impl FeosGrpc for FeOSAPI { - type GetFeOSKernelLogsStream = ReceiverStream>; - type GetFeOSLogsStream = ReceiverStream>; - type ConsoleVMStream = ReceiverStream>; + async fn reboot(&self, _: Request) -> Result, Status> { + info!("Got reboot request"); + tokio::spawn(async { + sleep(Duration::from_secs(1)).await; + match host::power::reboot() { + Ok(_) => info!("reboot"), + Err(e) => info!("failed to reboot: {:?}", e), + } + }); + Ok(Response::new(feos_grpc::RebootResponse {})) + } + async fn shutdown( + &self, + _: Request, + ) -> Result, Status> { + info!("Got shutdown request"); + tokio::spawn(async { + sleep(Duration::from_secs(1)).await; + match host::power::shutdown() { + Ok(_) => info!("shutdown"), + Err(e) => info!("failed to shutdown: {:?}", e), + } + }); + Ok(Response::new(feos_grpc::ShutdownResponse {})) + } + async fn host_info( + &self, + _: Request, + ) -> Result, Status> { + info!("Got host info request"); - async fn get_fe_os_kernel_logs( + let host = host::info::check_info(); + + let mut interfaces = Vec::new(); + for interface in host.net_interfaces { + interfaces.push(NetInterface { + name: interface.name, + pci_address: interface.pci_address.unwrap_or_default(), + mac_address: interface.mac_address.unwrap_or_default(), + }) + } + + Ok(Response::new(feos_grpc::HostInfoResponse { + uptime: host.uptime, + ram_total: host.ram_total, + ram_unused: host.ram_unused, + num_cores: host.num_cores, + net_interfaces: interfaces, + })) + } + + async fn ping( &self, - _: Request, - ) -> Result, Status> { - let (tx, rx) = mpsc::channel(4); - let tx = tx.clone(); + request: Request, // Accept request of type HelloRequest + ) -> Result, Status> { + // Return an instance of type HelloReply + info!("Got a request: {:?}", request); - tokio::spawn(async move { - let file = File::open("/dev/kmsg") - .await - .expect("Failed to open /dev/kmsg"); - let reader = BufReader::new(file); - let mut lines = reader.lines(); + let reply = feos_grpc::Empty {}; - while let Some(line) = lines.next_line().await.unwrap() { - let response = GetFeOsKernelLogResponse { message: line }; - if tx.send(Ok(response)).await.is_err() { - break; + Ok(Response::new(reply)) // Send back our formatted greeting + } + + async fn fetch_image( + &self, + request: Request, + ) -> Result, Status> { + info!("Got fetch_image request"); + + let id = Uuid::new_v4(); + let path: PathBuf = PathBuf::from(format!("./images/{}", id.clone())); + tokio::spawn(async move { + match vm::image::fetch_image(request.get_ref().image.clone(), path).await { + Ok(_) => info!("image pulled"), + Err(image::ImageError::IOError(e)) => { + info!("failed to pull image: io error: {:?}", e) + } + Err(image::ImageError::PullError(e)) => info!("failed to pull image: {:?}", e), + Err(image::ImageError::InvalidReference(e)) => { + info!("failed to pull image: invalid reference: {:?}", e) + } + Err(image::ImageError::MissingLayer(e)) => { + info!("failed to pull image: missing layer: {:?}", e) } } }); - Ok(Response::new(ReceiverStream::new(rx))) + Ok(Response::new(feos_grpc::FetchImageResponse { + uuid: id.to_string(), + })) + } + + async fn create_vm( + &self, + request: Request, + ) -> Result, Status> { + info!("Got create_vm request"); + + let id = Uuid::new_v4(); + self.vmm + .init_vmm(id, true) + .map_err(|e| self.handle_error(e))?; + + let root_fs = PathBuf::from(format!( + "./images/{}/application.vnd.ironcore.image.rootfs.v1alpha1.rootfs", + request.get_ref().image_uuid + )); + self.vmm + .create_vm( + id, + request.get_ref().cpu, + request.get_ref().memory_bytes, + vm::BootMode::FirmwareBoot(vm::FirmwareBootMode { root_fs }), + request.get_ref().ignition.clone(), + ) + .map_err(|e| self.handle_error(e))?; + + Ok(Response::new(feos_grpc::CreateVmResponse { + uuid: id.to_string(), + })) } + async fn get_vm( + &self, + request: Request, + ) -> Result, Status> { + info!("Got get_vm request"); + + let id = request.get_ref().uuid.to_owned(); + let id = + Uuid::parse_str(&id).map_err(|_| Status::invalid_argument("failed to parse uuid"))?; + self.vmm.ping_vmm(id).map_err(|e| self.handle_error(e))?; + let vm_status = self.vmm.get_vm(id).map_err(|e| self.handle_error(e))?; + + Ok(Response::new(feos_grpc::GetVmResponse { info: vm_status })) + } + + async fn boot_vm( + &self, + request: Request, + ) -> Result, Status> { + info!("Received boot_vm request"); + + let id = Uuid::parse_str(&request.get_ref().uuid) + .map_err(|_| Status::invalid_argument("Failed to parse UUID"))?; + + self.vmm.boot_vm(id).map_err(|e| self.handle_error(e))?; + + Ok(Response::new(feos_grpc::BootVmResponse {})) + } + + type ConsoleVMStream = ReceiverStream>; + async fn console_vm( &self, request: Request>, @@ -148,7 +266,10 @@ impl FeosGrpc for FeOSAPI { }; let id = Uuid::parse_str(&initial_request.uuid) .map_err(|_| Status::invalid_argument("failed to parse uuid"))?; - let socket_path = self.vmm.get_vm_console_path(id).map_err(handle_error)?; + let socket_path = self + .vmm + .get_vm_console_path(id) + .map_err(|e| self.handle_error(e))?; tokio::spawn(async move { let stream = match UnixStream::connect(&socket_path).await { @@ -196,103 +317,6 @@ impl FeosGrpc for FeOSAPI { Ok(Response::new(ReceiverStream::new(rx))) } - async fn get_fe_os_logs( - &self, - _: Request, - ) -> Result>>, Status> { - let (tx, rx) = mpsc::channel(4); - let buffer = self.buffer.clone(); - let log_receiver = self.log_receiver.clone(); - - tokio::spawn(async move { - let logs = buffer.get_lines().await; - for log in logs { - let response = GetFeOsLogResponse { message: log }; - if tx.send(Ok(response)).await.is_err() { - break; - } - } - - let mut log_receiver = log_receiver.lock().await; - while let Some(log_entry) = log_receiver.recv().await { - let response = GetFeOsLogResponse { message: log_entry }; - if tx.send(Ok(response)).await.is_err() { - break; - } - } - }); - - Ok(Response::new(ReceiverStream::new(rx))) - } - - async fn ping( - &self, - request: Request, // Accept request of type HelloRequest - ) -> Result, Status> { - // Return an instance of type HelloReply - info!("Got a request: {:?}", request); - - let reply = feos_grpc::Empty {}; - - Ok(Response::new(reply)) // Send back our formatted greeting - } - - async fn fetch_image( - &self, - request: Request, - ) -> Result, Status> { - info!("Got fetch_image request"); - - let id = Uuid::new_v4(); - let path: PathBuf = PathBuf::from(format!("./images/{}", id.clone())); - tokio::spawn(async move { - match vm::image::fetch_image(request.get_ref().image.clone(), path).await { - Ok(_) => info!("image pulled"), - Err(image::ImageError::IOError(e)) => { - info!("failed to pull image: io error: {:?}", e) - } - Err(image::ImageError::PullError(e)) => info!("failed to pull image: {:?}", e), - Err(image::ImageError::InvalidReference(e)) => { - info!("failed to pull image: invalid reference: {:?}", e) - } - Err(image::ImageError::MissingLayer(e)) => { - info!("failed to pull image: missing layer: {:?}", e) - } - } - }); - - Ok(Response::new(feos_grpc::FetchImageResponse { - uuid: id.to_string(), - })) - } - - async fn reboot(&self, _: Request) -> Result, Status> { - info!("Got reboot request"); - tokio::spawn(async { - sleep(Duration::from_secs(1)).await; - match host::power::reboot() { - Ok(_) => info!("reboot"), - Err(e) => info!("failed to reboot: {:?}", e), - } - }); - Ok(Response::new(feos_grpc::RebootResponse {})) - } - - async fn shutdown( - &self, - _: Request, - ) -> Result, Status> { - info!("Got shutdown request"); - tokio::spawn(async { - sleep(Duration::from_secs(1)).await; - match host::power::shutdown() { - Ok(_) => info!("shutdown"), - Err(e) => info!("failed to shutdown: {:?}", e), - } - }); - Ok(Response::new(feos_grpc::ShutdownResponse {})) - } - async fn attach_nic_vm( &self, request: Request, @@ -303,93 +327,21 @@ impl FeosGrpc for FeOSAPI { let id = Uuid::parse_str(&id).map_err(|_| Status::invalid_argument("failed to parse uuid"))?; - let mac_address = if request.get_ref().mac_address.is_empty() { - None - } else { - Some(request.get_ref().mac_address.clone()) - }; - let pci_address = if request.get_ref().pci_address.is_empty() { - None + let net_config = if !request.get_ref().mac_address.is_empty() { + vm::NetworkMode::MACAddress(request.get_ref().mac_address.clone()) + } else if !request.get_ref().pci_address.is_empty() { + vm::NetworkMode::PCIAddress(request.get_ref().pci_address.clone()) } else { - Some(request.get_ref().pci_address.clone()) + return Err(Status::invalid_argument("no network config provided")); }; self.vmm - ._add_net_device(id, mac_address, pci_address) - .map_err(handle_error)?; + .add_net_device(id, net_config) + .map_err(|e| self.handle_error(e))?; Ok(Response::new(feos_grpc::AttachNicVmResponse {})) } - async fn host_info( - &self, - _: Request, - ) -> Result, Status> { - info!("Got host info request"); - - let host = host::info::check_info(); - - let mut interfaces = Vec::new(); - for interface in host.net_interfaces { - interfaces.push(NetInterface { - name: interface.name, - pci_address: interface.pci_address.unwrap_or_default(), - mac_address: interface.mac_address.unwrap_or_default(), - }) - } - - Ok(Response::new(feos_grpc::HostInfoResponse { - uptime: host.uptime, - ram_total: host.ram_total, - ram_unused: host.ram_unused, - num_cores: host.num_cores, - net_interfaces: interfaces, - })) - } - - async fn create_vm( - &self, - request: Request, - ) -> Result, Status> { - info!("Got create_vm request"); - - let id = Uuid::new_v4(); - self.vmm.init_vmm(id, true).map_err(handle_error)?; - - let root_fs = PathBuf::from(format!( - "./images/{}/application.vnd.ironcore.image.rootfs.v1alpha1.rootfs", - request.get_ref().image_uuid - )); - self.vmm - .create_vm( - id, - request.get_ref().cpu, - request.get_ref().memory_bytes, - root_fs, - request.get_ref().ignition.clone(), - ) - .map_err(handle_error)?; - - Ok(Response::new(feos_grpc::CreateVmResponse { - uuid: id.to_string(), - })) - } - - async fn get_vm( - &self, - request: Request, - ) -> Result, Status> { - info!("Got get_vm request"); - - let id = request.get_ref().uuid.to_owned(); - let id = - Uuid::parse_str(&id).map_err(|_| Status::invalid_argument("failed to parse uuid"))?; - self.vmm.ping_vmm(id).map_err(handle_error)?; - let vm_status = self.vmm.get_vm(id).map_err(handle_error)?; - - Ok(Response::new(feos_grpc::GetVmResponse { info: vm_status })) - } - async fn shutdown_vm( &self, request: Request, @@ -399,7 +351,8 @@ impl FeosGrpc for FeOSAPI { let id = Uuid::parse_str(&request.get_ref().uuid) .map_err(|_| Status::invalid_argument("Failed to parse UUID"))?; - self.vmm.shutdown_vm(id).map_err(handle_error)?; + // TODO differentiate between kill and shutdown + self.vmm.kill_vm(id).map_err(|e| self.handle_error(e))?; Ok(Response::new(feos_grpc::ShutdownVmResponse {})) } @@ -412,7 +365,7 @@ impl FeosGrpc for FeOSAPI { let id = Uuid::parse_str(&request.get_ref().uuid) .map_err(|_| Status::invalid_argument("Failed to parse UUID"))?; - let path = format!("vsock{}.sock", Manager::vm_tap_name(&id)); + let path = format!("vsock{}.sock", network::Manager::vm_tap_name(&id)); let path_clone = path.clone(); let channel = Endpoint::try_from("http://[::]:50051") @@ -461,85 +414,74 @@ impl FeosGrpc for FeOSAPI { Ok(Response::new(feos_grpc::PingVmResponse {})) } - async fn boot_vm( + type GetFeOSKernelLogsStream = ReceiverStream>; + + async fn get_fe_os_kernel_logs( &self, - request: Request, - ) -> Result, Status> { - info!("Received boot_vm request"); + _: Request, + ) -> Result, Status> { + let (tx, rx) = mpsc::channel(4); + let tx = tx.clone(); - let id = Uuid::parse_str(&request.get_ref().uuid) - .map_err(|_| Status::invalid_argument("Failed to parse UUID"))?; + tokio::spawn(async move { + let file = File::open("/dev/kmsg") + .await + .expect("Failed to open /dev/kmsg"); + let reader = BufReader::new(file); + let mut lines = reader.lines(); - self.vmm.boot_vm(id).map_err(handle_error)?; + while let Some(line) = lines.next_line().await.unwrap() { + let response = GetFeOsKernelLogResponse { message: line }; + if tx.send(Ok(response)).await.is_err() { + break; + } + } + }); - let interface_name = Manager::vm_tap_name(&id); - let (base_ip, prefix_length, prefix_count) = self.vmm.get_ipv6_info(); - let adjusted_base_ip = adjust_base_ip(base_ip, prefix_length, prefix_count); - let new_prefix_length = prefix_length + 16; + Ok(Response::new(ReceiverStream::new(rx))) + } - let ip_start = add_to_ipv6(adjusted_base_ip, new_prefix_length, 100); - let ip_end = add_to_ipv6(adjusted_base_ip, new_prefix_length, 200); - debug!("IP Range: {} - {}", ip_start, ip_end); + type GetFeOSLogsStream = ReceiverStream>; - let ip_range = IpRange { - start: ip_start, - end: ip_end, - }; + async fn get_fe_os_logs( + &self, + _: Request, + ) -> Result>>, Status> { + let (tx, rx) = mpsc::channel(4); + let buffer = self.buffer.clone(); + let log_receiver = self.log_receiver.clone(); - let radv_handle = { - let interface_name = interface_name.clone(); - spawn(async move { - if let Err(e) = - start_radv_server(interface_name, adjusted_base_ip, new_prefix_length).await - { - error!("Failed to start RADV server: {}", e); + tokio::spawn(async move { + let logs = buffer.get_lines().await; + for log in logs { + let response = GetFeOsLogResponse { message: log }; + if tx.send(Ok(response)).await.is_err() { + break; } - }) - }; + } - self.vmm - .set_radv_handle(id, radv_handle) - .map_err(|_| Status::internal("Failed to set RADV handle"))?; - - let dhcpv6_handle = { - let interface_name = interface_name.clone(); - spawn(async move { - if let Err(e) = run_dhcpv6_server(interface_name, ip_range).await { - error!("Failed to run DHCPv6 server: {}", e); + let mut log_receiver = log_receiver.lock().await; + while let Some(log_entry) = log_receiver.recv().await { + let response = GetFeOsLogResponse { message: log_entry }; + if tx.send(Ok(response)).await.is_err() { + break; } - }) - }; - - self.vmm - .set_dhcpv6_handle(id, dhcpv6_handle) - .map_err(|_| Status::internal("Failed to set DHCPv6 handle"))?; - - let (connection, handle, _) = - new_connection().map_err(|_| Status::internal("Failed to establish new connection"))?; - spawn(connection); - - add_ipv6_route( - &handle, - &interface_name, - adjusted_base_ip, - new_prefix_length, - None, - 1024, - ) - .await - .map_err(|_| Status::internal("Failed to add IPv6 route"))?; + } + }); - Ok(Response::new(feos_grpc::BootVmResponse {})) + Ok(Response::new(ReceiverStream::new(rx))) } } pub async fn daemon_start( - vmm: vm::Manager, + vmm: Arc, + network: Arc, buffer: Arc, log_receiver: Arc>>, is_nested: bool, ) -> Result<(), Box> { - let api = FeOSAPI::new(vmm, buffer, log_receiver); + let api = FeOSAPI::new(vmm.clone(), buffer, log_receiver); + let isolated_container_api = IsolatedContainerAPI::new(vmm, network); if is_nested { let sockaddr = VsockAddr::new(VMADDR_CID_ANY, 1337); @@ -563,6 +505,7 @@ pub async fn daemon_start( container::ContainerAPI {}, ), ) + .add_service(isolated_container_service::isolated_container_service_server::IsolatedContainerServiceServer::new(isolated_container_api)) .serve(addr) .await?; } @@ -570,11 +513,7 @@ pub async fn daemon_start( Ok(()) } -pub async fn start_feos( - ipv6_address: Ipv6Addr, - prefix_length: u8, - test_mode: bool, -) -> Result<(), String> { +pub async fn start_feos(ipv6_address: Ipv6Addr, prefix_length: u8) -> Result<(), String> { println!( " @@ -608,13 +547,10 @@ pub async fn start_feos( ); } - let is_nested = match is_running_on_vm().await { - Ok(result) => result, - Err(e) => { - error!("Error checking VM status: {}", e); - false // Default to false in case of error - } - }; + let is_nested = is_running_on_vm().await.unwrap_or_else(|e| { + error!("Error checking VM status: {}", e); + false // Default to false in case of error + }); if std::process::id() == 1 { info!("Configuring network devices..."); @@ -632,16 +568,20 @@ pub async fn start_feos( } } - let vmm = Manager::new( - String::from("cloud-hypervisor"), - is_nested, - test_mode, - ipv6_address, - prefix_length, - ); + let vmm = Manager::new(String::from("cloud-hypervisor")); + + let network_manager = network::Manager::new(ipv6_address, prefix_length); info!("Starting FeOS daemon..."); - match daemon_start(vmm, buffer, log_receiver, is_nested).await { + match daemon_start( + Arc::new(vmm), + Arc::new(network_manager), + buffer, + log_receiver, + is_nested, + ) + .await + { Err(e) => { error!("FeOS daemon crashed: {}", e); Err(format!("FeOS daemon crashed: {}", e)) diff --git a/src/isolated_container/mod.rs b/src/isolated_container/mod.rs new file mode 100644 index 0000000..6451fb4 --- /dev/null +++ b/src/isolated_container/mod.rs @@ -0,0 +1,324 @@ +use crate::container::container_service::container_service_client::ContainerServiceClient; +use crate::container::container_service::{ + CreateContainerRequest, RunContainerRequest, StateContainerRequest, +}; +use crate::vm::NetworkMode; +use crate::{network, vm}; +use hyper_util::rt::TokioIo; +use isolated_container_service::isolated_container_service_server::IsolatedContainerService; +use log::info; +use std::sync::Arc; +use std::{collections::HashMap, sync::Mutex}; +use std::{fmt::Debug, io, path::PathBuf}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::UnixStream; +use tonic::transport::{Channel, Endpoint, Uri}; +use tonic::{transport, Request, Response, Status}; +use tower::service_fn; +use uuid::Uuid; + +pub mod isolated_container_service { + tonic::include_proto!("isolated_container"); +} + +#[derive(Debug, Default)] +pub struct IsolatedContainerAPI { + vmm: Arc, + network: Arc, + vm_to_container: Mutex>, +} + +#[derive(Debug, Clone)] +struct IsolatedContainerInfo { + pub container_id: Uuid, + pub sock: Channel, +} + +impl IsolatedContainerAPI { + pub fn new(vmm: Arc, network: Arc) -> Self { + IsolatedContainerAPI { + vmm, + network, + vm_to_container: Mutex::new(HashMap::new()), + } + } +} + +#[derive(Debug)] +pub enum Error { + VMConnectionError(transport::Error), + VMConnectionMaxRetriesError, + VMError(vm::Error), + NetworkingError(network::Error), + InvalidID, + Error(String), +} + +async fn get_channel(path: String) -> Result { + async fn establish_connection(path: String) -> Result { + let endpoint = Endpoint::try_from("http://[::]:50051")?; + + let connector = service_fn(move |_: Uri| { + let path = path.clone(); + async move { + let mut stream = UnixStream::connect(&path).await?; + + let connect_cmd = format!("CONNECT {}\n", 1337); + stream + .write_all(connect_cmd.as_bytes()) + .await + .map_err(|e| { + io::Error::new(io::ErrorKind::Other, format!("Write error: {}", e)) + })?; + + let mut buffer = [0u8; 128]; + let n = stream.read(&mut buffer).await?; + + let response = String::from_utf8_lossy(&buffer[..n]); + if !response.starts_with("OK") { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("Failed to connect to vsock: {}", response.trim()), + )); + } + + info!("Connected to vsock: {}", response.trim()); + Ok::<_, io::Error>(TokioIo::new(stream)) + } + }); + + endpoint.connect_with_connector(connector).await + } + + const RETRIES: u8 = 20; + const DELAY: tokio::time::Duration = tokio::time::Duration::from_millis(2000); + + for attempt in 0..RETRIES { + match establish_connection(path.clone()).await { + Ok(channel) => return Ok(channel), + Err(e) => { + info!("Attempt {} failed: {:?}", attempt + 1, e); + if attempt < RETRIES - 1 { + info!("Retrying in {:?}", DELAY); + tokio::time::sleep(DELAY).await; + } + } + } + } + + Err(Error::VMConnectionMaxRetriesError) +} + +impl IsolatedContainerAPI { + fn prepare_vm(&self, id: uuid::Uuid) -> Result<(), Error> { + self.vmm.init_vmm(id, true).map_err(Error::VMError)?; + self.vmm + .create_vm( + id, + 2, + // TODO make configurable through container request + 536870912, + vm::BootMode::KernelBoot(vm::KernelBootMode { + kernel: PathBuf::from("/usr/share/feos/vmlinuz"), + initramfs: PathBuf::from("/usr/share/feos/initramfs"), + // TODO + cmdline: "console=tty0 console=ttyS0,115200 intel_iommu=on iommu=pt" + .to_string(), + }), + None, + ) + .map_err(Error::VMError)?; + + self.vmm.boot_vm(id).map_err(Error::VMError)?; + + self.vmm + .add_net_device( + id, + NetworkMode::TAPDeviceName(network::Manager::device_name(&id)), + ) + .map_err(Error::VMError)?; + + Ok(()) + } + + fn handle_error(&self, e: Error) -> tonic::Status { + match e { + Error::VMConnectionError(e) => Status::new( + tonic::Code::Internal, + format!("failed to connect to vm: {}", e), + ), + Error::VMConnectionMaxRetriesError => Status::new( + tonic::Code::Internal, + format!("failed to connect to vm: mac retries reached: {:?}", e), + ), + Error::VMError(e) => Status::new( + tonic::Code::Internal, + format!("failed to prepare vm: {:?}", e), + ), + Error::NetworkingError(e) => Status::new( + tonic::Code::Internal, + format!("failed to prepare network: {:?}", e), + ), + Error::InvalidID => Status::invalid_argument("failed to parse uuid"), + Error::Error(m) => Status::internal(m), + } + } + + fn get_container_info(&self, vm_id: Uuid) -> Result { + let container_id = { + let vm_to_container = self + .vm_to_container + .lock() + .map_err(|_| Error::Error("Failed to lock mutex".to_string()))?; + vm_to_container + .get(&vm_id) + .cloned() + .ok_or_else(|| Error::Error(format!("VM with ID '{}' not found", vm_id)))? + }; + + Ok(container_id) + } +} + +#[tonic::async_trait] +impl IsolatedContainerService for IsolatedContainerAPI { + async fn create_container( + &self, + request: Request, + ) -> Result, Status> { + info!("Got create_container request"); + + let id = Uuid::new_v4(); + + self.prepare_vm(id).map_err(|e| self.handle_error(e))?; + + self.network + .start_dhcp(id) + .await + .map_err(Error::NetworkingError) + .map_err(|e| self.handle_error(e))?; + + let path = format!("vsock{}.sock", network::Manager::device_name(&id)); + let channel = get_channel(path).await.map_err(|e| self.handle_error(e))?; + + let mut client = ContainerServiceClient::new(channel.clone()); + let request = tonic::Request::new(CreateContainerRequest { + image: request.get_ref().image.to_string(), + command: request.get_ref().command.clone(), + }); + let response = client + .create_container(request) + .await + .map_err(|_| match self.vmm.kill_vm(id) { + Ok(_) => Error::Error("failed to create container".to_string()), + Err(e) => Error::Error(format!("failed to create container: {:?}", e)), + }) + .map_err(|e| self.handle_error(e))?; + + info!("created container with id: {}", response.get_ref().uuid); + + let container_id = Uuid::parse_str(&response.get_ref().uuid) + .map_err(|_| Error::InvalidID) + .map_err(|e| self.handle_error(e))?; + + let mut vm_to_container = self.vm_to_container.lock().unwrap(); + vm_to_container.insert( + id, + IsolatedContainerInfo { + container_id, + sock: channel, + }, + ); + + Ok(Response::new( + isolated_container_service::CreateContainerResponse { + uuid: id.to_string(), + }, + )) + } + + async fn run_container( + &self, + request: Request, + ) -> Result, Status> { + info!("Got run_container request"); + + let vm_id: String = request.get_ref().uuid.clone(); + let vm_id = Uuid::parse_str(&vm_id) + .map_err(|_| Error::InvalidID) + .map_err(|e| self.handle_error(e))?; + + let container = self + .get_container_info(vm_id) + .map_err(|e| self.handle_error(e))?; + + let mut client = ContainerServiceClient::new(container.sock.clone()); + let request = tonic::Request::new(RunContainerRequest { + uuid: container.container_id.to_string(), + }); + client.run_container(request).await?; + + Ok(Response::new( + isolated_container_service::RunContainerResponse {}, + )) + } + + async fn stop_container( + &self, + request: Request, + ) -> Result, Status> { + info!("Got stop_container request"); + + let vm_id: String = request.get_ref().uuid.clone(); + let vm_id = Uuid::parse_str(&vm_id) + .map_err(|_| Error::InvalidID) + .map_err(|e| self.handle_error(e))?; + + self.network + .stop_dhcp(vm_id) + .await + .map_err(Error::NetworkingError) + .map_err(|e| self.handle_error(e))?; + + self.vmm + .kill_vm(vm_id) + .map_err(Error::VMError) + .map_err(|e| self.handle_error(e))?; + + let mut vm_to_container = self.vm_to_container.lock().unwrap(); + vm_to_container.remove(&vm_id); + + Ok(Response::new( + isolated_container_service::StopContainerResponse {}, + )) + } + + async fn state_container( + &self, + request: Request, + ) -> Result, Status> { + info!("Got state_container request"); + + let vm_id: String = request.get_ref().uuid.clone(); + let vm_id = Uuid::parse_str(&vm_id) + .map_err(|_| Error::InvalidID) + .map_err(|e| self.handle_error(e))?; + + let container = self + .get_container_info(vm_id) + .map_err(|e| self.handle_error(e))?; + + let mut client = ContainerServiceClient::new(container.sock.clone()); + let request = tonic::Request::new(StateContainerRequest { + uuid: container.container_id.to_string(), + }); + let response = client.state_container(request).await?; + + Ok(Response::new( + isolated_container_service::StateContainerResponse { + state: response.get_ref().state.to_string(), + pid: response.get_ref().pid, + }, + )) + } +} diff --git a/src/lib.rs b/src/lib.rs index 475fdd6..1df036a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,11 @@ pub mod container; pub mod daemon; -pub mod dhcpv6; pub mod filesystem; pub mod fsmount; pub mod host; +pub mod isolated_container; pub mod move_root; pub mod network; -pub mod radv; pub mod ringbuffer; pub mod vm; diff --git a/src/main.rs b/src/main.rs index a137b1e..42e7ca0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,9 +29,7 @@ async fn main() -> Result<(), String> { (ipv6_address, prefix_length) = parse_command_line()?; } - let test_mode = env::var("RUN_MODE").map_or(false, |v| v == "test"); - - start_feos(ipv6_address, prefix_length, test_mode).await?; + start_feos(ipv6_address, prefix_length).await?; Err("FeOS exited".to_string()) } diff --git a/src/dhcpv6.rs b/src/network/dhcpv6.rs similarity index 100% rename from src/dhcpv6.rs rename to src/network/dhcpv6.rs diff --git a/src/network/mod.rs b/src/network/mod.rs new file mode 100644 index 0000000..e75238b --- /dev/null +++ b/src/network/mod.rs @@ -0,0 +1,167 @@ +use log::{debug, error, info}; +use rtnetlink::new_connection; +use std::collections::HashMap; +use std::net::Ipv6Addr; +use std::sync::atomic::{AtomicU16, Ordering}; +use std::sync::{Arc, Mutex}; +use tokio::spawn; +use tokio::task::JoinHandle; +use uuid::Uuid; + +pub mod dhcpv6; +pub mod radv; +mod utils; + +use crate::network::dhcpv6::{ + add_ipv6_route, add_to_ipv6, adjust_base_ip, run_dhcpv6_server, IpRange, +}; +use radv::start_radv_server; +pub use utils::configure_network_devices; +pub use utils::configure_sriov; + +#[derive(Debug)] +pub enum Error { + Failed, + AlreadyExists, +} + +#[derive(Debug)] +pub struct Manager { + ipv6_address: Ipv6Addr, + prefix_length: u8, + prefix_count: AtomicU16, + instances: Mutex>, +} + +#[derive(Debug)] +struct Handles { + pub radv_handle: Arc>, + pub dhcpv6_handle: Arc>, +} + +impl Default for Manager { + fn default() -> Self { + Self { + ipv6_address: Ipv6Addr::UNSPECIFIED, + prefix_length: 64, + prefix_count: AtomicU16::new(1), + instances: Mutex::new(HashMap::new()), + } + } +} + +impl Manager { + pub fn new(ipv6_address: Ipv6Addr, prefix_length: u8) -> Self { + Self { + ipv6_address, + prefix_length, + prefix_count: AtomicU16::new(1), + instances: Mutex::new(HashMap::new()), + } + } + + pub fn vm_tap_name(id: &Uuid) -> String { + format!("vmtap{}", &id.to_string()[..8]) + } + + fn exists(&self, id: Uuid) -> Result<(), Error> { + let instances = self.instances.lock().unwrap(); + if instances.contains_key(&id) { + return Err(Error::AlreadyExists); + } + Ok(()) + } + + pub async fn stop_dhcp(&self, id: Uuid) -> Result<(), Error> { + let mut instances = self.instances.lock().unwrap(); + if let Some(handle) = instances.remove(&id) { + handle.radv_handle.abort(); + handle.dhcpv6_handle.abort(); + } + + Ok(()) + } + pub async fn start_dhcp(&self, id: Uuid) -> Result<(), Error> { + self.exists(id)?; + + let interface_name = Manager::device_name(&id); + + info!("created tap device: {}", &interface_name); + + let (base_ip, prefix_length, prefix_count) = self.get_ipv6_info(); + let adjusted_base_ip = adjust_base_ip(base_ip, prefix_length, prefix_count); + let new_prefix_length = prefix_length + 16; + + let ip_start = add_to_ipv6(adjusted_base_ip, new_prefix_length, 100); + let ip_end = add_to_ipv6(adjusted_base_ip, new_prefix_length, 200); + debug!("IP Range: {} - {}", ip_start, ip_end); + + let ip_range = IpRange { + start: ip_start, + end: ip_end, + }; + + let radv_handle = { + let interface_name = interface_name.clone(); + spawn(async move { + if let Err(e) = start_radv_server( + interface_name.to_string(), + adjusted_base_ip, + new_prefix_length, + ) + .await + { + error!("Failed to start RADV server: {}", e); + } + }) + }; + + let dhcpv6_handle = { + let interface_name = interface_name.clone(); + spawn(async move { + if let Err(e) = run_dhcpv6_server(interface_name.to_string(), ip_range).await { + error!("Failed to run DHCPv6 server: {}", e); + } + }) + }; + + let mut instances = self.instances.lock().unwrap(); + instances.insert( + id, + Handles { + radv_handle: Arc::new(radv_handle), + dhcpv6_handle: Arc::new(dhcpv6_handle), + }, + ); + + let (connection, handle, _) = new_connection().map_err(|_| Error::Failed)?; + spawn(connection); + + spawn(async move { + if let Err(e) = add_ipv6_route( + &handle, + &interface_name, + adjusted_base_ip, + new_prefix_length, + None, + 1024, + ) + .await + { + error!("Failed to add ipv6 route: {}", e); + } + }); + + Ok(()) + } + + pub fn device_name(id: &Uuid) -> String { + format!("vmtap{}", &id.to_string()[..8]) + } + + fn get_ipv6_info(&self) -> (Ipv6Addr, u8, u16) { + let new_count = self.prefix_count.fetch_add(1, Ordering::SeqCst) + 1; + + (self.ipv6_address, self.prefix_length, new_count) + } +} diff --git a/src/radv.rs b/src/network/radv.rs similarity index 100% rename from src/radv.rs rename to src/network/radv.rs diff --git a/src/network.rs b/src/network/utils.rs similarity index 99% rename from src/network.rs rename to src/network/utils.rs index 90c10d9..42d319b 100644 --- a/src/network.rs +++ b/src/network/utils.rs @@ -1,6 +1,6 @@ use std::io; -use crate::dhcpv6::*; +use crate::network::dhcpv6::*; use futures::stream::TryStreamExt; use log::{info, warn}; use rtnetlink::new_connection; diff --git a/src/vm/mod.rs b/src/vm/mod.rs index c80a090..41ef1ac 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,4 +1,4 @@ -use log::{error, info}; +use log::info; use serde_json::json; use std::{ collections::HashMap, @@ -14,35 +14,16 @@ use uuid::Uuid; use vmm::vm_config; use vmm::config::{ - ConsoleConfig, ConsoleOutputMode, CpusConfig, DiskConfig, MemoryConfig, NetConfig, - PayloadConfig, PlatformConfig, VsockConfig, + ConsoleConfig, ConsoleOutputMode, CpusConfig, DiskConfig, MemoryConfig, PayloadConfig, + PlatformConfig, VsockConfig, }; +use crate::network; use net_util::MacAddr; -use pelite::pe64::{Pe, PeFile}; -use std::fs; -use std::fs::{create_dir_all, File}; -use std::io::{self, Write}; -use std::net::Ipv6Addr; -use std::sync::atomic::{AtomicU16, Ordering}; -use std::sync::Arc; -use thiserror::Error; -use tokio::task::JoinHandle; - pub mod config; pub mod image; -#[derive(Error, Debug)] -pub enum ExtractionError { - #[error("failed to read PE/COFF image")] - ReadImage(#[from] pelite::Error), - #[error("failed to write file")] - WriteFile(#[from] io::Error), - #[error("section not found in PE/COFF image")] - SectionNotFound, -} - #[derive(Debug)] pub enum Error { AlreadyExists, @@ -51,31 +32,38 @@ pub enum Error { InvalidInput(TryFromIntError), CHCommandFailure(std::io::Error), CHApiFailure(api_client::Error), - ExtractionFailure(ExtractionError), - Failed, } -impl From for Error { - fn from(err: ExtractionError) -> Self { - Error::ExtractionFailure(err) - } +pub enum BootMode { + FirmwareBoot(FirmwareBootMode), + KernelBoot(KernelBootMode), +} + +pub struct FirmwareBootMode { + pub root_fs: PathBuf, } + +pub struct KernelBootMode { + pub kernel: PathBuf, + pub initramfs: PathBuf, + pub cmdline: String, +} + +pub enum NetworkMode { + PCIAddress(String), + MACAddress(String), + TAPDeviceName(String), +} + #[derive(Debug)] -pub struct VmInfo { - pub child: Child, - pub radv_handle: Option>>, - pub dhcpv6_handle: Option>>, +struct VmInfo { + child: Child, } #[derive(Debug)] pub struct Manager { pub ch_bin: String, vms: Mutex>, - pub is_nested: bool, - pub test_mode: bool, - ipv6_address: Ipv6Addr, - prefix_length: u8, - prefix_count: AtomicU16, } impl Default for Manager { @@ -83,38 +71,18 @@ impl Default for Manager { Self { ch_bin: String::default(), vms: Mutex::new(HashMap::new()), - is_nested: false, - test_mode: false, - ipv6_address: Ipv6Addr::UNSPECIFIED, - prefix_length: 64, - prefix_count: AtomicU16::new(1), } } } impl Manager { - pub fn new( - ch_bin: String, - is_nested: bool, - test_mode: bool, - ipv6_address: Ipv6Addr, - prefix_length: u8, - ) -> Self { + pub fn new(ch_bin: String) -> Self { Self { ch_bin, vms: Mutex::new(HashMap::new()), - is_nested, - test_mode, - ipv6_address, - prefix_length, - prefix_count: AtomicU16::new(1), } } - pub fn vm_tap_name(id: &Uuid) -> String { - format!("vmtap{}", &id.to_string()[..8]) - } - pub fn init_vmm(&self, id: Uuid, wait: bool) -> Result<(), Error> { let mut vms = self.vms.lock().unwrap(); if vms.contains_key(&id) { @@ -127,14 +95,7 @@ impl Manager { .spawn() .map_err(Error::CHCommandFailure)?; - vms.insert( - id, - VmInfo { - child: vm, - radv_handle: None, - dhcpv6_handle: None, - }, - ); + vms.insert(id, VmInfo { child: vm }); info!("created vmm with id: {}", id.to_string()); @@ -167,7 +128,7 @@ impl Manager { id: Uuid, cpu: u32, memory: u64, - root_fs: PathBuf, + boot_mode: BootMode, ignition: Option, ) -> Result<(), Error> { let vms = self.vms.lock().unwrap(); @@ -189,46 +150,46 @@ impl Manager { ..Default::default() }; - if self.test_mode { - // Fetched OCI image for FeOS doesnt boot with hypervisor-fw - // For local development and integration tests, use the extracted UKI image - let (kernel_path, cmdline_path, initramfs_path) = extract_uki_image(&root_fs)?; - let mut cmdline_contents = - fs::read_to_string(&cmdline_path).map_err(Error::SocketFailure)?; - cmdline_contents = cmdline_contents - .chars() - .filter(|c| c.is_ascii_graphic() || c.is_whitespace()) - .collect(); - vm_config.payload = Some(PayloadConfig { - kernel: Some(kernel_path), - cmdline: Some(cmdline_contents.clone()), - initramfs: Some(initramfs_path), - firmware: None, - }); - } else { - vm_config.payload = Some(PayloadConfig { - kernel: None, - cmdline: None, - initramfs: None, - firmware: Some(PathBuf::from("/usr/share/cloud-hypervisor/hypervisor-fw")), - }); - } + let mut disks = vec![]; + match boot_mode { + BootMode::FirmwareBoot(firmware_boot) => { + vm_config.payload = Some(PayloadConfig { + firmware: Some(PathBuf::from("/usr/share/cloud-hypervisor/hypervisor-fw")), + kernel: None, + cmdline: None, + initramfs: None, + }); + disks.push(DiskConfig { + path: Some(firmware_boot.root_fs), + ..config::default_disk_cfg() + }); + } + BootMode::KernelBoot(kernel_boot) => { + vm_config.payload = Some(PayloadConfig { + kernel: Some(kernel_boot.kernel), + cmdline: Some(kernel_boot.cmdline.clone()), + initramfs: Some(kernel_boot.initramfs), + firmware: None, + }); + } + }; vm_config.vsock = Some(VsockConfig { cid: 33, - socket: PathBuf::from(format!("vsock{}.sock", Manager::vm_tap_name(&id))), + socket: PathBuf::from(format!("vsock{}.sock", network::Manager::device_name(&id))), id: None, iommu: false, pci_segment: 0, }); - vm_config.net = Some(vec![NetConfig { - tap: Some(Manager::vm_tap_name(&id)), - ..config::_default_net_cfg() - }]); - vm_config.disks = Some(vec![DiskConfig { - path: Some(root_fs), - ..config::default_disk_cfg() - }]); + // vm_config.net = Some(vec![NetConfig { + // tap: Some(Manager::vm_tap_name(&id)), + // ..config::_default_net_cfg() + // }]); + + if !disks.is_empty() { + vm_config.disks = Some(disks); + } + vm_config.serial = ConsoleConfig { socket: Some(PathBuf::from(id.to_string() + ".console")), mode: ConsoleOutputMode::Socket, @@ -301,56 +262,7 @@ impl Manager { Ok(socket_path) } - pub fn get_radv_handle(&self, id: Uuid) -> Result>>, Error> { - let vms = self.vms.lock().unwrap(); - if let Some(vm_info) = vms.get(&id) { - Ok(vm_info.radv_handle.clone()) - } else { - Err(Error::NotFound) - } - } - - pub fn set_radv_handle(&self, id: Uuid, handle: JoinHandle<()>) -> Result<(), Error> { - let mut vms = self.vms.lock().unwrap(); - if let Some(vm_info) = vms.get_mut(&id) { - vm_info.radv_handle = Some(Arc::new(handle)); - Ok(()) - } else { - Err(Error::NotFound) - } - } - - pub fn get_dhcpv6_handle(&self, id: Uuid) -> Result>>, Error> { - let vms = self.vms.lock().unwrap(); - if let Some(vm_info) = vms.get(&id) { - Ok(vm_info.dhcpv6_handle.clone()) - } else { - Err(Error::NotFound) - } - } - - pub fn set_dhcpv6_handle(&self, id: Uuid, handle: JoinHandle<()>) -> Result<(), Error> { - let mut vms = self.vms.lock().unwrap(); - if let Some(vm_info) = vms.get_mut(&id) { - vm_info.dhcpv6_handle = Some(Arc::new(handle)); - Ok(()) - } else { - Err(Error::NotFound) - } - } - - pub fn get_ipv6_info(&self) -> (Ipv6Addr, u8, u16) { - let new_count = self.prefix_count.fetch_add(1, Ordering::SeqCst) + 1; - - (self.ipv6_address, self.prefix_length, new_count) - } - - pub fn _add_net_device( - &self, - id: Uuid, - mac: Option, - pci: Option, - ) -> Result<(), Error> { + pub fn add_net_device(&self, id: Uuid, config: NetworkMode) -> Result<(), Error> { let vms = self.vms.lock().unwrap(); if !vms.contains_key(&id) { return Err(Error::NotFound); @@ -358,68 +270,60 @@ impl Manager { let mut socket = UnixStream::connect(id.to_string()).map_err(Error::SocketFailure)?; - if let Some(pci) = pci { - // Check if the path exists - let path = PathBuf::from(format!("/sys/bus/pci/devices/{}/", pci)); - info!("check if path exists {}", path.display()); - if !path.exists() { - info!("The path {} does not exist.", path.display()); - return Err(Error::NotFound); + let request: (String, String); + + match config { + NetworkMode::PCIAddress(pci) => { + let path = PathBuf::from(format!("/sys/bus/pci/devices/{}/", pci)); + info!("check if path exists {}", path.display()); + if !path.exists() { + info!("The path {} does not exist.", path.display()); + return Err(Error::NotFound); + } + + info!("add device"); + request = ( + "vm.add-device".to_string(), + json!(vm_config::DeviceConfig { + path, + iommu: false, + id: None, + pci_segment: 0, + x_nv_gpudirect_clique: None, + }) + .to_string(), + ); } - - info!("add device"); - let device_config = json!(vm_config::DeviceConfig { - path, - iommu: false, - id: None, - pci_segment: 0, - x_nv_gpudirect_clique: None, - }); - - let response = api_client::simple_api_full_command_and_response( - &mut socket, - "PUT", - "vm.add-device", - Some(&device_config.to_string()), - ) - .map_err(Error::CHApiFailure)?; - if response.is_some() { - info!( - "add-device to vm: id {}, response: {}", - id.to_string(), - response.unwrap() - ) + NetworkMode::MACAddress(mac) => { + let mac = MacAddr::parse_str(&mac).map_err(Error::CHCommandFailure)?; + let mut net_config = config::_default_net_cfg(); + net_config.host_mac = Some(mac); + request = ("vm.add-net".to_string(), json!(net_config).to_string()); + } + NetworkMode::TAPDeviceName(tap) => { + let mut net_config = config::_default_net_cfg(); + net_config.tap = Some(tap); + request = ("vm.add-net".to_string(), json!(net_config).to_string()); } - - return Ok(()); } - if let Some(mac) = mac { - let mac = MacAddr::parse_str(&mac).map_err(Error::CHCommandFailure)?; - - let mut net_config = config::_default_net_cfg(); - net_config.host_mac = Some(mac); - let net_config = json!(net_config); - - let response = api_client::simple_api_full_command_and_response( - &mut socket, - "PUT", - "vm.add-net", - Some(&net_config.to_string()), + let response = api_client::simple_api_full_command_and_response( + &mut socket, + "PUT", + &request.0, + Some(&request.1), + ) + .map_err(Error::CHApiFailure)?; + if response.is_some() { + info!( + "{} to vm: id {}, response: {}", + request.0, + id.to_string(), + response.unwrap() ) - .map_err(Error::CHApiFailure)?; - if response.is_some() { - info!( - "add_net_device to vm: id {}, response: {}", - id.to_string(), - response.unwrap() - ) - } - - return Ok(()); } - Err(Error::Failed) + Ok(()) } pub fn ping_vmm(&self, id: Uuid) -> Result<(), Error> { @@ -461,12 +365,8 @@ impl Manager { Ok(String::new()) } - pub fn shutdown_vm(&self, id: Uuid) -> Result { + pub fn kill_vm(&self, id: Uuid) -> Result { let mut vms = self.vms.lock().unwrap(); - let vm_info = match vms.get_mut(&id) { - Some(info) => info, - None => return Err(Error::NotFound), - }; let mut socket = UnixStream::connect(id.to_string()).map_err(Error::SocketFailure)?; let response = api_client::simple_api_full_command_and_response( @@ -481,61 +381,24 @@ impl Manager { info!("shutdown vm: id {}, response: {}", id, x); } - if let Err(e) = vm_info.child.kill() { - error!("Failed to kill child process for VM {}: {}", id, e); - } else { - info!("Sent kill signal to VM {}", id); - } + let response = api_client::simple_api_full_command_and_response( + &mut socket, + "PUT", + "vmm.shutdown", + None, + ) + .map_err(Error::CHApiFailure)?; - match vm_info.child.wait() { - Ok(status) => info!("VM {} exited with status {}", id, status), - Err(e) => error!("Failed to wait for VM {}: {}", id, e), + if let Some(x) = &response { + info!("shutdown vmm: id {}, response: {}", id, x); } - vms.remove(&id); + if let Some(mut info) = vms.remove(&id) { + if let Err(e) = info.child.kill() { + info!("failed to kill vm process {}: {}", id, e); + } + } Ok(String::new()) } } - -fn extract_section( - buffer: &[u8], - pe: &PeFile, - section_name: &str, - output_path: &Path, -) -> Result<(), ExtractionError> { - let section = pe - .section_headers() - .iter() - .find(|header| header.Name.starts_with(section_name.as_bytes())) - .ok_or(ExtractionError::SectionNotFound)?; - - let file_offset = section.PointerToRawData as usize; - let data_size = section.SizeOfRawData as usize; - - if file_offset + data_size > buffer.len() { - return Err(ExtractionError::SectionNotFound); - } - - let data = &buffer[file_offset..file_offset + data_size]; - let mut file = File::create(output_path)?; - file.write_all(data)?; - Ok(()) -} -fn extract_uki_image(uki_path: &Path) -> Result<(PathBuf, PathBuf, PathBuf), ExtractionError> { - let buffer = std::fs::read(uki_path)?; - let pe = PeFile::from_bytes(&buffer)?; - - let extract_dir = std::path::PathBuf::from("extracted"); - create_dir_all(&extract_dir)?; - - let kernel_path = extract_dir.join("kernel"); - let cmdline_path = extract_dir.join("cmdline"); - let initramfs_path = extract_dir.join("initramfs"); - - extract_section(&buffer, &pe, ".linux", &kernel_path)?; - extract_section(&buffer, &pe, ".cmdline", &cmdline_path)?; - extract_section(&buffer, &pe, ".initrd", &initramfs_path)?; - - Ok((kernel_path, cmdline_path, initramfs_path)) -}