Skip to content

Commit

Permalink
[antlir2][vm] make share type generic
Browse files Browse the repository at this point in the history
Summary: This diff changes nothing, but prepares for additional type of shares.

Test Plan: CI should cover all signals

Differential Revision: D50479779

fbshipit-source-id: 6d688bc0bd0618d2436ddd1c8e8bc32ecf8bee1e
  • Loading branch information
wujj123456 authored and facebook-github-bot committed Oct 20, 2023
1 parent 12cf5db commit db90c9f
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 89 deletions.
6 changes: 5 additions & 1 deletion antlir/antlir2/antlir2_vm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use crate::isolation::is_isolated;
use crate::isolation::isolated;
use crate::isolation::Platform;
use crate::runtime::set_runtime;
use crate::share::VirtiofsShare;
use crate::types::MachineOpts;
use crate::types::RuntimeOpts;
use crate::types::VMArgs;
Expand Down Expand Up @@ -105,7 +106,10 @@ fn run(args: &RunCmdArgs) -> Result<()> {

set_runtime(args.runtime_spec.clone().into_inner())
.map_err(|_| anyhow!("Failed to set runtime"))?;
Ok(VM::new(args.machine_spec.clone().into_inner(), args.vm_args.clone())?.run()?)
Ok(
VM::<VirtiofsShare>::new(args.machine_spec.clone().into_inner(), args.vm_args.clone())?
.run()?,
)
}

