Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ slow-timeout = { period = "30s", terminate-after = 2 }
filter = 'package(~vmm_tests)'
# VMM tests contain their own watchdog timer, but keep an extra long timer
# here as a backup.
slow-timeout = { period = "15m", terminate-after = 1 }
slow-timeout = { period = "10m", terminate-after = 2 }
3 changes: 3 additions & 0 deletions .github/workflows/upload-petri-results.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ jobs:
BASE_URL: https://openvmmghtestresults.blob.core.windows.net/results

steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/[email protected]

- name: Authenticate to Azure
uses: azure/login@v1
with:
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5625,6 +5625,7 @@ dependencies = [
"chipset_resources",
"clap",
"diag_client",
"disk_backend",
"disk_backend_resources",
"disk_vhd1",
"disk_vhdmp",
Expand Down
1 change: 1 addition & 0 deletions petri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ petri_artifacts_core.workspace = true
petri_artifacts_vmm_test.workspace = true
chipset_resources.workspace = true
diag_client.workspace = true
disk_backend.workspace = true
disk_vhd1.workspace = true
hvlite_defs.workspace = true
hvlite_helpers.workspace = true
Expand Down
Binary file modified petri/guest-bootstrap/imc.hiv
Binary file not shown.
3 changes: 3 additions & 0 deletions petri/guest-bootstrap/user-data
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ bootcmd:
# Mounts module is not configured in some (all?) distros, so mount /cidata manually.
- [mkdir, -p, /cidata]
- [mount, LABEL=cidata, /cidata, -o, ro]
# Mount the crash dump folder manually too
- [mkdir, -p, /crash]
- [mount, LABEL=crashdump, /crash, -o, rw]
# Disable snapd, which takes a long time to start and is not needed.
- systemctl disable snapd.service snapd.apparmor snapd.seeded.service || true
- systemctl mask snapd.service || true
Expand Down
6 changes: 3 additions & 3 deletions petri/guest-bootstrap/user-data-no-agent
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ users:
groups: users, admin

bootcmd:
# Mounts module is not configured in some (all?) distros, so mount /cidata manually.
- [mkdir, -p, /cidata]
- [mount, LABEL=cidata, /cidata, -o, ro]
# Mount the crash dump folder for if things go wrong
- [mkdir, -p, /crash]
- [mount, LABEL=crashdump, /crash, -o, rw]
# Disable snapd, which takes a long time to start and is not needed.
- systemctl disable snapd.service snapd.apparmor snapd.seeded.service || true
- systemctl mask snapd.service || true
13 changes: 13 additions & 0 deletions petri/make_imc_hive/src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ pub(crate) fn main() -> anyhow::Result<()> {
)?;
vmbus_key.set_dword("AllowAllDevicesWhenIsolated", 1)?;

// Enable kernel mode crash dump and info
let crash_control_key = create_subkeys(
&hive,
&["SYSTEM", "CurrentControlSet", "Control", "CrashControl"],
)?;
crash_control_key.set_dword("AutoReboot", 1)?;
crash_control_key.set_dword("AlwaysKeepMemoryDump", 1)?;
crash_control_key.set_dword("CrashDumpEnabled", 2)?; // kernel memory dump
crash_control_key.set_sz("DedicatedDumpFile", "E:\\dumpfile.dmp")?;
crash_control_key.set_expand_sz("DumpFile", "E:\\memory.dmp")?;
// Set the size to the largest possible size FAT32 lets us have
crash_control_key.set_dword("DumpFileSize", 4095)?; // in MB

// Windows defaults to 1, so we need to set it to 2 to cause Windows to
// apply the IMC changes on first boot.
hive.set_dword("Sequence", 2)?;
Expand Down
18 changes: 18 additions & 0 deletions petri/make_imc_hive/src/windows/offreg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use windows_sys::Wdk::System::OfflineRegistry::ORHKEY;
use windows_sys::Wdk::System::OfflineRegistry::ORSaveHive;
use windows_sys::Wdk::System::OfflineRegistry::ORSetValue;
use windows_sys::Win32::System::Registry::REG_DWORD;
use windows_sys::Win32::System::Registry::REG_EXPAND_SZ;
use windows_sys::Win32::System::Registry::REG_MULTI_SZ;
use windows_sys::Win32::System::Registry::REG_SZ;

Expand Down Expand Up @@ -153,6 +154,23 @@ impl Key {
Ok(())
}

pub fn set_expand_sz(&self, name: &str, value: &str) -> std::io::Result<()> {
let name16 = name.encode_utf16().chain([0]).collect::<Vec<_>>();
let value16 = value.encode_utf16().chain([0]).collect::<Vec<_>>();
// SAFETY: calling as documented with owned key and null-terminated
// name and value.
unsafe {
chk(ORSetValue(
self.0,
name16.as_ptr(),
REG_EXPAND_SZ,
value16.as_ptr().cast(),
value16.len() as u32 * 2,
))?;
}
Ok(())
}

pub fn set_multi_sz<'a>(
&self,
name: &str,
Expand Down
7 changes: 4 additions & 3 deletions petri/src/disk_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,14 @@ impl AgentImage {
}
}

