1mod construct;
12#[cfg(target_os = "linux")]
13mod hugetlb;
14mod modify;
15mod mshv;
16mod runtime;
17mod start;
18
19#[cfg(target_os = "linux")]
20pub use hugetlb::HUGETLB_2MB_PAGE_SIZE;
21#[cfg(target_os = "linux")]
22pub use hugetlb::ensure_2mb_hugetlb_pages;
23pub use runtime::OpenVmmFramebufferAccess;
24pub use runtime::OpenVmmInspector;
25pub use runtime::PetriVmOpenVmm;
26
27use crate::Disk;
28use crate::DiskPath;
29use crate::Firmware;
30use crate::ModifyFn;
31use crate::OpenHclServicingFlags;
32use crate::OpenvmmLogConfig;
33use crate::PetriLogFile;
34use crate::PetriVmConfig;
35use crate::PetriVmResources;
36use crate::PetriVmRuntimeConfig;
37use crate::PetriVmgsDisk;
38use crate::PetriVmgsResource;
39use crate::PetriVmmBackend;
40use crate::VmmQuirks;
41use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
42use crate::vm::PetriVmProperties;
43use anyhow::Context;
44use async_trait::async_trait;
45use disk_backend_resources::DiskLayerDescription;
46use disk_backend_resources::LayeredDiskHandle;
47use disk_backend_resources::layer::DiskLayerHandle;
48use disk_backend_resources::layer::RamDiskLayerHandle;
49use disk_backend_resources::layer::SqliteAutoCacheDiskLayerHandle;
50use get_resources::ged::FirmwareEvent;
51use guid::Guid;
52use hyperv_ic_resources::shutdown::ShutdownRpc;
53use mesh::Receiver;
54use mesh::Sender;
55use net_backend_resources::mac_address::MacAddress;
56use openvmm_defs::config::Config;
57use openvmm_helpers::disk::OpenDiskOptions;
58use openvmm_helpers::disk::open_disk_type;
59use pal_async::DefaultDriver;
60use pal_async::socket::PolledSocket;
61use pal_async::task::Task;
62use petri_artifacts_common::tags::GuestQuirksInner;
63use petri_artifacts_common::tags::MachineArch;
64use petri_artifacts_core::ArtifactResolver;
65use petri_artifacts_core::ResolvedArtifact;
66use std::path::Path;
67use std::path::PathBuf;
68use std::sync::Arc;
69use std::time::Duration;
70use tempfile::TempPath;
71use unix_socket::UnixListener;
72use vm_resource::IntoResource;
73use vm_resource::Resource;
74use vm_resource::kind::DiskHandleKind;
75use vmgs_resources::VmgsDisk;
76use vmgs_resources::VmgsResource;
77
78const MANA_INSTANCE: Guid = guid::guid!("f9641cf4-d915-4743-a7d8-efa75db7b85a");
80
81pub const NIC_MAC_ADDRESS: MacAddress = MacAddress::new([0x00, 0x15, 0x5D, 0x12, 0x12, 0x12]);
83
84#[derive(Debug)]
86pub struct OpenVmmPetriBackend {
87 openvmm_path: ResolvedArtifact,
88}
89
90impl OpenVmmPetriBackend {
91 pub fn from_binary(path: PathBuf) -> Self {
93 Self {
94 openvmm_path: ResolvedArtifact::from_local_path(path),
95 }
96 }
97}
98
99#[async_trait]
100impl PetriVmmBackend for OpenVmmPetriBackend {
101 type VmmConfig = PetriVmConfigOpenVmm;
102 type VmRuntime = PetriVmOpenVmm;
103 const SUPPORTS_VMBUS: bool = true;
104 const SUPPORTS_FILE_SHARING: bool = cfg!(any(windows, target_os = "linux"));
105
106 fn check_compat(firmware: &Firmware, arch: MachineArch) -> bool {
107 !(firmware.is_openhcl() && (!cfg!(windows) || arch == MachineArch::Aarch64))
108 }
109
110 fn quirks(firmware: &Firmware) -> (GuestQuirksInner, VmmQuirks) {
111 (
112 firmware.quirks().openvmm,
113 VmmQuirks {
114 flaky_boot: firmware.is_pcat().then_some(Duration::from_secs(15)),
116 },
117 )
118 }
119
120 fn default_servicing_flags() -> OpenHclServicingFlags {
121 OpenHclServicingFlags {
122 enable_nvme_keepalive: true,
123 enable_mana_keepalive: true,
124 override_version_checks: false,
125 stop_timeout_hint_secs: None,
126 }
127 }
128
129 fn create_guest_dump_disk() -> anyhow::Result<
130 Option<(
131 Arc<TempPath>,
132 Box<dyn FnOnce() -> anyhow::Result<Box<dyn fatfs::ReadWriteSeek>>>,
133 )>,
134 > {
135 Ok(None) }
137
138 fn build_custom_init_script(_pipette_path: &str) -> Option<String> {
139 None
140 }
141
142 fn new(resolver: &ArtifactResolver<'_>, arch: MachineArch) -> Self {
143 assert_eq!(arch, MachineArch::host());
145 OpenVmmPetriBackend {
146 openvmm_path: resolver
147 .require(petri_artifacts_vmm_test::artifacts::OPENVMM_NATIVE)
148 .erase(),
149 }
150 }
151
152 async fn run(
153 self,
154 config: PetriVmConfig,
155 modify_vmm_config: Option<ModifyFn<Self::VmmConfig>>,
156 resources: &PetriVmResources,
157 properties: &PetriVmProperties,
158 ) -> anyhow::Result<(Self::VmRuntime, PetriVmRuntimeConfig)> {
159 let mut config =
160 PetriVmConfigOpenVmm::new(&self.openvmm_path, config, resources, properties).await?;
161
162 if let Some(f) = modify_vmm_config {
163 config = f.0(config);
164 }
165
166 config.run().await
167 }
168}
169
170pub struct PetriVmConfigOpenVmm {
172 guest_shares: std::collections::HashMap<String, crate::GuestShare>,
173 runtime_config: PetriVmRuntimeConfig,
175 arch: MachineArch,
176 host_log_levels: Option<OpenvmmLogConfig>,
177 config: Config,
178
179 mesh: mesh_process::Mesh,
181
182 resources: PetriVmResourcesOpenVmm,
184
185 openvmm_log_file: PetriLogFile,
187
188 memory_backing_file: Option<PathBuf>,
190
191 requested_private_memory: Option<bool>,
197
198 ged: Option<get_resources::ged::GuestEmulationDeviceHandle>,
200 framebuffer_view: Option<framebuffer::View>,
201
202 pending_iommu: Vec<(String, openvmm_defs::config::PcieIommuConfig)>,
205}
206struct PetriVmResourcesOpenVmm {
208 log_stream_tasks: Vec<Task<anyhow::Result<()>>>,
209 firmware_event_recv: Receiver<FirmwareEvent>,
210 ipmi_sel_event_recv: Receiver<get_resources::ged::IpmiSelEvent>,
211 shutdown_ic_send: Option<Sender<ShutdownRpc>>,
212 kvp_ic_send: Option<Sender<hyperv_ic_resources::kvp::KvpConnectRpc>>,
213 ged_send: Option<Sender<get_resources::ged::GuestEmulationRequest>>,
214 pipette_listener: PolledSocket<UnixListener>,
215 vtl2_pipette_listener: Option<PolledSocket<UnixListener>>,
216 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
217
218 tcp_pipette_port: Option<mesh::OneshotReceiver<u16>>,
224
225 driver: DefaultDriver,
227 openvmm_path: ResolvedArtifact,
228 output_dir: PathBuf,
229
230 vtl2_vsock_path: Option<TempPath>,
232 _vsock_path: TempPath,
233
234 properties: PetriVmProperties,
236
237 #[cfg(windows)]
241 _switch_ports: Vec<vmswitch::kernel::SwitchPort>,
242}
243
244#[cfg(windows)]
256pub fn find_switch() -> Option<Guid> {
257 if vmswitch::hcn::Network::open(&vmswitch::hcn::DEFAULT_SWITCH).is_ok() {
258 return Some(vmswitch::hcn::DEFAULT_SWITCH);
259 }
260 let networks = match vmswitch::hcn::enumerate_networks() {
261 Ok(n) => n,
262 Err(e) => {
263 tracing::warn!(
264 error = &e as &dyn std::error::Error,
265 "failed to enumerate HCN networks"
266 );
267 return None;
268 }
269 };
270 networks.into_iter().find(|guid| {
271 if let Err(e) = vmswitch::hcn::Network::open(guid) {
272 tracing::debug!(
273 %guid,
274 error = &e as &dyn std::error::Error,
275 "skipping unopenable HCN network"
276 );
277 false
278 } else {
279 true
280 }
281 })
282}
283
284#[cfg(not(windows))]
288pub fn find_switch() -> Option<Guid> {
289 None
290}
291
292async fn memdiff_disk(path: &Path) -> anyhow::Result<Resource<DiskHandleKind>> {
293 let disk = open_disk_type(
294 path,
295 OpenDiskOptions {
296 read_only: true,
297 direct: false,
298 },
299 )
300 .await
301 .with_context(|| format!("failed to open disk: {}", path.display()))?;
302 Ok(LayeredDiskHandle {
303 layers: vec![
304 RamDiskLayerHandle {
305 len: None,
306 sector_size: None,
307 }
308 .into_resource()
309 .into(),
310 DiskLayerHandle(disk).into_resource().into(),
311 ],
312 }
313 .into_resource())
314}
315
316fn memdiff_remote_disk(url: &str) -> anyhow::Result<Resource<DiskHandleKind>> {
317 let url_path = url.split(['?', '#']).next().unwrap_or(url);
319 let format = if url_path.ends_with(".vhd") || url_path.ends_with(".vmgs") {
320 disk_backend_resources::BlobDiskFormat::FixedVhd1
321 } else {
322 disk_backend_resources::BlobDiskFormat::Flat
323 };
324
325 let cache_dir = super::petri_disk_cache_dir();
326
327 let cache_key = match format {
333 disk_backend_resources::BlobDiskFormat::FixedVhd1 => None,
334 disk_backend_resources::BlobDiskFormat::Flat => {
335 Some(url_path.rsplit('/').next().unwrap_or(url_path).to_owned())
336 }
337 };
338
339 Ok(LayeredDiskHandle {
340 layers: vec![
341 RamDiskLayerHandle {
342 len: None,
343 sector_size: None,
344 }
345 .into_resource()
346 .into(),
347 DiskLayerDescription {
348 read_cache: true,
349 write_through: false,
350 layer: SqliteAutoCacheDiskLayerHandle {
351 cache_path: cache_dir,
352 cache_key,
353 }
354 .into_resource(),
355 },
356 DiskLayerHandle(
357 disk_backend_resources::BlobDiskHandle {
358 url: url.to_owned(),
359 format,
360 }
361 .into_resource(),
362 )
363 .into_resource()
364 .into(),
365 ],
366 }
367 .into_resource())
368}
369
370async fn memdiff_vmgs(vmgs: &PetriVmgsResource) -> anyhow::Result<VmgsResource> {
371 async fn convert_disk(disk: &PetriVmgsDisk) -> anyhow::Result<VmgsDisk> {
372 Ok(VmgsDisk {
373 disk: petri_disk_to_openvmm(&disk.disk).await?,
374 encryption_policy: disk.encryption_policy,
375 })
376 }
377
378 Ok(match vmgs {
379 PetriVmgsResource::Disk(disk) => VmgsResource::Disk(convert_disk(disk).await?),
380 PetriVmgsResource::ReprovisionOnFailure(disk) => {
381 VmgsResource::ReprovisionOnFailure(convert_disk(disk).await?)
382 }
383 PetriVmgsResource::Reprovision(disk) => {
384 VmgsResource::Reprovision(convert_disk(disk).await?)
385 }
386 PetriVmgsResource::Ephemeral => VmgsResource::Ephemeral,
387 })
388}
389
390async fn petri_disk_to_openvmm(disk: &Disk) -> anyhow::Result<Resource<DiskHandleKind>> {
391 Ok(match disk {
392 Disk::Memory(len) => LayeredDiskHandle::single_layer(RamDiskLayerHandle {
393 len: Some(*len),
394 sector_size: None,
395 })
396 .into_resource(),
397 Disk::Differencing(DiskPath::Local(path)) => memdiff_disk(path).await?,
398 Disk::Differencing(DiskPath::Remote { url }) => memdiff_remote_disk(url)?,
399 Disk::Persistent(path) => {
400 open_disk_type(
401 path.as_ref(),
402 OpenDiskOptions {
403 read_only: false,
404 direct: false,
405 },
406 )
407 .await?
408 }
409 Disk::Temporary(path) => {
410 open_disk_type(
411 path.as_ref(),
412 OpenDiskOptions {
413 read_only: false,
414 direct: false,
415 },
416 )
417 .await?
418 }
419 })
420}