config: Reorganize command line parsing

The command line parsing of the user input was not properly
abstracted from the vmm specific code. In the case of --net,
the parsing was done when the device manager was adding devices.

In order to fix this confusion, this patch introduces a new
module "config" dedicated to the translation of a VmParams
structure into a VmCfg structure. The former is built based
on the input provided by the user, while the latter is the
result of the parsing of every options.

VmCfg is meant to be consumed by the vmm specific code, and
it is also a fully public structure so that it can directly
be built from a testing environment.

Fixes #31

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Sebastien Boeuf 2019-05-23 12:48:05 -07:00 committed by Rob Bradford
parent 9900daacf8
commit e5e651895b
4 changed files with 317 additions and 196 deletions

View File

@ -9,16 +9,26 @@ extern crate vmm;
extern crate clap;
use clap::{App, Arg};
use std::path::PathBuf;
use vmm::vm::*;
use std::process;
use vmm::config;
fn main() {
let cmd_arguments = App::new("cloud-hypervisor")
.version(crate_version!())
.author(crate_authors!())
.about("Launch a cloud-hypervisor VMM.")
.arg(
Arg::with_name("cpus")
.long("cpus")
.help("Number of virtual CPUs")
.default_value(config::DEFAULT_VCPUS),
)
.arg(
Arg::with_name("memory")
.long("memory")
.help("Amount of RAM (in MiB)")
.default_value(config::DEFAULT_MEMORY),
)
.arg(
Arg::with_name("kernel")
.long("kernel")
@ -41,82 +51,68 @@ fn main() {
.arg(
Arg::with_name("net")
.long("net")
.help("Network parameters \"tap=<if_name>,ip=<ip_addr>,mask=<net_mask>,mac=<mac_addr>\"")
.takes_value(true),
)
.arg(
Arg::with_name("cpus")
.long("cpus")
.help("Number of virtual CPUs")
.takes_value(true),
)
.arg(
Arg::with_name("memory")
.long("memory")
.help("Amount of RAM (in MB)")
.help(
"Network parameters \"tap=<if_name>,\
ip=<ip_addr>,mask=<net_mask>,mac=<mac_addr>\"",
)
.takes_value(true),
)
.arg(
Arg::with_name("rng")
.long("rng")
.help("Path to entropy source")
.default_value("/dev/urandom"),
.default_value(config::DEFAULT_RNG_SOURCE),
)
.get_matches();
let kernel_arg = cmd_arguments
.value_of("kernel")
.map(PathBuf::from)
.expect("Missing argument: kernel");
let kernel_path = kernel_arg.as_path();
// These .unwrap()s cannot fail as there is a default value defined
let cpus = cmd_arguments.value_of("cpus").unwrap();
let memory = cmd_arguments.value_of("memory").unwrap();
let disk_paths: Vec<PathBuf> = cmd_arguments
let kernel = cmd_arguments
.value_of("kernel")
.expect("Missing argument: kernel");
let cmdline = cmd_arguments.value_of("cmdline");
let disks: Vec<&str> = cmd_arguments
.values_of("disk")
.expect("Missing arguments on disk")
.map(PathBuf::from)
.expect("Missing argument: disk. Provide at least one")
.collect();
let cmdline = cmd_arguments
.value_of("cmdline")
.map(std::string::ToString::to_string)
.unwrap_or_else(String::new);
let net = cmd_arguments.value_of("net");
let net_params = cmd_arguments
.value_of("net")
.map(std::string::ToString::to_string);
// This .unwrap() cannot fail as there is a default value defined
let rng = cmd_arguments.value_of("rng").unwrap();
let rng_path = match cmd_arguments.occurrences_of("rng") {
0 => None,
_ => Some(cmd_arguments.value_of("rng").unwrap().to_string()),
let vm_config = match config::VmConfig::parse(config::VmParams {
cpus,
memory,
kernel,
cmdline,
disks,
rng,
net,
}) {
Ok(config) => config,
Err(e) => {
println!("Failed parsing parameters {:?}", e);
process::exit(1);
}
};
let vcpus = cmd_arguments
.value_of("cpus")
.and_then(|c| c.parse::<u8>().ok())
.unwrap_or(DEFAULT_VCPUS);
let memory = cmd_arguments
.value_of("memory")
.and_then(|m| m.parse::<u64>().ok())
.unwrap_or(DEFAULT_MEMORY);
println!(
"Cloud Hypervisor Guest\n\tvCPUs: {}\n\tMemory: {} MB\n\tKernel: {:?}\n\tKernel cmdline: {}\n\tDisk(s): {:?}",
vcpus, memory, kernel_path, cmdline, disk_paths,
"Cloud Hypervisor Guest\n\tvCPUs: {}\n\tMemory: {} MB\
\n\tKernel: {:?}\n\tKernel cmdline: {}\n\tDisk(s): {:?}",
u8::from(&vm_config.cpus),
u64::from(&vm_config.memory),
vm_config.kernel.path,
vm_config.cmdline.args.as_str(),
vm_config.disks,
);
let vm_config = VmConfig::new(
kernel_path,
disk_paths,
rng_path,
cmdline,
net_params,
vcpus,
memory,
)
.unwrap();
if let Err(e) = vmm::boot_kernel(vm_config) {
println!("Guest boot failed: {}", e);
process::exit(1);
}
}

225
vmm/src/config.rs Normal file
View File

@ -0,0 +1,225 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use linux_loader::cmdline::Cmdline;
use net_util::MacAddr;
use std::convert::From;
use std::net::AddrParseError;
use std::net::Ipv4Addr;
use std::path::Path;
use std::result;
use vm_memory::GuestAddress;
pub const DEFAULT_VCPUS: &str = "1";
pub const DEFAULT_MEMORY: &str = "512";
pub const DEFAULT_RNG_SOURCE: &str = "/dev/urandom";
const CMDLINE_OFFSET: GuestAddress = GuestAddress(0x20000);
/// Errors associated with VM configuration parameters.
#[derive(Debug)]
pub enum Error<'a> {
/// Failed parsing cpus parameters.
ParseCpusParams(std::num::ParseIntError),
/// Failed parsing memory parameters.
ParseMemoryParams(std::num::ParseIntError),
/// Failed parsing kernel parameters.
ParseKernelParams,
/// Failed parsing kernel command line parameters.
ParseCmdlineParams,
/// Failed parsing disks parameters.
ParseDisksParams,
/// Failed parsing random number generator parameters.
ParseRngParams,
/// Failed parsing network ip parameter.
ParseNetIpParam(AddrParseError),
/// Failed parsing network mask parameter.
ParseNetMaskParam(AddrParseError),
/// Failed parsing network mac parameter.
ParseNetMacParam(&'a str),
}
pub type Result<'a, T> = result::Result<T, Error<'a>>;
pub struct VmParams<'a> {
pub cpus: &'a str,
pub memory: &'a str,
pub kernel: &'a str,
pub cmdline: Option<&'a str>,
pub disks: Vec<&'a str>,
pub rng: &'a str,
pub net: Option<&'a str>,
}
pub struct CpusConfig(pub u8);
impl CpusConfig {
pub fn parse(cpus: &str) -> Result<Self> {
Ok(CpusConfig(
cpus.parse::<u8>().map_err(Error::ParseCpusParams)?,
))
}
}
impl From<&CpusConfig> for u8 {
fn from(val: &CpusConfig) -> Self {
val.0
}
}
pub struct MemoryConfig(pub u64);
impl MemoryConfig {
pub fn parse(memory: &str) -> Result<Self> {
Ok(MemoryConfig(
memory.parse::<u64>().map_err(Error::ParseMemoryParams)?,
))
}
}
impl From<&MemoryConfig> for u64 {
fn from(val: &MemoryConfig) -> Self {
val.0
}
}
pub struct KernelConfig<'a> {
pub path: &'a Path,
}
impl<'a> KernelConfig<'a> {
pub fn parse(kernel: &'a str) -> Result<Self> {
Ok(KernelConfig {
path: Path::new(kernel),
})
}
}
pub struct CmdlineConfig {
pub args: Cmdline,
pub offset: GuestAddress,
}
impl CmdlineConfig {
pub fn parse(cmdline: Option<&str>) -> Result<Self> {
let cmdline_str = cmdline
.map(std::string::ToString::to_string)
.unwrap_or_else(String::new);
let mut args = Cmdline::new(arch::CMDLINE_MAX_SIZE);
args.insert_str(cmdline_str).unwrap();
Ok(CmdlineConfig {
args,
offset: CMDLINE_OFFSET,
})
}
}
#[derive(Debug)]
pub struct DiskConfig<'a> {
pub path: &'a Path,
}
impl<'a> DiskConfig<'a> {
pub fn parse(disk: &'a str) -> Result<Self> {
Ok(DiskConfig {
path: Path::new(disk),
})
}
}
pub struct RngConfig<'a> {
pub src: &'a Path,
}
impl<'a> RngConfig<'a> {
pub fn parse(rng: &'a str) -> Result<Self> {
Ok(RngConfig {
src: Path::new(rng),
})
}
}
pub struct NetConfig<'a> {
pub tap: Option<&'a str>,
pub ip: Ipv4Addr,
pub mask: Ipv4Addr,
pub mac: MacAddr,
}
impl<'a> NetConfig<'a> {
pub fn parse(net: Option<&'a str>) -> Result<Option<Self>> {
if net.is_none() {
return Ok(None);
}
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = net.unwrap().split(',').collect();
let mut tap_str: &str = "";
let mut ip_str: &str = "";
let mut mask_str: &str = "";
let mut mac_str: &str = "";
for param in params_list.iter() {
if param.starts_with("tap=") {
tap_str = &param[4..];
} else if param.starts_with("ip=") {
ip_str = &param[3..];
} else if param.starts_with("mask=") {
mask_str = &param[5..];
} else if param.starts_with("mac=") {
mac_str = &param[4..];
}
}
let mut tap: Option<&str> = None;
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 249, 1);
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);;
let mut mac: MacAddr = MacAddr::local_random();
if !tap_str.is_empty() {
tap = Some(tap_str);
}
if !ip_str.is_empty() {
ip = ip_str.parse().map_err(Error::ParseNetIpParam)?;
}
if !mask_str.is_empty() {
mask = mask_str.parse().map_err(Error::ParseNetMaskParam)?;
}
if !mac_str.is_empty() {
mac = MacAddr::parse_str(mac_str).map_err(Error::ParseNetMacParam)?;
}
Ok(Some(NetConfig { tap, ip, mask, mac }))
}
}
pub struct VmConfig<'a> {
pub cpus: CpusConfig,
pub memory: MemoryConfig,
pub kernel: KernelConfig<'a>,
pub cmdline: CmdlineConfig,
pub disks: Vec<DiskConfig<'a>>,
pub rng: RngConfig<'a>,
pub net: Option<NetConfig<'a>>,
}
impl<'a> VmConfig<'a> {
pub fn parse(vm_params: VmParams<'a>) -> Result<Self> {
let mut disks: Vec<DiskConfig> = Vec::new();
for disk in vm_params.disks.iter() {
disks.push(DiskConfig::parse(disk)?);
}
Ok(VmConfig {
cpus: CpusConfig::parse(vm_params.cpus)?,
memory: MemoryConfig::parse(vm_params.memory)?,
kernel: KernelConfig::parse(vm_params.kernel)?,
cmdline: CmdlineConfig::parse(vm_params.cmdline)?,
disks,
rng: RngConfig::parse(vm_params.rng)?,
net: NetConfig::parse(vm_params.net)?,
})
}
}

