Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 126 additions & 60 deletions openvmm/openvmm_defs/src/microvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,23 @@ fn validate_microvm_virtio_reservations() -> anyhow::Result<()> {
Ok(())
}

fn validate_microvm_sandbox_blocks(blocks: &[MicrovmSandboxBlockConfig]) -> anyhow::Result<()> {
/// Returns whether the guest explicitly requests a RAM-backed overlay upper.
/// The opt-in permits a read-only distro block without a writable scratch block.
pub fn ramfs_overlay_requested(cmdline: &str) -> anyhow::Result<bool> {
let mut values = cmdline
.split_ascii_whitespace()
.filter_map(|token| token.strip_prefix("nvx_overlay_upper="));
match (values.next(), values.next()) {
(None, None) => Ok(false),
(Some("ramfs"), None) => Ok(true),
_ => anyhow::bail!("microVM nvx_overlay_upper must appear once and equal ramfs"),
}
}

fn validate_microvm_sandbox_blocks(
blocks: &[MicrovmSandboxBlockConfig],
ramfs_overlay: bool,
) -> anyhow::Result<()> {
anyhow::ensure!(
blocks.len() <= MICROVM_VIRTIO_SANDBOX_BLOCK_MMIO_BASES.len(),
"microVM permits at most three read-only layers and one writable scratch device"
Expand All @@ -842,7 +858,14 @@ fn validate_microvm_sandbox_blocks(blocks: &[MicrovmSandboxBlockConfig]) -> anyh
);
}
}
if !blocks.is_empty() {
if ramfs_overlay {
anyhow::ensure!(
blocks.len() == 1
&& blocks[0].role == MicrovmSandboxBlockRole::Distro
&& blocks[0].read_only,
"microVM RAM-backed overlay requires exactly one read-only distro block"
);
} else if !blocks.is_empty() {
anyhow::ensure!(
blocks
.last()
Expand Down Expand Up @@ -881,7 +904,7 @@ pub fn append_microvm_virtio_discovery(
has_control_console: bool,
blocks: &[MicrovmSandboxBlockConfig],
) -> anyhow::Result<()> {
validate_microvm_sandbox_blocks(blocks)?;
validate_microvm_sandbox_blocks(blocks, ramfs_overlay_requested(cmdline)?)?;
anyhow::ensure!(
!cmdline.split_ascii_whitespace().any(|token| {
if has_control_console {
Expand Down Expand Up @@ -1040,7 +1063,10 @@ fn validate_microvm_command_line(
has_network == config.microvm.network.is_some(),
"microVM virtio-net device and static network identity must be configured together"
);
validate_microvm_sandbox_blocks(&config.microvm.sandbox_blocks)?;
validate_microvm_sandbox_blocks(
&config.microvm.sandbox_blocks,
ramfs_overlay_requested(cmdline)?,
)?;
anyhow::ensure!(
block_count == config.microvm.sandbox_blocks.len(),
"microVM sandbox block roles do not match the virtio-blk device inventory"
Expand Down Expand Up @@ -1666,7 +1692,6 @@ pub fn validate_machine_config(config: &Config, hypervisor_id: Option<&str>) ->
!has_control_console || has_console,
"microVM control console requires the boot console"
);
validate_microvm_sandbox_blocks(&config.microvm.sandbox_blocks)?;
anyhow::ensure!(
block_count == config.microvm.sandbox_blocks.len(),
"microVM sandbox block roles do not match the virtio-blk device inventory"
Expand Down Expand Up @@ -1881,7 +1906,7 @@ mod tests {
read_only: false,
},
];
validate_microvm_sandbox_blocks(&blocks).unwrap();
validate_microvm_sandbox_blocks(&blocks, false).unwrap();

let mut cmdline = MICROVM_BASE_COMMAND_LINE.to_owned();
append_microvm_virtio_discovery(&mut cmdline, None, false, &[], false, false, &blocks)
Expand Down Expand Up @@ -2273,70 +2298,111 @@ mod tests {
#[test]
fn microvm_sandbox_block_validation_rejects_invalid_layouts() {
assert!(
validate_microvm_sandbox_blocks(&[MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: true,
}])
validate_microvm_sandbox_blocks(
&[MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: true,
}],
false
)
.is_err()
);
assert!(
validate_microvm_sandbox_blocks(&[
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Runtime,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
},
])
validate_microvm_sandbox_blocks(
&[
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Runtime,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
},
],
false
)
.is_err()
);
assert!(
validate_microvm_sandbox_blocks(&[
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
},
])
validate_microvm_sandbox_blocks(
&[
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
},
],
false
)
.is_err()
);
assert!(
validate_microvm_sandbox_blocks(&[
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Runtime,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Custom,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
},
])
validate_microvm_sandbox_blocks(
&[
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Runtime,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Custom,
read_only: true,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
},
MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
},
],
false
)
.is_err()
);
}

