vmm: config: Extend CpusConfig to add the topology

This allows the user to optionally specify the desired CPU topology. All
parts of the topology must be specified and the product of all parts
must match the maximum vCPUs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2020-06-16 10:56:36 +01:00 committed by Sebastien Boeuf
parent 9099b067e1
commit 4a0439a993
3 changed files with 118 additions and 2 deletions

View File

@ -487,6 +487,7 @@ mod unit_tests {
cpus: CpusConfig {
boot_vcpus: 1,
max_vcpus: 1,
topology: None,
},
memory: MemoryConfig {
size: 536_870_912,

View File

@ -396,6 +396,18 @@ components:
default: false
description: Virtual machine configuration
CpuTopology:
type: object
properties:
threads_per_core:
type: integer
cores_per_die:
type: integer
dies_per_package:
type: integer
packages:
type: integer
CpusConfig:
required:
- boot_vcpus
@ -410,6 +422,8 @@ components:
minimum: 1
default: 1
type: integer
topology:
$ref: '#/components/schemas/CpuTopology'
MemoryConfig:
required:

View File

@ -88,6 +88,10 @@ pub enum ValidationError {
IommuUnsupported,
/// Trying to use VFIO without PCI
VfioUnsupported,
/// CPU topology count doesn't match max
CpuTopologyCount,
/// One part of the CPU topology was zero
CpuTopologyZeroPart,
}
type ValidationResult<T> = std::result::Result<T, ValidationError>;
@ -106,6 +110,11 @@ impl fmt::Display for ValidationError {
}
IommuUnsupported => write!(f, "Using an IOMMU without PCI support is unsupported"),
VfioUnsupported => write!(f, "Using VFIO without PCI support is unsupported"),
CpuTopologyZeroPart => write!(f, "No part of the CPU topology can be zero"),
CpuTopologyCount => write!(
f,
"Product of CPU topology parts does not match maximum vCPUs"
),
}
}
}
@ -397,16 +406,59 @@ impl FromStr for ByteSized {
}
}
pub enum CpuTopologyParseError {
InvalidValue(String),
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct CpuTopology {
pub threads_per_core: u8,
pub cores_per_die: u8,
pub dies_per_package: u8,
pub packages: u8,
}
impl FromStr for CpuTopology {
type Err = CpuTopologyParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 4 {
return Err(Self::Err::InvalidValue(s.to_owned()));
}
let t = CpuTopology {
threads_per_core: parts[0]
.parse()
.map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
cores_per_die: parts[1]
.parse()
.map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
dies_per_package: parts[2]
.parse()
.map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
packages: parts[3]
.parse()
.map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
};
Ok(t)
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct CpusConfig {
pub boot_vcpus: u8,
pub max_vcpus: u8,
#[serde(default)]
pub topology: Option<CpuTopology>,
}
impl CpusConfig {
pub fn parse(cpus: &str) -> Result<Self> {
let mut parser = OptionParser::new();
parser.add("boot").add("max");
parser.add("boot").add("max").add("topology");
parser.parse(cpus).map_err(Error::ParseCpus)?;
let boot_vcpus: u8 = parser
@ -417,10 +469,12 @@ impl CpusConfig {
.convert("max")
.map_err(Error::ParseCpus)?
.unwrap_or(boot_vcpus);
let topology = parser.convert("topology").map_err(Error::ParseCpus)?;
Ok(CpusConfig {
boot_vcpus,
max_vcpus,
topology,
})
}
}
@ -430,6 +484,7 @@ impl Default for CpusConfig {
CpusConfig {
boot_vcpus: DEFAULT_VCPUS,
max_vcpus: DEFAULT_VCPUS,
topology: None,
}
}
}
@ -1297,6 +1352,21 @@ impl VmConfig {
}
}
if let Some(t) = &self.cpus.topology {
if t.threads_per_core == 0
|| t.cores_per_die == 0
|| t.dies_per_package == 0
|| t.packages == 0
{
return Err(ValidationError::CpuTopologyZeroPart);
}
let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages;
if total != self.cpus.max_vcpus {
return Err(ValidationError::CpuTopologyCount);
}
}
Ok(())
}
@ -1456,7 +1526,8 @@ mod tests {
CpusConfig::parse("boot=1")?,
CpusConfig {
boot_vcpus: 1,
max_vcpus: 1
max_vcpus: 1,
topology: None
}
);
assert_eq!(
@ -1464,8 +1535,26 @@ mod tests {
CpusConfig {
boot_vcpus: 1,
max_vcpus: 2,
topology: None
}
);
assert_eq!(
CpusConfig::parse("boot=8,topology=2:2:1:2")?,
CpusConfig {
boot_vcpus: 8,
max_vcpus: 8,
topology: Some(CpuTopology {
threads_per_core: 2,
cores_per_die: 2,
dies_per_package: 1,
packages: 2
})
}
);
assert!(CpusConfig::parse("boot=8,topology=2:2:1").is_err());
assert!(CpusConfig::parse("boot=8,topology=2:2:1:x").is_err());
Ok(())
}
@ -1929,6 +2018,7 @@ mod tests {
cpus: CpusConfig {
boot_vcpus: 1,
max_vcpus: 1,
topology: None,
},
memory: MemoryConfig {
size: 536_870_912,
@ -1990,6 +2080,17 @@ mod tests {
invalid_config.cpus.boot_vcpus = 32;
assert!(invalid_config.validate().is_err());
let mut invalid_config = valid_config.clone();
invalid_config.cpus.max_vcpus = 16;
invalid_config.cpus.boot_vcpus = 16;
invalid_config.cpus.topology = Some(CpuTopology {
threads_per_core: 2,
cores_per_die: 8,
dies_per_package: 1,
packages: 2,
});
assert!(invalid_config.validate().is_err());
let mut invalid_config = valid_config.clone();
invalid_config.disks = Some(vec![DiskConfig {
vhost_socket: Some("/path/to/sock".to_owned()),