|
|
|
|
@ -13,9 +13,9 @@ use virt::connect::Connect;
|
|
|
|
|
use virt::domain::Domain; |
|
|
|
|
use virt::sys; |
|
|
|
|
|
|
|
|
|
use crate::cloudinit::CloudInitManager; |
|
|
|
|
use crate::config::Config; |
|
|
|
|
use crate::domain::{DomainInfo, DomainState, extract_disk_paths_from_xml}; |
|
|
|
|
use crate::cloudinit::CloudInitManager; |
|
|
|
|
|
|
|
|
|
pub struct VirtualMachine { |
|
|
|
|
pub name: String, |
|
|
|
|
@ -282,6 +282,22 @@ impl VirtualMachine {
|
|
|
|
|
pub async fn prepare_image(&mut self, distro: &str, config: &Config) -> Result<()> { |
|
|
|
|
info!("Preparing image for VM: {}", self.name); |
|
|
|
|
|
|
|
|
|
// First check if domain exists in libvirt
|
|
|
|
|
if let Ok(true) = self.domain_exists() { |
|
|
|
|
return Err(anyhow::anyhow!( |
|
|
|
|
"Domain '{}' already exists in libvirt", |
|
|
|
|
self.name |
|
|
|
|
)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check if VM files exist on disk
|
|
|
|
|
if self.vm_files_exist() { |
|
|
|
|
return Err(anyhow::anyhow!( |
|
|
|
|
"VM '{}' files already exist on disk. Use destroy command with --remove-disk flag first", |
|
|
|
|
self.name |
|
|
|
|
)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Get distribution info
|
|
|
|
|
let distro_info = config.get_distro(distro)?; |
|
|
|
|
debug!("Using distro: {}", distro); |
|
|
|
|
@ -302,7 +318,8 @@ impl VirtualMachine {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create disk path for the VM
|
|
|
|
|
self.disk_path = vm_dir.join(format!("{}.qcow2", self.name)) |
|
|
|
|
self.disk_path = vm_dir |
|
|
|
|
.join(format!("{}.qcow2", self.name)) |
|
|
|
|
.to_string_lossy() |
|
|
|
|
.to_string(); |
|
|
|
|
debug!("Disk path: {}", self.disk_path); |
|
|
|
|
@ -312,9 +329,12 @@ impl VirtualMachine {
|
|
|
|
|
let mut cmd = Command::new("qemu-img"); |
|
|
|
|
cmd.args([ |
|
|
|
|
"create", |
|
|
|
|
"-f", "qcow2", |
|
|
|
|
"-F", "qcow2", |
|
|
|
|
"-b", cloud_image.to_str().unwrap(), |
|
|
|
|
"-f", |
|
|
|
|
"qcow2", |
|
|
|
|
"-F", |
|
|
|
|
"qcow2", |
|
|
|
|
"-b", |
|
|
|
|
cloud_image.to_str().unwrap(), |
|
|
|
|
&self.disk_path, |
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
@ -358,12 +378,8 @@ impl VirtualMachine {
|
|
|
|
|
)?; |
|
|
|
|
|
|
|
|
|
// Create cloud-init ISO
|
|
|
|
|
let iso_path = CloudInitManager::create_cloud_init_iso( |
|
|
|
|
&vm_dir, |
|
|
|
|
&self.name, |
|
|
|
|
&user_data, |
|
|
|
|
&meta_data, |
|
|
|
|
)?; |
|
|
|
|
let iso_path = |
|
|
|
|
CloudInitManager::create_cloud_init_iso(&vm_dir, &self.name, &user_data, &meta_data)?; |
|
|
|
|
debug!("Cloud-init ISO created at: {}", iso_path.display()); |
|
|
|
|
|
|
|
|
|
info!("Image preparation completed successfully"); |
|
|
|
|
@ -390,6 +406,10 @@ impl VirtualMachine {
|
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if self.domain_exists()? { |
|
|
|
|
return Err(anyhow::anyhow!("Domain '{}' already exists", self.name)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check if disk image exists and create if needed
|
|
|
|
|
if !Path::new(&self.disk_path).exists() { |
|
|
|
|
debug!("Disk image doesn't exist, creating it"); |
|
|
|
|
@ -458,9 +478,7 @@ impl VirtualMachine {
|
|
|
|
|
debug!("Executing command: {:?}", cmd); |
|
|
|
|
|
|
|
|
|
// Execute the command
|
|
|
|
|
let output = cmd |
|
|
|
|
.output() |
|
|
|
|
.context("Failed to execute qemu-img command")?; |
|
|
|
|
let output = cmd.output().context("Failed to execute qemu-img command")?; |
|
|
|
|
|
|
|
|
|
if !output.status.success() { |
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr); |
|
|
|
|
@ -542,11 +560,17 @@ impl VirtualMachine {
|
|
|
|
|
Ok(_) => { |
|
|
|
|
info!("Domain stopped successfully"); |
|
|
|
|
println!("Domain stopped successfully"); |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
Err(e) => { |
|
|
|
|
warn!("Warning: Failed to stop domain cleanly: {}. Continuing with undefine...", e); |
|
|
|
|
println!("Warning: Failed to stop domain cleanly: {}. Continuing with undefine...", e); |
|
|
|
|
}, |
|
|
|
|
warn!( |
|
|
|
|
"Warning: Failed to stop domain cleanly: {}. Continuing with undefine...", |
|
|
|
|
e |
|
|
|
|
); |
|
|
|
|
println!( |
|
|
|
|
"Warning: Failed to stop domain cleanly: {}. Continuing with undefine...", |
|
|
|
|
e |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
info!("Domain '{}' is already stopped", name); |
|
|
|
|
@ -576,11 +600,11 @@ impl VirtualMachine {
|
|
|
|
|
Ok(_) => { |
|
|
|
|
info!("Successfully removed disk: {}", path); |
|
|
|
|
println!("Successfully removed disk: {}", path); |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
Err(e) => { |
|
|
|
|
warn!("Warning: Failed to remove disk {}: {}", path, e); |
|
|
|
|
println!("Warning: Failed to remove disk {}: {}", path, e); |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else if !disk_paths.is_empty() { |
|
|
|
|
@ -620,24 +644,18 @@ impl VirtualMachine {
|
|
|
|
|
|
|
|
|
|
// Get state
|
|
|
|
|
let state = match domain.get_state() { |
|
|
|
|
Ok((state, _)) => { |
|
|
|
|
match state { |
|
|
|
|
Ok((state, _)) => match state { |
|
|
|
|
virt::sys::VIR_DOMAIN_RUNNING => DomainState::Running, |
|
|
|
|
virt::sys::VIR_DOMAIN_PAUSED => DomainState::Paused, |
|
|
|
|
virt::sys::VIR_DOMAIN_SHUTDOWN => DomainState::Shutdown, |
|
|
|
|
virt::sys::VIR_DOMAIN_SHUTOFF => DomainState::Shutoff, |
|
|
|
|
virt::sys::VIR_DOMAIN_CRASHED => DomainState::Crashed, |
|
|
|
|
_ => DomainState::Unknown, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
Err(_) => DomainState::Unknown, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
domain_infos.push(DomainInfo { |
|
|
|
|
id, |
|
|
|
|
name, |
|
|
|
|
state, |
|
|
|
|
}); |
|
|
|
|
domain_infos.push(DomainInfo { id, name, state }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Process inactive domains
|
|
|
|
|
@ -654,7 +672,12 @@ impl VirtualMachine {
|
|
|
|
|
Ok(domain_infos) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn print_domain_list(uri: Option<&str>, show_all: bool, show_running: bool, show_inactive: bool) -> Result<()> { |
|
|
|
|
pub fn print_domain_list( |
|
|
|
|
uri: Option<&str>, |
|
|
|
|
show_all: bool, |
|
|
|
|
show_running: bool, |
|
|
|
|
show_inactive: bool, |
|
|
|
|
) -> Result<()> { |
|
|
|
|
// Get domain list
|
|
|
|
|
let domains = Self::list_domains(uri)?; |
|
|
|
|
|
|
|
|
|
@ -665,7 +688,10 @@ impl VirtualMachine {
|
|
|
|
|
|
|
|
|
|
// Print header
|
|
|
|
|
println!("{:<5} {:<20} {:<10}", "ID", "Name", "State"); |
|
|
|
|
println!("{:<5} {:<20} {:<10}", "-----", "--------------------", "----------"); |
|
|
|
|
println!( |
|
|
|
|
"{:<5} {:<20} {:<10}", |
|
|
|
|
"-----", "--------------------", "----------" |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Print domain information
|
|
|
|
|
for domain in domains { |
|
|
|
|
@ -677,13 +703,60 @@ impl VirtualMachine {
|
|
|
|
|
let is_running = domain.state == DomainState::Running; |
|
|
|
|
let is_inactive = domain.state == DomainState::Shutoff; |
|
|
|
|
|
|
|
|
|
if show_all ||
|
|
|
|
|
(show_running && is_running) ||
|
|
|
|
|
(show_inactive && is_inactive) { |
|
|
|
|
if show_all || (show_running && is_running) || (show_inactive && is_inactive) { |
|
|
|
|
println!("{:<5} {:<20} {:<10}", id, domain.name, domain.state); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn domain_exists(&self) -> Result<bool> { |
|
|
|
|
// Ensure connection is established
|
|
|
|
|
let conn = match &self.connection { |
|
|
|
|
Some(c) => c, |
|
|
|
|
None => { |
|
|
|
|
return Err(anyhow::anyhow!( |
|
|
|
|
"Connection not established before domain_exists() call" |
|
|
|
|
)); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Try to lookup the domain by name
|
|
|
|
|
match virt::domain::Domain::lookup_by_name(conn, &self.name) { |
|
|
|
|
Ok(_) => { |
|
|
|
|
// Domain exists
|
|
|
|
|
info!("Domain '{}' already exists", self.name); |
|
|
|
|
Ok(true) |
|
|
|
|
} |
|
|
|
|
Err(e) => { |
|
|
|
|
if e.code() == virt::error::ErrorNumber::DomExist { |
|
|
|
|
debug!("Domain '{}' does not exist", self.name); |
|
|
|
|
Ok(false) |
|
|
|
|
} else { |
|
|
|
|
// Some other error occurred
|
|
|
|
|
error!("Error checking if domain exists: {}", e); |
|
|
|
|
Err(anyhow::anyhow!("Error checking if domain exists: {}", e)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn vm_files_exist(&self) -> bool { |
|
|
|
|
// Check if VM directory exists
|
|
|
|
|
if let Some(parent) = Path::new(&self.disk_path).parent() { |
|
|
|
|
if parent.exists() { |
|
|
|
|
debug!("VM directory already exists: {}", parent.display()); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check if disk file exists
|
|
|
|
|
if Path::new(&self.disk_path).exists() { |
|
|
|
|
debug!("VM disk already exists: {}", self.disk_path); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
false |
|
|
|
|
} |
|
|
|
|
} |