diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index 17612a579..303e0f814 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -170,3 +170,47 @@ impl FromStr for ByteSized { })) } } + +pub struct IntegerList(pub Vec); + +pub enum IntegerListParseError { + InvalidValue(String), +} + +impl FromStr for IntegerList { + type Err = IntegerListParseError; + + fn from_str(s: &str) -> std::result::Result { + 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::() + .map_err(|_| IntegerListParseError::InvalidValue(items[0].to_owned()))?; + + integer_list.push(start_range); + + if items.len() == 2 { + let end_range = items[1] + .parse::() + .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)) + } +} diff --git a/src/main.rs b/src/main.rs index aa40ac9e7..19d98033e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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); diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 07b067a07..b8c2b5347 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -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: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index f30f12155..21bcbc5d2 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -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>, + pub numa: Option>, } 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> = args.values_of("sgx-epc").map(|x| x.collect()); + let numa: Option> = 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>, +} + +impl NumaConfig { + pub const SYNTAX: &'static str = "Settings related to a given NUMA node \ + \"id=,cpus=\""; + pub fn parse(numa: &str) -> Result { + let mut parser = OptionParser::new(); + parser.add("id").add("cpus"); + parser.parse(numa).map_err(Error::ParseNuma)?; + + let id = parser + .convert::("id") + .map_err(Error::ParseNuma)? + .unwrap_or(0); + let cpus = parser + .convert::("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>, + pub numa: Option>, } impl VmConfig { @@ -1438,6 +1474,16 @@ impl VmConfig { } } + let mut numa: Option> = 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 = 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());