1mod 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::DiskPath;
22use crate::Firmware;
23use crate::ModifyFn;
24use crate::OpenHclServicingFlags;
25use crate::OpenvmmLogConfig;
26use crate::PetriLogFile;
27use crate::PetriVmConfig;
28use crate::PetriVmResources;
29use crate::PetriVmRuntimeConfig;
30use crate::PetriVmgsDisk;
31use crate::PetriVmgsResource;
32use crate::PetriVmmBackend;
33use crate::VmmQuirks;
34use crate::linux_direct_serial_agent::LinuxDirectSerialAgent;
35use crate::vm::PetriVmProperties;
36use anyhow::Context;
37use async_trait::async_trait;
38use disk_backend_resources::DiskLayerDescription;
39use disk_backend_resources::LayeredDiskHandle;
40use disk_backend_resources::layer::DiskLayerHandle;
41use disk_backend_resources::layer::RamDiskLayerHandle;
42use disk_backend_resources::layer::SqliteAutoCacheDiskLayerHandle;
43use get_resources::ged::FirmwareEvent;
44use guid::Guid;
45use hyperv_ic_resources::shutdown::ShutdownRpc;
46use mesh::Receiver;
47use mesh::Sender;
48use net_backend_resources::mac_address::MacAddress;
49use openvmm_defs::config::Config;
50use openvmm_helpers::disk::OpenDiskOptions;
51use openvmm_helpers::disk::open_disk_type;
52use pal_async::DefaultDriver;
53use pal_async::socket::PolledSocket;
54use pal_async::task::Task;
55use petri_artifacts_common::tags::GuestQuirksInner;
56use petri_artifacts_common::tags::MachineArch;
57use petri_artifacts_core::ArtifactResolver;
58use petri_artifacts_core::ResolvedArtifact;
59use std::path::Path;
60use std::path::PathBuf;
61use std::sync::Arc;
62use std::time::Duration;
63use tempfile::TempPath;
64use unix_socket::UnixListener;
65use vm_resource::IntoResource;
66use vm_resource::Resource;
67use vm_resource::kind::DiskHandleKind;
68use vmgs_resources::VmgsDisk;
69use vmgs_resources::VmgsResource;
70
71const MANA_INSTANCE: Guid = guid::guid!("f9641cf4-d915-4743-a7d8-efa75db7b85a");
73
74pub const NIC_MAC_ADDRESS: MacAddress = MacAddress::new([0x00, 0x15, 0x5D, 0x12, 0x12, 0x12]);
76
77#[derive(Debug)]
79pub struct OpenVmmPetriBackend {
80 openvmm_path: ResolvedArtifact,
81}
82
83#[async_trait]
84impl PetriVmmBackend for OpenVmmPetriBackend {
85 type VmmConfig = PetriVmConfigOpenVmm;
86 type VmRuntime = PetriVmOpenVmm;
87
88 fn check_compat(firmware: &Firmware, arch: MachineArch) -> bool {
89 arch == MachineArch::host()
90 && !(firmware.is_openhcl() && (!cfg!(windows) || arch == MachineArch::Aarch64))
91 && !(firmware.is_pcat() && arch == MachineArch::Aarch64)
92 }
93
94 fn quirks(firmware: &Firmware) -> (GuestQuirksInner, VmmQuirks) {
95 (
96 firmware.quirks().openvmm,
97 VmmQuirks {
98 flaky_boot: firmware.is_pcat().then_some(Duration::from_secs(15)),
100 },
101 )
102 }
103
104 fn default_servicing_flags() -> OpenHclServicingFlags {
105 OpenHclServicingFlags {
106 enable_nvme_keepalive: true,
107 enable_mana_keepalive: true,
108 override_version_checks: false,
109 stop_timeout_hint_secs: None,
110 }
111 }
112
113 fn create_guest_dump_disk() -> anyhow::Result<
114 Option<(
115 Arc<TempPath>,
116 Box<dyn FnOnce() -> anyhow::Result<Box<dyn fatfs::ReadWriteSeek>>>,
117 )>,
118 > {
119 Ok(None) }
121
122 fn new(resolver: &ArtifactResolver<'_>) -> Self {
123 OpenVmmPetriBackend {
124 openvmm_path: resolver
125 .require(petri_artifacts_vmm_test::artifacts::OPENVMM_NATIVE)
126 .erase(),
127 }
128 }
129
130 async fn run(
131 self,
132 config: PetriVmConfig,
133 modify_vmm_config: Option<ModifyFn<Self::VmmConfig>>,
134 resources: &PetriVmResources,
135 properties: PetriVmProperties,
136 ) -> anyhow::Result<(Self::VmRuntime, PetriVmRuntimeConfig)> {
137 let mut config =
138 PetriVmConfigOpenVmm::new(&self.openvmm_path, config, resources, properties).await?;
139
140 if let Some(f) = modify_vmm_config {
141 config = f.0(config);
142 }
143
144 config.run().await
145 }
146}
147
148pub struct PetriVmConfigOpenVmm {
150 runtime_config: PetriVmRuntimeConfig,
152 arch: MachineArch,
153 host_log_levels: Option<OpenvmmLogConfig>,
154 config: Config,
155
156 mesh: mesh_process::Mesh,
158
159 resources: PetriVmResourcesOpenVmm,
161
162 openvmm_log_file: PetriLogFile,
164
165 memory_backing_file: Option<PathBuf>,
167
168 ged: Option<get_resources::ged::GuestEmulationDeviceHandle>,
170 framebuffer_view: Option<framebuffer::View>,
171}
172struct PetriVmResourcesOpenVmm {
174 log_stream_tasks: Vec<Task<anyhow::Result<()>>>,
175 firmware_event_recv: Receiver<FirmwareEvent>,
176 shutdown_ic_send: Sender<ShutdownRpc>,
177 kvp_ic_send: Sender<hyperv_ic_resources::kvp::KvpConnectRpc>,
178 ged_send: Option<Sender<get_resources::ged::GuestEmulationRequest>>,
179 pipette_listener: PolledSocket<UnixListener>,
180 vtl2_pipette_listener: Option<PolledSocket<UnixListener>>,
181 linux_direct_serial_agent: Option<LinuxDirectSerialAgent>,
182
183 driver: DefaultDriver,
185 openvmm_path: ResolvedArtifact,
186 output_dir: PathBuf,
187
188 vtl2_vsock_path: Option<TempPath>,
190 _vmbus_vsock_path: TempPath,
191
192 properties: PetriVmProperties,
194}
195
196async fn memdiff_disk(path: &Path) -> anyhow::Result<Resource<DiskHandleKind>> {
197 let disk = open_disk_type(
198 path,
199 OpenDiskOptions {
200 read_only: true,
201 direct: false,
202 },
203 )
204 .await
205 .with_context(|| format!("failed to open disk: {}", path.display()))?;
206 Ok(LayeredDiskHandle {
207 layers: vec![
208 RamDiskLayerHandle {
209 len: None,
210 sector_size: None,
211 }
212 .into_resource()
213 .into(),
214 DiskLayerHandle(disk).into_resource().into(),
215 ],
216 }
217 .into_resource())
218}
219
220fn memdiff_remote_disk(url: &str) -> anyhow::Result<Resource<DiskHandleKind>> {
221 let url_path = url.split(['?', '#']).next().unwrap_or(url);
223 let format = if url_path.ends_with(".vhd") || url_path.ends_with(".vmgs") {
224 disk_backend_resources::BlobDiskFormat::FixedVhd1
225 } else {
226 disk_backend_resources::BlobDiskFormat::Flat
227 };
228
229 let cache_dir = super::petri_disk_cache_dir();
230
231 let cache_key = match format {
237 disk_backend_resources::BlobDiskFormat::FixedVhd1 => None,
238 disk_backend_resources::BlobDiskFormat::Flat => {
239 Some(url_path.rsplit('/').next().unwrap_or(url_path).to_owned())
240 }
241 };
242
243 Ok(LayeredDiskHandle {
244 layers: vec![
245 RamDiskLayerHandle {
246 len: None,
247 sector_size: None,
248 }
249 .into_resource()
250 .into(),
251 DiskLayerDescription {
252 read_cache: true,
253 write_through: false,
254 layer: SqliteAutoCacheDiskLayerHandle {
255 cache_path: cache_dir,
256 cache_key,
257 }
258 .into_resource(),
259 },
260 DiskLayerHandle(
261 disk_backend_resources::BlobDiskHandle {
262 url: url.to_owned(),
263 format,
264 }
265 .into_resource(),
266 )
267 .into_resource()
268 .into(),
269 ],
270 }
271 .into_resource())
272}
273
274async fn memdiff_vmgs(vmgs: &PetriVmgsResource) -> anyhow::Result<VmgsResource> {
275 async fn convert_disk(disk: &PetriVmgsDisk) -> anyhow::Result<VmgsDisk> {
276 Ok(VmgsDisk {
277 disk: petri_disk_to_openvmm(&disk.disk).await?,
278 encryption_policy: disk.encryption_policy,
279 })
280 }
281
282 Ok(match vmgs {
283 PetriVmgsResource::Disk(disk) => VmgsResource::Disk(convert_disk(disk).await?),
284 PetriVmgsResource::ReprovisionOnFailure(disk) => {
285 VmgsResource::ReprovisionOnFailure(convert_disk(disk).await?)
286 }
287 PetriVmgsResource::Reprovision(disk) => {
288 VmgsResource::Reprovision(convert_disk(disk).await?)
289 }
290 PetriVmgsResource::Ephemeral => VmgsResource::Ephemeral,
291 })
292}
293
294async fn petri_disk_to_openvmm(disk: &Disk) -> anyhow::Result<Resource<DiskHandleKind>> {
295 Ok(match disk {
296 Disk::Memory(len) => LayeredDiskHandle::single_layer(RamDiskLayerHandle {
297 len: Some(*len),
298 sector_size: None,
299 })
300 .into_resource(),
301 Disk::Differencing(DiskPath::Local(path)) => memdiff_disk(path).await?,
302 Disk::Differencing(DiskPath::Remote { url }) => memdiff_remote_disk(url)?,
303 Disk::Persistent(path) => {
304 open_disk_type(
305 path.as_ref(),
306 OpenDiskOptions {
307 read_only: false,
308 direct: false,
309 },
310 )
311 .await?
312 }
313 Disk::Temporary(path) => {
314 open_disk_type(
315 path.as_ref(),
316 OpenDiskOptions {
317 read_only: false,
318 direct: false,
319 },
320 )
321 .await?
322 }
323 })
324}