1 // Copyright © 2019 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 use clap::ArgMatches; 7 use net_util::MacAddr; 8 use option_parser::{ 9 ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, TupleTwoIntegers, 10 }; 11 use std::collections::HashMap; 12 use std::convert::From; 13 use std::fmt; 14 use std::net::Ipv4Addr; 15 use std::path::PathBuf; 16 use std::result; 17 use std::str::FromStr; 18 19 use virtio_devices::{RateLimiterConfig, TokenBucketConfig}; 20 21 pub const DEFAULT_VCPUS: u8 = 1; 22 pub const DEFAULT_MEMORY_MB: u64 = 512; 23 pub const DEFAULT_RNG_SOURCE: &str = "/dev/urandom"; 24 pub const DEFAULT_NUM_QUEUES_VUNET: usize = 2; 25 pub const DEFAULT_QUEUE_SIZE_VUNET: u16 = 256; 26 pub const DEFAULT_NUM_QUEUES_VUBLK: usize = 1; 27 pub const DEFAULT_QUEUE_SIZE_VUBLK: u16 = 128; 28 29 /// Errors associated with VM configuration parameters. 30 #[derive(Debug)] 31 pub enum Error { 32 /// Filesystem tag is missing 33 ParseFsTagMissing, 34 /// Filesystem socket is missing 35 ParseFsSockMissing, 36 /// Cannot have dax=off along with cache_size parameter. 37 InvalidCacheSizeWithDaxOff, 38 /// Missing persistent memory file parameter. 39 ParsePmemFileMissing, 40 /// Missing vsock socket path parameter. 41 ParseVsockSockMissing, 42 /// Missing vsock cid parameter. 43 ParseVsockCidMissing, 44 /// Missing restore source_url parameter. 45 ParseRestoreSourceUrlMissing, 46 /// Error parsing CPU options 47 ParseCpus(OptionParserError), 48 /// Error parsing memory options 49 ParseMemory(OptionParserError), 50 /// Error parsing memory zone options 51 ParseMemoryZone(OptionParserError), 52 /// Missing 'id' from memory zone 53 ParseMemoryZoneIdMissing, 54 /// Error parsing disk options 55 ParseDisk(OptionParserError), 56 /// Error parsing network options 57 ParseNetwork(OptionParserError), 58 /// Error parsing RNG options 59 ParseRng(OptionParserError), 60 /// Error parsing balloon options 61 ParseBalloon(OptionParserError), 62 /// Error parsing filesystem parameters 63 ParseFileSystem(OptionParserError), 64 /// Error parsing persistent memory parameters 65 ParsePersistentMemory(OptionParserError), 66 /// Failed parsing console 67 ParseConsole(OptionParserError), 68 /// No mode given for console 69 ParseConsoleInvalidModeGiven, 70 /// Failed parsing device parameters 71 ParseDevice(OptionParserError), 72 /// Missing path from device, 73 ParseDevicePathMissing, 74 /// Failed to parse vsock parameters 75 ParseVsock(OptionParserError), 76 /// Failed to parse restore parameters 77 ParseRestore(OptionParserError), 78 /// Failed to parse SGX EPC parameters 79 #[cfg(target_arch = "x86_64")] 80 ParseSgxEpc(OptionParserError), 81 /// Missing 'id' from SGX EPC section 82 #[cfg(target_arch = "x86_64")] 83 ParseSgxEpcIdMissing, 84 /// Failed to parse NUMA parameters 85 ParseNuma(OptionParserError), 86 /// Failed to validate configuration 87 Validation(ValidationError), 88 #[cfg(feature = "tdx")] 89 /// Failed to parse TDX config 90 ParseTdx(OptionParserError), 91 #[cfg(feature = "tdx")] 92 // No TDX firmware 93 FirmwarePathMissing, 94 /// Failed to parse userspace device 95 ParseUserDevice(OptionParserError), 96 /// Missing socket for userspace device 97 ParseUserDeviceSocketMissing, 98 } 99 100 #[derive(Debug)] 101 pub enum ValidationError { 102 /// Both console and serial are tty. 103 DoubleTtyMode, 104 /// No kernel specified 105 KernelMissing, 106 /// Missing file value for console 107 ConsoleFileMissing, 108 /// Max is less than boot 109 CpusMaxLowerThanBoot, 110 /// Both socket and path specified 111 DiskSocketAndPath, 112 /// Using vhost user requires shared memory 113 VhostUserRequiresSharedMemory, 114 /// No socket provided for vhost_use 115 VhostUserMissingSocket, 116 /// Trying to use IOMMU without PCI 117 IommuUnsupported, 118 /// Trying to use VFIO without PCI 119 VfioUnsupported, 120 /// CPU topology count doesn't match max 121 CpuTopologyCount, 122 /// One part of the CPU topology was zero 123 CpuTopologyZeroPart, 124 /// Virtio needs a min of 2 queues 125 VnetQueueLowerThan2, 126 /// The input queue number for virtio_net must match the number of input fds 127 VnetQueueFdMismatch, 128 /// Using reserved fd 129 VnetReservedFd, 130 /// Hugepages not turned on 131 HugePageSizeWithoutHugePages, 132 /// Huge page size is not power of 2 133 InvalidHugePageSize(u64), 134 /// CPU Hotplug not permitted with TDX 135 #[cfg(feature = "tdx")] 136 TdxNoCpuHotplug, 137 /// Specifying kernel not permitted with TDX 138 #[cfg(feature = "tdx")] 139 TdxKernelSpecified, 140 /// Insuffient vCPUs for queues 141 TooManyQueues, 142 /// Need shared memory for vfio-user 143 UserDevicesRequireSharedMemory, 144 /// Memory zone is reused across NUMA nodes 145 MemoryZoneReused(String, u32, u32), 146 } 147 148 type ValidationResult<T> = std::result::Result<T, ValidationError>; 149 150 impl fmt::Display for ValidationError { 151 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 152 use self::ValidationError::*; 153 match self { 154 DoubleTtyMode => write!(f, "Console mode tty specified for both serial and console"), 155 KernelMissing => write!(f, "No kernel specified"), 156 ConsoleFileMissing => write!(f, "Path missing when using file console mode"), 157 CpusMaxLowerThanBoot => write!(f, "Max CPUs greater than boot CPUs"), 158 DiskSocketAndPath => write!(f, "Disk path and vhost socket both provided"), 159 VhostUserRequiresSharedMemory => { 160 write!(f, "Using vhost-user requires using shared memory") 161 } 162 VhostUserMissingSocket => write!(f, "No socket provided when using vhost-user"), 163 IommuUnsupported => write!(f, "Using an IOMMU without PCI support is unsupported"), 164 VfioUnsupported => write!(f, "Using VFIO without PCI support is unsupported"), 165 CpuTopologyZeroPart => write!(f, "No part of the CPU topology can be zero"), 166 CpuTopologyCount => write!( 167 f, 168 "Product of CPU topology parts does not match maximum vCPUs" 169 ), 170 VnetQueueLowerThan2 => write!(f, "Number of queues to virtio_net less than 2"), 171 VnetQueueFdMismatch => write!( 172 f, 173 "Number of queues to virtio_net does not match the number of input FDs" 174 ), 175 VnetReservedFd => write!(f, "Reserved fd number (<= 2)"), 176 HugePageSizeWithoutHugePages => { 177 write!(f, "Huge page size specified but huge pages not enabled") 178 } 179 InvalidHugePageSize(s) => { 180 write!(f, "Huge page size is not power of 2: {}", s) 181 } 182 #[cfg(feature = "tdx")] 183 TdxNoCpuHotplug => { 184 write!(f, "CPU hotplug not possible with TDX") 185 } 186 #[cfg(feature = "tdx")] 187 TdxKernelSpecified => { 188 write!(f, "Direct kernel boot not possible with TDX") 189 } 190 TooManyQueues => { 191 write!(f, "Number of vCPUs is insufficient for number of queues") 192 } 193 UserDevicesRequireSharedMemory => { 194 write!(f, "Using user devices requires using shared memory") 195 } 196 MemoryZoneReused(s, u1, u2) => { 197 write!( 198 f, 199 "Memory zone: {} belongs to multiple NUMA nodes {} and {}", 200 s, u1, u2 201 ) 202 } 203 } 204 } 205 } 206 207 impl fmt::Display for Error { 208 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 209 use self::Error::*; 210 match self { 211 ParseConsole(o) => write!(f, "Error parsing --console: {}", o), 212 ParseConsoleInvalidModeGiven => { 213 write!(f, "Error parsing --console: invalid console mode given") 214 } 215 ParseCpus(o) => write!(f, "Error parsing --cpus: {}", o), 216 217 ParseDevice(o) => write!(f, "Error parsing --device: {}", o), 218 ParseDevicePathMissing => write!(f, "Error parsing --device: path missing"), 219 ParseFileSystem(o) => write!(f, "Error parsing --fs: {}", o), 220 ParseFsSockMissing => write!(f, "Error parsing --fs: socket missing"), 221 ParseFsTagMissing => write!(f, "Error parsing --fs: tag missing"), 222 InvalidCacheSizeWithDaxOff => { 223 write!(f, "Error parsing --fs: cache_size used with dax=on") 224 } 225 ParsePersistentMemory(o) => write!(f, "Error parsing --pmem: {}", o), 226 ParsePmemFileMissing => write!(f, "Error parsing --pmem: file missing"), 227 ParseVsock(o) => write!(f, "Error parsing --vsock: {}", o), 228 ParseVsockCidMissing => write!(f, "Error parsing --vsock: cid missing"), 229 ParseVsockSockMissing => write!(f, "Error parsing --vsock: socket missing"), 230 ParseMemory(o) => write!(f, "Error parsing --memory: {}", o), 231 ParseMemoryZone(o) => write!(f, "Error parsing --memory-zone: {}", o), 232 ParseMemoryZoneIdMissing => write!(f, "Error parsing --memory-zone: id missing"), 233 ParseNetwork(o) => write!(f, "Error parsing --net: {}", o), 234 ParseDisk(o) => write!(f, "Error parsing --disk: {}", o), 235 ParseRng(o) => write!(f, "Error parsing --rng: {}", o), 236 ParseBalloon(o) => write!(f, "Error parsing --balloon: {}", o), 237 ParseRestore(o) => write!(f, "Error parsing --restore: {}", o), 238 #[cfg(target_arch = "x86_64")] 239 ParseSgxEpc(o) => write!(f, "Error parsing --sgx-epc: {}", o), 240 #[cfg(target_arch = "x86_64")] 241 ParseSgxEpcIdMissing => write!(f, "Error parsing --sgx-epc: id missing"), 242 ParseNuma(o) => write!(f, "Error parsing --numa: {}", o), 243 ParseRestoreSourceUrlMissing => { 244 write!(f, "Error parsing --restore: source_url missing") 245 } 246 ParseUserDeviceSocketMissing => { 247 write!(f, "Error parsing --user-device: socket missing") 248 } 249 ParseUserDevice(o) => write!(f, "Error parsing --user-device: {}", o), 250 Validation(v) => write!(f, "Error validating configuration: {}", v), 251 #[cfg(feature = "tdx")] 252 ParseTdx(o) => write!(f, "Error parsing --tdx: {}", o), 253 #[cfg(feature = "tdx")] 254 FirmwarePathMissing => write!(f, "TDX firmware missing"), 255 } 256 } 257 } 258 259 pub type Result<T> = result::Result<T, Error>; 260 261 pub struct VmParams<'a> { 262 pub cpus: &'a str, 263 pub memory: &'a str, 264 pub memory_zones: Option<Vec<&'a str>>, 265 pub kernel: Option<&'a str>, 266 pub initramfs: Option<&'a str>, 267 pub cmdline: Option<&'a str>, 268 pub disks: Option<Vec<&'a str>>, 269 pub net: Option<Vec<&'a str>>, 270 pub rng: &'a str, 271 pub balloon: Option<&'a str>, 272 pub fs: Option<Vec<&'a str>>, 273 pub pmem: Option<Vec<&'a str>>, 274 pub serial: &'a str, 275 pub console: &'a str, 276 pub devices: Option<Vec<&'a str>>, 277 pub user_devices: Option<Vec<&'a str>>, 278 pub vsock: Option<&'a str>, 279 #[cfg(target_arch = "x86_64")] 280 pub sgx_epc: Option<Vec<&'a str>>, 281 pub numa: Option<Vec<&'a str>>, 282 pub watchdog: bool, 283 #[cfg(feature = "tdx")] 284 pub tdx: Option<&'a str>, 285 } 286 287 impl<'a> VmParams<'a> { 288 pub fn from_arg_matches(args: &'a ArgMatches) -> Self { 289 // These .unwrap()s cannot fail as there is a default value defined 290 let cpus = args.value_of("cpus").unwrap(); 291 let memory = args.value_of("memory").unwrap(); 292 let memory_zones: Option<Vec<&str>> = args.values_of("memory-zone").map(|x| x.collect()); 293 let rng = args.value_of("rng").unwrap(); 294 let serial = args.value_of("serial").unwrap(); 295 296 let kernel = args.value_of("kernel"); 297 let initramfs = args.value_of("initramfs"); 298 let cmdline = args.value_of("cmdline"); 299 300 let disks: Option<Vec<&str>> = args.values_of("disk").map(|x| x.collect()); 301 let net: Option<Vec<&str>> = args.values_of("net").map(|x| x.collect()); 302 let console = args.value_of("console").unwrap(); 303 let balloon = args.value_of("balloon"); 304 let fs: Option<Vec<&str>> = args.values_of("fs").map(|x| x.collect()); 305 let pmem: Option<Vec<&str>> = args.values_of("pmem").map(|x| x.collect()); 306 let devices: Option<Vec<&str>> = args.values_of("device").map(|x| x.collect()); 307 let user_devices: Option<Vec<&str>> = args.values_of("user-device").map(|x| x.collect()); 308 let vsock: Option<&str> = args.value_of("vsock"); 309 #[cfg(target_arch = "x86_64")] 310 let sgx_epc: Option<Vec<&str>> = args.values_of("sgx-epc").map(|x| x.collect()); 311 let numa: Option<Vec<&str>> = args.values_of("numa").map(|x| x.collect()); 312 let watchdog = args.is_present("watchdog"); 313 #[cfg(feature = "tdx")] 314 let tdx = args.value_of("tdx"); 315 VmParams { 316 cpus, 317 memory, 318 memory_zones, 319 kernel, 320 initramfs, 321 cmdline, 322 disks, 323 net, 324 rng, 325 balloon, 326 fs, 327 pmem, 328 serial, 329 console, 330 devices, 331 user_devices, 332 vsock, 333 #[cfg(target_arch = "x86_64")] 334 sgx_epc, 335 numa, 336 watchdog, 337 #[cfg(feature = "tdx")] 338 tdx, 339 } 340 } 341 } 342 343 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 344 pub enum HotplugMethod { 345 Acpi, 346 VirtioMem, 347 } 348 349 impl Default for HotplugMethod { 350 fn default() -> Self { 351 HotplugMethod::Acpi 352 } 353 } 354 355 #[derive(Debug)] 356 pub enum ParseHotplugMethodError { 357 InvalidValue(String), 358 } 359 360 impl FromStr for HotplugMethod { 361 type Err = ParseHotplugMethodError; 362 363 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 364 match s.to_lowercase().as_str() { 365 "acpi" => Ok(HotplugMethod::Acpi), 366 "virtio-mem" => Ok(HotplugMethod::VirtioMem), 367 _ => Err(ParseHotplugMethodError::InvalidValue(s.to_owned())), 368 } 369 } 370 } 371 372 pub enum CpuTopologyParseError { 373 InvalidValue(String), 374 } 375 376 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 377 pub struct CpuTopology { 378 pub threads_per_core: u8, 379 pub cores_per_die: u8, 380 pub dies_per_package: u8, 381 pub packages: u8, 382 } 383 384 impl FromStr for CpuTopology { 385 type Err = CpuTopologyParseError; 386 387 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 388 let parts: Vec<&str> = s.split(':').collect(); 389 390 if parts.len() != 4 { 391 return Err(Self::Err::InvalidValue(s.to_owned())); 392 } 393 394 let t = CpuTopology { 395 threads_per_core: parts[0] 396 .parse() 397 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 398 cores_per_die: parts[1] 399 .parse() 400 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 401 dies_per_package: parts[2] 402 .parse() 403 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 404 packages: parts[3] 405 .parse() 406 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 407 }; 408 409 Ok(t) 410 } 411 } 412 413 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 414 pub struct CpusConfig { 415 pub boot_vcpus: u8, 416 pub max_vcpus: u8, 417 #[serde(default)] 418 pub topology: Option<CpuTopology>, 419 #[serde(default)] 420 pub kvm_hyperv: bool, 421 #[serde(default)] 422 pub max_phys_bits: Option<u8>, 423 } 424 425 impl CpusConfig { 426 pub fn parse(cpus: &str) -> Result<Self> { 427 let mut parser = OptionParser::new(); 428 parser 429 .add("boot") 430 .add("max") 431 .add("topology") 432 .add("kvm_hyperv") 433 .add("max_phys_bits"); 434 parser.parse(cpus).map_err(Error::ParseCpus)?; 435 436 let boot_vcpus: u8 = parser 437 .convert("boot") 438 .map_err(Error::ParseCpus)? 439 .unwrap_or(DEFAULT_VCPUS); 440 let max_vcpus: u8 = parser 441 .convert("max") 442 .map_err(Error::ParseCpus)? 443 .unwrap_or(boot_vcpus); 444 let topology = parser.convert("topology").map_err(Error::ParseCpus)?; 445 let kvm_hyperv = parser 446 .convert::<Toggle>("kvm_hyperv") 447 .map_err(Error::ParseCpus)? 448 .unwrap_or(Toggle(false)) 449 .0; 450 let max_phys_bits = parser 451 .convert::<u8>("max_phys_bits") 452 .map_err(Error::ParseCpus)?; 453 454 Ok(CpusConfig { 455 boot_vcpus, 456 max_vcpus, 457 topology, 458 kvm_hyperv, 459 max_phys_bits, 460 }) 461 } 462 } 463 464 impl Default for CpusConfig { 465 fn default() -> Self { 466 CpusConfig { 467 boot_vcpus: DEFAULT_VCPUS, 468 max_vcpus: DEFAULT_VCPUS, 469 topology: None, 470 kvm_hyperv: false, 471 max_phys_bits: None, 472 } 473 } 474 } 475 476 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 477 pub struct MemoryZoneConfig { 478 pub id: String, 479 pub size: u64, 480 #[serde(default)] 481 pub file: Option<PathBuf>, 482 #[serde(default)] 483 pub shared: bool, 484 #[serde(default)] 485 pub hugepages: bool, 486 #[serde(default)] 487 pub hugepage_size: Option<u64>, 488 #[serde(default)] 489 pub host_numa_node: Option<u32>, 490 #[serde(default)] 491 pub hotplug_size: Option<u64>, 492 #[serde(default)] 493 pub hotplugged_size: Option<u64>, 494 } 495 496 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 497 pub struct MemoryConfig { 498 pub size: u64, 499 #[serde(default)] 500 pub mergeable: bool, 501 #[serde(default)] 502 pub hotplug_method: HotplugMethod, 503 #[serde(default)] 504 pub hotplug_size: Option<u64>, 505 #[serde(default)] 506 pub hotplugged_size: Option<u64>, 507 #[serde(default)] 508 pub shared: bool, 509 #[serde(default)] 510 pub hugepages: bool, 511 #[serde(default)] 512 pub hugepage_size: Option<u64>, 513 #[serde(default)] 514 pub zones: Option<Vec<MemoryZoneConfig>>, 515 } 516 517 impl MemoryConfig { 518 pub fn parse(memory: &str, memory_zones: Option<Vec<&str>>) -> Result<Self> { 519 let mut parser = OptionParser::new(); 520 parser 521 .add("size") 522 .add("file") 523 .add("mergeable") 524 .add("hotplug_method") 525 .add("hotplug_size") 526 .add("hotplugged_size") 527 .add("shared") 528 .add("hugepages") 529 .add("hugepage_size"); 530 parser.parse(memory).map_err(Error::ParseMemory)?; 531 532 let size = parser 533 .convert::<ByteSized>("size") 534 .map_err(Error::ParseMemory)? 535 .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20)) 536 .0; 537 let mergeable = parser 538 .convert::<Toggle>("mergeable") 539 .map_err(Error::ParseMemory)? 540 .unwrap_or(Toggle(false)) 541 .0; 542 let hotplug_method = parser 543 .convert("hotplug_method") 544 .map_err(Error::ParseMemory)? 545 .unwrap_or_default(); 546 let hotplug_size = parser 547 .convert::<ByteSized>("hotplug_size") 548 .map_err(Error::ParseMemory)? 549 .map(|v| v.0); 550 let hotplugged_size = parser 551 .convert::<ByteSized>("hotplugged_size") 552 .map_err(Error::ParseMemory)? 553 .map(|v| v.0); 554 let shared = parser 555 .convert::<Toggle>("shared") 556 .map_err(Error::ParseMemory)? 557 .unwrap_or(Toggle(false)) 558 .0; 559 let hugepages = parser 560 .convert::<Toggle>("hugepages") 561 .map_err(Error::ParseMemory)? 562 .unwrap_or(Toggle(false)) 563 .0; 564 let hugepage_size = parser 565 .convert::<ByteSized>("hugepage_size") 566 .map_err(Error::ParseMemory)? 567 .map(|v| v.0); 568 569 let zones: Option<Vec<MemoryZoneConfig>> = if let Some(memory_zones) = &memory_zones { 570 let mut zones = Vec::new(); 571 for memory_zone in memory_zones.iter() { 572 let mut parser = OptionParser::new(); 573 parser 574 .add("id") 575 .add("size") 576 .add("file") 577 .add("shared") 578 .add("hugepages") 579 .add("hugepage_size") 580 .add("host_numa_node") 581 .add("hotplug_size") 582 .add("hotplugged_size"); 583 parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?; 584 585 let id = parser.get("id").ok_or(Error::ParseMemoryZoneIdMissing)?; 586 let size = parser 587 .convert::<ByteSized>("size") 588 .map_err(Error::ParseMemoryZone)? 589 .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20)) 590 .0; 591 let file = parser.get("file").map(PathBuf::from); 592 let shared = parser 593 .convert::<Toggle>("shared") 594 .map_err(Error::ParseMemoryZone)? 595 .unwrap_or(Toggle(false)) 596 .0; 597 let hugepages = parser 598 .convert::<Toggle>("hugepages") 599 .map_err(Error::ParseMemoryZone)? 600 .unwrap_or(Toggle(false)) 601 .0; 602 let hugepage_size = parser 603 .convert::<ByteSized>("hugepage_size") 604 .map_err(Error::ParseMemoryZone)? 605 .map(|v| v.0); 606 607 let host_numa_node = parser 608 .convert::<u32>("host_numa_node") 609 .map_err(Error::ParseMemoryZone)?; 610 let hotplug_size = parser 611 .convert::<ByteSized>("hotplug_size") 612 .map_err(Error::ParseMemoryZone)? 613 .map(|v| v.0); 614 let hotplugged_size = parser 615 .convert::<ByteSized>("hotplugged_size") 616 .map_err(Error::ParseMemoryZone)? 617 .map(|v| v.0); 618 619 zones.push(MemoryZoneConfig { 620 id, 621 size, 622 file, 623 shared, 624 hugepages, 625 hugepage_size, 626 host_numa_node, 627 hotplug_size, 628 hotplugged_size, 629 }); 630 } 631 Some(zones) 632 } else { 633 None 634 }; 635 636 Ok(MemoryConfig { 637 size, 638 mergeable, 639 hotplug_method, 640 hotplug_size, 641 hotplugged_size, 642 shared, 643 hugepages, 644 hugepage_size, 645 zones, 646 }) 647 } 648 649 pub fn total_size(&self) -> u64 { 650 let mut size = self.size; 651 if let Some(hotplugged_size) = self.hotplugged_size { 652 size += hotplugged_size; 653 } 654 655 if let Some(zones) = &self.zones { 656 for zone in zones.iter() { 657 size += zone.size; 658 if let Some(hotplugged_size) = zone.hotplugged_size { 659 size += hotplugged_size; 660 } 661 } 662 } 663 664 size 665 } 666 } 667 668 impl Default for MemoryConfig { 669 fn default() -> Self { 670 MemoryConfig { 671 size: DEFAULT_MEMORY_MB << 20, 672 mergeable: false, 673 hotplug_method: HotplugMethod::Acpi, 674 hotplug_size: None, 675 hotplugged_size: None, 676 shared: false, 677 hugepages: false, 678 hugepage_size: None, 679 zones: None, 680 } 681 } 682 } 683 684 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 685 pub struct KernelConfig { 686 pub path: PathBuf, 687 } 688 689 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 690 pub struct InitramfsConfig { 691 pub path: PathBuf, 692 } 693 694 #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] 695 pub struct CmdlineConfig { 696 pub args: String, 697 } 698 699 impl CmdlineConfig { 700 pub fn parse(cmdline: Option<&str>) -> Result<Self> { 701 let args = cmdline 702 .map(std::string::ToString::to_string) 703 .unwrap_or_else(String::new); 704 705 Ok(CmdlineConfig { args }) 706 } 707 } 708 709 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 710 pub struct DiskConfig { 711 pub path: Option<PathBuf>, 712 #[serde(default)] 713 pub readonly: bool, 714 #[serde(default)] 715 pub direct: bool, 716 #[serde(default)] 717 pub iommu: bool, 718 #[serde(default = "default_diskconfig_num_queues")] 719 pub num_queues: usize, 720 #[serde(default = "default_diskconfig_queue_size")] 721 pub queue_size: u16, 722 #[serde(default)] 723 pub vhost_user: bool, 724 pub vhost_socket: Option<String>, 725 #[serde(default = "default_diskconfig_poll_queue")] 726 pub poll_queue: bool, 727 #[serde(default)] 728 pub rate_limiter_config: Option<RateLimiterConfig>, 729 #[serde(default)] 730 pub id: Option<String>, 731 // For testing use only. Not exposed in API. 732 #[serde(default)] 733 pub disable_io_uring: bool, 734 } 735 736 fn default_diskconfig_num_queues() -> usize { 737 DEFAULT_NUM_QUEUES_VUBLK 738 } 739 740 fn default_diskconfig_queue_size() -> u16 { 741 DEFAULT_QUEUE_SIZE_VUBLK 742 } 743 744 fn default_diskconfig_poll_queue() -> bool { 745 true 746 } 747 748 impl Default for DiskConfig { 749 fn default() -> Self { 750 Self { 751 path: None, 752 readonly: false, 753 direct: false, 754 iommu: false, 755 num_queues: default_diskconfig_num_queues(), 756 queue_size: default_diskconfig_queue_size(), 757 vhost_user: false, 758 vhost_socket: None, 759 poll_queue: default_diskconfig_poll_queue(), 760 id: None, 761 disable_io_uring: false, 762 rate_limiter_config: None, 763 } 764 } 765 } 766 767 impl DiskConfig { 768 pub const SYNTAX: &'static str = "Disk parameters \ 769 \"path=<disk_image_path>,readonly=on|off,direct=on|off,iommu=on|off,\ 770 num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,\ 771 vhost_user=on|off,socket=<vhost_user_socket_path>,poll_queue=on|off,\ 772 bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\ 773 ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,\ 774 id=<device_id>\""; 775 776 pub fn parse(disk: &str) -> Result<Self> { 777 let mut parser = OptionParser::new(); 778 parser 779 .add("path") 780 .add("readonly") 781 .add("direct") 782 .add("iommu") 783 .add("queue_size") 784 .add("num_queues") 785 .add("vhost_user") 786 .add("socket") 787 .add("poll_queue") 788 .add("bw_size") 789 .add("bw_one_time_burst") 790 .add("bw_refill_time") 791 .add("ops_size") 792 .add("ops_one_time_burst") 793 .add("ops_refill_time") 794 .add("id") 795 .add("_disable_io_uring"); 796 parser.parse(disk).map_err(Error::ParseDisk)?; 797 798 let path = parser.get("path").map(PathBuf::from); 799 let readonly = parser 800 .convert::<Toggle>("readonly") 801 .map_err(Error::ParseDisk)? 802 .unwrap_or(Toggle(false)) 803 .0; 804 let direct = parser 805 .convert::<Toggle>("direct") 806 .map_err(Error::ParseDisk)? 807 .unwrap_or(Toggle(false)) 808 .0; 809 let iommu = parser 810 .convert::<Toggle>("iommu") 811 .map_err(Error::ParseDisk)? 812 .unwrap_or(Toggle(false)) 813 .0; 814 let queue_size = parser 815 .convert("queue_size") 816 .map_err(Error::ParseDisk)? 817 .unwrap_or_else(default_diskconfig_queue_size); 818 let num_queues = parser 819 .convert("num_queues") 820 .map_err(Error::ParseDisk)? 821 .unwrap_or_else(default_diskconfig_num_queues); 822 let vhost_user = parser 823 .convert::<Toggle>("vhost_user") 824 .map_err(Error::ParseDisk)? 825 .unwrap_or(Toggle(false)) 826 .0; 827 let vhost_socket = parser.get("socket"); 828 let poll_queue = parser 829 .convert::<Toggle>("poll_queue") 830 .map_err(Error::ParseDisk)? 831 .unwrap_or_else(|| Toggle(default_diskconfig_poll_queue())) 832 .0; 833 let id = parser.get("id"); 834 let disable_io_uring = parser 835 .convert::<Toggle>("_disable_io_uring") 836 .map_err(Error::ParseDisk)? 837 .unwrap_or(Toggle(false)) 838 .0; 839 let bw_size = parser 840 .convert("bw_size") 841 .map_err(Error::ParseDisk)? 842 .unwrap_or_default(); 843 let bw_one_time_burst = parser 844 .convert("bw_one_time_burst") 845 .map_err(Error::ParseDisk)? 846 .unwrap_or_default(); 847 let bw_refill_time = parser 848 .convert("bw_refill_time") 849 .map_err(Error::ParseDisk)? 850 .unwrap_or_default(); 851 let ops_size = parser 852 .convert("ops_size") 853 .map_err(Error::ParseDisk)? 854 .unwrap_or_default(); 855 let ops_one_time_burst = parser 856 .convert("ops_one_time_burst") 857 .map_err(Error::ParseDisk)? 858 .unwrap_or_default(); 859 let ops_refill_time = parser 860 .convert("ops_refill_time") 861 .map_err(Error::ParseDisk)? 862 .unwrap_or_default(); 863 let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { 864 Some(TokenBucketConfig { 865 size: bw_size, 866 one_time_burst: Some(bw_one_time_burst), 867 refill_time: bw_refill_time, 868 }) 869 } else { 870 None 871 }; 872 let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 { 873 Some(TokenBucketConfig { 874 size: ops_size, 875 one_time_burst: Some(ops_one_time_burst), 876 refill_time: ops_refill_time, 877 }) 878 } else { 879 None 880 }; 881 let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() { 882 Some(RateLimiterConfig { 883 bandwidth: bw_tb_config, 884 ops: ops_tb_config, 885 }) 886 } else { 887 None 888 }; 889 890 if parser.is_set("poll_queue") && !vhost_user { 891 warn!("poll_queue parameter currently only has effect when used vhost_user=true"); 892 } 893 894 Ok(DiskConfig { 895 path, 896 readonly, 897 direct, 898 iommu, 899 num_queues, 900 queue_size, 901 vhost_user, 902 vhost_socket, 903 poll_queue, 904 rate_limiter_config, 905 id, 906 disable_io_uring, 907 }) 908 } 909 910 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 911 if self.num_queues > vm_config.cpus.boot_vcpus as usize { 912 return Err(ValidationError::TooManyQueues); 913 } 914 915 Ok(()) 916 } 917 } 918 919 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 920 pub enum VhostMode { 921 Client, 922 Server, 923 } 924 925 impl Default for VhostMode { 926 fn default() -> Self { 927 VhostMode::Client 928 } 929 } 930 931 #[derive(Debug)] 932 pub enum ParseVhostModeError { 933 InvalidValue(String), 934 } 935 936 impl FromStr for VhostMode { 937 type Err = ParseVhostModeError; 938 939 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 940 match s.to_lowercase().as_str() { 941 "client" => Ok(VhostMode::Client), 942 "server" => Ok(VhostMode::Server), 943 _ => Err(ParseVhostModeError::InvalidValue(s.to_owned())), 944 } 945 } 946 } 947 948 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 949 pub struct NetConfig { 950 #[serde(default = "default_netconfig_tap")] 951 pub tap: Option<String>, 952 #[serde(default = "default_netconfig_ip")] 953 pub ip: Ipv4Addr, 954 #[serde(default = "default_netconfig_mask")] 955 pub mask: Ipv4Addr, 956 #[serde(default = "default_netconfig_mac")] 957 pub mac: MacAddr, 958 #[serde(default)] 959 pub host_mac: Option<MacAddr>, 960 #[serde(default)] 961 pub iommu: bool, 962 #[serde(default = "default_netconfig_num_queues")] 963 pub num_queues: usize, 964 #[serde(default = "default_netconfig_queue_size")] 965 pub queue_size: u16, 966 #[serde(default)] 967 pub vhost_user: bool, 968 pub vhost_socket: Option<String>, 969 #[serde(default)] 970 pub vhost_mode: VhostMode, 971 #[serde(default)] 972 pub id: Option<String>, 973 #[serde(default)] 974 pub fds: Option<Vec<i32>>, 975 #[serde(default)] 976 pub rate_limiter_config: Option<RateLimiterConfig>, 977 } 978 979 fn default_netconfig_tap() -> Option<String> { 980 None 981 } 982 983 fn default_netconfig_ip() -> Ipv4Addr { 984 Ipv4Addr::new(192, 168, 249, 1) 985 } 986 987 fn default_netconfig_mask() -> Ipv4Addr { 988 Ipv4Addr::new(255, 255, 255, 0) 989 } 990 991 fn default_netconfig_mac() -> MacAddr { 992 MacAddr::local_random() 993 } 994 995 fn default_netconfig_num_queues() -> usize { 996 DEFAULT_NUM_QUEUES_VUNET 997 } 998 999 fn default_netconfig_queue_size() -> u16 { 1000 DEFAULT_QUEUE_SIZE_VUNET 1001 } 1002 1003 impl Default for NetConfig { 1004 fn default() -> Self { 1005 Self { 1006 tap: default_netconfig_tap(), 1007 ip: default_netconfig_ip(), 1008 mask: default_netconfig_mask(), 1009 mac: default_netconfig_mac(), 1010 host_mac: None, 1011 iommu: false, 1012 num_queues: default_netconfig_num_queues(), 1013 queue_size: default_netconfig_queue_size(), 1014 vhost_user: false, 1015 vhost_socket: None, 1016 vhost_mode: VhostMode::Client, 1017 id: None, 1018 fds: None, 1019 rate_limiter_config: None, 1020 } 1021 } 1022 } 1023 1024 impl NetConfig { 1025 pub const SYNTAX: &'static str = "Network parameters \ 1026 \"tap=<if_name>,ip=<ip_addr>,mask=<net_mask>,mac=<mac_addr>,fd=<fd1:fd2...>,iommu=on|off,\ 1027 num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,id=<device_id>,\ 1028 vhost_user=<vhost_user_enable>,socket=<vhost_user_socket_path>,vhost_mode=client|server,\ 1029 bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\ 1030 ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>\""; 1031 1032 pub fn parse(net: &str) -> Result<Self> { 1033 let mut parser = OptionParser::new(); 1034 1035 parser 1036 .add("tap") 1037 .add("ip") 1038 .add("mask") 1039 .add("mac") 1040 .add("host_mac") 1041 .add("iommu") 1042 .add("queue_size") 1043 .add("num_queues") 1044 .add("vhost_user") 1045 .add("socket") 1046 .add("vhost_mode") 1047 .add("id") 1048 .add("fd") 1049 .add("bw_size") 1050 .add("bw_one_time_burst") 1051 .add("bw_refill_time") 1052 .add("ops_size") 1053 .add("ops_one_time_burst") 1054 .add("ops_refill_time"); 1055 parser.parse(net).map_err(Error::ParseNetwork)?; 1056 1057 let tap = parser.get("tap"); 1058 let ip = parser 1059 .convert("ip") 1060 .map_err(Error::ParseNetwork)? 1061 .unwrap_or_else(default_netconfig_ip); 1062 let mask = parser 1063 .convert("mask") 1064 .map_err(Error::ParseNetwork)? 1065 .unwrap_or_else(default_netconfig_mask); 1066 let mac = parser 1067 .convert("mac") 1068 .map_err(Error::ParseNetwork)? 1069 .unwrap_or_else(default_netconfig_mac); 1070 let host_mac = parser.convert("host_mac").map_err(Error::ParseNetwork)?; 1071 let iommu = parser 1072 .convert::<Toggle>("iommu") 1073 .map_err(Error::ParseNetwork)? 1074 .unwrap_or(Toggle(false)) 1075 .0; 1076 let queue_size = parser 1077 .convert("queue_size") 1078 .map_err(Error::ParseNetwork)? 1079 .unwrap_or_else(default_netconfig_queue_size); 1080 let num_queues = parser 1081 .convert("num_queues") 1082 .map_err(Error::ParseNetwork)? 1083 .unwrap_or_else(default_netconfig_num_queues); 1084 let vhost_user = parser 1085 .convert::<Toggle>("vhost_user") 1086 .map_err(Error::ParseNetwork)? 1087 .unwrap_or(Toggle(false)) 1088 .0; 1089 let vhost_socket = parser.get("socket"); 1090 let vhost_mode = parser 1091 .convert("vhost_mode") 1092 .map_err(Error::ParseNetwork)? 1093 .unwrap_or_default(); 1094 let id = parser.get("id"); 1095 let fds = parser 1096 .convert::<IntegerList>("fd") 1097 .map_err(Error::ParseNetwork)? 1098 .map(|v| v.0.iter().map(|e| *e as i32).collect()); 1099 1100 let bw_size = parser 1101 .convert("bw_size") 1102 .map_err(Error::ParseDisk)? 1103 .unwrap_or_default(); 1104 let bw_one_time_burst = parser 1105 .convert("bw_one_time_burst") 1106 .map_err(Error::ParseDisk)? 1107 .unwrap_or_default(); 1108 let bw_refill_time = parser 1109 .convert("bw_refill_time") 1110 .map_err(Error::ParseDisk)? 1111 .unwrap_or_default(); 1112 let ops_size = parser 1113 .convert("ops_size") 1114 .map_err(Error::ParseDisk)? 1115 .unwrap_or_default(); 1116 let ops_one_time_burst = parser 1117 .convert("ops_one_time_burst") 1118 .map_err(Error::ParseDisk)? 1119 .unwrap_or_default(); 1120 let ops_refill_time = parser 1121 .convert("ops_refill_time") 1122 .map_err(Error::ParseDisk)? 1123 .unwrap_or_default(); 1124 let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { 1125 Some(TokenBucketConfig { 1126 size: bw_size, 1127 one_time_burst: Some(bw_one_time_burst), 1128 refill_time: bw_refill_time, 1129 }) 1130 } else { 1131 None 1132 }; 1133 let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 { 1134 Some(TokenBucketConfig { 1135 size: ops_size, 1136 one_time_burst: Some(ops_one_time_burst), 1137 refill_time: ops_refill_time, 1138 }) 1139 } else { 1140 None 1141 }; 1142 let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() { 1143 Some(RateLimiterConfig { 1144 bandwidth: bw_tb_config, 1145 ops: ops_tb_config, 1146 }) 1147 } else { 1148 None 1149 }; 1150 1151 let config = NetConfig { 1152 tap, 1153 ip, 1154 mask, 1155 mac, 1156 host_mac, 1157 iommu, 1158 num_queues, 1159 queue_size, 1160 vhost_user, 1161 vhost_socket, 1162 vhost_mode, 1163 id, 1164 fds, 1165 rate_limiter_config, 1166 }; 1167 Ok(config) 1168 } 1169 1170 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1171 if self.num_queues < 2 { 1172 return Err(ValidationError::VnetQueueLowerThan2); 1173 } 1174 1175 if self.fds.is_some() && self.fds.as_ref().unwrap().len() * 2 != self.num_queues { 1176 return Err(ValidationError::VnetQueueFdMismatch); 1177 } 1178 1179 if let Some(fds) = self.fds.as_ref() { 1180 for fd in fds { 1181 if *fd <= 2 { 1182 return Err(ValidationError::VnetReservedFd); 1183 } 1184 } 1185 } 1186 1187 if (self.num_queues / 2) > vm_config.cpus.boot_vcpus as usize { 1188 return Err(ValidationError::TooManyQueues); 1189 } 1190 1191 Ok(()) 1192 } 1193 } 1194 1195 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 1196 pub struct RngConfig { 1197 pub src: PathBuf, 1198 #[serde(default)] 1199 pub iommu: bool, 1200 } 1201 1202 impl RngConfig { 1203 pub fn parse(rng: &str) -> Result<Self> { 1204 let mut parser = OptionParser::new(); 1205 parser.add("src").add("iommu"); 1206 parser.parse(rng).map_err(Error::ParseRng)?; 1207 1208 let src = PathBuf::from( 1209 parser 1210 .get("src") 1211 .unwrap_or_else(|| DEFAULT_RNG_SOURCE.to_owned()), 1212 ); 1213 let iommu = parser 1214 .convert::<Toggle>("iommu") 1215 .map_err(Error::ParseRng)? 1216 .unwrap_or(Toggle(false)) 1217 .0; 1218 1219 Ok(RngConfig { src, iommu }) 1220 } 1221 } 1222 1223 impl Default for RngConfig { 1224 fn default() -> Self { 1225 RngConfig { 1226 src: PathBuf::from(DEFAULT_RNG_SOURCE), 1227 iommu: false, 1228 } 1229 } 1230 } 1231 1232 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 1233 pub struct BalloonConfig { 1234 pub size: u64, 1235 /// Option to deflate the balloon in case the guest is out of memory. 1236 #[serde(default)] 1237 pub deflate_on_oom: bool, 1238 } 1239 1240 impl BalloonConfig { 1241 pub const SYNTAX: &'static str = 1242 "Balloon parameters \"size=<balloon_size>,deflate_on_oom=on|off\""; 1243 1244 pub fn parse(balloon: &str) -> Result<Self> { 1245 let mut parser = OptionParser::new(); 1246 parser.add("size"); 1247 parser.add("deflate_on_oom"); 1248 parser.parse(balloon).map_err(Error::ParseBalloon)?; 1249 1250 let size = parser 1251 .convert::<ByteSized>("size") 1252 .map_err(Error::ParseBalloon)? 1253 .map(|v| v.0) 1254 .unwrap_or(0); 1255 1256 let deflate_on_oom = parser 1257 .convert::<Toggle>("deflate_on_oom") 1258 .map_err(Error::ParseBalloon)? 1259 .unwrap_or(Toggle(false)) 1260 .0; 1261 1262 Ok(BalloonConfig { 1263 size, 1264 deflate_on_oom, 1265 }) 1266 } 1267 } 1268 1269 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 1270 pub struct FsConfig { 1271 pub tag: String, 1272 pub socket: PathBuf, 1273 #[serde(default = "default_fsconfig_num_queues")] 1274 pub num_queues: usize, 1275 #[serde(default = "default_fsconfig_queue_size")] 1276 pub queue_size: u16, 1277 #[serde(default = "default_fsconfig_dax")] 1278 pub dax: bool, 1279 #[serde(default = "default_fsconfig_cache_size")] 1280 pub cache_size: u64, 1281 #[serde(default)] 1282 pub id: Option<String>, 1283 } 1284 1285 fn default_fsconfig_num_queues() -> usize { 1286 1 1287 } 1288 1289 fn default_fsconfig_queue_size() -> u16 { 1290 1024 1291 } 1292 1293 fn default_fsconfig_dax() -> bool { 1294 true 1295 } 1296 1297 fn default_fsconfig_cache_size() -> u64 { 1298 0x0002_0000_0000 1299 } 1300 1301 impl Default for FsConfig { 1302 fn default() -> Self { 1303 Self { 1304 tag: "".to_owned(), 1305 socket: PathBuf::new(), 1306 num_queues: default_fsconfig_num_queues(), 1307 queue_size: default_fsconfig_queue_size(), 1308 dax: default_fsconfig_dax(), 1309 cache_size: default_fsconfig_cache_size(), 1310 id: None, 1311 } 1312 } 1313 } 1314 1315 impl FsConfig { 1316 pub const SYNTAX: &'static str = "virtio-fs parameters \ 1317 \"tag=<tag_name>,socket=<socket_path>,num_queues=<number_of_queues>,\ 1318 queue_size=<size_of_each_queue>,dax=on|off,cache_size=<DAX cache size: \ 1319 default 8Gib>,id=<device_id>\""; 1320 1321 pub fn parse(fs: &str) -> Result<Self> { 1322 let mut parser = OptionParser::new(); 1323 parser 1324 .add("tag") 1325 .add("dax") 1326 .add("cache_size") 1327 .add("queue_size") 1328 .add("num_queues") 1329 .add("socket") 1330 .add("id"); 1331 parser.parse(fs).map_err(Error::ParseFileSystem)?; 1332 1333 let tag = parser.get("tag").ok_or(Error::ParseFsTagMissing)?; 1334 let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseFsSockMissing)?); 1335 1336 let queue_size = parser 1337 .convert("queue_size") 1338 .map_err(Error::ParseFileSystem)? 1339 .unwrap_or_else(default_fsconfig_queue_size); 1340 let num_queues = parser 1341 .convert("num_queues") 1342 .map_err(Error::ParseFileSystem)? 1343 .unwrap_or_else(default_fsconfig_num_queues); 1344 1345 let dax = parser 1346 .convert::<Toggle>("dax") 1347 .map_err(Error::ParseFileSystem)? 1348 .unwrap_or_else(|| Toggle(default_fsconfig_dax())) 1349 .0; 1350 1351 if parser.is_set("cache_size") && !dax { 1352 return Err(Error::InvalidCacheSizeWithDaxOff); 1353 } 1354 1355 let cache_size = parser 1356 .convert::<ByteSized>("cache_size") 1357 .map_err(Error::ParseFileSystem)? 1358 .unwrap_or_else(|| ByteSized(default_fsconfig_cache_size())) 1359 .0; 1360 1361 let id = parser.get("id"); 1362 1363 Ok(FsConfig { 1364 tag, 1365 socket, 1366 num_queues, 1367 queue_size, 1368 dax, 1369 cache_size, 1370 id, 1371 }) 1372 } 1373 1374 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1375 if self.num_queues > vm_config.cpus.boot_vcpus as usize { 1376 return Err(ValidationError::TooManyQueues); 1377 } 1378 1379 Ok(()) 1380 } 1381 } 1382 1383 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1384 pub struct PmemConfig { 1385 pub file: PathBuf, 1386 #[serde(default)] 1387 pub size: Option<u64>, 1388 #[serde(default)] 1389 pub iommu: bool, 1390 #[serde(default)] 1391 pub mergeable: bool, 1392 #[serde(default)] 1393 pub discard_writes: bool, 1394 #[serde(default)] 1395 pub id: Option<String>, 1396 } 1397 1398 impl PmemConfig { 1399 pub const SYNTAX: &'static str = "Persistent memory parameters \ 1400 \"file=<backing_file_path>,size=<persistent_memory_size>,iommu=on|off,\ 1401 mergeable=on|off,discard_writes=on|off,id=<device_id>\""; 1402 pub fn parse(pmem: &str) -> Result<Self> { 1403 let mut parser = OptionParser::new(); 1404 parser 1405 .add("size") 1406 .add("file") 1407 .add("mergeable") 1408 .add("iommu") 1409 .add("discard_writes") 1410 .add("id"); 1411 parser.parse(pmem).map_err(Error::ParsePersistentMemory)?; 1412 1413 let file = PathBuf::from(parser.get("file").ok_or(Error::ParsePmemFileMissing)?); 1414 let size = parser 1415 .convert::<ByteSized>("size") 1416 .map_err(Error::ParsePersistentMemory)? 1417 .map(|v| v.0); 1418 let mergeable = parser 1419 .convert::<Toggle>("mergeable") 1420 .map_err(Error::ParsePersistentMemory)? 1421 .unwrap_or(Toggle(false)) 1422 .0; 1423 let iommu = parser 1424 .convert::<Toggle>("iommu") 1425 .map_err(Error::ParsePersistentMemory)? 1426 .unwrap_or(Toggle(false)) 1427 .0; 1428 let discard_writes = parser 1429 .convert::<Toggle>("discard_writes") 1430 .map_err(Error::ParsePersistentMemory)? 1431 .unwrap_or(Toggle(false)) 1432 .0; 1433 let id = parser.get("id"); 1434 1435 Ok(PmemConfig { 1436 file, 1437 size, 1438 iommu, 1439 mergeable, 1440 discard_writes, 1441 id, 1442 }) 1443 } 1444 } 1445 1446 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 1447 pub enum ConsoleOutputMode { 1448 Off, 1449 Pty, 1450 Tty, 1451 File, 1452 Null, 1453 } 1454 1455 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 1456 pub struct ConsoleConfig { 1457 #[serde(default = "default_consoleconfig_file")] 1458 pub file: Option<PathBuf>, 1459 pub mode: ConsoleOutputMode, 1460 #[serde(default)] 1461 pub iommu: bool, 1462 } 1463 1464 fn default_consoleconfig_file() -> Option<PathBuf> { 1465 None 1466 } 1467 1468 impl ConsoleConfig { 1469 pub fn parse(console: &str) -> Result<Self> { 1470 let mut parser = OptionParser::new(); 1471 parser 1472 .add_valueless("off") 1473 .add_valueless("pty") 1474 .add_valueless("tty") 1475 .add_valueless("null") 1476 .add("file") 1477 .add("iommu"); 1478 parser.parse(console).map_err(Error::ParseConsole)?; 1479 1480 let mut file: Option<PathBuf> = default_consoleconfig_file(); 1481 let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off; 1482 1483 if parser.is_set("off") { 1484 } else if parser.is_set("pty") { 1485 mode = ConsoleOutputMode::Pty 1486 } else if parser.is_set("tty") { 1487 mode = ConsoleOutputMode::Tty 1488 } else if parser.is_set("null") { 1489 mode = ConsoleOutputMode::Null 1490 } else if parser.is_set("file") { 1491 mode = ConsoleOutputMode::File; 1492 file = 1493 Some(PathBuf::from(parser.get("file").ok_or( 1494 Error::Validation(ValidationError::ConsoleFileMissing), 1495 )?)); 1496 } else { 1497 return Err(Error::ParseConsoleInvalidModeGiven); 1498 } 1499 let iommu = parser 1500 .convert::<Toggle>("iommu") 1501 .map_err(Error::ParseConsole)? 1502 .unwrap_or(Toggle(false)) 1503 .0; 1504 1505 Ok(Self { file, mode, iommu }) 1506 } 1507 1508 pub fn default_serial() -> Self { 1509 ConsoleConfig { 1510 file: None, 1511 mode: ConsoleOutputMode::Null, 1512 iommu: false, 1513 } 1514 } 1515 1516 pub fn default_console() -> Self { 1517 ConsoleConfig { 1518 file: None, 1519 mode: ConsoleOutputMode::Tty, 1520 iommu: false, 1521 } 1522 } 1523 } 1524 1525 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1526 pub struct DeviceConfig { 1527 pub path: PathBuf, 1528 #[serde(default)] 1529 pub iommu: bool, 1530 #[serde(default)] 1531 pub id: Option<String>, 1532 } 1533 1534 impl DeviceConfig { 1535 pub const SYNTAX: &'static str = 1536 "Direct device assignment parameters \"path=<device_path>,iommu=on|off,id=<device_id>\""; 1537 pub fn parse(device: &str) -> Result<Self> { 1538 let mut parser = OptionParser::new(); 1539 parser.add("path").add("id").add("iommu"); 1540 parser.parse(device).map_err(Error::ParseDevice)?; 1541 1542 let path = parser 1543 .get("path") 1544 .map(PathBuf::from) 1545 .ok_or(Error::ParseDevicePathMissing)?; 1546 let iommu = parser 1547 .convert::<Toggle>("iommu") 1548 .map_err(Error::ParseDevice)? 1549 .unwrap_or(Toggle(false)) 1550 .0; 1551 let id = parser.get("id"); 1552 Ok(DeviceConfig { path, iommu, id }) 1553 } 1554 } 1555 1556 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1557 pub struct UserDeviceConfig { 1558 pub socket: PathBuf, 1559 #[serde(default)] 1560 pub id: Option<String>, 1561 } 1562 1563 impl UserDeviceConfig { 1564 pub const SYNTAX: &'static str = "Userspace device socket=<socket_path>,id=<device_id>\""; 1565 pub fn parse(user_device: &str) -> Result<Self> { 1566 let mut parser = OptionParser::new(); 1567 parser.add("socket").add("id"); 1568 parser.parse(user_device).map_err(Error::ParseUserDevice)?; 1569 1570 let socket = parser 1571 .get("socket") 1572 .map(PathBuf::from) 1573 .ok_or(Error::ParseUserDeviceSocketMissing)?; 1574 1575 let id = parser.get("id"); 1576 1577 Ok(UserDeviceConfig { socket, id }) 1578 } 1579 } 1580 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1581 pub struct VsockConfig { 1582 pub cid: u64, 1583 pub socket: PathBuf, 1584 #[serde(default)] 1585 pub iommu: bool, 1586 #[serde(default)] 1587 pub id: Option<String>, 1588 } 1589 1590 impl VsockConfig { 1591 pub const SYNTAX: &'static str = "Virtio VSOCK parameters \ 1592 \"cid=<context_id>,socket=<socket_path>,iommu=on|off,id=<device_id>\""; 1593 pub fn parse(vsock: &str) -> Result<Self> { 1594 let mut parser = OptionParser::new(); 1595 parser.add("socket").add("cid").add("iommu").add("id"); 1596 parser.parse(vsock).map_err(Error::ParseVsock)?; 1597 1598 let socket = parser 1599 .get("socket") 1600 .map(PathBuf::from) 1601 .ok_or(Error::ParseVsockSockMissing)?; 1602 let iommu = parser 1603 .convert::<Toggle>("iommu") 1604 .map_err(Error::ParseVsock)? 1605 .unwrap_or(Toggle(false)) 1606 .0; 1607 let cid = parser 1608 .convert("cid") 1609 .map_err(Error::ParseVsock)? 1610 .ok_or(Error::ParseVsockCidMissing)?; 1611 let id = parser.get("id"); 1612 1613 Ok(VsockConfig { 1614 cid, 1615 socket, 1616 iommu, 1617 id, 1618 }) 1619 } 1620 } 1621 1622 #[cfg(feature = "tdx")] 1623 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1624 pub struct TdxConfig { 1625 pub firmware: PathBuf, 1626 } 1627 1628 #[cfg(feature = "tdx")] 1629 impl TdxConfig { 1630 pub fn parse(tdx: &str) -> Result<Self> { 1631 let mut parser = OptionParser::new(); 1632 parser.add("firmware"); 1633 parser.parse(tdx).map_err(Error::ParseTdx)?; 1634 let firmware = parser 1635 .get("firmware") 1636 .map(PathBuf::from) 1637 .ok_or(Error::FirmwarePathMissing)?; 1638 Ok(TdxConfig { firmware }) 1639 } 1640 } 1641 1642 #[cfg(target_arch = "x86_64")] 1643 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1644 pub struct SgxEpcConfig { 1645 pub id: String, 1646 #[serde(default)] 1647 pub size: u64, 1648 #[serde(default)] 1649 pub prefault: bool, 1650 } 1651 1652 #[cfg(target_arch = "x86_64")] 1653 impl SgxEpcConfig { 1654 pub const SYNTAX: &'static str = "SGX EPC parameters \ 1655 \"id=<epc_section_identifier>,size=<epc_section_size>,prefault=on|off\""; 1656 pub fn parse(sgx_epc: &str) -> Result<Self> { 1657 let mut parser = OptionParser::new(); 1658 parser.add("id").add("size").add("prefault"); 1659 parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?; 1660 1661 let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?; 1662 let size = parser 1663 .convert::<ByteSized>("size") 1664 .map_err(Error::ParseSgxEpc)? 1665 .unwrap_or(ByteSized(0)) 1666 .0; 1667 let prefault = parser 1668 .convert::<Toggle>("prefault") 1669 .map_err(Error::ParseSgxEpc)? 1670 .unwrap_or(Toggle(false)) 1671 .0; 1672 1673 Ok(SgxEpcConfig { id, size, prefault }) 1674 } 1675 } 1676 1677 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1678 pub struct NumaDistance { 1679 #[serde(default)] 1680 pub destination: u32, 1681 #[serde(default)] 1682 pub distance: u8, 1683 } 1684 1685 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1686 pub struct NumaConfig { 1687 #[serde(default)] 1688 pub guest_numa_id: u32, 1689 #[serde(default)] 1690 pub cpus: Option<Vec<u8>>, 1691 #[serde(default)] 1692 pub distances: Option<Vec<NumaDistance>>, 1693 #[serde(default)] 1694 pub memory_zones: Option<Vec<String>>, 1695 #[cfg(target_arch = "x86_64")] 1696 #[serde(default)] 1697 pub sgx_epc_sections: Option<Vec<String>>, 1698 } 1699 1700 impl NumaConfig { 1701 pub const SYNTAX: &'static str = "Settings related to a given NUMA node \ 1702 \"guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,\ 1703 memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>\""; 1704 pub fn parse(numa: &str) -> Result<Self> { 1705 let mut parser = OptionParser::new(); 1706 parser 1707 .add("guest_numa_id") 1708 .add("cpus") 1709 .add("distances") 1710 .add("memory_zones") 1711 .add("sgx_epc_sections"); 1712 parser.parse(numa).map_err(Error::ParseNuma)?; 1713 1714 let guest_numa_id = parser 1715 .convert::<u32>("guest_numa_id") 1716 .map_err(Error::ParseNuma)? 1717 .unwrap_or(0); 1718 let cpus = parser 1719 .convert::<IntegerList>("cpus") 1720 .map_err(Error::ParseNuma)? 1721 .map(|v| v.0.iter().map(|e| *e as u8).collect()); 1722 let distances = parser 1723 .convert::<TupleTwoIntegers>("distances") 1724 .map_err(Error::ParseNuma)? 1725 .map(|v| { 1726 v.0.iter() 1727 .map(|(e1, e2)| NumaDistance { 1728 destination: *e1 as u32, 1729 distance: *e2 as u8, 1730 }) 1731 .collect() 1732 }); 1733 let memory_zones = parser 1734 .convert::<StringList>("memory_zones") 1735 .map_err(Error::ParseNuma)? 1736 .map(|v| v.0); 1737 #[cfg(target_arch = "x86_64")] 1738 let sgx_epc_sections = parser 1739 .convert::<StringList>("sgx_epc_sections") 1740 .map_err(Error::ParseNuma)? 1741 .map(|v| v.0); 1742 1743 Ok(NumaConfig { 1744 guest_numa_id, 1745 cpus, 1746 distances, 1747 memory_zones, 1748 #[cfg(target_arch = "x86_64")] 1749 sgx_epc_sections, 1750 }) 1751 } 1752 } 1753 1754 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] 1755 pub struct RestoreConfig { 1756 pub source_url: PathBuf, 1757 #[serde(default)] 1758 pub prefault: bool, 1759 } 1760 1761 impl RestoreConfig { 1762 pub const SYNTAX: &'static str = "Restore from a VM snapshot. \ 1763 \nRestore parameters \"source_url=<source_url>,prefault=on|off\" \ 1764 \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \ 1765 \n`prefault` brings memory pages in when enabled (disabled by default)"; 1766 pub fn parse(restore: &str) -> Result<Self> { 1767 let mut parser = OptionParser::new(); 1768 parser.add("source_url").add("prefault"); 1769 parser.parse(restore).map_err(Error::ParseRestore)?; 1770 1771 let source_url = parser 1772 .get("source_url") 1773 .map(PathBuf::from) 1774 .ok_or(Error::ParseRestoreSourceUrlMissing)?; 1775 let prefault = parser 1776 .convert::<Toggle>("prefault") 1777 .map_err(Error::ParseRestore)? 1778 .unwrap_or(Toggle(false)) 1779 .0; 1780 1781 Ok(RestoreConfig { 1782 source_url, 1783 prefault, 1784 }) 1785 } 1786 } 1787 1788 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 1789 pub struct VmConfig { 1790 #[serde(default)] 1791 pub cpus: CpusConfig, 1792 #[serde(default)] 1793 pub memory: MemoryConfig, 1794 pub kernel: Option<KernelConfig>, 1795 #[serde(default)] 1796 pub initramfs: Option<InitramfsConfig>, 1797 #[serde(default)] 1798 pub cmdline: CmdlineConfig, 1799 pub disks: Option<Vec<DiskConfig>>, 1800 pub net: Option<Vec<NetConfig>>, 1801 #[serde(default)] 1802 pub rng: RngConfig, 1803 pub balloon: Option<BalloonConfig>, 1804 pub fs: Option<Vec<FsConfig>>, 1805 pub pmem: Option<Vec<PmemConfig>>, 1806 #[serde(default = "ConsoleConfig::default_serial")] 1807 pub serial: ConsoleConfig, 1808 #[serde(default = "ConsoleConfig::default_console")] 1809 pub console: ConsoleConfig, 1810 pub devices: Option<Vec<DeviceConfig>>, 1811 pub user_devices: Option<Vec<UserDeviceConfig>>, 1812 pub vsock: Option<VsockConfig>, 1813 #[serde(default)] 1814 pub iommu: bool, 1815 #[cfg(target_arch = "x86_64")] 1816 pub sgx_epc: Option<Vec<SgxEpcConfig>>, 1817 pub numa: Option<Vec<NumaConfig>>, 1818 #[serde(default)] 1819 pub watchdog: bool, 1820 #[cfg(feature = "tdx")] 1821 pub tdx: Option<TdxConfig>, 1822 } 1823 1824 impl VmConfig { 1825 pub fn validate(&self) -> ValidationResult<()> { 1826 #[cfg(not(feature = "tdx"))] 1827 self.kernel.as_ref().ok_or(ValidationError::KernelMissing)?; 1828 1829 #[cfg(feature = "tdx")] 1830 { 1831 let tdx_enabled = self.tdx.is_some(); 1832 if !tdx_enabled && self.kernel.is_none() { 1833 return Err(ValidationError::KernelMissing); 1834 } 1835 if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) { 1836 return Err(ValidationError::TdxNoCpuHotplug); 1837 } 1838 if tdx_enabled && self.kernel.is_some() { 1839 return Err(ValidationError::TdxKernelSpecified); 1840 } 1841 } 1842 1843 if self.console.mode == ConsoleOutputMode::Tty && self.serial.mode == ConsoleOutputMode::Tty 1844 { 1845 return Err(ValidationError::DoubleTtyMode); 1846 } 1847 1848 if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() { 1849 return Err(ValidationError::ConsoleFileMissing); 1850 } 1851 1852 if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() { 1853 return Err(ValidationError::ConsoleFileMissing); 1854 } 1855 1856 if self.cpus.max_vcpus < self.cpus.boot_vcpus { 1857 return Err(ValidationError::CpusMaxLowerThanBoot); 1858 } 1859 1860 if let Some(disks) = &self.disks { 1861 for disk in disks { 1862 if disk.vhost_socket.as_ref().and(disk.path.as_ref()).is_some() { 1863 return Err(ValidationError::DiskSocketAndPath); 1864 } 1865 if disk.vhost_user && !self.memory.shared { 1866 return Err(ValidationError::VhostUserRequiresSharedMemory); 1867 } 1868 if disk.vhost_user && disk.vhost_socket.is_none() { 1869 return Err(ValidationError::VhostUserMissingSocket); 1870 } 1871 disk.validate(self)?; 1872 } 1873 } 1874 1875 if let Some(nets) = &self.net { 1876 for net in nets { 1877 if net.vhost_user && !self.memory.shared { 1878 return Err(ValidationError::VhostUserRequiresSharedMemory); 1879 } 1880 net.validate(self)?; 1881 } 1882 } 1883 1884 if let Some(fses) = &self.fs { 1885 if !fses.is_empty() && !self.memory.shared { 1886 return Err(ValidationError::VhostUserRequiresSharedMemory); 1887 } 1888 for fs in fses { 1889 fs.validate(self)?; 1890 } 1891 } 1892 1893 if let Some(t) = &self.cpus.topology { 1894 if t.threads_per_core == 0 1895 || t.cores_per_die == 0 1896 || t.dies_per_package == 0 1897 || t.packages == 0 1898 { 1899 return Err(ValidationError::CpuTopologyZeroPart); 1900 } 1901 1902 let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages; 1903 if total != self.cpus.max_vcpus { 1904 return Err(ValidationError::CpuTopologyCount); 1905 } 1906 } 1907 1908 if let Some(hugepage_size) = &self.memory.hugepage_size { 1909 if !self.memory.hugepages { 1910 return Err(ValidationError::HugePageSizeWithoutHugePages); 1911 } 1912 if !hugepage_size.is_power_of_two() { 1913 return Err(ValidationError::InvalidHugePageSize(*hugepage_size)); 1914 } 1915 } 1916 1917 if let Some(user_devices) = &self.user_devices { 1918 if !user_devices.is_empty() && !self.memory.shared { 1919 return Err(ValidationError::UserDevicesRequireSharedMemory); 1920 } 1921 } 1922 1923 if let Some(numa) = &self.numa { 1924 let mut used_numa_node_memory_zones = HashMap::new(); 1925 for numa_node in numa.iter() { 1926 for memory_zone in numa_node.memory_zones.clone().unwrap().iter() { 1927 if !used_numa_node_memory_zones.contains_key(memory_zone) { 1928 used_numa_node_memory_zones 1929 .insert(memory_zone.to_string(), numa_node.guest_numa_id); 1930 } else { 1931 return Err(ValidationError::MemoryZoneReused( 1932 memory_zone.to_string(), 1933 *used_numa_node_memory_zones 1934 .get(&memory_zone.to_string()) 1935 .unwrap(), 1936 numa_node.guest_numa_id, 1937 )); 1938 } 1939 } 1940 } 1941 } 1942 1943 Ok(()) 1944 } 1945 1946 pub fn parse(vm_params: VmParams) -> Result<Self> { 1947 let mut iommu = false; 1948 1949 let mut disks: Option<Vec<DiskConfig>> = None; 1950 if let Some(disk_list) = &vm_params.disks { 1951 let mut disk_config_list = Vec::new(); 1952 for item in disk_list.iter() { 1953 let disk_config = DiskConfig::parse(item)?; 1954 if disk_config.iommu { 1955 iommu = true; 1956 } 1957 disk_config_list.push(disk_config); 1958 } 1959 disks = Some(disk_config_list); 1960 } 1961 1962 let mut net: Option<Vec<NetConfig>> = None; 1963 if let Some(net_list) = &vm_params.net { 1964 let mut net_config_list = Vec::new(); 1965 for item in net_list.iter() { 1966 let net_config = NetConfig::parse(item)?; 1967 if net_config.iommu { 1968 iommu = true; 1969 } 1970 net_config_list.push(net_config); 1971 } 1972 net = Some(net_config_list); 1973 } 1974 1975 let rng = RngConfig::parse(vm_params.rng)?; 1976 if rng.iommu { 1977 iommu = true; 1978 } 1979 1980 let mut balloon: Option<BalloonConfig> = None; 1981 if let Some(balloon_params) = &vm_params.balloon { 1982 balloon = Some(BalloonConfig::parse(balloon_params)?); 1983 } 1984 1985 let mut fs: Option<Vec<FsConfig>> = None; 1986 if let Some(fs_list) = &vm_params.fs { 1987 let mut fs_config_list = Vec::new(); 1988 for item in fs_list.iter() { 1989 fs_config_list.push(FsConfig::parse(item)?); 1990 } 1991 fs = Some(fs_config_list); 1992 } 1993 1994 let mut pmem: Option<Vec<PmemConfig>> = None; 1995 if let Some(pmem_list) = &vm_params.pmem { 1996 let mut pmem_config_list = Vec::new(); 1997 for item in pmem_list.iter() { 1998 let pmem_config = PmemConfig::parse(item)?; 1999 if pmem_config.iommu { 2000 iommu = true; 2001 } 2002 pmem_config_list.push(pmem_config); 2003 } 2004 pmem = Some(pmem_config_list); 2005 } 2006 2007 let console = ConsoleConfig::parse(vm_params.console)?; 2008 if console.iommu { 2009 iommu = true; 2010 } 2011 let serial = ConsoleConfig::parse(vm_params.serial)?; 2012 2013 let mut devices: Option<Vec<DeviceConfig>> = None; 2014 if let Some(device_list) = &vm_params.devices { 2015 let mut device_config_list = Vec::new(); 2016 for item in device_list.iter() { 2017 let device_config = DeviceConfig::parse(item)?; 2018 if device_config.iommu { 2019 iommu = true; 2020 } 2021 device_config_list.push(device_config); 2022 } 2023 devices = Some(device_config_list); 2024 } 2025 2026 let mut user_devices: Option<Vec<UserDeviceConfig>> = None; 2027 if let Some(user_device_list) = &vm_params.user_devices { 2028 let mut user_device_config_list = Vec::new(); 2029 for item in user_device_list.iter() { 2030 let user_device_config = UserDeviceConfig::parse(item)?; 2031 user_device_config_list.push(user_device_config); 2032 } 2033 user_devices = Some(user_device_config_list); 2034 } 2035 2036 let mut vsock: Option<VsockConfig> = None; 2037 if let Some(vs) = &vm_params.vsock { 2038 let vsock_config = VsockConfig::parse(vs)?; 2039 if vsock_config.iommu { 2040 iommu = true; 2041 } 2042 vsock = Some(vsock_config); 2043 } 2044 2045 #[cfg(target_arch = "x86_64")] 2046 let mut sgx_epc: Option<Vec<SgxEpcConfig>> = None; 2047 #[cfg(target_arch = "x86_64")] 2048 { 2049 if let Some(sgx_epc_list) = &vm_params.sgx_epc { 2050 let mut sgx_epc_config_list = Vec::new(); 2051 for item in sgx_epc_list.iter() { 2052 let sgx_epc_config = SgxEpcConfig::parse(item)?; 2053 sgx_epc_config_list.push(sgx_epc_config); 2054 } 2055 sgx_epc = Some(sgx_epc_config_list); 2056 } 2057 } 2058 2059 let mut numa: Option<Vec<NumaConfig>> = None; 2060 if let Some(numa_list) = &vm_params.numa { 2061 let mut numa_config_list = Vec::new(); 2062 for item in numa_list.iter() { 2063 let numa_config = NumaConfig::parse(item)?; 2064 numa_config_list.push(numa_config); 2065 } 2066 numa = Some(numa_config_list); 2067 } 2068 2069 let mut kernel: Option<KernelConfig> = None; 2070 if let Some(k) = vm_params.kernel { 2071 kernel = Some(KernelConfig { 2072 path: PathBuf::from(k), 2073 }); 2074 } 2075 2076 let mut initramfs: Option<InitramfsConfig> = None; 2077 if let Some(k) = vm_params.initramfs { 2078 initramfs = Some(InitramfsConfig { 2079 path: PathBuf::from(k), 2080 }); 2081 } 2082 2083 #[cfg(feature = "tdx")] 2084 let tdx = vm_params.tdx.map(TdxConfig::parse).transpose()?; 2085 2086 let config = VmConfig { 2087 cpus: CpusConfig::parse(vm_params.cpus)?, 2088 memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?, 2089 kernel, 2090 initramfs, 2091 cmdline: CmdlineConfig::parse(vm_params.cmdline)?, 2092 disks, 2093 net, 2094 rng, 2095 balloon, 2096 fs, 2097 pmem, 2098 serial, 2099 console, 2100 devices, 2101 user_devices, 2102 vsock, 2103 iommu, 2104 #[cfg(target_arch = "x86_64")] 2105 sgx_epc, 2106 numa, 2107 watchdog: vm_params.watchdog, 2108 #[cfg(feature = "tdx")] 2109 tdx, 2110 }; 2111 config.validate().map_err(Error::Validation)?; 2112 Ok(config) 2113 } 2114 } 2115 2116 #[cfg(test)] 2117 mod tests { 2118 use super::*; 2119 2120 #[test] 2121 fn test_option_parser() { 2122 let mut parser = OptionParser::new(); 2123 parser 2124 .add("size") 2125 .add("mergeable") 2126 .add("hotplug_method") 2127 .add("hotplug_size"); 2128 2129 assert!(parser.parse("size=128M,hanging_param").is_err()); 2130 assert!(parser.parse("size=128M,too_many_equals=foo=bar").is_err()); 2131 assert!(parser.parse("size=128M,file=/dev/shm").is_err()); 2132 assert!(parser.parse("size=128M").is_ok()); 2133 2134 assert_eq!(parser.get("size"), Some("128M".to_owned())); 2135 assert!(!parser.is_set("mergeable")); 2136 assert!(parser.is_set("size")); 2137 } 2138 2139 #[test] 2140 fn test_cpu_parsing() -> Result<()> { 2141 assert_eq!(CpusConfig::parse("")?, CpusConfig::default()); 2142 2143 assert_eq!( 2144 CpusConfig::parse("boot=1")?, 2145 CpusConfig { 2146 boot_vcpus: 1, 2147 max_vcpus: 1, 2148 ..Default::default() 2149 } 2150 ); 2151 assert_eq!( 2152 CpusConfig::parse("boot=1,max=2")?, 2153 CpusConfig { 2154 boot_vcpus: 1, 2155 max_vcpus: 2, 2156 ..Default::default() 2157 } 2158 ); 2159 assert_eq!( 2160 CpusConfig::parse("boot=8,topology=2:2:1:2")?, 2161 CpusConfig { 2162 boot_vcpus: 8, 2163 max_vcpus: 8, 2164 topology: Some(CpuTopology { 2165 threads_per_core: 2, 2166 cores_per_die: 2, 2167 dies_per_package: 1, 2168 packages: 2 2169 }), 2170 ..Default::default() 2171 } 2172 ); 2173 2174 assert!(CpusConfig::parse("boot=8,topology=2:2:1").is_err()); 2175 assert!(CpusConfig::parse("boot=8,topology=2:2:1:x").is_err()); 2176 assert_eq!( 2177 CpusConfig::parse("boot=1,kvm_hyperv=on")?, 2178 CpusConfig { 2179 boot_vcpus: 1, 2180 max_vcpus: 1, 2181 kvm_hyperv: true, 2182 ..Default::default() 2183 } 2184 ); 2185 Ok(()) 2186 } 2187 2188 #[test] 2189 fn test_mem_parsing() -> Result<()> { 2190 assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default()); 2191 // Default string 2192 assert_eq!( 2193 MemoryConfig::parse("size=512M", None)?, 2194 MemoryConfig::default() 2195 ); 2196 assert_eq!( 2197 MemoryConfig::parse("size=512M,mergeable=on", None)?, 2198 MemoryConfig { 2199 size: 512 << 20, 2200 mergeable: true, 2201 ..Default::default() 2202 } 2203 ); 2204 assert_eq!( 2205 MemoryConfig::parse("mergeable=on", None)?, 2206 MemoryConfig { 2207 mergeable: true, 2208 ..Default::default() 2209 } 2210 ); 2211 assert_eq!( 2212 MemoryConfig::parse("size=1G,mergeable=off", None)?, 2213 MemoryConfig { 2214 size: 1 << 30, 2215 mergeable: false, 2216 ..Default::default() 2217 } 2218 ); 2219 assert_eq!( 2220 MemoryConfig::parse("hotplug_method=acpi", None)?, 2221 MemoryConfig { 2222 ..Default::default() 2223 } 2224 ); 2225 assert_eq!( 2226 MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?, 2227 MemoryConfig { 2228 hotplug_size: Some(512 << 20), 2229 ..Default::default() 2230 } 2231 ); 2232 assert_eq!( 2233 MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?, 2234 MemoryConfig { 2235 hotplug_size: Some(512 << 20), 2236 hotplug_method: HotplugMethod::VirtioMem, 2237 ..Default::default() 2238 } 2239 ); 2240 assert_eq!( 2241 MemoryConfig::parse("hugepages=on,size=1G,hugepage_size=2M", None)?, 2242 MemoryConfig { 2243 hugepage_size: Some(2 << 20), 2244 size: 1 << 30, 2245 hugepages: true, 2246 ..Default::default() 2247 } 2248 ); 2249 Ok(()) 2250 } 2251 2252 #[test] 2253 fn test_disk_parsing() -> Result<()> { 2254 assert_eq!( 2255 DiskConfig::parse("path=/path/to_file")?, 2256 DiskConfig { 2257 path: Some(PathBuf::from("/path/to_file")), 2258 ..Default::default() 2259 } 2260 ); 2261 assert_eq!( 2262 DiskConfig::parse("path=/path/to_file,id=mydisk0")?, 2263 DiskConfig { 2264 path: Some(PathBuf::from("/path/to_file")), 2265 id: Some("mydisk0".to_owned()), 2266 ..Default::default() 2267 } 2268 ); 2269 assert_eq!( 2270 DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?, 2271 DiskConfig { 2272 vhost_socket: Some(String::from("/tmp/sock")), 2273 vhost_user: true, 2274 ..Default::default() 2275 } 2276 ); 2277 assert_eq!( 2278 DiskConfig::parse("path=/path/to_file,iommu=on")?, 2279 DiskConfig { 2280 path: Some(PathBuf::from("/path/to_file")), 2281 iommu: true, 2282 ..Default::default() 2283 } 2284 ); 2285 assert_eq!( 2286 DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?, 2287 DiskConfig { 2288 path: Some(PathBuf::from("/path/to_file")), 2289 iommu: true, 2290 queue_size: 256, 2291 ..Default::default() 2292 } 2293 ); 2294 assert_eq!( 2295 DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?, 2296 DiskConfig { 2297 path: Some(PathBuf::from("/path/to_file")), 2298 iommu: true, 2299 queue_size: 256, 2300 num_queues: 4, 2301 ..Default::default() 2302 } 2303 ); 2304 assert_eq!( 2305 DiskConfig::parse("path=/path/to_file,direct=on")?, 2306 DiskConfig { 2307 path: Some(PathBuf::from("/path/to_file")), 2308 direct: true, 2309 ..Default::default() 2310 } 2311 ); 2312 assert_eq!( 2313 DiskConfig::parse("path=/path/to_file")?, 2314 DiskConfig { 2315 path: Some(PathBuf::from("/path/to_file")), 2316 ..Default::default() 2317 } 2318 ); 2319 assert_eq!( 2320 DiskConfig::parse("path=/path/to_file")?, 2321 DiskConfig { 2322 path: Some(PathBuf::from("/path/to_file")), 2323 ..Default::default() 2324 } 2325 ); 2326 assert_eq!( 2327 DiskConfig::parse("path=/path/to_file,poll_queue=false")?, 2328 DiskConfig { 2329 path: Some(PathBuf::from("/path/to_file")), 2330 poll_queue: false, 2331 ..Default::default() 2332 } 2333 ); 2334 assert_eq!( 2335 DiskConfig::parse("path=/path/to_file,poll_queue=true")?, 2336 DiskConfig { 2337 path: Some(PathBuf::from("/path/to_file")), 2338 poll_queue: true, 2339 ..Default::default() 2340 } 2341 ); 2342 2343 Ok(()) 2344 } 2345 2346 #[test] 2347 fn test_net_parsing() -> Result<()> { 2348 // mac address is random 2349 assert_eq!( 2350 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef")?, 2351 NetConfig { 2352 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 2353 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), 2354 ..Default::default() 2355 } 2356 ); 2357 2358 assert_eq!( 2359 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?, 2360 NetConfig { 2361 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 2362 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), 2363 id: Some("mynet0".to_owned()), 2364 ..Default::default() 2365 } 2366 ); 2367 2368 assert_eq!( 2369 NetConfig::parse( 2370 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,tap=tap0,ip=192.168.100.1,mask=255.255.255.128" 2371 )?, 2372 NetConfig { 2373 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 2374 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), 2375 tap: Some("tap0".to_owned()), 2376 ip: "192.168.100.1".parse().unwrap(), 2377 mask: "255.255.255.128".parse().unwrap(), 2378 ..Default::default() 2379 } 2380 ); 2381 2382 assert_eq!( 2383 NetConfig::parse( 2384 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,vhost_user=true,socket=/tmp/sock" 2385 )?, 2386 NetConfig { 2387 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 2388 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), 2389 vhost_user: true, 2390 vhost_socket: Some("/tmp/sock".to_owned()), 2391 ..Default::default() 2392 } 2393 ); 2394 2395 assert_eq!( 2396 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,num_queues=4,queue_size=1024,iommu=on")?, 2397 NetConfig { 2398 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 2399 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), 2400 num_queues: 4, 2401 queue_size: 1024, 2402 iommu: true, 2403 ..Default::default() 2404 } 2405 ); 2406 2407 assert_eq!( 2408 NetConfig::parse("mac=de:ad:be:ef:12:34,fd=3:7,num_queues=4")?, 2409 NetConfig { 2410 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 2411 fds: Some(vec![3, 7]), 2412 num_queues: 4, 2413 ..Default::default() 2414 } 2415 ); 2416 2417 Ok(()) 2418 } 2419 2420 #[test] 2421 fn test_parse_rng() -> Result<()> { 2422 assert_eq!(RngConfig::parse("")?, RngConfig::default()); 2423 assert_eq!( 2424 RngConfig::parse("src=/dev/random")?, 2425 RngConfig { 2426 src: PathBuf::from("/dev/random"), 2427 ..Default::default() 2428 } 2429 ); 2430 assert_eq!( 2431 RngConfig::parse("src=/dev/random,iommu=on")?, 2432 RngConfig { 2433 src: PathBuf::from("/dev/random"), 2434 iommu: true, 2435 } 2436 ); 2437 assert_eq!( 2438 RngConfig::parse("iommu=on")?, 2439 RngConfig { 2440 iommu: true, 2441 ..Default::default() 2442 } 2443 ); 2444 Ok(()) 2445 } 2446 2447 #[test] 2448 fn test_parse_fs() -> Result<()> { 2449 // "tag" and "socket" must be supplied 2450 assert!(FsConfig::parse("").is_err()); 2451 assert!(FsConfig::parse("tag=mytag").is_err()); 2452 assert!(FsConfig::parse("socket=/tmp/sock").is_err()); 2453 assert_eq!( 2454 FsConfig::parse("tag=mytag,socket=/tmp/sock")?, 2455 FsConfig { 2456 socket: PathBuf::from("/tmp/sock"), 2457 tag: "mytag".to_owned(), 2458 ..Default::default() 2459 } 2460 ); 2461 assert_eq!( 2462 FsConfig::parse("tag=mytag,socket=/tmp/sock")?, 2463 FsConfig { 2464 socket: PathBuf::from("/tmp/sock"), 2465 tag: "mytag".to_owned(), 2466 ..Default::default() 2467 } 2468 ); 2469 assert_eq!( 2470 FsConfig::parse("tag=mytag,socket=/tmp/sock,num_queues=4,queue_size=1024")?, 2471 FsConfig { 2472 socket: PathBuf::from("/tmp/sock"), 2473 tag: "mytag".to_owned(), 2474 num_queues: 4, 2475 queue_size: 1024, 2476 ..Default::default() 2477 } 2478 ); 2479 // DAX on -> default cache size 2480 assert_eq!( 2481 FsConfig::parse("tag=mytag,socket=/tmp/sock,dax=on")?, 2482 FsConfig { 2483 socket: PathBuf::from("/tmp/sock"), 2484 tag: "mytag".to_owned(), 2485 dax: true, 2486 cache_size: default_fsconfig_cache_size(), 2487 ..Default::default() 2488 } 2489 ); 2490 assert_eq!( 2491 FsConfig::parse("tag=mytag,socket=/tmp/sock,dax=on,cache_size=4G")?, 2492 FsConfig { 2493 socket: PathBuf::from("/tmp/sock"), 2494 tag: "mytag".to_owned(), 2495 dax: true, 2496 cache_size: 4 << 30, 2497 ..Default::default() 2498 } 2499 ); 2500 // Cache size without DAX is an error 2501 assert!(FsConfig::parse("tag=mytag,socket=/tmp/sock,dax=off,cache_size=4G").is_err()); 2502 Ok(()) 2503 } 2504 2505 #[test] 2506 fn test_pmem_parsing() -> Result<()> { 2507 // Must always give a file and size 2508 assert!(PmemConfig::parse("").is_err()); 2509 assert!(PmemConfig::parse("size=128M").is_err()); 2510 assert_eq!( 2511 PmemConfig::parse("file=/tmp/pmem,size=128M")?, 2512 PmemConfig { 2513 file: PathBuf::from("/tmp/pmem"), 2514 size: Some(128 << 20), 2515 ..Default::default() 2516 } 2517 ); 2518 assert_eq!( 2519 PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?, 2520 PmemConfig { 2521 file: PathBuf::from("/tmp/pmem"), 2522 size: Some(128 << 20), 2523 id: Some("mypmem0".to_owned()), 2524 ..Default::default() 2525 } 2526 ); 2527 assert_eq!( 2528 PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,mergeable=on,discard_writes=on")?, 2529 PmemConfig { 2530 file: PathBuf::from("/tmp/pmem"), 2531 size: Some(128 << 20), 2532 mergeable: true, 2533 discard_writes: true, 2534 iommu: true, 2535 ..Default::default() 2536 } 2537 ); 2538 2539 Ok(()) 2540 } 2541 2542 #[test] 2543 fn test_console_parsing() -> Result<()> { 2544 assert!(ConsoleConfig::parse("").is_err()); 2545 assert!(ConsoleConfig::parse("badmode").is_err()); 2546 assert_eq!( 2547 ConsoleConfig::parse("off")?, 2548 ConsoleConfig { 2549 mode: ConsoleOutputMode::Off, 2550 iommu: false, 2551 file: None, 2552 } 2553 ); 2554 assert_eq!( 2555 ConsoleConfig::parse("pty")?, 2556 ConsoleConfig { 2557 mode: ConsoleOutputMode::Pty, 2558 iommu: false, 2559 file: None, 2560 } 2561 ); 2562 assert_eq!( 2563 ConsoleConfig::parse("tty")?, 2564 ConsoleConfig { 2565 mode: ConsoleOutputMode::Tty, 2566 iommu: false, 2567 file: None, 2568 } 2569 ); 2570 assert_eq!( 2571 ConsoleConfig::parse("null")?, 2572 ConsoleConfig { 2573 mode: ConsoleOutputMode::Null, 2574 iommu: false, 2575 file: None, 2576 } 2577 ); 2578 assert_eq!( 2579 ConsoleConfig::parse("file=/tmp/console")?, 2580 ConsoleConfig { 2581 mode: ConsoleOutputMode::File, 2582 iommu: false, 2583 file: Some(PathBuf::from("/tmp/console")) 2584 } 2585 ); 2586 assert_eq!( 2587 ConsoleConfig::parse("null,iommu=on")?, 2588 ConsoleConfig { 2589 mode: ConsoleOutputMode::Null, 2590 iommu: true, 2591 file: None, 2592 } 2593 ); 2594 assert_eq!( 2595 ConsoleConfig::parse("file=/tmp/console,iommu=on")?, 2596 ConsoleConfig { 2597 mode: ConsoleOutputMode::File, 2598 iommu: true, 2599 file: Some(PathBuf::from("/tmp/console")) 2600 } 2601 ); 2602 Ok(()) 2603 } 2604 2605 #[test] 2606 fn test_device_parsing() -> Result<()> { 2607 // Device must have a path provided 2608 assert!(DeviceConfig::parse("").is_err()); 2609 assert_eq!( 2610 DeviceConfig::parse("path=/path/to/device")?, 2611 DeviceConfig { 2612 path: PathBuf::from("/path/to/device"), 2613 id: None, 2614 iommu: false 2615 } 2616 ); 2617 2618 assert_eq!( 2619 DeviceConfig::parse("path=/path/to/device,iommu=on")?, 2620 DeviceConfig { 2621 path: PathBuf::from("/path/to/device"), 2622 id: None, 2623 iommu: true 2624 } 2625 ); 2626 2627 assert_eq!( 2628 DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?, 2629 DeviceConfig { 2630 path: PathBuf::from("/path/to/device"), 2631 id: Some("mydevice0".to_owned()), 2632 iommu: true 2633 } 2634 ); 2635 2636 Ok(()) 2637 } 2638 2639 #[test] 2640 fn test_vsock_parsing() -> Result<()> { 2641 // socket and cid is required 2642 assert!(VsockConfig::parse("").is_err()); 2643 assert_eq!( 2644 VsockConfig::parse("socket=/tmp/sock,cid=1")?, 2645 VsockConfig { 2646 cid: 1, 2647 socket: PathBuf::from("/tmp/sock"), 2648 iommu: false, 2649 id: None, 2650 } 2651 ); 2652 assert_eq!( 2653 VsockConfig::parse("socket=/tmp/sock,cid=1,iommu=on")?, 2654 VsockConfig { 2655 cid: 1, 2656 socket: PathBuf::from("/tmp/sock"), 2657 iommu: true, 2658 id: None, 2659 } 2660 ); 2661 Ok(()) 2662 } 2663 2664 #[test] 2665 fn test_config_validation() { 2666 let valid_config = VmConfig { 2667 cpus: CpusConfig { 2668 boot_vcpus: 1, 2669 max_vcpus: 1, 2670 ..Default::default() 2671 }, 2672 memory: MemoryConfig { 2673 size: 536_870_912, 2674 mergeable: false, 2675 hotplug_method: HotplugMethod::Acpi, 2676 hotplug_size: None, 2677 hotplugged_size: None, 2678 shared: false, 2679 hugepages: false, 2680 hugepage_size: None, 2681 zones: None, 2682 }, 2683 kernel: Some(KernelConfig { 2684 path: PathBuf::from("/path/to/kernel"), 2685 }), 2686 initramfs: None, 2687 cmdline: CmdlineConfig { 2688 args: String::from(""), 2689 }, 2690 disks: None, 2691 net: None, 2692 rng: RngConfig { 2693 src: PathBuf::from("/dev/urandom"), 2694 iommu: false, 2695 }, 2696 balloon: None, 2697 fs: None, 2698 pmem: None, 2699 serial: ConsoleConfig { 2700 file: None, 2701 mode: ConsoleOutputMode::Null, 2702 iommu: false, 2703 }, 2704 console: ConsoleConfig { 2705 file: None, 2706 mode: ConsoleOutputMode::Tty, 2707 iommu: false, 2708 }, 2709 devices: None, 2710 user_devices: None, 2711 vsock: None, 2712 iommu: false, 2713 #[cfg(target_arch = "x86_64")] 2714 sgx_epc: None, 2715 numa: None, 2716 watchdog: false, 2717 #[cfg(feature = "tdx")] 2718 tdx: None, 2719 }; 2720 2721 assert!(valid_config.validate().is_ok()); 2722 2723 let mut invalid_config = valid_config.clone(); 2724 invalid_config.serial.mode = ConsoleOutputMode::Tty; 2725 invalid_config.console.mode = ConsoleOutputMode::Tty; 2726 assert!(invalid_config.validate().is_err()); 2727 2728 let mut invalid_config = valid_config.clone(); 2729 invalid_config.kernel = None; 2730 assert!(invalid_config.validate().is_err()); 2731 2732 let mut invalid_config = valid_config.clone(); 2733 invalid_config.serial.mode = ConsoleOutputMode::File; 2734 invalid_config.serial.file = None; 2735 assert!(invalid_config.validate().is_err()); 2736 2737 let mut invalid_config = valid_config.clone(); 2738 invalid_config.cpus.max_vcpus = 16; 2739 invalid_config.cpus.boot_vcpus = 32; 2740 assert!(invalid_config.validate().is_err()); 2741 2742 let mut invalid_config = valid_config.clone(); 2743 invalid_config.cpus.max_vcpus = 16; 2744 invalid_config.cpus.boot_vcpus = 16; 2745 invalid_config.cpus.topology = Some(CpuTopology { 2746 threads_per_core: 2, 2747 cores_per_die: 8, 2748 dies_per_package: 1, 2749 packages: 2, 2750 }); 2751 assert!(invalid_config.validate().is_err()); 2752 2753 let mut invalid_config = valid_config.clone(); 2754 invalid_config.disks = Some(vec![DiskConfig { 2755 vhost_socket: Some("/path/to/sock".to_owned()), 2756 path: Some(PathBuf::from("/path/to/image")), 2757 ..Default::default() 2758 }]); 2759 assert!(invalid_config.validate().is_err()); 2760 2761 let mut invalid_config = valid_config.clone(); 2762 invalid_config.disks = Some(vec![DiskConfig { 2763 vhost_user: true, 2764 ..Default::default() 2765 }]); 2766 assert!(invalid_config.validate().is_err()); 2767 2768 let mut invalid_config = valid_config.clone(); 2769 invalid_config.disks = Some(vec![DiskConfig { 2770 vhost_user: true, 2771 vhost_socket: Some("/path/to/sock".to_owned()), 2772 ..Default::default() 2773 }]); 2774 assert!(invalid_config.validate().is_err()); 2775 2776 let mut still_valid_config = valid_config.clone(); 2777 still_valid_config.disks = Some(vec![DiskConfig { 2778 vhost_user: true, 2779 vhost_socket: Some("/path/to/sock".to_owned()), 2780 ..Default::default() 2781 }]); 2782 still_valid_config.memory.shared = true; 2783 assert!(still_valid_config.validate().is_ok()); 2784 2785 let mut invalid_config = valid_config.clone(); 2786 invalid_config.net = Some(vec![NetConfig { 2787 vhost_user: true, 2788 ..Default::default() 2789 }]); 2790 assert!(invalid_config.validate().is_err()); 2791 2792 let mut still_valid_config = valid_config.clone(); 2793 still_valid_config.net = Some(vec![NetConfig { 2794 vhost_user: true, 2795 vhost_socket: Some("/path/to/sock".to_owned()), 2796 ..Default::default() 2797 }]); 2798 still_valid_config.memory.shared = true; 2799 assert!(still_valid_config.validate().is_ok()); 2800 2801 let mut invalid_config = valid_config.clone(); 2802 invalid_config.net = Some(vec![NetConfig { 2803 fds: Some(vec![0]), 2804 ..Default::default() 2805 }]); 2806 assert!(invalid_config.validate().is_err()); 2807 2808 let mut invalid_config = valid_config.clone(); 2809 invalid_config.fs = Some(vec![FsConfig { 2810 ..Default::default() 2811 }]); 2812 assert!(invalid_config.validate().is_err()); 2813 2814 let mut still_valid_config = valid_config.clone(); 2815 still_valid_config.memory.shared = true; 2816 assert!(still_valid_config.validate().is_ok()); 2817 2818 let mut still_valid_config = valid_config.clone(); 2819 still_valid_config.memory.hugepages = true; 2820 assert!(still_valid_config.validate().is_ok()); 2821 2822 let mut still_valid_config = valid_config.clone(); 2823 still_valid_config.memory.hugepages = true; 2824 still_valid_config.memory.hugepage_size = Some(2 << 20); 2825 assert!(still_valid_config.validate().is_ok()); 2826 2827 let mut invalid_config = valid_config.clone(); 2828 invalid_config.memory.hugepages = false; 2829 invalid_config.memory.hugepage_size = Some(2 << 20); 2830 assert!(invalid_config.validate().is_err()); 2831 2832 let mut invalid_config = valid_config; 2833 invalid_config.memory.hugepages = true; 2834 invalid_config.memory.hugepage_size = Some(3 << 20); 2835 assert!(invalid_config.validate().is_err()); 2836 } 2837 } 2838