View File

@ -11,9 +11,11 @@ use kvm_ioctls::*;
use std::fmt::{self, Display};
use std::result;
pub mod config;
pub mod vm;
use self::vm::{Vm, VmConfig};
use self::config::VmConfig;
use self::vm::Vm;
/// Errors associated with VM management
#[derive(Debug)]

View File

@ -21,20 +21,18 @@ extern crate vm_memory;
extern crate vm_virtio;
extern crate vmm_sys_util;
use crate::config::VmConfig;
use kvm_bindings::{kvm_pit_config, kvm_userspace_memory_region, KVM_PIT_SPEAKER_DUMMY};
use kvm_ioctls::*;
use libc::{c_void, siginfo_t, EFD_NONBLOCK};
use linux_loader::cmdline;
use linux_loader::loader::KernelLoader;
use net_util::{MacAddr, Tap};
use net_util::Tap;
use pci::{PciConfigIo, PciDevice, PciInterruptPin, PciRoot};
use qcow::{self, ImageType, QcowFile};
use std::ffi::CString;
use std::fs::{File, OpenOptions};
use std::io::{self, stdout};
use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Barrier, Mutex};
use std::{result, str, thread};
use vm_allocator::SystemAllocator;
@ -48,9 +46,6 @@ use vmm_sys_util::terminal::Terminal;
use vmm_sys_util::EventFd;
const VCPU_RTSIG_OFFSET: i32 = 0;
pub const DEFAULT_VCPUS: u8 = 1;
pub const DEFAULT_MEMORY: GuestUsize = 512;
const CMDLINE_OFFSET: GuestAddress = GuestAddress(0x20000);
const X86_64_IRQ_BASE: u32 = 5;
// CPUID feature bits
@ -288,98 +283,6 @@ impl Vcpu {
}
}
pub struct VmConfig<'a> {
kernel_path: &'a Path,
disk_paths: Vec<PathBuf>,
rng_path: Option<String>,
cmdline: cmdline::Cmdline,
cmdline_addr: GuestAddress,
net_params: Option<String>,
memory_size: GuestUsize,
vcpu_count: u8,
}
impl<'a> VmConfig<'a> {
pub fn new(
kernel_path: &'a Path,
disk_paths: Vec<PathBuf>,
rng_path: Option<String>,
cmdline_str: String,
net_params: Option<String>,
vcpus: u8,
memory_size: GuestUsize,
) -> Result<Self> {
let mut cmdline = cmdline::Cmdline::new(arch::CMDLINE_MAX_SIZE);
cmdline.insert_str(cmdline_str).unwrap();
Ok(VmConfig {
kernel_path,
disk_paths,
rng_path,
cmdline,
cmdline_addr: CMDLINE_OFFSET,
net_params,
memory_size,
vcpu_count: vcpus,
})
}
}
#[derive(Debug)]
struct NetParams<'a> {
tap_if_name: Option<&'a str>,
ip_addr: Ipv4Addr,
net_mask: Ipv4Addr,
mac_addr: MacAddr,
}
fn parse_net_params(net_params: &str) -> Result<NetParams> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = net_params.split(',').collect();
let mut tap: &str = "";
let mut ip: &str = "";
let mut mask: &str = "";
let mut mac: &str = "";
for param in params_list.iter() {
if param.starts_with("tap=") {
tap = &param[4..];
} else if param.starts_with("ip=") {
ip = &param[3..];
} else if param.starts_with("mask=") {
mask = &param[5..];
} else if param.starts_with("mac=") {
mac = &param[4..];
}
}
let mut tap_if_name: Option<&str> = None;
let mut ip_addr: Ipv4Addr = "192.168.249.1".parse().unwrap();
let mut net_mask: Ipv4Addr = "255.255.255.0".parse().unwrap();
let mut mac_addr: MacAddr = MacAddr::local_random();
if !tap.is_empty() {
tap_if_name = Some(tap);
}
if !ip.is_empty() {
ip_addr = ip.parse().unwrap();
}
if !mask.is_empty() {
net_mask = mask.parse().unwrap();
}
if !mac.is_empty() {
mac_addr = MacAddr::parse_str(mac).unwrap();
}
Ok(NetParams {
tap_if_name,
ip_addr,
net_mask,
mac_addr,
})
}
struct DeviceManager {
io_bus: devices::Bus,
mmio_bus: devices::Bus,
@ -420,12 +323,12 @@ impl DeviceManager {
let mut pci_root = PciRoot::new(None);
for disk_path in &vm_cfg.disk_paths {
for disk_cfg in &vm_cfg.disks {
// Open block device path
let raw_img: File = OpenOptions::new()
.read(true)
.write(true)
.open(disk_path.as_path())
.open(disk_cfg.path)
.map_err(DeviceManagerError::Disk)?;
// Add virtio-blk
@ -434,14 +337,14 @@ impl DeviceManager {
let block = match image_type {
ImageType::Raw => {
let raw_img = vm_virtio::RawFile::new(raw_img);
let dev = vm_virtio::Block::new(raw_img, disk_path.to_path_buf(), false)
let dev = vm_virtio::Block::new(raw_img, disk_cfg.path.to_path_buf(), false)
.map_err(DeviceManagerError::CreateVirtioBlock)?;
Box::new(dev) as Box<vm_virtio::VirtioDevice>
}
ImageType::Qcow2 => {
let qcow_img =
QcowFile::from(raw_img).map_err(DeviceManagerError::QcowDeviceCreate)?;
let dev = vm_virtio::Block::new(qcow_img, disk_path.to_path_buf(), false)
let dev = vm_virtio::Block::new(qcow_img, disk_cfg.path.to_path_buf(), false)
.map_err(DeviceManagerError::CreateVirtioBlock)?;
Box::new(dev) as Box<vm_virtio::VirtioDevice>
}
@ -458,37 +361,32 @@ impl DeviceManager {
}
// Add virtio-net if required
if let Some(net_params) = &vm_cfg.net_params {
if let Ok(net_params) = parse_net_params(net_params) {
let mut virtio_net_device: vm_virtio::Net;
if let Some(net_cfg) = &vm_cfg.net {
let mut virtio_net_device: vm_virtio::Net;
if let Some(tap_if_name) = net_params.tap_if_name {
let tap = Tap::open_named(tap_if_name).map_err(DeviceManagerError::OpenTap)?;
virtio_net_device =
vm_virtio::Net::new_with_tap(tap, Some(&net_params.mac_addr))
.map_err(DeviceManagerError::CreateVirtioNet)?;
} else {
virtio_net_device = vm_virtio::Net::new(
net_params.ip_addr,
net_params.net_mask,
Some(&net_params.mac_addr),
)
if let Some(tap_if_name) = net_cfg.tap {
let tap = Tap::open_named(tap_if_name).map_err(DeviceManagerError::OpenTap)?;
virtio_net_device = vm_virtio::Net::new_with_tap(tap, Some(&net_cfg.mac))
.map_err(DeviceManagerError::CreateVirtioNet)?;
}
DeviceManager::add_virtio_pci_device(
Box::new(virtio_net_device),
memory.clone(),
allocator,
vm_fd,
&mut pci_root,
&mut mmio_bus,
)?;
} else {
virtio_net_device =
vm_virtio::Net::new(net_cfg.ip, net_cfg.mask, Some(&net_cfg.mac))
.map_err(DeviceManagerError::CreateVirtioNet)?;
}
DeviceManager::add_virtio_pci_device(
Box::new(virtio_net_device),
memory.clone(),
allocator,
vm_fd,
&mut pci_root,
&mut mmio_bus,
)?;
}
// Add virtio-rng if required
if let Some(rng_path) = &vm_cfg.rng_path {
if let Some(rng_path) = vm_cfg.rng.src.to_str() {
println!("VIRTIO_RNG PATH {}", rng_path);
let virtio_rng_device =
vm_virtio::Rng::new(rng_path).map_err(DeviceManagerError::CreateVirtioRng)?;
@ -652,11 +550,11 @@ pub struct Vm<'a> {
impl<'a> Vm<'a> {
pub fn new(kvm: &Kvm, config: VmConfig<'a>) -> Result<Self> {
let kernel = File::open(&config.kernel_path).map_err(Error::KernelFile)?;
let kernel = File::open(&config.kernel.path).map_err(Error::KernelFile)?;
let fd = kvm.create_vm().map_err(Error::VmCreate)?;
// Init guest memory
let arch_mem_regions = arch::arch_memory_regions(config.memory_size << 20);
let arch_mem_regions = arch::arch_memory_regions(u64::from(&config.memory) << 20);
let guest_memory = GuestMemoryMmap::new(&arch_mem_regions).map_err(Error::GuestMemory)?;
guest_memory
@ -718,7 +616,7 @@ impl<'a> Vm<'a> {
.add_event(&device_manager.exit_evt, EpollDispatch::Exit)
.map_err(Error::EpollError)?;
let vcpus = Vec::with_capacity(config.vcpu_count as usize);
let vcpus = Vec::with_capacity(u8::from(&config.cpus) as usize);
Ok(Vm {
fd,
@ -734,7 +632,7 @@ impl<'a> Vm<'a> {
pub fn load_kernel(&mut self) -> Result<GuestAddress> {
let cmdline_cstring =
CString::new(self.config.cmdline.clone()).map_err(|_| Error::CmdLine)?;
CString::new(self.config.cmdline.args.clone()).map_err(|_| Error::CmdLine)?;
let entry_addr = linux_loader::loader::Elf::load(
&self.memory,
None,
@ -745,16 +643,16 @@ impl<'a> Vm<'a> {
linux_loader::loader::load_cmdline(
&self.memory,
self.config.cmdline_addr,
self.config.cmdline.offset,
&cmdline_cstring,
)
.map_err(|_| Error::CmdLine)?;
let vcpu_count = self.config.vcpu_count;
let vcpu_count = u8::from(&self.config.cpus);
arch::configure_system(
&self.memory,
self.config.cmdline_addr,
self.config.cmdline.offset,
cmdline_cstring.to_bytes().len() + 1,
vcpu_count,
)
@ -818,7 +716,7 @@ impl<'a> Vm<'a> {
pub fn start(&mut self, entry_addr: GuestAddress) -> Result<()> {
self.devices.register_devices()?;
let vcpu_count = self.config.vcpu_count;
let vcpu_count = u8::from(&self.config.cpus);
// let vcpus: Vec<thread::JoinHandle<()>> = Vec::with_capacity(vcpu_count as usize);
let vcpu_thread_barrier = Arc::new(Barrier::new((vcpu_count + 1) as usize));