#[test]
fn ramfs_overlay_allows_only_a_read_only_distro_without_scratch() {
let distro = MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Distro,
read_only: true,
};
let scratch = MicrovmSandboxBlockConfig {
role: MicrovmSandboxBlockRole::Scratch,
read_only: false,
};
assert!(!ramfs_overlay_requested("quiet").unwrap());
assert!(ramfs_overlay_requested("quiet nvx_overlay_upper=ramfs").unwrap());
assert!(ramfs_overlay_requested("nvx_overlay_upper=disk").is_err());
assert!(
ramfs_overlay_requested("nvx_overlay_upper=ramfs nvx_overlay_upper=ramfs").is_err()
);
assert!(validate_microvm_sandbox_blocks(&[distro], false).is_err());
validate_microvm_sandbox_blocks(&[distro], true).unwrap();
assert!(validate_microvm_sandbox_blocks(&[], true).is_err());
assert!(validate_microvm_sandbox_blocks(&[scratch], true).is_err());
assert!(validate_microvm_sandbox_blocks(&[distro, scratch], true).is_err());

let mut cmdline = format!("{MICROVM_BASE_COMMAND_LINE} nvx_overlay_upper=ramfs");
append_microvm_virtio_discovery(&mut cmdline, None, false, &[], false, false, &[distro])
.unwrap();
assert!(cmdline.contains("virtio_mmio.device=0x1000@0xd0003000:4"));
assert!(!cmdline.contains("virtio_mmio.device=0x1000@0xd0006000:11"));
}
}
80 changes: 79 additions & 1 deletion openvmm/openvmm_entry/src/cli_args/microvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,14 @@ impl Options {
cfg!(guest_arch = "x86_64"),
"microVM requires an x86-64 guest"
);
let ramfs_overlay =
openvmm_defs::microvm::ramfs_overlay_requested(&self.cmdline.join(" "))?;
if ramfs_overlay {
anyhow::ensure!(
self.microvm.snapshot_destination.is_none() && self.restore_snapshot.is_none(),
"microVM RAM-backed overlay does not support snapshot or restore"
);
}
anyhow::ensure!(
openvmm_defs::microvm::microvm_processor_count_supported(self.processors),
"microVM does not support {} vCPUs",
Expand Down Expand Up @@ -866,7 +874,16 @@ impl Options {
);
}
}
if !self.microvm.microvm_sandbox_block.is_empty() && self.restore_snapshot.is_none() {
if ramfs_overlay {
anyhow::ensure!(
self.microvm.microvm_sandbox_block.len() == 1
&& self.microvm.microvm_sandbox_block[0].role
== MicrovmSandboxBlockRole::Distro
&& self.microvm.microvm_sandbox_block[0].disk.read_only,
"microVM RAM-backed overlay requires exactly one read-only distro block"
);
} else if !self.microvm.microvm_sandbox_block.is_empty() && self.restore_snapshot.is_none()
{
anyhow::ensure!(
self.microvm
.microvm_sandbox_block
Expand Down Expand Up @@ -1310,6 +1327,67 @@ mod tests {
}
}

#[test]
fn test_microvm_ramfs_overlay_requires_one_distro_and_no_snapshot() {
let valid = Options::try_parse_from([
"openvmm",
"--machine",
"microvm",
"--cmdline",
"quiet nvx_overlay_upper=ramfs",
"--microvm-sandbox-block",
"distro:mem:1M,ro",
])
.unwrap();
valid.validate_microvm_options().unwrap();

for args in [
vec![
"openvmm",
"--machine",
"microvm",
"--microvm-sandbox-block",
"distro:mem:1M,ro",
],
vec![
"openvmm",
"--machine",
"microvm",
"--cmdline",
"nvx_overlay_upper=ramfs",
"--microvm-sandbox-block",
"distro:mem:1M,ro",
"--microvm-sandbox-block",
"scratch:mem:1M",
],
vec![
"openvmm",
"--machine",
"microvm",
"--cmdline",
"nvx_overlay_upper=ramfs nvx_overlay_upper=ramfs",
"--microvm-sandbox-block",
"distro:mem:1M,ro",
],
vec![
"openvmm",
"--machine",
"microvm",
"--cmdline",
"nvx_overlay_upper=ramfs",
"--microvm-sandbox-block",
"distro:mem:1M,ro",
"--snapshot-destination",
"snapshot",
"--snapshot-tier",
"workload-start",
],
] {
let options = Options::try_parse_from(args).unwrap();
assert!(options.validate_microvm_options().is_err());
}
}

#[test]
fn test_microvm_snapshot_tier_is_explicit() {
for tier in ["platform", "workload-start", "instance-checkpoint"] {
Expand Down
Loading