Skip to content

Commit ec417c4

Browse files
committed
petri: hyper-v reset support (microsoft#1916)
Add support for guest resets for the Hyper-V petri backend. Refactors some of the framebuffer code to make this possible. Enables Hyper-V reboot tests that are now possible due to this change. Also includes some other minor Petri refactoring to move more logic into backend-agnostic code.
1 parent d59e810 commit ec417c4

File tree

20 files changed

+714
-427
lines changed

20 files changed

+714
-427
lines changed

Cargo.lock

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8898,7 +8898,6 @@ dependencies = [
88988898
"cfg-if",
88998899
"disk_backend_resources",
89008900
"futures",
8901-
"get_resources",
89028901
"guid",
89038902
"hvdef",
89048903
"hvlite_defs",
@@ -8925,7 +8924,6 @@ dependencies = [
89258924
"unix_socket",
89268925
"virtio_resources",
89278926
"vm_resource",
8928-
"vmm_core_defs",
89298927
"vmm_test_macros",
89308928
"vtl2_settings_proto",
89318929
"x86defs",

petri/petri_artifacts_common/src/lib.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,45 @@ pub mod tags {
6464
}
6565

6666
/// Quirks needed to boot a guest.
67-
#[derive(Default, Copy, Clone, Debug)]
68-
pub struct GuestQuirks {
67+
#[derive(Default, Clone, Debug)]
68+
pub struct GuestQuirksInner {
6969
/// How long to wait after the shutdown IC reports ready before sending
7070
/// the shutdown command.
7171
///
7272
/// This is necessary because some guests will ignore shutdown requests
7373
/// that arrive too early in the boot process.
7474
pub hyperv_shutdown_ic_sleep: Option<std::time::Duration>,
75+
/// Some guests reboot automatically soon after first boot.
76+
pub initial_reboot: Option<InitialRebootCondition>,
77+
}
78+
79+
/// Some guests may automatically reboot only in certain configurations
80+
#[derive(Clone, Copy, Debug)]
81+
pub enum InitialRebootCondition {
82+
/// This guest always reboots on this VMM.
83+
Always,
84+
/// This guest only reboot when using OpenHCL and UEFI on this VMM.
85+
WithOpenHclUefi,
86+
// TODO: add WithTpm here once with_tpm() is backend-agnostic.
87+
}
88+
89+
/// Quirks needed to boot a guest, allowing for differences based on backend
90+
#[derive(Default, Clone, Debug)]
91+
pub struct GuestQuirks {
92+
/// Quirks when running in OpenVMM
93+
pub openvmm: GuestQuirksInner,
94+
/// Quirks when running in Hyper-V
95+
pub hyperv: GuestQuirksInner,
96+
}
97+
98+
impl GuestQuirks {
99+
/// Use the same quirks for all backends
100+
pub fn for_all_backends(quirks: GuestQuirksInner) -> GuestQuirks {
101+
GuestQuirks {
102+
openvmm: quirks.clone(),
103+
hyperv: quirks,
104+
}
105+
}
75106
}
76107

77108
/// Artifact is a OpenHCL IGVM file

petri/src/vm/hyperv/hyperv.psm1

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,4 +432,20 @@ function Get-VmScreenshot
432432
[IO.File]::WriteAllBytes($Path, $image.ImageData)
433433

434434
return "$x,$y"
435+
}
436+
437+
function Set-TurnOffOnGuestRestart
438+
{
439+
[CmdletBinding()]
440+
Param (
441+
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
442+
[System.Object]
443+
$Vm,
444+
445+
[bool] $Enable
446+
)
447+
448+
$vssd = Get-Vssd $Vm
449+
$vssd.TurnOffOnGuestRestart = $Enable
450+
Set-VmSystemSettings $vssd
435451
}

petri/src/vm/hyperv/mod.rs

Lines changed: 24 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,14 @@ use crate::IsolationType;
1313
use crate::NoPetriVmInspector;
1414
use crate::OpenHclConfig;
1515
use crate::OpenHclServicingFlags;
16+
use crate::PetriHaltReason;
1617
use crate::PetriVmConfig;
17-
use crate::PetriVmFramebufferAccess;
1818
use crate::PetriVmResources;
1919
use crate::PetriVmRuntime;
2020
use crate::PetriVmmBackend;
2121
use crate::SecureBootTemplate;
2222
use crate::ShutdownKind;
2323
use crate::UefiConfig;
24-
use crate::VmScreenshotMeta;
2524
use crate::disk_image::AgentImage;
2625
use crate::hyperv::powershell::HyperVSecureBootTemplate;
2726
use crate::kmsg_log_task;
@@ -38,32 +37,31 @@ use pal_async::socket::PolledSocket;
3837
use pal_async::task::Spawn;
3938
use pal_async::task::Task;
4039
use pal_async::timer::PolledTimer;
40+
use petri_artifacts_common::tags::GuestQuirks;
41+
use petri_artifacts_common::tags::GuestQuirksInner;
4142
use petri_artifacts_common::tags::MachineArch;
4243
use petri_artifacts_common::tags::OsFlavor;
4344
use petri_artifacts_core::ArtifactResolver;
4445
use petri_artifacts_core::ResolvedArtifact;
4546
use pipette_client::PipetteClient;
46-
use std::fs;
4747
use std::io::ErrorKind;
4848
use std::io::Write;
4949
use std::path::Path;
50-
use std::sync::Arc;
51-
use std::sync::Weak;
5250
use std::time::Duration;
5351
use vm::HyperVVM;
54-
use vmm_core_defs::HaltReason;
5552

5653
/// The Hyper-V Petri backend
5754
pub struct HyperVPetriBackend {}
5855

5956
/// Resources needed at runtime for a Hyper-V Petri VM
6057
pub struct HyperVPetriRuntime {
61-
vm: Arc<HyperVVM>,
58+
vm: HyperVVM,
6259
log_tasks: Vec<Task<anyhow::Result<()>>>,
6360
temp_dir: tempfile::TempDir,
61+
driver: DefaultDriver,
62+
6463
is_openhcl: bool,
6564
is_isolated: bool,
66-
driver: DefaultDriver,
6765
}
6866

6967
#[async_trait]
@@ -77,6 +75,10 @@ impl PetriVmmBackend for HyperVPetriBackend {
7775
&& !(firmware.is_pcat() && arch == MachineArch::Aarch64)
7876
}
7977

78+
fn select_quirks(quirks: GuestQuirks) -> GuestQuirksInner {
79+
quirks.hyperv
80+
}
81+
8082
fn new(_resolver: &ArtifactResolver<'_>) -> Self {
8183
HyperVPetriBackend {}
8284
}
@@ -187,7 +189,6 @@ impl PetriVmmBackend for HyperVPetriBackend {
187189
guest_state_isolation_type,
188190
memory.startup_bytes,
189191
log_source.log_file("hyperv")?,
190-
firmware.expected_boot_event(),
191192
driver.clone(),
192193
)
193194
.await?;
@@ -292,7 +293,7 @@ impl PetriVmmBackend for HyperVPetriBackend {
292293
// location at runtime.
293294
let imc_hive = temp_dir.path().join("imc.hiv");
294295
{
295-
let mut imc_hive_file = fs::File::create_new(&imc_hive)?;
296+
let mut imc_hive_file = fs_err::File::create_new(&imc_hive)?;
296297
imc_hive_file
297298
.write_all(include_bytes!("../../../guest-bootstrap/imc.hiv"))
298299
.context("failed to write imc hive")?;
@@ -427,32 +428,28 @@ impl PetriVmmBackend for HyperVPetriBackend {
427428
vm.start().await?;
428429

429430
Ok(HyperVPetriRuntime {
430-
vm: Arc::new(vm),
431+
vm,
431432
log_tasks,
432433
temp_dir,
434+
driver: driver.clone(),
433435
is_openhcl: openhcl_config.is_some(),
434436
is_isolated: firmware.isolation().is_some(),
435-
driver: driver.clone(),
436437
})
437438
}
438439
}
439440

440441
#[async_trait]
441442
impl PetriVmRuntime for HyperVPetriRuntime {
442443
type VmInspector = NoPetriVmInspector;
443-
type VmFramebufferAccess = HyperVFramebufferAccess;
444+
type VmFramebufferAccess = vm::HyperVFramebufferAccess;
444445

445446
async fn teardown(mut self) -> anyhow::Result<()> {
446447
futures::future::join_all(self.log_tasks.into_iter().map(|t| t.cancel())).await;
447-
Arc::into_inner(self.vm)
448-
.context("all references to the Hyper-V VM object have not been closed")?
449-
.remove()
450-
.await
448+
self.vm.remove().await
451449
}
452450

453-
async fn wait_for_halt(&mut self) -> anyhow::Result<HaltReason> {
454-
self.vm.wait_for_halt().await?;
455-
Ok(HaltReason::PowerOff) // TODO: Get actual halt reason
451+
async fn wait_for_halt(&mut self, allow_reset: bool) -> anyhow::Result<PetriHaltReason> {
452+
self.vm.wait_for_halt(allow_reset).await
456453
}
457454

458455
async fn wait_for_agent(&mut self, set_high_vtl: bool) -> anyhow::Result<PipetteClient> {
@@ -501,10 +498,6 @@ impl PetriVmRuntime for HyperVPetriRuntime {
501498
})
502499
}
503500

504-
async fn wait_for_successful_boot_event(&mut self) -> anyhow::Result<()> {
505-
self.vm.wait_for_successful_boot_event().await
506-
}
507-
508501
async fn wait_for_boot_event(&mut self) -> anyhow::Result<FirmwareEvent> {
509502
self.vm.wait_for_boot_event().await
510503
}
@@ -531,26 +524,8 @@ impl PetriVmRuntime for HyperVPetriRuntime {
531524
self.vm.restart_openhcl(flags).await
532525
}
533526

534-
fn take_framebuffer_access(&mut self) -> Option<HyperVFramebufferAccess> {
535-
(!self.is_isolated).then(|| HyperVFramebufferAccess {
536-
vm: Arc::downgrade(&self.vm),
537-
})
538-
}
539-
}
540-
541-
/// Interface to the Hyper-V framebuffer
542-
pub struct HyperVFramebufferAccess {
543-
vm: Weak<HyperVVM>,
544-
}
545-
546-
#[async_trait]
547-
impl PetriVmFramebufferAccess for HyperVFramebufferAccess {
548-
async fn screenshot(&mut self, image: &mut Vec<u8>) -> anyhow::Result<VmScreenshotMeta> {
549-
self.vm
550-
.upgrade()
551-
.context("VM no longer exists")?
552-
.screenshot(image)
553-
.await
527+
fn take_framebuffer_access(&mut self) -> Option<vm::HyperVFramebufferAccess> {
528+
(!self.is_isolated).then(|| self.vm.get_framebuffer_access())
554529
}
555530
}
556531

@@ -644,13 +619,15 @@ async fn hyperv_serial_log_task(
644619
) -> anyhow::Result<()> {
645620
let mut timer = None;
646621
loop {
647-
match fs_err::OpenOptions::new()
622+
// using `std::fs` here instead of `fs_err` since `raw_os_error` always
623+
// returns `None` for `fs_err` errors.
624+
match std::fs::OpenOptions::new()
648625
.read(true)
649626
.write(true)
650627
.open(&serial_pipe_path)
651628
{
652629
Ok(file) => {
653-
let pipe = PolledPipe::new(&driver, file.into()).expect("failed to create pipe");
630+
let pipe = PolledPipe::new(&driver, file).expect("failed to create pipe");
654631
// connect/disconnect messages logged internally
655632
_ = crate::log_task(log_file.clone(), pipe, &serial_pipe_path).await;
656633
}
@@ -661,7 +638,7 @@ async fn hyperv_serial_log_task(
661638
if !(err.kind() == ErrorKind::NotFound
662639
|| matches!(err.raw_os_error(), Some(ERROR_PIPE_BUSY)))
663640
{
664-
tracing::warn!("{err:#}")
641+
tracing::warn!("failed to open {serial_pipe_path}: {err:#}",)
665642
}
666643
// Wait a bit and try again.
667644
timer

0 commit comments

Comments
 (0)