use crate::cloudhv::errors::{CloudHypervisorError, Result};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl Version {
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
pub fn parse(s: &str) -> Result<Self> {
let s = s.trim().trim_start_matches('v');
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 3 {
return Err(CloudHypervisorError::Validation(format!(
"Invalid version format: {}. Expected format: major.minor.patch",
s
)));
}
let major = parts[0].parse::<u32>().map_err(|_| {
CloudHypervisorError::Validation(format!("Invalid major version: {}", parts[0]))
})?;
let minor = parts[1].parse::<u32>().map_err(|_| {
CloudHypervisorError::Validation(format!("Invalid minor version: {}", parts[1]))
})?;
let patch = parts[2].parse::<u32>().map_err(|_| {
CloudHypervisorError::Validation(format!("Invalid patch version: {}", parts[2]))
})?;
Ok(Self::new(major, minor, patch))
}
pub fn is_at_least(&self, other: &Version) -> bool {
self >= other
}
pub fn is_compatible_with(&self, other: &Version) -> bool {
self.major == other.major && self >= other
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
match self.major.cmp(&other.major) {
Ordering::Equal => match self.minor.cmp(&other.minor) {
Ordering::Equal => self.patch.cmp(&other.patch),
other => other,
},
other => other,
}
}
}
#[derive(Debug, Clone)]
pub struct Capabilities {
pub version: Version,
}
impl Capabilities {
pub fn new(version: Version) -> Self {
Self { version }
}
pub fn from_version_string(s: &str) -> Result<Self> {
let version = Version::parse(s)?;
Ok(Self::new(version))
}
pub fn supports_snapshot(&self) -> bool {
self.version.is_at_least(&Version::new(28, 0, 0))
}
pub fn supports_vcpu_hotadd(&self) -> bool {
self.version.is_at_least(&Version::new(0, 1, 0))
}
pub fn supports_vcpu_hotremove(&self) -> bool {
false }
pub fn supports_memory_hotadd(&self) -> bool {
self.version.is_at_least(&Version::new(0, 1, 0))
}
pub fn supports_memory_hotshrink(&self) -> bool {
false }
pub fn supports_device_hotplug(&self) -> bool {
self.version.is_at_least(&Version::new(0, 1, 0))
}
pub fn supports_vfio_hotplug(&self) -> bool {
false }
pub fn supports_seccomp(&self) -> bool {
self.version.is_at_least(&Version::new(20, 0, 0))
}
pub fn supports_landlock(&self) -> bool {
self.version.is_at_least(&Version::new(30, 0, 0))
}
pub fn minimum_recommended() -> Version {
Version::new(28, 0, 0)
}
pub fn meets_minimum_recommended(&self) -> bool {
self.version.is_at_least(&Self::minimum_recommended())
}
pub fn feature_summary(&self) -> String {
format!(
"Cloud Hypervisor v{}\n\
Supported Features:\n\
- vCPU hot-add: {}\n\
- vCPU hot-remove: {} (kernel limitation)\n\
- Memory hot-add: {}\n\
- Memory hot-shrink: {} (not supported)\n\
- Device hot-plug: {}\n\
- VFIO hot-plug: {} (PCI/IOMMU constraints)\n\
- Snapshot/Restore: {}\n\
- Seccomp: {}\n\
- Landlock: {}",
self.version,
self.supports_vcpu_hotadd(),
self.supports_vcpu_hotremove(),
self.supports_memory_hotadd(),
self.supports_memory_hotshrink(),
self.supports_device_hotplug(),
self.supports_vfio_hotplug(),
self.supports_snapshot(),
self.supports_seccomp(),
self.supports_landlock(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_parse() {
let v = Version::parse("28.0.0").unwrap();
assert_eq!(v.major, 28);
assert_eq!(v.minor, 0);
assert_eq!(v.patch, 0);
let v = Version::parse("v30.1.2").unwrap();
assert_eq!(v.major, 30);
assert_eq!(v.minor, 1);
assert_eq!(v.patch, 2);
}
#[test]
fn test_version_parse_invalid() {
assert!(Version::parse("invalid").is_err());
assert!(Version::parse("1.2").is_err());
assert!(Version::parse("1.2.3.4").is_err());
}
#[test]
fn test_version_comparison() {
let v1 = Version::new(28, 0, 0);
let v2 = Version::new(30, 0, 0);
let v3 = Version::new(28, 1, 0);
assert!(v2 > v1);
assert!(v3 > v1);
assert!(v2 > v3);
assert!(v1 < v2);
}
#[test]
fn test_version_is_at_least() {
let v = Version::new(28, 0, 0);
assert!(v.is_at_least(&Version::new(28, 0, 0)));
assert!(v.is_at_least(&Version::new(27, 0, 0)));
assert!(!v.is_at_least(&Version::new(29, 0, 0)));
}
#[test]
fn test_version_display() {
let v = Version::new(28, 0, 0);
assert_eq!(v.to_string(), "28.0.0");
}
#[test]
fn test_capabilities_snapshot_support() {
let caps_old = Capabilities::new(Version::new(27, 0, 0));
assert!(!caps_old.supports_snapshot());
let caps_new = Capabilities::new(Version::new(28, 0, 0));
assert!(caps_new.supports_snapshot());
}
#[test]
fn test_capabilities_unsupported_features() {
let caps = Capabilities::new(Version::new(30, 0, 0));
assert!(!caps.supports_vcpu_hotremove());
assert!(!caps.supports_memory_hotshrink());
assert!(!caps.supports_vfio_hotplug());
}
#[test]
fn test_capabilities_from_version_string() {
let caps = Capabilities::from_version_string("v28.0.0").unwrap();
assert_eq!(caps.version, Version::new(28, 0, 0));
assert!(caps.supports_snapshot());
}
#[test]
fn test_minimum_recommended() {
let caps_old = Capabilities::new(Version::new(27, 0, 0));
assert!(!caps_old.meets_minimum_recommended());
let caps_new = Capabilities::new(Version::new(28, 0, 0));
assert!(caps_new.meets_minimum_recommended());
}
}