Skip to content
Merged
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
11 changes: 10 additions & 1 deletion Guide/src/reference/openvmm/management/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,16 @@ describes the source definitions.
`microvm` uses one socket and one die,
with one core per vCPU, no SMT, xAPIC mode, and contiguous APIC IDs from 0.
Guest-requested snapshot capture and new-process restore are available for
blockless and fixed-block machines on Linux/KVM, Linux/MSHV, and Windows/WHP.
blockless and fixed-block machines on Linux/KVM, Linux/MSHV, and Windows/WHP,
except for the RAM-backed overlay layout.

A non-empty `--microvm-sandbox-block` topology must end with the writable
`scratch` block unless the command line carries exactly one
`nvx_overlay_upper=ramfs` token. That RAM-backed overlay layout, for a guest
that keeps its overlay upper in RAM, requires exactly one read-only `distro`
block and no other block, and it rejects `--snapshot-destination` and
`--restore-snapshot`. Any other `nvx_overlay_upper` token, including a bare
or repeated one, is rejected.

```admonish warning title="microVM migration"
The canonical `microvm` spelling now selects the contract formerly exposed
Expand Down
17 changes: 16 additions & 1 deletion Guide/src/user_guide/openvmm/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ locations:

Use `--microvm-sandbox-block ROLE:DISK`, in the order shown. Lower-layer
roles require the normal disk `,ro` option and a non-empty topology must end
with `scratch`; ordinary `--virtio-blk` is intentionally rejected.
with `scratch`, except for the RAM-backed overlay layout described below;
ordinary `--virtio-blk` is intentionally rejected.
For example:

```shell
Expand All @@ -69,6 +70,20 @@ accepts the read-only layer arguments again and creates a private scratch copy
from that artifact. A pre-mount request may select fresh-scratch policy instead,
in which case restore requires a new writable scratch file of matching size.

A guest that keeps its overlay upper in RAM can boot from one read-only image
without scratch. Pass exactly one `nvx_overlay_upper=ramfs` token on the kernel
command line and exactly one read-only `distro` block:

```shell
openvmm --machine microvm --kernel vmlinux --initrd initramfs.cpio.gz \
--cmdline "nvx_overlay_upper=ramfs" \
--microvm-sandbox-block distro:file:distro.erofs,ro
```

OpenVMM rejects any other `nvx_overlay_upper` token, including a bare or
repeated one, and any other block in this layout. The layout supports neither
`--snapshot-destination` nor `--restore-snapshot`.

### microVM deterministic SMP

`--machine microvm --processors N` selects the microVM machine.
Expand Down
206 changes: 146 additions & 60 deletions openvmm/openvmm_defs/src/microvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,25 @@ 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> {
// A bare key has no value, so it fails like any value other than ramfs.
let mut values = cmdline.split_ascii_whitespace().filter_map(|token| {
let (name, value) = token.split_once('=').unwrap_or((token, ""));
(name == "nvx_overlay_upper").then_some(value)
});
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 +860,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 +906,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 +1065,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 +1694,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 +1908,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,69 +2300,128 @@ 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=").is_err());
assert!(ramfs_overlay_requested("quiet nvx_overlay_upper").is_err());
assert!(
ramfs_overlay_requested("nvx_overlay_upper=ramfs nvx_overlay_upper=ramfs").is_err()
);
assert!(ramfs_overlay_requested("nvx_overlay_upper=ramfs nvx_overlay_upper").is_err());
assert!(ramfs_overlay_requested("nvx_overlay_upper 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"));

let mut cmdline = format!("{MICROVM_BASE_COMMAND_LINE} nvx_overlay_upper");
assert!(
append_microvm_virtio_discovery(
&mut cmdline,
None,
false,
&[],
false,
false,
&[distro, scratch]
)
.is_err()
);
}
Expand Down
Loading
Loading