petri/vm/openvmm/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Code managing the lifetime of a `PetriVmOpenVmm`. All VMs live the same lifecycle:
5//! * A `PetriVmConfigOpenVmm` is built for the given firmware and architecture in `construct`.
6//! * The configuration is optionally modified from the defaults using the helpers in `modify`.
7//! * The `PetriVmOpenVmm` is started by the code in `start`.
8//! * The VM is interacted with through the methods in `runtime`.
9//! * The VM is either shut down by the code in `runtime`, or gets dropped and cleaned up automatically.
10
11mod construct;
12mod modify;
13mod runtime;
14mod start;
15
16pub use runtime::OpenVmmFramebufferAccess;
17pub use runtime::OpenVmmInspector;
18pub use runtime::PetriVmOpenVmm;
19
20use crate::Disk;
21use crate::Firmware;
22use crate::ModifyFn;
23use crate::OpenHclServicingFlags;
24use crate::OpenvmmLogConfig;
25use crate::PetriLogFile;
26use crate::PetriVmConfig;
27use crate::PetriVmResources;
28use crate::PetriVmRuntimeConfig;
29use crate::PetriVmgsDisk;
30use crate::PetriVmgsResource;
31use crate::PetriVmmBackend;
32use crate::VmmQuirks;
33use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
34use crate::vm::PetriVmProperties;
35use anyhow::Context;
36use async_trait::async_trait;
37use disk_backend_resources::LayeredDiskHandle;
38use disk_backend_resources::layer::DiskLayerHandle;
39use disk_backend_resources::layer::RamDiskLayerHandle;
40use get_resources::ged::FirmwareEvent;
41use guid::Guid;
42use hyperv_ic_resources::shutdown::ShutdownRpc;
43use mesh::Receiver;
44use mesh::Sender;
45use net_backend_resources::mac_address::MacAddress;
46use openvmm_defs::config::Config;
47use openvmm_helpers::disk::open_disk_type;
48use pal_async::DefaultDriver;
49use pal_async::socket::PolledSocket;
50use pal_async::task::Task;
51use petri_artifacts_common::tags::GuestQuirksInner;
52use petri_artifacts_common::tags::MachineArch;
53use petri_artifacts_core::ArtifactResolver;
54use petri_artifacts_core::ResolvedArtifact;
55use std::path::Path;
56use std::path::PathBuf;
57use std::sync::Arc;
58use std::time::Duration;
59use tempfile::TempPath;
60use unix_socket::UnixListener;
61use vm_resource::IntoResource;
62use vm_resource::Resource;
63use vm_resource::kind::DiskHandleKind;
64use vmgs_resources::VmgsDisk;
65use vmgs_resources::VmgsResource;
66
67/// The instance guid for the MANA nic automatically added when specifying `PetriVmConfigOpenVmm::with_nic`
68const MANA_INSTANCE: Guid = guid::guid!("f9641cf4-d915-4743-a7d8-efa75db7b85a");
69
70/// The MAC address used by the NIC assigned with [`PetriVmConfigOpenVmm::with_nic`].
71pub const NIC_MAC_ADDRESS: MacAddress = MacAddress::new([0x00, 0x15, 0x5D, 0x12, 0x12, 0x12]);
72
73/// OpenVMM Petri Backend
74#[derive(Debug)]
75pub struct OpenVmmPetriBackend {
76    openvmm_path: ResolvedArtifact,
77}
78
79#[async_trait]
80impl PetriVmmBackend for OpenVmmPetriBackend {
81    type VmmConfig = PetriVmConfigOpenVmm;
82    type VmRuntime = PetriVmOpenVmm;
83
84    fn check_compat(firmware: &Firmware, arch: MachineArch) -> bool {
85        arch == MachineArch::host()
86            && !(firmware.is_openhcl() && (!cfg!(windows) || arch == MachineArch::Aarch64))
87            && !(firmware.is_pcat() && arch == MachineArch::Aarch64)
88    }
89
90    fn quirks(firmware: &Firmware) -> (GuestQuirksInner, VmmQuirks) {
91        (
92            firmware.quirks().openvmm,
93            VmmQuirks {
94                // Workaround for #1684
95                flaky_boot: firmware.is_pcat().then_some(Duration::from_secs(15)),
96            },
97        )
98    }
99
100    fn default_servicing_flags() -> OpenHclServicingFlags {
101        OpenHclServicingFlags {
102            enable_nvme_keepalive: true,
103            enable_mana_keepalive: true,
104            override_version_checks: false,
105            stop_timeout_hint_secs: None,
106        }
107    }
108
109    fn create_guest_dump_disk() -> anyhow::Result<
110        Option<(
111            Arc<TempPath>,
112            Box<dyn FnOnce() -> anyhow::Result<Box<dyn fatfs::ReadWriteSeek>>>,
113        )>,
114    > {
115        Ok(None) // TODO #2403
116    }
117
118    fn new(resolver: &ArtifactResolver<'_>) -> Self {
119        OpenVmmPetriBackend {
120            openvmm_path: resolver
121                .require(petri_artifacts_vmm_test::artifacts::OPENVMM_NATIVE)
122                .erase(),
123        }
124    }
125
126    async fn run(
127        self,
128        config: PetriVmConfig,
129        modify_vmm_config: Option<ModifyFn<Self::VmmConfig>>,
130        resources: &PetriVmResources,
131        properties: PetriVmProperties,
132    ) -> anyhow::Result<(Self::VmRuntime, PetriVmRuntimeConfig)> {
133        let mut config =
134            PetriVmConfigOpenVmm::new(&self.openvmm_path, config, resources, properties).await?;
135
136        if let Some(f) = modify_vmm_config {
137            config = f.0(config);
138        }
139
140        config.run().await
141    }
142}
143
144/// Configuration state for a test VM.
145pub struct PetriVmConfigOpenVmm {
146    // Direct configuration related information.
147    runtime_config: PetriVmRuntimeConfig,
148    arch: MachineArch,
149    host_log_levels: Option<OpenvmmLogConfig>,
150    config: Config,
151
152    // Mesh host
153    mesh: mesh_process::Mesh,
154
155    // Runtime resources
156    resources: PetriVmResourcesOpenVmm,
157
158    // Logging
159    openvmm_log_file: PetriLogFile,
160
161    // Resources that are only used during startup.
162    ged: Option<get_resources::ged::GuestEmulationDeviceHandle>,
163    framebuffer_view: Option<framebuffer::View>,
164}
165/// Various channels and resources used to interact with the VM while it is running.
166struct PetriVmResourcesOpenVmm {
167    log_stream_tasks: Vec<Task<anyhow::Result<()>>>,
168    firmware_event_recv: Receiver<FirmwareEvent>,
169    shutdown_ic_send: Sender<ShutdownRpc>,
170    kvp_ic_send: Sender<hyperv_ic_resources::kvp::KvpConnectRpc>,
171    ged_send: Option<Sender<get_resources::ged::GuestEmulationRequest>>,
172    pipette_listener: PolledSocket<UnixListener>,
173    vtl2_pipette_listener: Option<PolledSocket<UnixListener>>,
174    linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
175
176    // Externally injected management stuff also needed at runtime.
177    driver: DefaultDriver,
178    openvmm_path: ResolvedArtifact,
179    output_dir: PathBuf,
180
181    // TempPaths that cannot be dropped until the end.
182    vtl2_vsock_path: Option<TempPath>,
183    _vmbus_vsock_path: TempPath,
184
185    // properties needed at runtime
186    properties: PetriVmProperties,
187}
188
189fn memdiff_disk(path: &Path) -> anyhow::Result<Resource<DiskHandleKind>> {
190    let disk = open_disk_type(path, true)
191        .with_context(|| format!("failed to open disk: {}", path.display()))?;
192    Ok(LayeredDiskHandle {
193        layers: vec![
194            RamDiskLayerHandle { len: None }.into_resource().into(),
195            DiskLayerHandle(disk).into_resource().into(),
196        ],
197    }
198    .into_resource())
199}
200
201fn memdiff_vmgs(vmgs: &PetriVmgsResource) -> anyhow::Result<VmgsResource> {
202    let convert_disk = |disk: &PetriVmgsDisk| -> anyhow::Result<VmgsDisk> {
203        Ok(VmgsDisk {
204            disk: petri_disk_to_openvmm(&disk.disk)?,
205            encryption_policy: disk.encryption_policy,
206        })
207    };
208
209    Ok(match vmgs {
210        PetriVmgsResource::Disk(disk) => VmgsResource::Disk(convert_disk(disk)?),
211        PetriVmgsResource::ReprovisionOnFailure(disk) => {
212            VmgsResource::ReprovisionOnFailure(convert_disk(disk)?)
213        }
214        PetriVmgsResource::Reprovision(disk) => VmgsResource::Reprovision(convert_disk(disk)?),
215        PetriVmgsResource::Ephemeral => VmgsResource::Ephemeral,
216    })
217}
218
219fn petri_disk_to_openvmm(disk: &Disk) -> anyhow::Result<Resource<DiskHandleKind>> {
220    Ok(match disk {
221        Disk::Memory(len) => {
222            LayeredDiskHandle::single_layer(RamDiskLayerHandle { len: Some(*len) }).into_resource()
223        }
224        Disk::Differencing(path) => memdiff_disk(path)?,
225        Disk::Persistent(path) => open_disk_type(path.as_ref(), false)?,
226        Disk::Temporary(path) => open_disk_type(path.as_ref(), false)?,
227    })
228}