herolib-virt 0.3.13

Virtualization and container management for herolib (buildah, nerdctl, kubernetes)
Documentation
// Cloud Hypervisor Version Detection and Feature Capabilities
//
// This module provides version detection and feature capability checking
// for Cloud Hypervisor to enable runtime feature gating.

use crate::cloudhv::errors::{CloudHypervisorError, Result};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;

/// Represents a semantic version (major.minor.patch)
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Version {
    pub major: u32,
    pub minor: u32,
    pub patch: u32,
}

impl Version {
    /// Create a new version
    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
        Self {
            major,
            minor,
            patch,
        }
    }

    /// Parse version from string (e.g., "v28.0.0" or "28.0.0")
    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))
    }

    /// Check if this version is at least the specified version
    pub fn is_at_least(&self, other: &Version) -> bool {
        self >= other
    }

    /// Check if this version is compatible with the specified version
    /// (same major version, this version >= other version)
    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,
        }
    }
}

/// Cloud Hypervisor feature capabilities based on version
#[derive(Debug, Clone)]
pub struct Capabilities {
    pub version: Version,
}

impl Capabilities {
    /// Create capabilities from version
    pub fn new(version: Version) -> Self {
        Self { version }
    }

    /// Parse capabilities from version string
    pub fn from_version_string(s: &str) -> Result<Self> {
        let version = Version::parse(s)?;
        Ok(Self::new(version))
    }

    /// Check if snapshot/restore is supported
    /// Snapshot support was added in v28.0.0
    pub fn supports_snapshot(&self) -> bool {
        self.version.is_at_least(&Version::new(28, 0, 0))
    }

    /// Check if vCPU hot-add is supported
    /// vCPU hot-add has been supported for a long time
    pub fn supports_vcpu_hotadd(&self) -> bool {
        self.version.is_at_least(&Version::new(0, 1, 0))
    }

    /// Check if vCPU hot-remove is supported
    /// vCPU hot-remove is NOT supported (Linux kernel limitation)
    pub fn supports_vcpu_hotremove(&self) -> bool {
        false // Never supported due to kernel limitations
    }

    /// Check if memory hot-add is supported
    /// Memory hot-add has been supported for a long time
    pub fn supports_memory_hotadd(&self) -> bool {
        self.version.is_at_least(&Version::new(0, 1, 0))
    }

    /// Check if memory hot-shrink is supported
    /// Memory hot-shrink is NOT supported
    pub fn supports_memory_hotshrink(&self) -> bool {
        false // Not supported by design
    }

    /// Check if device hot-plug is supported
    /// Device hot-plug has been supported for a long time
    pub fn supports_device_hotplug(&self) -> bool {
        self.version.is_at_least(&Version::new(0, 1, 0))
    }

    /// Check if VFIO device hot-plug is supported
    /// VFIO hot-plug is NOT supported (PCI topology constraints)
    pub fn supports_vfio_hotplug(&self) -> bool {
        false // Not supported due to PCI/IOMMU constraints
    }

    /// Check if seccomp is supported
    /// Seccomp support depends on compile-time flags
    pub fn supports_seccomp(&self) -> bool {
        // Seccomp is a compile-time feature, assume available in modern versions
        self.version.is_at_least(&Version::new(20, 0, 0))
    }

    /// Check if landlock is supported
    /// Landlock support was added in later versions
    pub fn supports_landlock(&self) -> bool {
        // Landlock support added around v30.0.0
        self.version.is_at_least(&Version::new(30, 0, 0))
    }

    /// Get minimum recommended version
    pub fn minimum_recommended() -> Version {
        Version::new(28, 0, 0)
    }

    /// Check if version meets minimum recommended
    pub fn meets_minimum_recommended(&self) -> bool {
        self.version.is_at_least(&Self::minimum_recommended())
    }

    /// Get a summary of supported features
    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));
        
        // These are NEVER supported regardless of version
        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());
    }
}