From be475ddc227b38a9b96a416f10a26ff38f57bd9b Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 18 Aug 2020 15:19:49 +0200 Subject: [PATCH] main, vmm: Let the user define distincts memory zones Introducing a new CLI option --memory-zone letting the user specify custom memory zones. When this option is present, the --memory size must be explicitly set to 0. Signed-off-by: Sebastien Boeuf --- src/main.rs | 13 +++ vmm/src/api/openapi/cloud-hypervisor.yaml | 25 ++++++ vmm/src/config.rs | 96 ++++++++++++++++++++--- 3 files changed, 123 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index d137a154f..969c7a955 100644 --- a/src/main.rs +++ b/src/main.rs @@ -114,6 +114,18 @@ fn create_app<'a, 'b>( .default_value(&default_memory) .group("vm-config"), ) + .arg( + Arg::with_name("memory-zone") + .long("memory-zone") + .help( + "User defined memory zone parameters \ + \"size=,file=,\ + mergeable=on|off,shared=on|off,hugepages=on|off\"", + ) + .takes_value(true) + .min_values(1) + .group("vm-config"), + ) .arg( Arg::with_name("kernel") .long("kernel") @@ -522,6 +534,7 @@ mod unit_tests { hugepages: false, balloon: false, balloon_size: 0, + zones: None, }, kernel: Some(KernelConfig { path: PathBuf::from("/path/to/kernel"), diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 8b9f26e49..a2d37fc9e 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -448,6 +448,27 @@ components: topology: $ref: '#/components/schemas/CpuTopology' + MemoryZoneConfig: + required: + - size + type: object + properties: + size: + type: integer + format: int64 + default: 512 MB + file: + type: string + mergeable: + type: boolean + default: false + shared: + type: boolean + default: false + hugepages: + type: boolean + default: false + MemoryConfig: required: - size @@ -477,6 +498,10 @@ components: balloon: type: boolean default: false + zones: + type: array + items: + $ref: '#/components/schemas/MemoryZoneConfig' KernelConfig: required: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 3230e4299..666d3db4d 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -42,6 +42,8 @@ pub enum Error { ParseCpus(OptionParserError), /// Error parsing memory options ParseMemory(OptionParserError), + /// Error parsing memory zone options + ParseMemoryZone(OptionParserError), /// Error parsing disk options ParseDisk(OptionParserError), /// Error parsing network options @@ -147,6 +149,7 @@ impl fmt::Display for Error { ParseVsockCidMissing => write!(f, "Error parsing --vsock: cid missing"), ParseVsockSockMissing => write!(f, "Error parsing --vsock: socket missing"), ParseMemory(o) => write!(f, "Error parsing --memory: {}", o), + ParseMemoryZone(o) => write!(f, "Error parsing --memory-zone: {}", o), ParseNetwork(o) => write!(f, "Error parsing --net: {}", o), ParseDisk(o) => write!(f, "Error parsing --disk: {}", o), ParseRNG(o) => write!(f, "Error parsing --rng: {}", o), @@ -166,6 +169,7 @@ pub type Result = result::Result; pub struct VmParams<'a> { pub cpus: &'a str, pub memory: &'a str, + pub memory_zones: Option>, pub kernel: Option<&'a str>, pub initramfs: Option<&'a str>, pub cmdline: Option<&'a str>, @@ -187,6 +191,7 @@ impl<'a> VmParams<'a> { // These .unwrap()s cannot fail as there is a default value defined let cpus = args.value_of("cpus").unwrap(); let memory = args.value_of("memory").unwrap(); + let memory_zones: Option> = args.values_of("memory-zone").map(|x| x.collect()); let rng = args.value_of("rng").unwrap(); let serial = args.value_of("serial").unwrap(); @@ -207,6 +212,7 @@ impl<'a> VmParams<'a> { VmParams { cpus, memory, + memory_zones, kernel, initramfs, cmdline, @@ -337,6 +343,19 @@ impl Default for CpusConfig { } } +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct MemoryZoneConfig { + pub size: u64, + #[serde(default)] + pub file: Option, + #[serde(default)] + pub mergeable: bool, + #[serde(default)] + pub shared: bool, + #[serde(default)] + pub hugepages: bool, +} + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct MemoryConfig { pub size: u64, @@ -356,10 +375,12 @@ pub struct MemoryConfig { pub balloon: bool, #[serde(default)] pub balloon_size: u64, + #[serde(default)] + pub zones: Option>, } impl MemoryConfig { - pub fn parse(memory: &str) -> Result { + pub fn parse(memory: &str, memory_zones: Option>) -> Result { let mut parser = OptionParser::new(); parser .add("size") @@ -407,6 +428,53 @@ impl MemoryConfig { .unwrap_or(Toggle(false)) .0; + let zones: Option> = if let Some(memory_zones) = &memory_zones { + let mut zones = Vec::new(); + for memory_zone in memory_zones.iter() { + let mut parser = OptionParser::new(); + parser + .add("size") + .add("file") + .add("mergeable") + .add("shared") + .add("hugepages"); + parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?; + + let size = parser + .convert::("size") + .map_err(Error::ParseMemoryZone)? + .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20)) + .0; + let file = parser.get("file").map(PathBuf::from); + let mergeable = parser + .convert::("mergeable") + .map_err(Error::ParseMemoryZone)? + .unwrap_or(Toggle(false)) + .0; + let shared = parser + .convert::("shared") + .map_err(Error::ParseMemoryZone)? + .unwrap_or(Toggle(false)) + .0; + let hugepages = parser + .convert::("hugepages") + .map_err(Error::ParseMemoryZone)? + .unwrap_or(Toggle(false)) + .0; + + zones.push(MemoryZoneConfig { + size, + file, + mergeable, + shared, + hugepages, + }); + } + Some(zones) + } else { + None + }; + Ok(MemoryConfig { size, file, @@ -417,6 +485,7 @@ impl MemoryConfig { hugepages, balloon, balloon_size: 0, + zones, }) } } @@ -433,6 +502,7 @@ impl Default for MemoryConfig { hugepages: false, balloon: false, balloon_size: 0, + zones: None, } } } @@ -1388,7 +1458,7 @@ impl VmConfig { let config = VmConfig { cpus: CpusConfig::parse(vm_params.cpus)?, - memory: MemoryConfig::parse(vm_params.memory)?, + memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?, kernel, initramfs, cmdline: CmdlineConfig::parse(vm_params.cmdline)?, @@ -1481,11 +1551,14 @@ mod tests { #[test] fn test_mem_parsing() -> Result<()> { - assert_eq!(MemoryConfig::parse("")?, MemoryConfig::default()); + assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default()); // Default string - assert_eq!(MemoryConfig::parse("size=512M")?, MemoryConfig::default()); assert_eq!( - MemoryConfig::parse("size=512M,file=/some/file")?, + MemoryConfig::parse("size=512M", None)?, + MemoryConfig::default() + ); + assert_eq!( + MemoryConfig::parse("size=512M,file=/some/file", None)?, MemoryConfig { size: 512 << 20, file: Some(PathBuf::from("/some/file")), @@ -1493,7 +1566,7 @@ mod tests { } ); assert_eq!( - MemoryConfig::parse("size=512M,mergeable=on")?, + MemoryConfig::parse("size=512M,mergeable=on", None)?, MemoryConfig { size: 512 << 20, mergeable: true, @@ -1501,14 +1574,14 @@ mod tests { } ); assert_eq!( - MemoryConfig::parse("mergeable=on")?, + MemoryConfig::parse("mergeable=on", None)?, MemoryConfig { mergeable: true, ..Default::default() } ); assert_eq!( - MemoryConfig::parse("size=1G,mergeable=off")?, + MemoryConfig::parse("size=1G,mergeable=off", None)?, MemoryConfig { size: 1 << 30, mergeable: false, @@ -1516,20 +1589,20 @@ mod tests { } ); assert_eq!( - MemoryConfig::parse("hotplug_method=acpi")?, + MemoryConfig::parse("hotplug_method=acpi", None)?, MemoryConfig { ..Default::default() } ); assert_eq!( - MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M")?, + MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?, MemoryConfig { hotplug_size: Some(512 << 20), ..Default::default() } ); assert_eq!( - MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M")?, + MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?, MemoryConfig { hotplug_size: Some(512 << 20), hotplug_method: HotplugMethod::VirtioMem, @@ -1951,6 +2024,7 @@ mod tests { hugepages: false, balloon: false, balloon_size: 0, + zones: None, }, kernel: Some(KernelConfig { path: PathBuf::from("/path/to/kernel"),