Skip to content

Commit

Permalink
feat(vm): add vm builtins for reboot, reset and status [BRND-23] (#2340)
Browse files Browse the repository at this point in the history
  • Loading branch information
kmd-fl authored Aug 14, 2024
1 parent 3af4541 commit a180a5c
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 74 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,6 @@ particle-node/spell
# autogenerated from .proto
crates/cid-utils/src/unixfs.rs
.env

.envrc
shell.nix
6 changes: 5 additions & 1 deletion crates/vm-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ mod vm_utils;

pub use nonempty::NonEmpty;
pub use vm_utils::create_domain;
pub use vm_utils::reboot_vm;
pub use vm_utils::remove_domain;
pub use vm_utils::reset_vm;
pub use vm_utils::start_vm;
pub use vm_utils::status_vm;
pub use vm_utils::stop_vm;
pub use vm_utils::CreateVMDomainParams;
pub use vm_utils::VMUtilsError;
pub use vm_utils::VmError;
pub use vm_utils::VmStatus;
177 changes: 143 additions & 34 deletions crates/vm-utils/src/vm_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ use ccp_shared::types::LogicalCoreId;
use mac_address::MacAddress;
use nonempty::NonEmpty;
use rand::Rng;
use std::fmt::Display;
use std::path::PathBuf;
use thiserror::Error;
use virt::connect::Connect;
use virt::domain::Domain;
use virt::sys::VIR_DOMAIN_DEFINE_VALIDATE;
use virt::sys::{VIR_DOMAIN_DEFINE_VALIDATE, VIR_DOMAIN_REBOOT_DEFAULT};

const MAC_PREFIX: [u8; 3] = [0x52, 0x54, 0x00];

Expand Down Expand Up @@ -35,7 +36,7 @@ impl CreateVMDomainParams {
}

#[derive(Error, Debug)]
pub enum VMUtilsError {
pub enum VmError {
#[error("Failed to connect to the hypervisor: {err}")]
FailedToConnect {
#[source]
Expand All @@ -52,7 +53,7 @@ pub enum VMUtilsError {
err: virt::error::Error,
name: String,
},
#[error("Failed to create VM {name}: {err}")]
#[error("Failed to run VM {name}: {err}")]
FailedToStartVM {
#[source]
err: virt::error::Error,
Expand All @@ -64,17 +65,79 @@ pub enum VMUtilsError {
#[source]
err: virt::error::Error,
},
#[error("Failed to shutdown VM: {err}")]
#[error("Failed to shutdown VM {name}: {err}")]
FailedToStopVM {
name: String,
#[source]
err: virt::error::Error,
},
#[error("Failed to get id for VM with name {name}")]
FailedToGetVMId { name: String },
#[error("Failed to reboot {name}: {err}")]
FailedToRebootVM {
err: virt::error::Error,
name: String,
},
#[error("Failed to reset {name}: {err}")]
FailedToResetVM {
err: virt::error::Error,
name: String,
},
#[error("Failed to get info for {name}: {err}")]
FailedToGetInfo {
err: virt::error::Error,
name: String,
},
}

// The list of states is taken from the libvirt documentation
#[derive(Debug)]
pub enum VmStatus {
NoState,
Running,
Blocked,
Paused,
Shutdown,
Shutoff,
Crashed,
PMSuspended,
UnknownState(u32),
}

pub fn create_domain(uri: &str, params: &CreateVMDomainParams) -> Result<(), VMUtilsError> {
let conn = Connect::open(Some(uri)).map_err(|err| VMUtilsError::FailedToConnect { err })?;
impl Display for VmStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VmStatus::NoState => write!(f, "No state"),
VmStatus::Running => write!(f, "Running"),
VmStatus::Blocked => write!(f, "Blocked"),
VmStatus::Paused => write!(f, "Paused"),
VmStatus::Shutdown => write!(f, "Shutdown"),
VmStatus::Shutoff => write!(f, "Shutoff"),
VmStatus::Crashed => write!(f, "Crashed"),
VmStatus::PMSuspended => write!(f, "PMSuspended"),
VmStatus::UnknownState(value) => write!(f, "Unknown state ({})", value),
}
}
}

impl VmStatus {
pub fn from_u32(value: u32) -> Self {
match value {
virt::sys::VIR_DOMAIN_NOSTATE => VmStatus::NoState,
virt::sys::VIR_DOMAIN_RUNNING => VmStatus::Running,
virt::sys::VIR_DOMAIN_BLOCKED => VmStatus::Blocked,
virt::sys::VIR_DOMAIN_PAUSED => VmStatus::Paused,
virt::sys::VIR_DOMAIN_SHUTDOWN => VmStatus::Shutdown,
virt::sys::VIR_DOMAIN_SHUTOFF => VmStatus::Shutoff,
virt::sys::VIR_DOMAIN_CRASHED => VmStatus::Crashed,
virt::sys::VIR_DOMAIN_PMSUSPENDED => VmStatus::PMSuspended,
_ => VmStatus::UnknownState(value),
}
}
}

pub fn create_domain(uri: &str, params: &CreateVMDomainParams) -> Result<(), VmError> {
let conn = Connect::open(Some(uri)).map_err(|err| VmError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, params.name.as_str()).ok();

match domain {
Expand All @@ -83,7 +146,7 @@ pub fn create_domain(uri: &str, params: &CreateVMDomainParams) -> Result<(), VMU
let mac = generate_random_mac();
let xml = prepare_xml(&params, mac.to_string().as_str());
Domain::define_xml_flags(&conn, xml.as_str(), VIR_DOMAIN_DEFINE_VALIDATE)
.map_err(|err| VMUtilsError::FailedToCreateVMDomain { err })?;
.map_err(|err| VmError::FailedToCreateVMDomain { err })?;
}
Some(_) => {
tracing::info!(target: "vm-utils","Domain with name {} already exists. Skipping", params.name);
Expand All @@ -92,63 +155,109 @@ pub fn create_domain(uri: &str, params: &CreateVMDomainParams) -> Result<(), VMU
Ok(())
}

pub fn remove_domain(uri: &str, name: &str) -> Result<(), VMUtilsError> {
pub fn remove_domain(uri: &str, name: &str) -> Result<(), VmError> {
tracing::info!(target: "vm-utils","Removing domain with name {}", name);
let conn = Connect::open(Some(uri)).map_err(|err| VMUtilsError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name).map_err(|err| VMUtilsError::VmNotFound {
let conn = Connect::open(Some(uri)).map_err(|err| VmError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name).map_err(|err| VmError::VmNotFound {
name: name.to_string(),
err,
})?;

domain
.destroy()
.map_err(|err| VMUtilsError::FailedToRemoveVMDomain {
.map_err(|err| VmError::FailedToRemoveVMDomain {
err,
name: name.to_string(),
})?;

domain
.undefine()
.map_err(|err| VMUtilsError::FailedToRemoveVMDomain {
.map_err(|err| VmError::FailedToRemoveVMDomain {
err,
name: name.to_string(),
})?;

Ok(())
}
pub fn start_vm(uri: &str, name: &str) -> Result<u32, VMUtilsError> {
pub fn start_vm(uri: &str, name: &str) -> Result<u32, VmError> {
tracing::info!(target: "vm-utils","Starting VM with name {name}");
let conn = Connect::open(Some(uri)).map_err(|err| VMUtilsError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name).map_err(|err| VMUtilsError::VmNotFound {
let conn = Connect::open(Some(uri)).map_err(|err| VmError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name).map_err(|err| VmError::VmNotFound {
name: name.to_string(),
err,
})?;
domain
.create()
.map_err(|err| VMUtilsError::FailedToStartVM {
err,
name: name.to_string(),
})?;
domain.create().map_err(|err| VmError::FailedToStartVM {
err,
name: name.to_string(),
})?;

let id = domain.get_id().ok_or(VMUtilsError::FailedToGetVMId {
let id = domain.get_id().ok_or(VmError::FailedToGetVMId {
name: name.to_string(),
})?;

Ok(id)
}

pub fn stop_vm(uri: &str, name: String) -> Result<(), VMUtilsError> {
pub fn stop_vm(uri: &str, name: &str) -> Result<(), VmError> {
tracing::info!(target: "vm-utils","Stopping VM with name {name}");
let conn = Connect::open(Some(uri)).map_err(|err| VMUtilsError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name.as_str())
.map_err(|err| VMUtilsError::VmNotFound { name, err })?;
let conn = Connect::open(Some(uri)).map_err(|err| VmError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name).map_err(|err| VmError::VmNotFound {
name: name.to_string(),
err,
})?;
domain.shutdown().map_err(|err| VmError::FailedToStopVM {
name: name.to_string(),
err,
})?;

Ok(())
}

pub fn reboot_vm(uri: &str, name: &str) -> Result<(), VmError> {
tracing::info!(target: "vm-utils","Rebooting VM with name {name}");
let conn = Connect::open(Some(uri)).map_err(|err| VmError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name).map_err(|err| VmError::VmNotFound {
name: name.to_string(),
err,
})?;
domain
.shutdown()
.map_err(|err| VMUtilsError::FailedToStopVM { err })?;
.reboot(VIR_DOMAIN_REBOOT_DEFAULT)
.map_err(|err| VmError::FailedToRebootVM {
err,
name: name.to_string(),
})?;
Ok(())
}

pub fn reset_vm(uri: &str, name: &str) -> Result<(), VmError> {
tracing::info!(target: "vm-utils","Resetting VM with name {name}");
let conn = Connect::open(Some(uri)).map_err(|err| VmError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name).map_err(|err| VmError::VmNotFound {
name: name.to_string(),
err,
})?;
domain.reset().map_err(|err| VmError::FailedToResetVM {
name: name.to_string(),
err,
})?;
Ok(())
}

pub fn status_vm(uri: &str, name: &str) -> Result<VmStatus, VmError> {
tracing::info!(target: "vm-utils","Getting info for VM with name {name}");
let conn = Connect::open(Some(uri)).map_err(|err| VmError::FailedToConnect { err })?;
let domain = Domain::lookup_by_name(&conn, name).map_err(|err| VmError::VmNotFound {
name: name.to_string(),
err,
})?;
let info = domain.get_info().map_err(|err| VmError::FailedToGetInfo {
name: name.to_string(),
err,
})?;

Ok(VmStatus::from_u32(info.state))
}

fn generate_random_mac() -> MacAddress {
let mut rng = rand::thread_rng();
let mut result = [0u8; 6];
Expand Down Expand Up @@ -185,15 +294,15 @@ mod tests {
use std::fs;
const DEFAULT_URI: &str = "test:///default";

fn list_defined() -> Result<Vec<String>, VMUtilsError> {
let conn = Connect::open(Some(DEFAULT_URI))
.map_err(|err| VMUtilsError::FailedToConnect { err })?;
fn list_defined() -> Result<Vec<String>, VmError> {
let conn =
Connect::open(Some(DEFAULT_URI)).map_err(|err| VmError::FailedToConnect { err })?;
Ok(conn.list_defined_domains().unwrap())
}

fn list() -> Result<Vec<u32>, VMUtilsError> {
let conn = Connect::open(Some(DEFAULT_URI))
.map_err(|err| VMUtilsError::FailedToConnect { err })?;
fn list() -> Result<Vec<u32>, VmError> {
let conn =
Connect::open(Some(DEFAULT_URI)).map_err(|err| VmError::FailedToConnect { err })?;
Ok(conn.list_domains().unwrap())
}

Expand Down
16 changes: 5 additions & 11 deletions crates/workers/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use std::path::PathBuf;
use thiserror::Error;
use types::peer_scope::WorkerId;
use types::DealId;
use vm_utils::VMUtilsError;
use vm_utils::VmError;

#[derive(Debug, Error)]
pub enum KeyStorageError {
Expand Down Expand Up @@ -165,16 +165,6 @@ pub enum WorkersError {
},
#[error("Failed to notify subsystem {worker_id}")]
FailedToNotifySubsystem { worker_id: WorkerId },
#[error("Failed to create VM {worker_id}")]
FailedToCreateVM {
worker_id: WorkerId,
err: VMUtilsError,
},
#[error("Failed to remove VM {worker_id}")]
FailedToRemoveVM {
worker_id: WorkerId,
err: VMUtilsError,
},
#[error("This feature is disabled")]
FeatureDisabled,
#[error("VM image {image} isn't file")]
Expand All @@ -183,4 +173,8 @@ pub enum WorkersError {
FailedToCopyVMImage { image: PathBuf, err: std::io::Error },
#[error("Logical cores can't be empty")]
WrongAssignment,
#[error("VM for worker {0} not found")]
VmNotFound(WorkerId),
#[error(transparent)]
VmError(#[from] VmError),
}
Loading

0 comments on commit a180a5c

Please sign in to comment.