Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions crates/kit/src/domain_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,7 @@ impl DomainLister {
.unwrap_or_default();

// Extract memory and vcpu from domain XML
let memory_mb = dom.find("memory").and_then(|node| {
// Memory might have unit attribute, but we'll try to parse the value
node.text_content().parse::<u32>().ok()
});
let memory_mb = dom.find("memory").and_then(|node| node.parse_memory_mb());

let vcpus = dom
.find("vcpu")
Expand Down
1 change: 1 addition & 0 deletions crates/kit/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! bcvk library - exposes internal modules for testing

pub mod qemu_img;
pub mod utils;
pub mod xml_utils;
57 changes: 57 additions & 0 deletions crates/kit/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,60 @@ pub(crate) fn parse_memory_to_mb(memory_str: &str) -> Result<u32> {
Err(eyre!("Memory specification cannot be empty - please provide a value like '2G', '1024M', or '512'"))
}
}

/// Convert memory value with unit to megabytes (MiB)
/// Handles libvirt-style units distinguishing between decimal (KB, MB, GB - powers of 1000)
/// and binary (KiB, MiB, GiB - powers of 1024) units per libvirt specification
pub(crate) fn convert_memory_to_mb(value: u32, unit: &str) -> u32 {
// Use u128 for calculations to prevent overflow with large units like TB
let value_u128 = value as u128;
let mib_u128 = 1024 * 1024;

let mb = match unit {
// Binary prefixes (powers of 1024), converting to MiB
"k" | "K" | "KiB" => value_u128 / 1024,
"M" | "MiB" => value_u128,
"G" | "GiB" => value_u128 * 1024,
"T" | "TiB" => value_u128 * 1024 * 1024,

// Decimal prefixes (powers of 1000), converting to MiB
"B" | "bytes" => value_u128 / mib_u128,
"KB" => (value_u128 * 1_000) / mib_u128,
"MB" => (value_u128 * 1_000_000) / mib_u128,
"GB" => (value_u128 * 1_000_000_000) / mib_u128,
"TB" => (value_u128 * 1_000_000_000_000) / mib_u128,

// Libvirt default is KiB for memory
_ => value_u128 / 1024,
};
mb as u32
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_convert_memory_to_mb() {
// Test binary units (powers of 1024)
assert_eq!(convert_memory_to_mb(4194304, "KiB"), 4096);
assert_eq!(convert_memory_to_mb(2097152, "KiB"), 2048);
assert_eq!(convert_memory_to_mb(2048, "MiB"), 2048);
assert_eq!(convert_memory_to_mb(4096, "MiB"), 4096);
assert_eq!(convert_memory_to_mb(4, "GiB"), 4096);
assert_eq!(convert_memory_to_mb(2, "GiB"), 2048);

// Test short forms (binary)
assert_eq!(convert_memory_to_mb(4, "G"), 4096);
assert_eq!(convert_memory_to_mb(2048, "M"), 2048);
assert_eq!(convert_memory_to_mb(2097152, "K"), 2048);

// Test decimal units (powers of 1000)
assert_eq!(convert_memory_to_mb(1048576, "KB"), 1000);
assert_eq!(convert_memory_to_mb(1024, "MB"), 976);
assert_eq!(convert_memory_to_mb(4, "GB"), 3814);

// Test default/unknown unit (defaults to KiB)
assert_eq!(convert_memory_to_mb(4194304, "unknown"), 4096);
}
}
36 changes: 36 additions & 0 deletions crates/kit/src/xml_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ impl XmlNode {
pub fn text_content(&self) -> &str {
&self.text
}

/// Parse memory value from an XML node with unit attribute
/// Returns the value in megabytes (MB)
pub fn parse_memory_mb(&self) -> Option<u32> {
let value = self.text_content().parse::<u32>().ok()?;
// Convert to MB based on unit attribute (default is KiB per libvirt spec)
let unit = self
.attributes
.get("unit")
.map(|s| s.as_str())
.unwrap_or("KiB");
Some(crate::utils::convert_memory_to_mb(value, unit))
}
}

/// Parse XML string into a simple DOM structure
Expand Down Expand Up @@ -370,4 +383,27 @@ mod tests {
assert!(xml.contains("<custom>raw content</custom>"));
assert!(xml.contains("</root>"));
}

#[test]
fn test_parse_memory_mb() {
// Test KiB (default unit)
let xml = r#"<memory>4194304</memory>"#;
let dom = parse_xml_dom(xml).unwrap();
assert_eq!(dom.parse_memory_mb(), Some(4096));

// Test MiB
let xml = r#"<memory unit='MiB'>2048</memory>"#;
let dom = parse_xml_dom(xml).unwrap();
assert_eq!(dom.parse_memory_mb(), Some(2048));

// Test GiB
let xml = r#"<memory unit='GiB'>4</memory>"#;
let dom = parse_xml_dom(xml).unwrap();
assert_eq!(dom.parse_memory_mb(), Some(4096));

// Test KB (decimal unit: 1000-based)
let xml = r#"<memory unit='KB'>1048576</memory>"#;
let dom = parse_xml_dom(xml).unwrap();
assert_eq!(dom.parse_memory_mb(), Some(1000));
}
}