enum PathOrBinary<'a> {
pub(crate) const SECTOR_SIZE: u64 = 512;

pub(crate) enum PathOrBinary<'a> {
Path(&'a Path),
Binary(&'a [u8]),
}

fn build_fat32_disk_image(
pub(crate) fn build_fat32_disk_image(
file: &mut (impl Read + Write + Seek),
gpt_name: &str,
volume_label: &[u8; 11],
Expand All @@ -163,7 +165,6 @@ fn build_fat32_disk_image(
}

fn build_gpt(file: &mut (impl Read + Write + Seek), name: &str) -> anyhow::Result<Range<u64>> {
const SECTOR_SIZE: u64 = 512;
let mut gpt = gptman::GPT::new_from(file, SECTOR_SIZE, Guid::new_random().into())?;

// Set up the "Protective" Master Boot Record
Expand Down
10 changes: 5 additions & 5 deletions petri/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl Test {

for hook in post_test_hooks {
tracing::info!(name = hook.name(), "Running post-test hook");
if let Err(e) = hook.run() {
if let Err(e) = hook.run(r.is_ok()) {
tracing::error!(
error = e.as_ref() as &dyn std::error::Error,
"Post-test hook failed"
Expand Down Expand Up @@ -247,11 +247,11 @@ pub struct PetriPostTestHook {
/// The name of the hook.
name: String,
/// The hook function.
hook: Box<dyn FnOnce() -> anyhow::Result<()> + Send>,
hook: Box<dyn FnOnce(bool) -> anyhow::Result<()>>,
}

impl PetriPostTestHook {
pub fn new(name: String, hook: impl FnOnce() -> anyhow::Result<()> + Send + 'static) -> Self {
pub fn new(name: String, hook: impl FnOnce(bool) -> anyhow::Result<()> + 'static) -> Self {
Self {
name,
hook: Box::new(hook),
Expand All @@ -262,8 +262,8 @@ impl PetriPostTestHook {
&self.name
}

pub fn run(self) -> anyhow::Result<()> {
(self.hook)()
pub fn run(self, test_passed: bool) -> anyhow::Result<()> {
(self.hook)(test_passed)
}
}

Expand Down
67 changes: 67 additions & 0 deletions petri/src/vm/hyperv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ use crate::openhcl_diag::OpenHclDiagHandler;
use crate::vm::append_cmdline;
use anyhow::Context;
use async_trait::async_trait;
use disk_backend::sync_wrapper::BlockingDisk;
use disk_vhdmp::VhdmpDisk;
use get_resources::ged::FirmwareEvent;
use pal_async::DefaultDriver;
use pal_async::pipe::PolledPipe;
Expand All @@ -50,7 +52,9 @@ use std::io::ErrorKind;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempPath;
use vm::HyperVVM;
use vmgs_resources::GuestStateEncryptionPolicy;

Expand Down Expand Up @@ -159,6 +163,58 @@ impl PetriVmmBackend for HyperVPetriBackend {
}
}

fn create_guest_dump_disk() -> anyhow::Result<
Option<(
Arc<TempPath>,
Box<dyn FnOnce() -> anyhow::Result<Box<dyn fatfs::ReadWriteSeek>>>,
)>,
> {
// Make a 16 GiB dynamic VHD for guest crash dumps.
let crash_disk = tempfile::Builder::new()
.suffix(".vhdx")
.make(|path| disk_vhdmp::Vhd::create_dynamic(path, 16 * 1024, true))
.context("error creating crash dump vhdx")?;
let (crash_disk, crash_disk_path) = crash_disk.into_parts();
crash_disk
.attach_for_raw_access(false)
.context("error attaching crash dump vhdx")?;
let mut crash_disk = BlockingDisk::new(
disk_backend::Disk::new(
VhdmpDisk::new(crash_disk, false).context("failed opening vhdmp")?,
)
.unwrap(),
);

// Format the VHD with FAT32.
crate::disk_image::build_fat32_disk_image(
&mut crash_disk,
"CRASHDUMP",
b"crashdump ",
&[],
)
.context("error writing empty crash disk filesystem")?;

// Prepare the hook to extract crash dumps after the test.
let crash_disk_path = Arc::new(crash_disk_path);
let hook_crash_disk = crash_disk_path.clone();
let disk_opener = Box::new(move || {
let mut vhd = Err(anyhow::Error::msg("haven't tried to open the vhd yet"));
// The VM may not be fully shut down immediately, do some retries
for _ in 0..5 {
vhd = VhdmpDisk::open_vhd(hook_crash_disk.as_ref(), true)
.context("failed opening vhd");
if vhd.is_ok() {
break;
} else {
std::thread::sleep(Duration::from_secs(3));
}
}
let vhdmp = VhdmpDisk::new(vhd?, true).context("failed opening vhdmp")?;
Ok(Box::new(BlockingDisk::new(disk_backend::Disk::new(vhdmp).unwrap())) as _)
});
Ok(Some((crash_disk_path, disk_opener)))
}

fn new(_resolver: &ArtifactResolver<'_>) -> Self {
HyperVPetriBackend {}
}
Expand All @@ -180,6 +236,7 @@ impl PetriVmmBackend for HyperVPetriBackend {
boot_device_type,
vmgs,
tpm_state_persistence,
guest_crash_disk,
} = config;

let PetriVmResources { driver, log_source } = resources;
Expand Down Expand Up @@ -570,6 +627,16 @@ impl PetriVmmBackend for HyperVPetriBackend {
}
}

if let Some(guest_crash_disk) = guest_crash_disk {
vm.add_vhd(
&guest_crash_disk,
powershell::ControllerType::Scsi,
Some(super::PETRI_VTL0_SCSI_CRASH_LUN),
Some(petri_vtl0_scsi),
)
.await?;
}

let serial_pipe_path = vm.set_vm_com_port(1).await?;
let serial_log_file = log_source.log_file("guest")?;
log_tasks.push(driver.spawn(
Expand Down
65 changes: 65 additions & 0 deletions petri/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::PetriLogSource;
use crate::PetriTestParams;
use crate::ShutdownKind;
use crate::disk_image::AgentImage;
use crate::disk_image::SECTOR_SIZE;
use crate::openhcl_diag::OpenHclDiagHandler;
use async_trait::async_trait;
use get_resources::ged::FirmwareEvent;
Expand All @@ -37,7 +38,9 @@ use std::hash::Hash;
use std::hash::Hasher;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempPath;
use vmgs_resources::GuestStateEncryptionPolicy;

/// The set of artifacts and resources needed to instantiate a
Expand Down Expand Up @@ -124,6 +127,8 @@ pub struct PetriVmConfig {
pub agent_image: Option<AgentImage>,
/// Agent to run in OpenHCL
pub openhcl_agent_image: Option<AgentImage>,
/// Disk to use for guest crash dumps
pub guest_crash_disk: Option<Arc<TempPath>>,
/// VM guest state
pub vmgs: PetriVmgsResource,
/// The boot device type for the VM
Expand Down Expand Up @@ -157,6 +162,15 @@ pub trait PetriVmmBackend {
/// Get the default servicing flags (based on what this backend supports)
fn default_servicing_flags() -> OpenHclServicingFlags;

/// Create a disk for guest crash dumps, and a post-test hook to open the disk
/// to allow for reading the dumps.
fn create_guest_dump_disk() -> anyhow::Result<
Option<(
Arc<TempPath>,
Box<dyn FnOnce() -> anyhow::Result<Box<dyn fatfs::ReadWriteSeek>>>,
)>,
>;

/// Resolve any artifacts needed to use this backend
fn new(resolver: &ArtifactResolver<'_>) -> Self;

Expand All @@ -171,6 +185,7 @@ pub trait PetriVmmBackend {

pub(crate) const PETRI_VTL0_SCSI_BOOT_LUN: u8 = 0;
pub(crate) const PETRI_VTL0_SCSI_PIPETTE_LUN: u8 = 1;
pub(crate) const PETRI_VTL0_SCSI_CRASH_LUN: u8 = 2;

/// A constructed Petri VM
pub struct PetriVm<T: PetriVmmBackend> {
Expand Down Expand Up @@ -209,6 +224,55 @@ impl<T: PetriVmmBackend> PetriVmBuilder<T> {
Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => BootDeviceType::Scsi,
};

let guest_crash_disk = if matches!(
artifacts.firmware.os_flavor(),
OsFlavor::Windows | OsFlavor::Linux
) {
let (guest_crash_disk, guest_dump_disk_hook) = T::create_guest_dump_disk()?.unzip();
if let Some(guest_dump_disk_hook) = guest_dump_disk_hook {
let logger = params.logger.clone();
params
.post_test_hooks
.push(crate::test::PetriPostTestHook::new(
"extract guest crash dumps".into(),
move |test_passed| {
if test_passed {
return Ok(());
}
let mut disk = guest_dump_disk_hook()?;
let gpt = gptman::GPT::read_from(&mut disk, SECTOR_SIZE)?;
let partition = fscommon::StreamSlice::new(
&mut disk,
gpt[1].starting_lba * SECTOR_SIZE,
gpt[1].ending_lba * SECTOR_SIZE,
)?;
let fs = fatfs::FileSystem::new(partition, fatfs::FsOptions::new())?;
for entry in fs.root_dir().iter() {
let Ok(entry) = entry else {
tracing::warn!(
?entry,
"failed to read entry in guest crash dump disk"
);
continue;
};
if !entry.is_file() {
tracing::warn!(
?entry,
"skipping non-file entry in guest crash dump disk"
);
continue;
}
logger.write_attachment(&entry.file_name(), entry.to_file())?;
}
Ok(())
},
));
}
guest_crash_disk
} else {
None
};

Ok(Self {
backend: artifacts.backend,
config: PetriVmConfig {
Expand All @@ -222,6 +286,7 @@ impl<T: PetriVmmBackend> PetriVmBuilder<T> {
openhcl_agent_image: artifacts.openhcl_agent_image,
vmgs: PetriVmgsResource::Ephemeral,
tpm_state_persistence: true,
guest_crash_disk,
},
modify_vmm_config: None,
resources: PetriVmResources {
Expand Down
Loading
Loading