main, vmm: Add new --numa parameter

Through this new parameter, we give users the opportunity to specify a
set of CPUs attached to a NUMA node that has been previously created
from the --memory-zone parameter.

This parameter will be extended in the future to describe the distance
between multiple nodes.

For instance, if a user wants to attach CPUs 0, 1, 2 and 6 to a NUMA
node, here are two different ways of doing so:
Either
	./cloud-hypervisor ... --numa id=0,cpus=0-2:6
Or
	./cloud-hypervisor ... --numa id=0,cpus=0:1:2:6

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
Sebastien Boeuf 2020-08-28 18:20:12 +02:00
parent 36171caba9
commit 42f963d6f2
4 changed files with 120 additions and 1 deletions

View File

@ -170,3 +170,47 @@ impl FromStr for ByteSized {
}))
}
}
pub struct IntegerList(pub Vec<u64>);
pub enum IntegerListParseError {
InvalidValue(String),
}
impl FromStr for IntegerList {
type Err = IntegerListParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut integer_list = Vec::new();
let ranges_list: Vec<&str> = s.trim().split(':').collect();
for range in ranges_list.iter() {
let items: Vec<&str> = range.split('-').collect();
if items.len() > 2 {
return Err(IntegerListParseError::InvalidValue(range.to_string()));
}
let start_range = items[0]
.parse::<u64>()
.map_err(|_| IntegerListParseError::InvalidValue(items[0].to_owned()))?;
integer_list.push(start_range);
if items.len() == 2 {
let end_range = items[1]
.parse::<u64>()
.map_err(|_| IntegerListParseError::InvalidValue(items[1].to_owned()))?;
if start_range >= end_range {
return Err(IntegerListParseError::InvalidValue(range.to_string()));
}
for i in start_range..end_range {
integer_list.push(i + 1);
}
}
}
Ok(IntegerList(integer_list))
}
}

View File

@ -221,6 +221,14 @@ fn create_app<'a, 'b>(
.number_of_values(1)
.group("vm-config"),
)
.arg(
Arg::with_name("numa")
.long("numa")
.help(config::NumaConfig::SYNTAX)
.takes_value(true)
.min_values(1)
.group("vm-config"),
)
.arg(
Arg::with_name("v")
.short("v")
@ -566,6 +574,7 @@ mod unit_tests {
iommu: false,
#[cfg(target_arch = "x86_64")]
sgx_epc: None,
numa: None,
};
aver_eq!(tb, expected_vm_config, result_vm_config);

View File

@ -413,6 +413,10 @@ components:
type: array
items:
$ref: '#/components/schemas/SgxEpcConfig'
numa:
type: array
items:
$ref: '#/components/schemas/NumaConfig'
iommu:
type: boolean
default: false
@ -717,6 +721,20 @@ components:
type: boolean
default: false
NumaConfig:
required:
- id
type: object
properties:
id:
type: integer
format: uint32
cpus:
type: array
items:
type: integer
format: uint8
VmResize:
type: object
properties:

View File

@ -5,7 +5,7 @@
use clap::ArgMatches;
use net_util::MacAddr;
use option_parser::{ByteSized, OptionParser, OptionParserError, Toggle};
use option_parser::{ByteSized, IntegerList, OptionParser, OptionParserError, Toggle};
use std::convert::From;
use std::fmt;
use std::net::Ipv4Addr;
@ -69,6 +69,8 @@ pub enum Error {
/// Failed to parse SGX EPC parameters
#[cfg(target_arch = "x86_64")]
ParseSgxEpc(OptionParserError),
/// Failed to parse NUMA parameters
ParseNuma(OptionParserError),
/// Failed to validate configuration
Validation(ValidationError),
}
@ -156,6 +158,7 @@ impl fmt::Display for Error {
ParseRestore(o) => write!(f, "Error parsing --restore: {}", o),
#[cfg(target_arch = "x86_64")]
ParseSgxEpc(o) => write!(f, "Error parsing --sgx-epc: {}", o),
ParseNuma(o) => write!(f, "Error parsing --numa: {}", o),
ParseRestoreSourceUrlMissing => {
write!(f, "Error parsing --restore: source_url missing")
}
@ -184,6 +187,7 @@ pub struct VmParams<'a> {
pub vsock: Option<&'a str>,
#[cfg(target_arch = "x86_64")]
pub sgx_epc: Option<Vec<&'a str>>,
pub numa: Option<Vec<&'a str>>,
}
impl<'a> VmParams<'a> {
@ -208,6 +212,7 @@ impl<'a> VmParams<'a> {
let vsock: Option<&str> = args.value_of("vsock");
#[cfg(target_arch = "x86_64")]
let sgx_epc: Option<Vec<&str>> = args.values_of("sgx-epc").map(|x| x.collect());
let numa: Option<Vec<&str>> = args.values_of("numa").map(|x| x.collect());
VmParams {
cpus,
@ -227,6 +232,7 @@ impl<'a> VmParams<'a> {
vsock,
#[cfg(target_arch = "x86_64")]
sgx_epc,
numa,
}
}
}
@ -1204,6 +1210,35 @@ impl SgxEpcConfig {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
pub struct NumaConfig {
#[serde(default)]
pub id: u32,
#[serde(default)]
pub cpus: Option<Vec<u8>>,
}
impl NumaConfig {
pub const SYNTAX: &'static str = "Settings related to a given NUMA node \
\"id=<node_id>,cpus=<cpus_id>\"";
pub fn parse(numa: &str) -> Result<Self> {
let mut parser = OptionParser::new();
parser.add("id").add("cpus");
parser.parse(numa).map_err(Error::ParseNuma)?;
let id = parser
.convert::<u32>("id")
.map_err(Error::ParseNuma)?
.unwrap_or(0);
let cpus = parser
.convert::<IntegerList>("cpus")
.map_err(Error::ParseNuma)?
.map(|v| v.0.iter().map(|e| *e as u8).collect());
Ok(NumaConfig { id, cpus })
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
pub struct RestoreConfig {
pub source_url: PathBuf,
@ -1265,6 +1300,7 @@ pub struct VmConfig {
pub iommu: bool,
#[cfg(target_arch = "x86_64")]
pub sgx_epc: Option<Vec<SgxEpcConfig>>,
pub numa: Option<Vec<NumaConfig>>,
}
impl VmConfig {
@ -1438,6 +1474,16 @@ impl VmConfig {
}
}
let mut numa: Option<Vec<NumaConfig>> = None;
if let Some(numa_list) = &vm_params.numa {
let mut numa_config_list = Vec::new();
for item in numa_list.iter() {
let numa_config = NumaConfig::parse(item)?;
numa_config_list.push(numa_config);
}
numa = Some(numa_config_list);
}
let mut kernel: Option<KernelConfig> = None;
if let Some(k) = vm_params.kernel {
kernel = Some(KernelConfig {
@ -1470,6 +1516,7 @@ impl VmConfig {
iommu,
#[cfg(target_arch = "x86_64")]
sgx_epc,
numa,
};
config.validate().map_err(Error::Validation)?;
Ok(config)
@ -2038,6 +2085,7 @@ mod tests {
iommu: false,
#[cfg(target_arch = "x86_64")]
sgx_epc: None,
numa: None,
};
assert!(valid_config.validate().is_ok());