Browse Source

feat: create method to destroy a domain

rust
Giovanni Torres 1 year ago
parent
commit
3209c73072
  1. 18
      .editorconfig
  2. 118
      Cargo.lock
  3. 6
      Cargo.toml
  4. 2
      build.rs
  5. 46
      src/cli.rs
  6. 98
      src/main.rs
  7. 138
      src/vm.rs
  8. 106
      tests/cli_tests.rs
  9. 290
      tests/vm_tests.rs

18
.editorconfig

@ -5,25 +5,25 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
charset = utf-8
[*.md]
max_line_length = 78
trim_trailing_whitespace = false
[*.rs]
indent_size = 4
indent_style = space
indent_size = 4
max_line_length = 100
[*.toml]
indent_size = 2
[*.{yml,yaml}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab

118
Cargo.lock generated

@ -114,52 +114,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "env_logger"
version = "0.10.2"
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "heck"
version = "0.5.0"
name = "env_logger"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "hermit-abi"
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "humantime"
version = "2.2.0"
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "is-terminal"
version = "0.4.16"
name = "jiff"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
name = "jiff-static"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "kvm-install-vm"
@ -203,6 +214,21 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "proc-macro2"
version = "1.0.94"
@ -250,6 +276,26 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "strsim"
version = "0.11.1"
@ -267,15 +313,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
@ -296,9 +333,9 @@ checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
[[package]]
name = "virt"
version = "0.3.2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5408c59dc1b3383e0da017a85a6799dd6e3d52790849ececadbf403513743fa6"
checksum = "77a05f77c836efa9be343b5419663cf829d75203b813579993cdd9c44f51767e"
dependencies = [
"libc",
"uuid",
@ -307,23 +344,14 @@ dependencies = [
[[package]]
name = "virt-sys"
version = "0.2.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d9a603af8e27b33f1c8d721cb5caafcf77810c79e6ea02d2436906a14683fd"
checksum = "c504e459878f09177f41bf2f8bb3e9a8af4fca7a09e73152fee02535d501601c"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.59.0"

6
Cargo.toml

@ -9,10 +9,10 @@ repository = "https://github.com/giovtorres/kvm-install-vm"
[dependencies]
anyhow = "1.0"
clap = { version = "4.4", features = ["derive"] }
env_logger = "0.10"
clap = { version = "4.5", features = ["derive"] }
env_logger = "0.11"
log = "0.4"
virt = "0.3.1"
virt = "0.4"
[build-dependencies]
pkg-config = "0.3"

2
build.rs

@ -1,6 +1,6 @@
fn main() {
println!("cargo:rustc-link-lib=virt");
// // Try using pkg-config to find libvirt
// match pkg_config::probe_library("libvirt") {
// Ok(_) => println!("Found libvirt via pkg-config"),

46
src/cli.rs

@ -1,26 +1,40 @@
use clap::Parser;
use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(author, version, about)]
pub struct Cli {
#[arg(short = 'n', long)]
pub name: String,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Create {
#[arg(short = 'n', long)]
name: String,
#[arg(short = 't', long, default_value = "centos8")]
distro: String,
#[arg(short = 't', long, default_value = "centos8")]
pub distro: String,
#[arg(short = 'c', long, default_value_t = 1)]
vcpus: u32,
#[arg(short = 'c', long, default_value_t = 1)]
pub vcpus: u32,
#[arg(short = 'm', long, default_value_t = 1024)]
memory_mb: u32,
#[arg(short = 'm', long, default_value_t = 1024)]
pub memory_mb: u32,
#[arg(short = 'd', long, default_value_t = 10)]
disk_size_gb: u32,
#[arg(short = 'd', long, default_value_t = 10)]
pub disk_size_gb: u32,
#[arg(long)]
graphics: bool,
#[arg(long)]
pub graphics: bool,
#[arg(long)]
dry_run: bool,
},
Destroy {
#[arg(short = 'n', long)]
name: String,
#[arg(long)]
pub dry_run: bool,
#[arg(short = 'r', long)]
remove_disk: bool,
},
}

98
src/main.rs

@ -1,5 +1,5 @@
use clap::Parser;
use kvm_install_vm::{Cli, vm::VirtualMachine};
use kvm_install_vm::{Cli, cli::Commands, vm::VirtualMachine};
use std::process;
fn main() {
@ -7,42 +7,68 @@ fn main() {
env_logger::init();
// Parse command line arguments
let args = Cli::parse();
println!("Starting kvm-install-vm Rust implementation...");
println!("VM Name: {}", args.name);
println!("Distribution: {}", args.distro);
println!("Configuration:");
println!(" vCPUs: {}", args.vcpus);
println!(" Memory: {} MB", args.memory_mb);
println!(" Disk Size: {} GB", args.disk_size_gb);
let disk_path = format!("/home/giovanni/virt/images/{}.qcow2", args.name);
let vm_name = args.name.clone();
let mut vm = VirtualMachine::new(
args.name,
args.vcpus,
args.memory_mb,
args.disk_size_gb,
disk_path,
// args.distro,
);
if let Err(e) = vm.connect(None) {
eprintln!("Failed to connect to libvirt: {}", e);
process::exit(1);
}
let cli = Cli::parse();
match &cli.command {
Commands::Create {
name,
distro,
vcpus,
memory_mb,
disk_size_gb,
graphics,
dry_run,
} => {
println!("Starting kvm-install-vm Rust implementation...");
println!("VM Name: {}", name);
println!("Distribution: {}", distro);
println!("Configuration:");
println!(" vCPUs: {}", vcpus);
println!(" Memory: {} MB", memory_mb);
println!(" Disk Size: {} GB", disk_size_gb);
println!(" Graphics: {}", graphics);
if *dry_run {
println!("Dry run mode - not creating VM");
return;
}
let disk_path = format!("/home/giovanni/virt/images/{}.qcow2", name);
let vm_name = name.clone();
let mut vm =
VirtualMachine::new(name.clone(), *vcpus, *memory_mb, *disk_size_gb, disk_path);
if let Err(e) = vm.connect(None) {
eprintln!("Failed to connect to libvirt: {}", e);
process::exit(1);
}
match vm.create() {
Ok(domain) => {
println!("Successfully created VM: {}", vm_name);
println!("Domain ID: {}", domain.get_id().unwrap_or(0));
}
Err(e) => {
eprintln!("Failed to create VM: {}", e);
process::exit(1);
}
}
}
Commands::Destroy { name, remove_disk } => {
println!("Destroying VM: {}", name);
match vm.create() {
Ok(domain) => {
println!("Successfully created VM: {}", vm_name);
println!("Domain ID: {}", domain.get_id().unwrap_or(0));
},
Err(e) => {
eprintln!("Failed to create VM: {}", e);
process::exit(1);
match VirtualMachine::destroy(name, None, *remove_disk) {
Ok(()) => {
println!("VM '{}' destroy operation completed successfully", name);
}
Err(e) => {
eprintln!("Failed to destroy VM '{}': {}", name, e);
process::exit(1);
}
}
}
}
}

138
src/vm.rs

@ -1,20 +1,26 @@
use anyhow::{Result, Context};
use anyhow::{Context, Result};
use std::path::Path;
use virt::connect::Connect;
use virt::domain::Domain;
pub struct VirtualMachine {
name: String,
vcpus: u32,
memory_mb: u32,
disk_size_gb: u32,
disk_path: String,
pub name: String,
pub vcpus: u32,
pub memory_mb: u32,
pub disk_size_gb: u32,
pub disk_path: String,
// distro: String,
connection: Option<Connect>,
pub connection: Option<Connect>,
}
impl VirtualMachine {
pub fn new(name: String, vcpus: u32, memory_mb: u32, disk_size_gb: u32, disk_path: String) -> Self {
pub fn new(
name: String,
vcpus: u32,
memory_mb: u32,
disk_size_gb: u32,
disk_path: String,
) -> Self {
VirtualMachine {
name,
vcpus,
@ -55,12 +61,21 @@ impl VirtualMachine {
fn create_disk_image(&self) -> Result<()> {
// Create disk image using qemu-img
let output = std::process::Command::new("qemu-img")
.args(&["create", "-f", "qcow2", &self.disk_path, &format!("{}G", self.disk_size_gb)])
.args(&[
"create",
"-f",
"qcow2",
&self.disk_path,
&format!("{}G", self.disk_size_gb),
])
.output()
.context("Failed to execute qemu-img command")?;
if !output.status.success() {
return Err(anyhow::anyhow!("Failed to create disk image: {:?}", output.stderr));
return Err(anyhow::anyhow!(
"Failed to create disk image: {:?}",
output.stderr
));
}
Ok(())
@ -68,7 +83,8 @@ impl VirtualMachine {
fn generate_domain_xml(&self) -> Result<String> {
// Generate domain XML
let xml = format!(r#"
let xml = format!(
r#"
<domain type='kvm'>
<name>{}</name>
<memory unit='MiB'>{}</memory>
@ -95,8 +111,106 @@ impl VirtualMachine {
<graphics type='vnc' port='-1'/>
</devices>
</domain>
"#, self.name, self.memory_mb, self.vcpus, self.disk_path);
"#,
self.name, self.memory_mb, self.vcpus, self.disk_path
);
Ok(xml)
}
// Destroy method for instance
pub fn destroy_instance(&mut self, remove_disk: bool) -> Result<()> {
// Ensure connection is established
if self.connection.is_none() {
return Err(anyhow::anyhow!("Connection not established"));
}
Self::destroy(&self.name, Some("qemu:///session"), remove_disk)
}
// Static destroy method
pub fn destroy(name: &str, uri: Option<&str>, remove_disk: bool) -> Result<()> {
let uri = uri.or(Some("qemu:///session"));
let conn = Connect::open(uri).context("Failed to connect to libvirt")?;
let domain = match Domain::lookup_by_name(&conn, name) {
Ok(dom) => dom,
Err(e) => {
return Err(anyhow::anyhow!("Failed to find domain {}: {}", name, e));
}
};
// Extract disk paths before destroying the domain
let xml = domain.get_xml_desc(0).context("Failed to get domain XML")?;
let disk_paths = extract_disk_paths_from_xml(&xml);
// Check domain state first
if domain.is_active().context("Failed to check domain state")? {
println!("Stopping running domain '{}'...", name);
match domain.destroy() {
Ok(_) => println!("Domain stopped successfully"),
Err(e) => println!(
"Warning: Failed to stop domain cleanly: {}. Continuing with undefine...",
e
),
}
} else {
println!("Domain '{}' is already stopped", name);
}
// Undefine the domain with flags
use virt::sys;
let flags = sys::VIR_DOMAIN_UNDEFINE_MANAGED_SAVE
| sys::VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA
| sys::VIR_DOMAIN_UNDEFINE_NVRAM;
unsafe {
let result = sys::virDomainUndefineFlags(domain.as_ptr(), flags);
if result < 0 {
return Err(anyhow::anyhow!("Failed to undefine domain"));
}
}
println!("Domain {} successfully undefined", name);
// Handle disk removal if requested
if remove_disk && !disk_paths.is_empty() {
println!("Removing disk images...");
for path in &disk_paths {
match std::fs::remove_file(path) {
Ok(_) => println!("Successfully removed disk: {}", path),
Err(e) => println!("Warning: Failed to remove disk {}: {}", path, e),
}
}
} else if !disk_paths.is_empty() {
println!("Note: The following disk images were not deleted:");
for path in &disk_paths {
println!(" - {}", path);
}
}
println!("Domain {} completely destroyed", name);
Ok(())
}
}
fn extract_disk_paths_from_xml(xml: &str) -> Vec<String> {
let mut disk_paths = Vec::new();
for line in xml.lines() {
if line.contains("<source file=") {
if let Some(start) = line.find("file='") {
if let Some(end) = line[start + 6..].find('\'') {
disk_paths.push(line[start + 6..start + 6 + end].to_string());
}
} else if let Some(start) = line.find("file=\"") {
if let Some(end) = line[start + 6..].find('\"') {
disk_paths.push(line[start + 6..start + 6 + end].to_string());
}
}
}
}
disk_paths
}

106
tests/cli_tests.rs

@ -1,5 +1,5 @@
use clap::Parser;
use kvm_install_vm::Cli;
use kvm_install_vm::{Cli, cli::Commands};
use std::ffi::OsString;
fn get_args(args: &[&str]) -> Vec<OsString> {
@ -10,43 +10,93 @@ fn get_args(args: &[&str]) -> Vec<OsString> {
}
#[test]
fn test_cli_defaults() {
let args = get_args(&["--name", "test-vm"]);
fn test_cli_create_defaults() {
let args = get_args(&["create", "--name", "test-vm"]);
let cli = Cli::parse_from(args);
assert_eq!(cli.name, "test-vm");
assert_eq!(cli.distro, "centos8");
assert_eq!(cli.vcpus, 1);
assert_eq!(cli.disk_size_gb, 10);
assert_eq!(cli.memory_mb, 1024);
assert_eq!(cli.graphics, false);
assert_eq!(cli.dry_run, false);
match cli.command {
Commands::Create {
name,
distro,
vcpus,
memory_mb,
disk_size_gb,
graphics,
dry_run
} => {
assert_eq!(name, "test-vm");
assert_eq!(distro, "centos8");
assert_eq!(vcpus, 1);
assert_eq!(disk_size_gb, 10);
assert_eq!(memory_mb, 1024);
assert_eq!(graphics, false);
assert_eq!(dry_run, false);
},
_ => panic!("Expected Create command"),
}
}
#[test]
fn test_cli_custom_values() {
fn test_cli_create_custom_values() {
let args = get_args(&[
"--name",
"custom-vm",
"--distro",
"ubuntu2004",
"--vcpus",
"4",
"--memory-mb",
"4096",
"--disk-size-gb",
"50",
"create",
"--name", "custom-vm",
"--distro", "ubuntu2004",
"--vcpus", "4",
"--memory-mb", "4096",
"--disk-size-gb", "50",
"--graphics",
"--dry-run",
]);
let cli = Cli::parse_from(args);
assert_eq!(cli.name, "custom-vm");
assert_eq!(cli.distro, "ubuntu2004");
assert_eq!(cli.vcpus, 4);
assert_eq!(cli.disk_size_gb, 50);
assert_eq!(cli.memory_mb, 4096);
assert_eq!(cli.graphics, true);
assert_eq!(cli.dry_run, true);
match cli.command {
Commands::Create {
name,
distro,
vcpus,
memory_mb,
disk_size_gb,
graphics,
dry_run
} => {
assert_eq!(name, "custom-vm");
assert_eq!(distro, "ubuntu2004");
assert_eq!(vcpus, 4);
assert_eq!(disk_size_gb, 50);
assert_eq!(memory_mb, 4096);
assert_eq!(graphics, true);
assert_eq!(dry_run, true);
},
_ => panic!("Expected Create command"),
}
}
#[test]
fn test_cli_destroy_defaults() {
let args = get_args(&["destroy", "--name", "test-vm"]);
let cli = Cli::parse_from(args);
match cli.command {
Commands::Destroy { name, remove_disk } => {
assert_eq!(name, "test-vm");
assert_eq!(remove_disk, false);
},
_ => panic!("Expected Destroy command"),
}
}
#[test]
fn test_cli_destroy_with_disk_removal() {
let args = get_args(&["destroy", "--name", "test-vm", "--remove-disk"]);
let cli = Cli::parse_from(args);
match cli.command {
Commands::Destroy { name, remove_disk } => {
assert_eq!(name, "test-vm");
assert_eq!(remove_disk, true);
},
_ => panic!("Expected Destroy command"),
}
}

290
tests/vm_tests.rs

@ -0,0 +1,290 @@
#[cfg(test)]
mod tests {
use kvm_install_vm::vm::VirtualMachine;
use std::fs;
use std::process::Command;
use anyhow::Result;
use virt::domain::Domain;
// Helper function to check if a VM with a given name exists
fn domain_exists(name: &str) -> bool {
let output = Command::new("virsh")
.args(["list", "--all", "--name"])
.output()
.expect("Failed to execute virsh command");
let output_str = String::from_utf8_lossy(&output.stdout);
output_str.lines().any(|line| line.trim() == name)
}
// Custom function to generate domain XML for testing
fn generate_test_domain_xml(vm: &VirtualMachine) -> String {
format!(r#"
<domain type='kvm'>
<name>{}</name>
<memory unit='MiB'>{}</memory>
<vcpu>{}</vcpu>
<os>
<type arch='x86_64'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
</features>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='{}'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='network'>
<source network='default'/>
<model type='virtio'/>
</interface>
<console type='pty'/>
<graphics type='vnc' port='-1'/>
</devices>
</domain>
"#, vm.name, vm.memory_mb, vm.vcpus, vm.disk_path)
}
// Extract disk paths function for testing
fn extract_disk_paths_from_xml(xml: &str) -> Vec<String> {
let mut disk_paths = Vec::new();
for line in xml.lines() {
if line.contains("<source file=") {
if let Some(start) = line.find("file='") {
if let Some(end) = line[start + 6..].find('\'') {
disk_paths.push(line[start + 6..start + 6 + end].to_string());
}
} else if let Some(start) = line.find("file=\"") {
if let Some(end) = line[start + 6..].find('\"') {
disk_paths.push(line[start + 6..start + 6 + end].to_string());
}
}
}
}
disk_paths
}
// Helper function to create a test VM for later destruction tests
fn create_test_vm(name: &str) -> Result<()> {
// Skip if domain already exists
if domain_exists(name) {
return Ok(());
}
let temp_dir = std::env::temp_dir();
let disk_path = temp_dir.join(format!("{}.qcow2", name));
// Create a minimal VM for testing
let mut vm = VirtualMachine::new(
name.to_string(),
1,
512,
1,
disk_path.to_string_lossy().to_string(),
);
vm.connect(None)?;
// Create disk if it doesn't exist
if !disk_path.exists() {
Command::new("qemu-img")
.args([
"create",
"-f", "qcow2",
disk_path.to_string_lossy().as_ref(),
"1G"
])
.output()
.expect("Failed to create test disk image");
}
// Define the domain but don't start it (to keep tests faster)
let conn = vm.connection.as_ref().unwrap();
let xml = generate_test_domain_xml(&vm);
Domain::define_xml(conn, &xml)?;
Ok(())
}
// Helper to clean up any leftover test resources
fn cleanup_test_resources(name: &str) {
if domain_exists(name) {
let _ = Command::new("virsh")
.args(["destroy", name])
.output();
let _ = Command::new("virsh")
.args(["undefine", name, "--managed-save", "--snapshots-metadata", "--nvram"])
.output();
}
let temp_dir = std::env::temp_dir();
let disk_path = temp_dir.join(format!("{}.qcow2", name));
if disk_path.exists() {
let _ = fs::remove_file(disk_path);
}
}
#[test]
fn test_create_new_vm_instance() {
let vm = VirtualMachine::new(
"test-vm".to_string(),
2,
1024,
10,
"/tmp/test-vm.qcow2".to_string(),
);
assert_eq!(vm.name, "test-vm");
assert_eq!(vm.vcpus, 2);
assert_eq!(vm.memory_mb, 1024);
assert_eq!(vm.disk_size_gb, 10);
assert_eq!(vm.disk_path, "/tmp/test-vm.qcow2");
assert!(vm.connection.is_none());
}
#[test]
fn test_extract_disk_paths() {
let xml = r#"
<domain type='kvm'>
<name>test-vm</name>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/path/to/disk1.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file="/path/to/disk2.qcow2"/>
<target dev='vdb' bus='virtio'/>
</disk>
</devices>
</domain>
"#;
let disk_paths = extract_disk_paths_from_xml(xml);
assert_eq!(disk_paths.len(), 2);
assert!(disk_paths.contains(&"/path/to/disk1.qcow2".to_string()));
assert!(disk_paths.contains(&"/path/to/disk2.qcow2".to_string()));
}
// This test requires libvirt to be running
// Use #[ignore] to skip it in normal test runs
#[test]
#[ignore]
fn test_connect_to_libvirt() -> Result<()> {
let mut vm = VirtualMachine::new(
"test-connect-vm".to_string(),
1,
512,
1,
"/tmp/test-connect-vm.qcow2".to_string(),
);
vm.connect(None)?;
assert!(vm.connection.is_some());
Ok(())
}
// This test creates and then destroys a VM
// It's marked as ignored because it makes actual system changes
#[test]
#[ignore]
fn test_create_and_destroy_vm() -> Result<()> {
let test_name = "test-create-destroy-vm";
let temp_dir = std::env::temp_dir();
let disk_path = temp_dir.join(format!("{}.qcow2", test_name));
// Clean up any previous test resources
cleanup_test_resources(test_name);
// Create a new VM
let mut vm = VirtualMachine::new(
test_name.to_string(),
1,
512,
1,
disk_path.to_string_lossy().to_string(),
);
vm.connect(None)?;
// Create disk if it doesn't exist
if !disk_path.exists() {
Command::new("qemu-img")
.args([
"create",
"-f", "qcow2",
disk_path.to_string_lossy().as_ref(),
"1G"
])
.output()
.expect("Failed to create test disk image");
}
// Create the VM via helper function since we can't call the private method
let conn = vm.connection.as_ref().unwrap();
let xml = generate_test_domain_xml(&vm);
let domain = Domain::define_xml(conn, &xml)?;
domain.create()?;
// Verify it exists
assert!(domain_exists(test_name));
// Now destroy it
vm.destroy_instance(false)?;
// Verify it no longer exists
assert!(!domain_exists(test_name));
// Disk should still exist since we used remove_disk=false
assert!(disk_path.exists());
// Clean up disk
let _ = fs::remove_file(disk_path);
Ok(())
}
// Test destroying a VM with the static method
#[test]
#[ignore]
fn test_destroy_static_method() -> Result<()> {
let test_name = "test-static-destroy-vm";
// Create a test VM first
create_test_vm(test_name)?;
// Verify it exists
assert!(domain_exists(test_name));
// Destroy it with the static method
VirtualMachine::destroy(test_name, None, true)?;
// Verify it no longer exists
assert!(!domain_exists(test_name));
// Disk should be gone since we used remove_disk=true
let temp_dir = std::env::temp_dir();
let disk_path = temp_dir.join(format!("{}.qcow2", test_name));
assert!(!disk_path.exists());
Ok(())
}
// Test destroying a non-existent VM (should return error)
#[test]
fn test_destroy_nonexistent_vm() {
let result = VirtualMachine::destroy("definitely-nonexistent-vm", None, false);
assert!(result.is_err());
}
}
Loading…
Cancel
Save