mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm: Add validation for Generic Initiator NUMA
Validate device_id in numa config is mutually exclusive with cpus and memory_zones Add NumaConfig::validate() and modify NumaConfig::parse() Add ValidationError::InvalidNumaConfig for detailed error messages Include unit tests covering valid and invalid configs Signed-off-by: Saravanan D <saravanand@crusoe.ai>
This commit is contained in:
committed by
Rob Bradford
parent
6d4827b5ff
commit
fa43548975
@@ -362,6 +362,9 @@ pub enum ValidationError {
|
|||||||
MaskProvidedWithoutIp,
|
MaskProvidedWithoutIp,
|
||||||
#[error("IP provided without a mask")]
|
#[error("IP provided without a mask")]
|
||||||
IpProvidedWithoutMask,
|
IpProvidedWithoutMask,
|
||||||
|
/// Invalid NUMA Configuration
|
||||||
|
#[error("NUMA Configuration is invalid")]
|
||||||
|
InvalidNumaConfig(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidationResult<T> = std::result::Result<T, ValidationError>;
|
type ValidationResult<T> = std::result::Result<T, ValidationError>;
|
||||||
@@ -2233,6 +2236,11 @@ impl NumaConfig {
|
|||||||
.convert::<IntegerList>("pci_segments")
|
.convert::<IntegerList>("pci_segments")
|
||||||
.map_err(Error::ParseNuma)?
|
.map_err(Error::ParseNuma)?
|
||||||
.map(|v| v.0.iter().map(|e| *e as u16).collect());
|
.map(|v| v.0.iter().map(|e| *e as u16).collect());
|
||||||
|
if device_id.is_some() && (cpus.is_some() || memory_zones.is_some()) {
|
||||||
|
return Err(Error::ParseNuma(OptionParserError::InvalidValue(
|
||||||
|
"device_id in numa config cannot be used with cpus or memory zones".to_string(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
Ok(NumaConfig {
|
Ok(NumaConfig {
|
||||||
guest_numa_id,
|
guest_numa_id,
|
||||||
cpus,
|
cpus,
|
||||||
@@ -2242,6 +2250,58 @@ impl NumaConfig {
|
|||||||
pci_segments,
|
pci_segments,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_generic_initiator(&self) -> bool {
|
||||||
|
self.device_id.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates NumaConfig
|
||||||
|
pub fn validate(&self) -> result::Result<(), ValidationError> {
|
||||||
|
match (&self.device_id, &self.cpus, &self.memory_zones) {
|
||||||
|
(Some(device_id), None, None) => {
|
||||||
|
// Valid generic initiator case
|
||||||
|
if device_id.is_empty() {
|
||||||
|
return Err(ValidationError::InvalidNumaConfig(
|
||||||
|
"device_id in numa config cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(None, Some(cpus), _) => {
|
||||||
|
// Standard NUMA with cpus
|
||||||
|
if cpus.is_empty() {
|
||||||
|
return Err(ValidationError::InvalidNumaConfig(
|
||||||
|
"cpus list in numa config cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(None, _, Some(memory_zones)) => {
|
||||||
|
// Standard NUMA with memory_zones (cpus is None here)
|
||||||
|
if memory_zones.is_empty() {
|
||||||
|
return Err(ValidationError::InvalidNumaConfig(
|
||||||
|
"memory_zones in numa config cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Default handles all error cases
|
||||||
|
if self.device_id.is_some() && (self.cpus.is_some() || self.memory_zones.is_some())
|
||||||
|
{
|
||||||
|
Err(ValidationError::InvalidNumaConfig(
|
||||||
|
"device_id in numa config is mutually exclusive with cpus and memory_zones"
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(ValidationError::InvalidNumaConfig(
|
||||||
|
"numa config must specify either device_id or cpus/memory_zones"
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
|
||||||
@@ -2778,6 +2838,7 @@ impl VmConfig {
|
|||||||
let mut used_numa_node_memory_zones = HashMap::new();
|
let mut used_numa_node_memory_zones = HashMap::new();
|
||||||
let mut used_pci_segments = HashMap::new();
|
let mut used_pci_segments = HashMap::new();
|
||||||
for numa_node in numa.iter() {
|
for numa_node in numa.iter() {
|
||||||
|
numa_node.validate()?;
|
||||||
if let Some(memory_zones) = numa_node.memory_zones.clone() {
|
if let Some(memory_zones) = numa_node.memory_zones.clone() {
|
||||||
for memory_zone in memory_zones.iter() {
|
for memory_zone in memory_zones.iter() {
|
||||||
if used_numa_node_memory_zones.contains_key(memory_zone) {
|
if used_numa_node_memory_zones.contains_key(memory_zone) {
|
||||||
@@ -3921,6 +3982,121 @@ mod unit_tests {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numa_config_parsing() -> Result<()> {
|
||||||
|
// Error when device_id and cpu/memory are present
|
||||||
|
let invalid_input = "guest_numa_id=0,cpus=[0,1],distances=[0@25,1@20],\
|
||||||
|
device_id=vfio0,memory_zones=[mem1],pci_segments=[0]";
|
||||||
|
NumaConfig::parse(invalid_input).unwrap_err();
|
||||||
|
// Successful numa config parsing
|
||||||
|
let standard_input = "guest_numa_id=1,cpus=[2,3],distances=[0@20],\
|
||||||
|
memory_zones=[mem0],pci_segments=[0]";
|
||||||
|
let expected_standard = NumaConfig {
|
||||||
|
guest_numa_id: 1,
|
||||||
|
cpus: Some(vec![2, 3]),
|
||||||
|
distances: Some(vec![NumaDistance {
|
||||||
|
destination: 0,
|
||||||
|
distance: 20,
|
||||||
|
}]),
|
||||||
|
device_id: None,
|
||||||
|
memory_zones: Some(vec!["mem0".to_string()]),
|
||||||
|
pci_segments: Some(vec![0]),
|
||||||
|
};
|
||||||
|
assert_eq!(NumaConfig::parse(standard_input)?, expected_standard);
|
||||||
|
// Successful generic initiator config parse
|
||||||
|
let gi_input = "guest_numa_id=2,device_id=vfio1,distances=[0@30],pci_segments=[1]";
|
||||||
|
let expected_gi = NumaConfig {
|
||||||
|
guest_numa_id: 2,
|
||||||
|
cpus: None,
|
||||||
|
distances: Some(vec![NumaDistance {
|
||||||
|
destination: 0,
|
||||||
|
distance: 30,
|
||||||
|
}]),
|
||||||
|
device_id: Some("vfio1".to_string()),
|
||||||
|
memory_zones: None,
|
||||||
|
pci_segments: Some(vec![1]),
|
||||||
|
};
|
||||||
|
assert_eq!(NumaConfig::parse(gi_input)?, expected_gi);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numa_config_generic_initiator_valid() {
|
||||||
|
// device_id specified, no cpus/memory_zones
|
||||||
|
let config = NumaConfig {
|
||||||
|
guest_numa_id: 0,
|
||||||
|
cpus: None,
|
||||||
|
distances: Some(vec![NumaDistance {
|
||||||
|
destination: 1,
|
||||||
|
distance: 20,
|
||||||
|
}]),
|
||||||
|
memory_zones: None,
|
||||||
|
device_id: Some("vfio0".to_string()),
|
||||||
|
pci_segments: None,
|
||||||
|
};
|
||||||
|
config.validate().unwrap();
|
||||||
|
assert!(config.is_generic_initiator());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numa_config_invalid_device_id() {
|
||||||
|
// empty device_id
|
||||||
|
let config = NumaConfig {
|
||||||
|
guest_numa_id: 0,
|
||||||
|
cpus: None,
|
||||||
|
distances: None,
|
||||||
|
memory_zones: None,
|
||||||
|
device_id: Some(String::new()),
|
||||||
|
pci_segments: None,
|
||||||
|
};
|
||||||
|
assert!(config.validate().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numa_config_invalid_both_device_cpus() {
|
||||||
|
// device_id and cpus specified
|
||||||
|
let config = NumaConfig {
|
||||||
|
guest_numa_id: 0,
|
||||||
|
cpus: Some(vec![0, 1]),
|
||||||
|
distances: None,
|
||||||
|
device_id: Some("vfio0".to_string()),
|
||||||
|
memory_zones: None,
|
||||||
|
pci_segments: None,
|
||||||
|
};
|
||||||
|
assert!(config.validate().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numa_config_invalid_both_device_memory() {
|
||||||
|
// device_id and memory zones specified
|
||||||
|
let config = NumaConfig {
|
||||||
|
guest_numa_id: 0,
|
||||||
|
cpus: None,
|
||||||
|
distances: None,
|
||||||
|
device_id: Some("vfio0".to_string()),
|
||||||
|
memory_zones: Some(vec!["mem0".to_string()]),
|
||||||
|
pci_segments: None,
|
||||||
|
};
|
||||||
|
assert!(config.validate().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numa_config_standard_valid() {
|
||||||
|
// No device_id
|
||||||
|
let config = NumaConfig {
|
||||||
|
guest_numa_id: 0,
|
||||||
|
cpus: Some(vec![0, 1]),
|
||||||
|
distances: Some(vec![NumaDistance {
|
||||||
|
destination: 1,
|
||||||
|
distance: 20,
|
||||||
|
}]),
|
||||||
|
device_id: None,
|
||||||
|
memory_zones: Some(vec!["mem0".to_string()]),
|
||||||
|
pci_segments: None,
|
||||||
|
};
|
||||||
|
config.validate().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_restore_parsing() -> Result<()> {
|
fn test_restore_parsing() -> Result<()> {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -4636,11 +4812,13 @@ mod unit_tests {
|
|||||||
invalid_config.numa = Some(vec![
|
invalid_config.numa = Some(vec![
|
||||||
NumaConfig {
|
NumaConfig {
|
||||||
guest_numa_id: 0,
|
guest_numa_id: 0,
|
||||||
|
cpus: Some(vec![0]),
|
||||||
pci_segments: Some(vec![1]),
|
pci_segments: Some(vec![1]),
|
||||||
..numa_fixture()
|
..numa_fixture()
|
||||||
},
|
},
|
||||||
NumaConfig {
|
NumaConfig {
|
||||||
guest_numa_id: 1,
|
guest_numa_id: 1,
|
||||||
|
cpus: Some(vec![1]),
|
||||||
pci_segments: Some(vec![1]),
|
pci_segments: Some(vec![1]),
|
||||||
..numa_fixture()
|
..numa_fixture()
|
||||||
},
|
},
|
||||||
@@ -4676,10 +4854,12 @@ mod unit_tests {
|
|||||||
invalid_config.numa = Some(vec![
|
invalid_config.numa = Some(vec![
|
||||||
NumaConfig {
|
NumaConfig {
|
||||||
guest_numa_id: 0,
|
guest_numa_id: 0,
|
||||||
|
cpus: Some(vec![0]),
|
||||||
..numa_fixture()
|
..numa_fixture()
|
||||||
},
|
},
|
||||||
NumaConfig {
|
NumaConfig {
|
||||||
guest_numa_id: 1,
|
guest_numa_id: 1,
|
||||||
|
cpus: Some(vec![1]),
|
||||||
pci_segments: Some(vec![0]),
|
pci_segments: Some(vec![0]),
|
||||||
..numa_fixture()
|
..numa_fixture()
|
||||||
},
|
},
|
||||||
@@ -4693,11 +4873,13 @@ mod unit_tests {
|
|||||||
invalid_config.numa = Some(vec![
|
invalid_config.numa = Some(vec![
|
||||||
NumaConfig {
|
NumaConfig {
|
||||||
guest_numa_id: 0,
|
guest_numa_id: 0,
|
||||||
|
cpus: Some(vec![0]),
|
||||||
pci_segments: Some(vec![0]),
|
pci_segments: Some(vec![0]),
|
||||||
..numa_fixture()
|
..numa_fixture()
|
||||||
},
|
},
|
||||||
NumaConfig {
|
NumaConfig {
|
||||||
guest_numa_id: 1,
|
guest_numa_id: 1,
|
||||||
|
cpus: Some(vec![1]),
|
||||||
pci_segments: Some(vec![1]),
|
pci_segments: Some(vec![1]),
|
||||||
..numa_fixture()
|
..numa_fixture()
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user