/// Enter isolated container and then respawn itself inside it with `run`
Expand Down
202 changes: 124 additions & 78 deletions antlir/antlir2/antlir2_vm/src/share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,63 +35,51 @@ pub(crate) enum ShareError {

type Result<T> = std::result::Result<T, ShareError>;

/// `ViriofsShare` setups sharing through virtiofs
#[derive(Debug, Default)]
pub(crate) struct VirtiofsShare {
/// User specified options for the share
opts: ShareOpts,
/// Index of the share, used to generate unique mount tag, chardev name
/// and socket file.
id: usize,
/// State directory
state_dir: PathBuf,
}

impl VirtiofsShare {
/// Create VirtiofsShare based on full set of ShareOpts
pub(crate) fn new(opts: ShareOpts, id: usize, state_dir: PathBuf) -> Self {
Self {
opts,
id,
state_dir,
}
}

fn mount_tag(&self) -> String {
match &self.opts.mount_tag {
Some(tag) => tag.clone(),
None => format!("fs{}", self.id),
}
}

fn chardev_node(&self) -> String {
format!("fs_chardev{}", self.id)
}

fn socket_path(&self) -> PathBuf {
self.state_dir.join(self.mount_tag())
}
pub(crate) trait Share {
/// Create Share based on full set of ShareOpts
fn new(opts: ShareOpts, id: usize, state_dir: PathBuf) -> Self;
/// Run any necessary setup to enable the Share
fn setup(&self) -> Result<()>;
/// Mount `Options` string for the mount unit
fn mount_options(&self) -> String;
/// Qemu args for the mounts.
fn qemu_args(&self) -> Vec<OsString>;

// Boilerplate getters
fn get_mount_type(&self) -> &str;
fn get_id(&self) -> usize;
fn get_opts(&self) -> &ShareOpts;

// The following methods should be same across different mount types

/// Generate file name according to systemd.mount(5)
fn mount_unit_name(&self) -> Result<String> {
let output = Command::new("systemd-escape")
.arg("--suffix=mount")
.arg("--path")
.arg(&self.opts.path)
.arg(&self.get_opts().path)
.output()
.map_err(|_| {
ShareError::InvalidMountTagError(self.opts.path.to_string_lossy().to_string())
ShareError::InvalidMountTagError(self.get_opts().path.to_string_lossy().to_string())
})?;
Ok(std::str::from_utf8(&output.stdout)
.map_err(|_| {
ShareError::InvalidMountTagError(self.opts.path.to_string_lossy().to_string())
ShareError::InvalidMountTagError(self.get_opts().path.to_string_lossy().to_string())
})?
.trim()
.to_string())
}

/// Generate mount tag with id unless it's specified
fn mount_tag(&self) -> String {
match &self.get_opts().mount_tag {
Some(tag) => tag.clone(),
None => format!("fs{}", self.get_id()),
}
}

/// Generate .mount unit content
pub(crate) fn mount_unit_content(&self) -> String {
fn mount_unit_content(&self) -> String {
format!(
r#"[Unit]
Description=Mount {tag} at {mountpoint}
Expand All @@ -102,16 +90,101 @@ Before=local-fs.target
[Mount]
What={tag}
Where={mountpoint}
Type=virtiofs
Options={ro_or_rw}"#,
Type={mount_type}
Options={mount_options}"#,
tag = self.mount_tag(),
mountpoint = self.opts.path.to_str().expect("Invalid UTF-8"),
ro_or_rw = match self.opts.read_only {
true => "ro",
false => "rw",
}
mountpoint = self.get_opts().path.to_str().expect("Invalid UTF-8"),
mount_type = self.get_mount_type(),
mount_options = self.mount_options(),
)
}
}

macro_rules! share_getters {
() => {
fn get_id(&self) -> usize {
self.id
}
fn get_opts(&self) -> &ShareOpts {
&self.opts
}
fn get_mount_type(&self) -> &str {
self.mount_type
}
};
}

/// `ViriofsShare` setups sharing through virtiofs
#[derive(Debug, Default)]
pub(crate) struct VirtiofsShare {
/// User specified options for the share
opts: ShareOpts,
/// Index of the share, used to generate unique mount tag, chardev name
/// and socket file.
id: usize,
/// State directory
state_dir: PathBuf,
/// Mount type
mount_type: &'static str,
}

impl Share for VirtiofsShare {
share_getters!();

fn new(opts: ShareOpts, id: usize, state_dir: PathBuf) -> Self {
Self {
opts,
id,
state_dir,
mount_type: "virtiofs",
}
}

fn setup(&self) -> Result<()> {
self.start_virtiofsd()?;
Ok(())
}

fn mount_options(&self) -> String {
if self.get_opts().read_only {
"ro"
} else {
"rw"
}
.to_owned()
}

fn qemu_args(&self) -> Vec<OsString> {
[
"-chardev",
&format!(
"socket,id={},path={}",
self.chardev_node(),
self.socket_path()
.to_str()
.expect("socket file should be valid string"),
),
"-device",
&format!(
"vhost-user-fs-pci,queue-size=1024,chardev={},tag={}",
self.chardev_node(),
self.mount_tag(),
),
]
.iter()
.map(|x| x.into())
.collect()
}
}

impl VirtiofsShare {
fn chardev_node(&self) -> String {
format!("fs_chardev{}", self.id)
}

fn socket_path(&self) -> PathBuf {
self.state_dir.join(self.mount_tag())
}

/// Rust virtiofsd seems to print out every request it gets on debug level,
/// rendering terminal unusable. While users can craft the `RUST_LOG` env to
Expand Down Expand Up @@ -158,51 +231,24 @@ Options={ro_or_rw}"#,
.spawn()
.map_err(ShareError::VirtiofsdError)
}

/// Qemu args for virtiofs mounts.
pub(crate) fn qemu_args(&self) -> Vec<OsString> {
[
"-chardev",
&format!(
"socket,id={},path={}",
self.chardev_node(),
self.socket_path()
.to_str()
.expect("socket file should be valid string"),
),
"-device",
&format!(
"vhost-user-fs-pci,queue-size=1024,chardev={},tag={}",
self.chardev_node(),
self.mount_tag(),
),
]
.iter()
.map(|x| x.into())
.collect()
}
}

/// In order to mount shares, we have to share something into the VM
/// that contains various mount units for mount generator. This struct
/// represents the initial trojan horse into the VM.
#[derive(Debug)]
pub(crate) struct Shares {
pub(crate) struct Shares<T: Share> {
/// Directories to be shared into VM
shares: Vec<VirtiofsShare>,
shares: Vec<T>,
/// Memory size of the qemu VM. This should match -m parameter.
/// This is used for memory-backend-file for virtiofsd shares.
mem_mb: usize,
/// Directory that holds unit files for other shares
unit_files_dir: PathBuf,
}

impl Shares {
pub(crate) fn new(
shares: Vec<VirtiofsShare>,
mem_mb: usize,
unit_files_dir: PathBuf,
) -> Result<Self> {
impl<T: Share> Shares<T> {
pub(crate) fn new(shares: Vec<T>, mem_mb: usize, unit_files_dir: PathBuf) -> Result<Self> {
if shares.is_empty() {
return Err(ShareError::EmptyShareError);
}
Expand Down
21 changes: 11 additions & 10 deletions antlir/antlir2/antlir2_vm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ use crate::pci::PCIBridge;
use crate::pci::PCIBridgeError;
use crate::pci::DEVICE_PER_BRIDGE;
use crate::runtime::get_runtime;
use crate::share::Share;
use crate::share::ShareError;
use crate::share::Shares;
use crate::share::VirtiofsShare;
use crate::ssh::GuestSSHCommand;
use crate::ssh::GuestSSHError;
use crate::tpm::TPMDevice;
Expand All @@ -53,7 +53,7 @@ use crate::types::VMArgs;
use crate::utils::log_command;

#[derive(Debug)]
pub(crate) struct VM {
pub(crate) struct VM<S: Share> {
/// VM machine specification
machine: MachineOpts,
/// VM execution behavior
Expand All @@ -64,7 +64,7 @@ pub(crate) struct VM {
/// to prevent the temporary disks from getting cleaned up prematuresly.
disks: Vec<QCow2Disk>,
/// All directories to be shared into the VM
shares: Shares,
shares: Shares<S>,
/// Virtual NICs to create and attach
nics: Vec<VirtualNIC>,
/// Directory to keep all ephemeral states
Expand Down Expand Up @@ -111,7 +111,7 @@ pub(crate) enum VMError {

type Result<T> = std::result::Result<T, VMError>;

impl VM {
impl<S: Share> VM<S> {
/// Create a new VM along with its virtual resources
pub(crate) fn new(machine: MachineOpts, args: VMArgs) -> Result<Self> {
let state_dir = Self::create_state_dir()?;
Expand Down Expand Up @@ -210,13 +210,13 @@ impl VM {
}

/// Create all shares, start virtiofsd daemon and generate necessary unit files
fn create_shares(shares: Vec<ShareOpts>, state_dir: &Path, mem_mb: usize) -> Result<Shares> {
fn create_shares(shares: Vec<ShareOpts>, state_dir: &Path, mem_mb: usize) -> Result<Shares<S>> {
let virtiofs_shares: Result<Vec<_>> = shares
.into_iter()
.enumerate()
.map(|(i, opts)| -> Result<VirtiofsShare> {
let share = VirtiofsShare::new(opts, i, state_dir.to_path_buf());
share.start_virtiofsd()?;
.map(|(i, opts)| -> Result<S> {
let share = S::new(opts, i, state_dir.to_path_buf());
share.setup()?;
Ok(share)
})
.collect();
Expand Down Expand Up @@ -684,12 +684,13 @@ mod test {

use super::*;
use crate::runtime::set_runtime;
use crate::share::VirtiofsShare;
use crate::types::NonDiskBootOpts;
use crate::types::RuntimeOpts;
use crate::types::VMArgs;
use crate::utils::qemu_args_to_string;

fn get_vm_no_disk() -> VM {
fn get_vm_no_disk() -> VM<VirtiofsShare> {
let machine = MachineOpts {
cpus: 1,
mem_mib: 1024,
Expand Down Expand Up @@ -936,7 +937,7 @@ mod test {
read_only: false,
mount_tag: None,
};
let all_opts = VM::get_all_shares_opts(&outputs);
let all_opts = VM::<VirtiofsShare>::get_all_shares_opts(&outputs);
assert!(all_opts.contains(&opt));
}

Expand Down

0 comments on commit db90c9f

Please sign in to comment.