1#[cfg(windows)]
6pub mod hyperv;
7pub mod openvmm;
9
10use crate::ShutdownKind;
11use async_trait::async_trait;
12use get_resources::ged::FirmwareEvent;
13use petri_artifacts_common::tags::GuestQuirks;
14use petri_artifacts_common::tags::IsTestVmgs;
15use petri_artifacts_common::tags::MachineArch;
16use petri_artifacts_common::tags::OsFlavor;
17use petri_artifacts_core::ArtifactResolver;
18use petri_artifacts_core::ResolvedArtifact;
19use petri_artifacts_core::ResolvedOptionalArtifact;
20use pipette_client::PipetteClient;
21use vmm_core_defs::HaltReason;
22
23#[async_trait]
27pub trait PetriVmConfig: Send {
28 async fn run_without_agent(self: Box<Self>) -> anyhow::Result<Box<dyn PetriVm>>;
31 async fn run_with_lazy_pipette(self: Box<Self>) -> anyhow::Result<Box<dyn PetriVm>>;
35 async fn run(self: Box<Self>) -> anyhow::Result<(Box<dyn PetriVm>, PipetteClient)>;
37
38 fn with_secure_boot(self: Box<Self>) -> Box<dyn PetriVmConfig>;
40 fn with_windows_secure_boot_template(self: Box<Self>) -> Box<dyn PetriVmConfig>;
42 fn with_uefi_ca_secure_boot_template(self: Box<Self>) -> Box<dyn PetriVmConfig>;
44 fn with_processor_topology(
46 self: Box<Self>,
47 topology: ProcessorTopology,
48 ) -> Box<dyn PetriVmConfig>;
49
50 fn with_custom_openhcl(self: Box<Self>, artifact: ResolvedArtifact) -> Box<dyn PetriVmConfig>;
52 fn with_openhcl_command_line(self: Box<Self>, command_line: &str) -> Box<dyn PetriVmConfig>;
54 fn with_agent_file(
56 self: Box<Self>,
57 name: &str,
58 artifact: ResolvedArtifact,
59 ) -> Box<dyn PetriVmConfig>;
60 fn with_openhcl_agent_file(
62 self: Box<Self>,
63 name: &str,
64 artifact: ResolvedArtifact,
65 ) -> Box<dyn PetriVmConfig>;
66 fn with_uefi_frontpage(self: Box<Self>, enable: bool) -> Box<dyn PetriVmConfig>;
68 fn with_vmbus_redirect(self: Box<Self>, enable: bool) -> Box<dyn PetriVmConfig>;
70
71 fn os_flavor(&self) -> OsFlavor;
73}
74
75pub struct ProcessorTopology {
77 pub vp_count: u32,
79 pub enable_smt: Option<bool>,
81 pub vps_per_socket: Option<u32>,
83 pub apic_mode: Option<ApicMode>,
85}
86
87impl Default for ProcessorTopology {
88 fn default() -> Self {
89 Self {
90 vp_count: 2,
91 enable_smt: None,
92 vps_per_socket: None,
93 apic_mode: None,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Copy)]
100pub enum ApicMode {
101 Xapic,
103 X2apicSupported,
105 X2apicEnabled,
107}
108
109#[async_trait]
111pub trait PetriVm: Send {
112 fn arch(&self) -> MachineArch;
114 async fn wait_for_halt(&mut self) -> anyhow::Result<HaltReason>;
116 async fn wait_for_teardown(self: Box<Self>) -> anyhow::Result<HaltReason>;
119 async fn test_inspect_openhcl(&mut self) -> anyhow::Result<()>;
121 async fn wait_for_agent(&mut self) -> anyhow::Result<PipetteClient>;
124 async fn wait_for_vtl2_agent(&mut self) -> anyhow::Result<PipetteClient>;
128 async fn wait_for_vtl2_ready(&mut self) -> anyhow::Result<()>;
134 async fn wait_for_successful_boot_event(&mut self) -> anyhow::Result<()>;
141 async fn wait_for_boot_event(&mut self) -> anyhow::Result<FirmwareEvent>;
144 async fn send_enlightened_shutdown(&mut self, kind: ShutdownKind) -> anyhow::Result<()>;
146}
147
148#[derive(Debug)]
150pub enum Firmware {
151 LinuxDirect {
153 kernel: ResolvedArtifact,
155 initrd: ResolvedArtifact,
157 },
158 OpenhclLinuxDirect {
160 igvm_path: ResolvedArtifact,
162 },
163 Pcat {
165 guest: PcatGuest,
167 bios_firmware: ResolvedOptionalArtifact,
169 svga_firmware: ResolvedOptionalArtifact,
171 },
172 Uefi {
174 guest: UefiGuest,
176 uefi_firmware: ResolvedArtifact,
178 },
179 OpenhclUefi {
181 guest: UefiGuest,
183 isolation: Option<IsolationType>,
185 vtl2_nvme_boot: bool,
188 igvm_path: ResolvedArtifact,
190 },
191}
192
193impl Firmware {
194 pub fn linux_direct(resolver: &ArtifactResolver<'_>, arch: MachineArch) -> Self {
196 use petri_artifacts_vmm_test::artifacts::loadable::*;
197 match arch {
198 MachineArch::X86_64 => Firmware::LinuxDirect {
199 kernel: resolver.require(LINUX_DIRECT_TEST_KERNEL_X64).erase(),
200 initrd: resolver.require(LINUX_DIRECT_TEST_INITRD_X64).erase(),
201 },
202 MachineArch::Aarch64 => Firmware::LinuxDirect {
203 kernel: resolver.require(LINUX_DIRECT_TEST_KERNEL_AARCH64).erase(),
204 initrd: resolver.require(LINUX_DIRECT_TEST_INITRD_AARCH64).erase(),
205 },
206 }
207 }
208
209 pub fn openhcl_linux_direct(resolver: &ArtifactResolver<'_>, arch: MachineArch) -> Self {
211 use petri_artifacts_vmm_test::artifacts::openhcl_igvm::*;
212 match arch {
213 MachineArch::X86_64 => Firmware::OpenhclLinuxDirect {
214 igvm_path: resolver.require(LATEST_LINUX_DIRECT_TEST_X64).erase(),
215 },
216 MachineArch::Aarch64 => todo!("Linux direct not yet supported on aarch64"),
217 }
218 }
219
220 pub fn pcat(resolver: &ArtifactResolver<'_>, guest: PcatGuest) -> Self {
222 use petri_artifacts_vmm_test::artifacts::loadable::*;
223 Firmware::Pcat {
224 guest,
225 bios_firmware: resolver.try_require(PCAT_FIRMWARE_X64).erase(),
226 svga_firmware: resolver.try_require(SVGA_FIRMWARE_X64).erase(),
227 }
228 }
229
230 pub fn uefi(resolver: &ArtifactResolver<'_>, arch: MachineArch, guest: UefiGuest) -> Self {
232 use petri_artifacts_vmm_test::artifacts::loadable::*;
233 let uefi_firmware = match arch {
234 MachineArch::X86_64 => resolver.require(UEFI_FIRMWARE_X64).erase(),
235 MachineArch::Aarch64 => resolver.require(UEFI_FIRMWARE_AARCH64).erase(),
236 };
237 Firmware::Uefi {
238 guest,
239 uefi_firmware,
240 }
241 }
242
243 pub fn openhcl_uefi(
245 resolver: &ArtifactResolver<'_>,
246 arch: MachineArch,
247 guest: UefiGuest,
248 isolation: Option<IsolationType>,
249 vtl2_nvme_boot: bool,
250 ) -> Self {
251 use petri_artifacts_vmm_test::artifacts::openhcl_igvm::*;
252 let igvm_path = match arch {
253 MachineArch::X86_64 if isolation.is_some() => resolver.require(LATEST_CVM_X64).erase(),
254 MachineArch::X86_64 => resolver.require(LATEST_STANDARD_X64).erase(),
255 MachineArch::Aarch64 => resolver.require(LATEST_STANDARD_AARCH64).erase(),
256 };
257 Firmware::OpenhclUefi {
258 guest,
259 isolation,
260 vtl2_nvme_boot,
261 igvm_path,
262 }
263 }
264
265 fn is_openhcl(&self) -> bool {
266 match self {
267 Firmware::OpenhclLinuxDirect { .. } | Firmware::OpenhclUefi { .. } => true,
268 Firmware::LinuxDirect { .. } | Firmware::Pcat { .. } | Firmware::Uefi { .. } => false,
269 }
270 }
271
272 fn isolation(&self) -> Option<IsolationType> {
273 match self {
274 Firmware::OpenhclUefi { isolation, .. } => *isolation,
275 Firmware::LinuxDirect { .. }
276 | Firmware::Pcat { .. }
277 | Firmware::Uefi { .. }
278 | Firmware::OpenhclLinuxDirect { .. } => None,
279 }
280 }
281
282 fn is_linux_direct(&self) -> bool {
283 match self {
284 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => true,
285 Firmware::Pcat { .. } | Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => false,
286 }
287 }
288
289 fn is_uefi(&self) -> bool {
290 match self {
291 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => true,
292 Firmware::LinuxDirect { .. }
293 | Firmware::OpenhclLinuxDirect { .. }
294 | Firmware::Pcat { .. } => false,
295 }
296 }
297
298 fn os_flavor(&self) -> OsFlavor {
299 match self {
300 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => OsFlavor::Linux,
301 Firmware::Uefi {
302 guest: UefiGuest::GuestTestUefi { .. } | UefiGuest::None,
303 ..
304 }
305 | Firmware::OpenhclUefi {
306 guest: UefiGuest::GuestTestUefi { .. } | UefiGuest::None,
307 ..
308 } => OsFlavor::Uefi,
309 Firmware::Pcat {
310 guest: PcatGuest::Vhd(cfg),
311 ..
312 }
313 | Firmware::Uefi {
314 guest: UefiGuest::Vhd(cfg),
315 ..
316 }
317 | Firmware::OpenhclUefi {
318 guest: UefiGuest::Vhd(cfg),
319 ..
320 } => cfg.os_flavor,
321 Firmware::Pcat {
322 guest: PcatGuest::Iso(cfg),
323 ..
324 } => cfg.os_flavor,
325 }
326 }
327
328 fn quirks(&self) -> GuestQuirks {
329 match self {
330 Firmware::Pcat {
331 guest: PcatGuest::Vhd(cfg),
332 ..
333 }
334 | Firmware::Uefi {
335 guest: UefiGuest::Vhd(cfg),
336 ..
337 }
338 | Firmware::OpenhclUefi {
339 guest: UefiGuest::Vhd(cfg),
340 ..
341 } => cfg.quirks,
342 Firmware::Pcat {
343 guest: PcatGuest::Iso(cfg),
344 ..
345 } => cfg.quirks,
346 _ => Default::default(),
347 }
348 }
349
350 fn expected_boot_event(&self) -> Option<FirmwareEvent> {
351 match self {
352 Firmware::LinuxDirect { .. } | Firmware::OpenhclLinuxDirect { .. } => None,
353 Firmware::Pcat { .. } => {
354 Some(FirmwareEvent::BootAttempt)
356 }
357 Firmware::Uefi {
358 guest: UefiGuest::None,
359 ..
360 }
361 | Firmware::OpenhclUefi {
362 guest: UefiGuest::None,
363 ..
364 } => Some(FirmwareEvent::NoBootDevice),
365 Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => {
366 Some(FirmwareEvent::BootSuccess)
367 }
368 }
369 }
370}
371
372#[derive(Debug)]
375pub enum PcatGuest {
376 Vhd(BootImageConfig<boot_image_type::Vhd>),
378 Iso(BootImageConfig<boot_image_type::Iso>),
380}
381
382impl PcatGuest {
383 fn artifact(&self) -> &ResolvedArtifact {
384 match self {
385 PcatGuest::Vhd(disk) => &disk.artifact,
386 PcatGuest::Iso(disk) => &disk.artifact,
387 }
388 }
389}
390
391#[derive(Debug)]
394pub enum UefiGuest {
395 Vhd(BootImageConfig<boot_image_type::Vhd>),
397 GuestTestUefi(ResolvedArtifact),
399 None,
401}
402
403impl UefiGuest {
404 pub fn guest_test_uefi(resolver: &ArtifactResolver<'_>, arch: MachineArch) -> Self {
406 use petri_artifacts_vmm_test::artifacts::test_vhd::*;
407 let artifact = match arch {
408 MachineArch::X86_64 => resolver.require(GUEST_TEST_UEFI_X64).erase(),
409 MachineArch::Aarch64 => resolver.require(GUEST_TEST_UEFI_AARCH64).erase(),
410 };
411 UefiGuest::GuestTestUefi(artifact)
412 }
413
414 fn artifact(&self) -> Option<&ResolvedArtifact> {
415 match self {
416 UefiGuest::Vhd(vhd) => Some(&vhd.artifact),
417 UefiGuest::GuestTestUefi(p) => Some(p),
418 UefiGuest::None => None,
419 }
420 }
421}
422
423pub mod boot_image_type {
425 mod private {
426 pub trait Sealed {}
427 impl Sealed for super::Vhd {}
428 impl Sealed for super::Iso {}
429 }
430
431 pub trait BootImageType: private::Sealed {}
434
435 #[derive(Debug)]
437 pub enum Vhd {}
438
439 #[derive(Debug)]
441 pub enum Iso {}
442
443 impl BootImageType for Vhd {}
444 impl BootImageType for Iso {}
445}
446
447#[derive(Debug)]
449pub struct BootImageConfig<T: boot_image_type::BootImageType> {
450 artifact: ResolvedArtifact,
452 os_flavor: OsFlavor,
454 quirks: GuestQuirks,
458 _type: core::marker::PhantomData<T>,
460}
461
462impl BootImageConfig<boot_image_type::Vhd> {
463 pub fn from_vhd<A>(artifact: ResolvedArtifact<A>) -> Self
465 where
466 A: petri_artifacts_common::tags::IsTestVhd,
467 {
468 BootImageConfig {
469 artifact: artifact.erase(),
470 os_flavor: A::OS_FLAVOR,
471 quirks: A::quirks(),
472 _type: std::marker::PhantomData,
473 }
474 }
475}
476
477impl BootImageConfig<boot_image_type::Iso> {
478 pub fn from_iso<A>(artifact: ResolvedArtifact<A>) -> Self
480 where
481 A: petri_artifacts_common::tags::IsTestIso,
482 {
483 BootImageConfig {
484 artifact: artifact.erase(),
485 os_flavor: A::OS_FLAVOR,
486 quirks: A::quirks(),
487 _type: std::marker::PhantomData,
488 }
489 }
490}
491
492#[derive(Debug, Clone, Copy)]
494pub enum IsolationType {
495 Vbs,
497 Snp,
499 Tdx,
501}
502
503#[derive(Default, Debug, Clone, Copy)]
505pub struct OpenHclServicingFlags {
506 pub enable_nvme_keepalive: bool,
508}
509
510pub enum PetriVmgsResource<T: IsTestVmgs> {
512 Disk(ResolvedArtifact<T>),
514 ReprovisionOnFailure(ResolvedArtifact<T>),
516 Reprovision(ResolvedArtifact<T>),
518 Ephemeral,
520}