From a1764380e4e9f0726f663f501d33915aa74ff68b Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Mon, 27 Apr 2026 13:38:52 -0700 Subject: [PATCH 01/15] petri: add NVMe emulator support for Hyper-V --- petri/src/vm/hyperv/hyperv.psm1 | 33 +++++- petri/src/vm/hyperv/mod.rs | 38 +++++-- petri/src/vm/hyperv/powershell.rs | 103 +++++++++++++++--- .../vmm_tests/tests/tests/x86_64/storage.rs | 78 ++++++++++++- 4 files changed, 224 insertions(+), 28 deletions(-) diff --git a/petri/src/vm/hyperv/hyperv.psm1 b/petri/src/vm/hyperv/hyperv.psm1 index 0cd4bb9d8d..09df9a0fcb 100644 --- a/petri/src/vm/hyperv/hyperv.psm1 +++ b/petri/src/vm/hyperv/hyperv.psm1 @@ -222,6 +222,21 @@ function New-CustomVM # } [hashtable] $ScsiControllers = $null, + # must be a hashtable with format: + # NvmeControllers => { + # Vsid => { + # Vtl, + # Drives => @( + # @{ Nsid; DiskPath }, + # ... + # ) + # }, + # ... + # } + # Drives are pre-sorted by NSID. The emulator assigns NSIDs 1..N + # by argument order. + [hashtable] $NvmeControllers = $null, + # must be a hashtable with format: # IdeControllers => { # ControllerNumber => { @@ -352,6 +367,22 @@ function New-CustomVM } } + if ($NvmeControllers) { + Import-Module HvlDeviceHost + foreach ($controller in $NvmeControllers.GetEnumerator()) { + $vsid = $controller.Name + $targetVtl = $controller.Value["Vtl"] + $drives = $controller.Value["Drives"] + # Drives arrive pre-sorted by NSID from the Rust layer. + $vhdPaths = @($drives | ForEach-Object { $_["DiskPath"] }) + $resourceSettings += New-NvmeEmulatorRasd ` + -VhdPaths $vhdPaths ` + -TargetVtl $targetVtl ` + -Vsid ([Guid]$vsid) ` + | ConvertTo-CimEmbeddedString + } + } + $vm = ($vmms | Invoke-CimMethod -Name "DefineSystem" -Arguments @{ "SystemSettings" = ($vssd | ConvertTo-CimEmbeddedString); "ResourceSettings" = $resourceSettings @@ -1418,4 +1449,4 @@ function Get-CimInstancePath { ) return $path -} \ No newline at end of file +} diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index 67b5af1959..00ff1266cf 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -221,8 +221,8 @@ impl PetriVmmBackend for HyperVPetriBackend { } } - // Map SCSI - let mut scsi_controllers = HashMap::new(); + // Map VMBus storage controllers (SCSI and NVMe). + let mut storage_controllers = HashMap::new(); for ( vsid, VmbusStorageController { @@ -232,10 +232,6 @@ impl PetriVmmBackend for HyperVPetriBackend { }, ) in config.vmbus_storage_controllers.iter() { - if !matches!(controller_type, crate::VmbusStorageType::Scsi) { - todo!("other storage types for hyper-v") - } - let mut hyperv_drives = HashMap::new(); for (lun, Drive { disk, is_dvd }) in drives { hyperv_drives.insert( @@ -246,9 +242,32 @@ impl PetriVmmBackend for HyperVPetriBackend { }, ); } - scsi_controllers.insert( + + let vmbus_controller_type = match controller_type { + crate::VmbusStorageType::Scsi => powershell::HyperVVmbusStorageType::Scsi, + crate::VmbusStorageType::Nvme => { + for (nsid, drive) in &hyperv_drives { + if drive.is_dvd { + anyhow::bail!("NVMe emulator does not support DVD drives"); + } + if drive.disk.is_none() { + anyhow::bail!("NVMe drive cannot be empty (NSID {})", nsid); + } + } + powershell::HyperVVmbusStorageType::Nvme + } + _ => { + todo!( + "storage type {:?} not yet supported for hyper-v", + controller_type + ) + } + }; + + storage_controllers.insert( *vsid, - powershell::HyperVScsiController { + powershell::HyperVVmbusStorageController { + controller_type: vmbus_controller_type, target_vtl: *target_vtl, drives: hyperv_drives, }, @@ -338,8 +357,7 @@ impl PetriVmmBackend for HyperVPetriBackend { firmware_file: igvm_file.clone(), firmware_parameters: openhcl_command_line, guest_state_path, - scsi_controllers, - ide_controllers, + storage_controllers, com_3: supports_com3, imc_hiv, management_vtl_settings, diff --git a/petri/src/vm/hyperv/powershell.rs b/petri/src/vm/hyperv/powershell.rs index e666f21828..26242091d7 100644 --- a/petri/src/vm/hyperv/powershell.rs +++ b/petri/src/vm/hyperv/powershell.rs @@ -8,7 +8,6 @@ use crate::OpenHclServicingFlags; use crate::PetriVmConfig; use crate::PetriVmProperties; use crate::VmScreenshotMeta; -use crate::Vtl; use crate::run_host_cmd; use crate::vm::append_cmdline; use anyhow::Context; @@ -290,8 +289,8 @@ pub struct HyperVNewCustomVMArgs { pub hw_threads_per_core: Option, /// Processors per socket pub max_processors_per_numa_node: Option, - /// SCSI controllers and associated drives/disks - pub scsi_controllers: HashMap, + /// VMBus storage controllers (SCSI and NVMe), keyed by VSID + pub storage_controllers: HashMap, /// IDE controllers and associated drives/disks pub ide_controllers: HashMap>, /// Temporary file containing initial machine configuration data @@ -306,11 +305,21 @@ pub struct HyperVNewCustomVMArgs { pub management_vtl_settings: Option, } -/// Hyper-V SCSI controller -pub struct HyperVScsiController { - /// The VTL to assign the storage controller to - pub target_vtl: Vtl, - /// Drives (with any inserted disks) attached to this storage controller +/// VMBus storage controller type +pub enum HyperVVmbusStorageType { + /// SCSI controller (Msvm_ResourceAllocationSettingData) + Scsi, + /// NVMe emulator controller (created via closed-source HvlDeviceHost module) + Nvme, +} + +/// VMBus storage controller configuration (SCSI or NVMe), keyed by VSID. +pub struct HyperVVmbusStorageController { + /// Controller type + pub controller_type: HyperVVmbusStorageType, + /// Target VTL + pub target_vtl: crate::Vtl, + /// Drives attached to this controller, keyed by LUN (SCSI) or namespace ID (NVMe). pub drives: HashMap, } @@ -565,7 +574,7 @@ impl HyperVNewCustomVMArgs { firmware_file: None, firmware_parameters: None, guest_state_path: None, - scsi_controllers: HashMap::new(), + storage_controllers: HashMap::new(), ide_controllers: HashMap::new(), com_3: false, imc_hiv: None, @@ -596,9 +605,23 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any } }); - let scsi_controllers = (!args.scsi_controllers.is_empty()).then(|| { - ps::HashTable::new(args.scsi_controllers.into_iter().map( - |(vsid, HyperVScsiController { target_vtl, drives })| { + // Partition storage controllers into SCSI and NVMe. + let mut scsi_map: HashMap = HashMap::new(); + let mut nvme_map: HashMap = HashMap::new(); + for (vsid, controller) in args.storage_controllers { + match controller.controller_type { + HyperVVmbusStorageType::Scsi => { + scsi_map.insert(vsid, controller); + } + HyperVVmbusStorageType::Nvme => { + nvme_map.insert(vsid, controller); + } + } + } + + let scsi_controllers = (!scsi_map.is_empty()).then(|| { + ps::HashTable::new(scsi_map.into_iter().map( + |(vsid, HyperVVmbusStorageController { target_vtl, drives, .. })| { ( format!("\"{vsid}\""), ps::Value::new(ps::HashTable::new([ @@ -645,11 +668,58 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any )) }); + // Serialize NVMe controllers as a hashtable keyed by VSID. + // Each value: @{ Vtl = N; Drives = @(@{Nsid = 1; DiskPath = "..."}, ...) } + // New-CustomVM imports HvlDeviceHost internally and calls New-NvmeEmulatorRasd. + let nvme_controllers = (!nvme_map.is_empty()).then(|| { + ps::HashTable::new(nvme_map.into_iter().map( + |(vsid, HyperVVmbusStorageController { target_vtl, drives, .. })| { + // Sort drives by namespace ID and validate they are exactly + // 1..N — the emulator assigns NSIDs sequentially by VHD + // argument order. + let mut sorted_drives: Vec<_> = drives.into_iter().collect(); + sorted_drives.sort_by_key(|(nsid, _)| *nsid); + let expected: Vec = (1..=sorted_drives.len() as u32).collect(); + let actual: Vec = sorted_drives.iter().map(|(nsid, _)| *nsid).collect(); + assert_eq!( + actual, expected, + "NVMe namespace IDs must be 1..{}, got {:?}", + expected.len(), + actual + ); + ( + format!("\"{vsid}\""), + ps::Value::new(ps::HashTable::new([ + ("Vtl", ps::Value::new(target_vtl as u32)), + ( + "Drives", + ps::Value::new(ps::Array::new(sorted_drives.into_iter().map( + |(nsid, HyperVDrive { disk, .. })| { + ps::HashTable::new([ + ("Nsid", ps::Value::new(nsid)), + ( + "DiskPath", + ps::Value::new( + disk.expect("NVMe drives must have disk paths"), + ), + ), + ]) + }, + ))), + ), + ])), + ) + }, + )) + }); + + let builder = PowerShellBuilder::new() + .cmdlet("Import-Module") + .positional(ps_mod) + .next(); + let vmid = run_host_cmd( - PowerShellBuilder::new() - .cmdlet("Import-Module") - .positional(ps_mod) - .next() + builder .cmdlet("New-CustomVM") .arg("VMName", args.name) .arg_opt("Generation", args.generation) @@ -686,6 +756,7 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any ) .arg_opt("ScsiControllers", scsi_controllers) .arg_opt("IdeControllers", ide_controllers) + .arg_opt("NvmeControllers", nvme_controllers) .arg_opt("ImcHive", args.imc_hiv.as_ref().map(|f| f.path())) .arg("Com1", args.com_1) .arg("Com3", args.com_3) diff --git a/vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs b/vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs index 227ebab123..b142d0cefa 100644 --- a/vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs +++ b/vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs @@ -381,7 +381,83 @@ async fn storvsp_hyperv( Ok(()) } -/// Test an OpenHCL Linux Stripe VM with two SCSI disk assigned to VTL2 via NVMe Emulator +/// Test a Hyper-V OpenHCL Linux VM with an NVMe emulator device assigned to +/// VTL2, relayed to VTL0 via SCSI. Validates that the guest can discover and +/// perform IO on the disk. +#[cfg(windows)] +#[vmm_test(unstable_hyperv_openhcl_uefi_x64(vhd(ubuntu_2504_server_x64)))] +async fn storvsp_nvme_hyperv( + config: PetriVmBuilder, +) -> Result<(), anyhow::Error> { + let vtl0_nvme_lun = 0; + let nvme_nsid = 1; + let nvme_vsid = Guid::new_random(); + let scsi_instance = Guid::new_random(); + const NVME_DISK_SECTORS: u64 = 0x5_0000; + const SECTOR_SIZE: u64 = 512; + const EXPECTED_NVME_DISK_SIZE_BYTES: u64 = NVME_DISK_SECTORS * SECTOR_SIZE; + + // Assumptions made by test infra & routines: + // + // 1. Some test-infra added disks are 64MiB in size. Since we find disks by size, + // ensure that our test disks are a different size. + // 2. Disks under test need to be at least 100MiB for the IO tests (see [`test_storage_linux`]), + // with some arbitrary buffer (5MiB in this case). + static_assertions::const_assert_ne!(EXPECTED_NVME_DISK_SIZE_BYTES, 64 * 1024 * 1024); + static_assertions::const_assert!(EXPECTED_NVME_DISK_SIZE_BYTES > 105 * 1024 * 1024); + + let mut vhd = + tempfile::NamedTempFile::with_suffix("nvme.vhd").context("create temp nvme vhd")?; + vhd.as_file() + .set_len(EXPECTED_NVME_DISK_SIZE_BYTES) + .context("set file length")?; + + disk_vhd1::Vhd1Disk::make_fixed(vhd.as_file_mut()).context("make fixed")?; + + // Close the handle without deleting the file, so Hyper-V can open it. + let vhd_path = vhd.into_temp_path(); + + let (vm, agent) = config + .with_vmbus_redirect(true) + .add_vmbus_storage_controller(&nvme_vsid, petri::Vtl::Vtl2, petri::VmbusStorageType::Nvme) + .add_vmbus_drive( + petri::Drive::new(Some(petri::Disk::Persistent(vhd_path.to_path_buf())), false), + &nvme_vsid, + Some(nvme_nsid), + ) + .add_vtl2_storage_controller( + Vtl2StorageControllerBuilder::new(ControllerType::Scsi) + .with_instance_id(scsi_instance) + .add_lun( + Vtl2LunBuilder::disk() + .with_location(vtl0_nvme_lun) + .with_physical_device(Vtl2StorageBackingDeviceBuilder::new( + ControllerType::Nvme, + nvme_vsid, + nvme_nsid, + )), + ) + .build(), + ) + .run() + .await?; + + test_storage_linux( + &agent, + scsi_instance, + vec![ExpectedGuestDevice { + lun: vtl0_nvme_lun, + disk_size_sectors: NVME_DISK_SECTORS as usize, + friendly_name: "nvme".to_string(), + }], + ) + .await?; + + agent.power_off().await?; + vm.wait_for_clean_teardown().await?; + + Ok(()) +} #[openvmm_test( openhcl_linux_direct_x64, openhcl_uefi_x64(vhd(ubuntu_2504_server_x64)) From 9d7e87f2b6b9953b2c6927c8e117a1de073ab78d Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Tue, 28 Apr 2026 16:17:43 +0000 Subject: [PATCH 02/15] fmt fix --- petri/src/vm/hyperv/powershell.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/petri/src/vm/hyperv/powershell.rs b/petri/src/vm/hyperv/powershell.rs index 26242091d7..33ae5c7d59 100644 --- a/petri/src/vm/hyperv/powershell.rs +++ b/petri/src/vm/hyperv/powershell.rs @@ -621,7 +621,12 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any let scsi_controllers = (!scsi_map.is_empty()).then(|| { ps::HashTable::new(scsi_map.into_iter().map( - |(vsid, HyperVVmbusStorageController { target_vtl, drives, .. })| { + |( + vsid, + HyperVVmbusStorageController { + target_vtl, drives, .. + }, + )| { ( format!("\"{vsid}\""), ps::Value::new(ps::HashTable::new([ @@ -673,7 +678,12 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any // New-CustomVM imports HvlDeviceHost internally and calls New-NvmeEmulatorRasd. let nvme_controllers = (!nvme_map.is_empty()).then(|| { ps::HashTable::new(nvme_map.into_iter().map( - |(vsid, HyperVVmbusStorageController { target_vtl, drives, .. })| { + |( + vsid, + HyperVVmbusStorageController { + target_vtl, drives, .. + }, + )| { // Sort drives by namespace ID and validate they are exactly // 1..N — the emulator assigns NSIDs sequentially by VHD // argument order. @@ -682,7 +692,8 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any let expected: Vec = (1..=sorted_drives.len() as u32).collect(); let actual: Vec = sorted_drives.iter().map(|(nsid, _)| *nsid).collect(); assert_eq!( - actual, expected, + actual, + expected, "NVMe namespace IDs must be 1..{}, got {:?}", expected.len(), actual From 2fa8b238daa98bae4b7308cd07af791ac0e2c0ff Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Tue, 28 Apr 2026 09:24:06 -0700 Subject: [PATCH 03/15] fix(petri): check HvlDeviceHost module availability before import Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- petri/src/vm/hyperv/hyperv.psm1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/petri/src/vm/hyperv/hyperv.psm1 b/petri/src/vm/hyperv/hyperv.psm1 index 09df9a0fcb..8a9325cbd8 100644 --- a/petri/src/vm/hyperv/hyperv.psm1 +++ b/petri/src/vm/hyperv/hyperv.psm1 @@ -368,7 +368,12 @@ function New-CustomVM } if ($NvmeControllers) { - Import-Module HvlDeviceHost + if (-not (Get-Module -ListAvailable HvlDeviceHost)) { + throw ("NVMe emulator support requires the HvlDeviceHost " + + "PowerShell module. Ensure hvldevicehost.dll is installed " + + "and the module is available on this host.") + } + Import-Module HvlDeviceHost -ErrorAction Stop foreach ($controller in $NvmeControllers.GetEnumerator()) { $vsid = $controller.Name $targetVtl = $controller.Value["Vtl"] From 4c631c0dfcaa79bfb8d7780554054817a36bb91e Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Tue, 28 Apr 2026 16:53:46 +0000 Subject: [PATCH 04/15] fix(petri): replace assert_eq with anyhow::ensure for NSID validation assert_eq! panics the test runner instead of returning a structured error. Use anyhow::ensure! so NSID validation failures surface as clean error messages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- petri/src/vm/hyperv/powershell.rs | 94 ++++++++++++++++--------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/petri/src/vm/hyperv/powershell.rs b/petri/src/vm/hyperv/powershell.rs index 33ae5c7d59..c9275566ad 100644 --- a/petri/src/vm/hyperv/powershell.rs +++ b/petri/src/vm/hyperv/powershell.rs @@ -676,53 +676,55 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any // Serialize NVMe controllers as a hashtable keyed by VSID. // Each value: @{ Vtl = N; Drives = @(@{Nsid = 1; DiskPath = "..."}, ...) } // New-CustomVM imports HvlDeviceHost internally and calls New-NvmeEmulatorRasd. - let nvme_controllers = (!nvme_map.is_empty()).then(|| { - ps::HashTable::new(nvme_map.into_iter().map( - |( - vsid, - HyperVVmbusStorageController { - target_vtl, drives, .. - }, - )| { - // Sort drives by namespace ID and validate they are exactly - // 1..N — the emulator assigns NSIDs sequentially by VHD - // argument order. - let mut sorted_drives: Vec<_> = drives.into_iter().collect(); - sorted_drives.sort_by_key(|(nsid, _)| *nsid); - let expected: Vec = (1..=sorted_drives.len() as u32).collect(); - let actual: Vec = sorted_drives.iter().map(|(nsid, _)| *nsid).collect(); - assert_eq!( - actual, - expected, - "NVMe namespace IDs must be 1..{}, got {:?}", - expected.len(), - actual - ); - ( - format!("\"{vsid}\""), - ps::Value::new(ps::HashTable::new([ - ("Vtl", ps::Value::new(target_vtl as u32)), - ( - "Drives", - ps::Value::new(ps::Array::new(sorted_drives.into_iter().map( - |(nsid, HyperVDrive { disk, .. })| { - ps::HashTable::new([ - ("Nsid", ps::Value::new(nsid)), - ( - "DiskPath", - ps::Value::new( - disk.expect("NVMe drives must have disk paths"), - ), - ), - ]) - }, - ))), - ), - ])), - ) + let nvme_controllers = if nvme_map.is_empty() { + None + } else { + let mut nvme_entries = Vec::new(); + for ( + vsid, + HyperVVmbusStorageController { + target_vtl, drives, .. }, - )) - }); + ) in nvme_map + { + // Sort drives by namespace ID and validate they are exactly + // 1..N — the emulator assigns NSIDs sequentially by VHD + // argument order. + let mut sorted_drives: Vec<_> = drives.into_iter().collect(); + sorted_drives.sort_by_key(|(nsid, _)| *nsid); + let expected: Vec = (1..=sorted_drives.len() as u32).collect(); + let actual: Vec = sorted_drives.iter().map(|(nsid, _)| *nsid).collect(); + anyhow::ensure!( + actual == expected, + "NVMe namespace IDs must be 1..{}, got {:?}", + expected.len(), + actual + ); + nvme_entries.push(( + format!("\"{vsid}\""), + ps::Value::new(ps::HashTable::new([ + ("Vtl", ps::Value::new(target_vtl as u32)), + ( + "Drives", + ps::Value::new(ps::Array::new(sorted_drives.into_iter().map( + |(nsid, HyperVDrive { disk, .. })| { + ps::HashTable::new([ + ("Nsid", ps::Value::new(nsid)), + ( + "DiskPath", + ps::Value::new( + disk.expect("NVMe drives must have disk paths"), + ), + ), + ]) + }, + ))), + ), + ])), + )); + } + Some(ps::HashTable::new(nvme_entries)) + }; let builder = PowerShellBuilder::new() .cmdlet("Import-Module") From cecb4a28f4f378c642c755a2eaff4a5ead352a40 Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Thu, 30 Apr 2026 23:04:49 +0000 Subject: [PATCH 05/15] temporary change - point to nvme pool --- .github/workflows/openvmm-ci.yaml | 12 ++++++------ .github/workflows/openvmm-pr-release.yaml | 12 ++++++------ .github/workflows/openvmm-pr.yaml | 10 +++++----- .../flowey_hvlite/src/pipelines_shared/gh_pools.rs | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/openvmm-ci.yaml b/.github/workflows/openvmm-ci.yaml index 549cd694e7..53df71b589 100644 --- a/.github/workflows/openvmm-ci.yaml +++ b/.github/workflows/openvmm-ci.yaml @@ -349,7 +349,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job10-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -2435,7 +2435,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job18-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -2969,7 +2969,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -4880,7 +4880,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5198,7 +5198,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5417,7 +5417,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/.github/workflows/openvmm-pr-release.yaml b/.github/workflows/openvmm-pr-release.yaml index f4ab8b7325..56bf6a76a9 100644 --- a/.github/workflows/openvmm-pr-release.yaml +++ b/.github/workflows/openvmm-pr-release.yaml @@ -387,7 +387,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job10-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -2378,7 +2378,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job18-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -2914,7 +2914,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -4608,7 +4608,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -4928,7 +4928,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5149,7 +5149,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/.github/workflows/openvmm-pr.yaml b/.github/workflows/openvmm-pr.yaml index 880adcf45e..dad04ff9da 100644 --- a/.github/workflows/openvmm-pr.yaml +++ b/.github/workflows/openvmm-pr.yaml @@ -3026,7 +3026,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3243,7 +3243,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job20-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5321,7 +5321,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5641,7 +5641,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5862,7 +5862,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs b/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs index 602dcceb03..d85828d974 100644 --- a/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs +++ b/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs @@ -9,7 +9,7 @@ pub fn windows_amd_1es() -> GhRunner { GhRunner::SelfHosted(vec![ "self-hosted".to_string(), "1ES.Pool=openvmm-gh-amd-westus3".to_string(), - "1ES.ImageOverride=win-amd64".to_string(), + "1ES.ImageOverride=win-amd64-nvme".to_string(), ]) } From 613b17174e8e417b8e103d1c059d06738986dc3f Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Thu, 30 Apr 2026 23:30:12 +0000 Subject: [PATCH 06/15] temp pool test --- .github/workflows/openvmm-ci.yaml | 12 ++++++------ .github/workflows/openvmm-pr-release.yaml | 12 ++++++------ .github/workflows/openvmm-pr.yaml | 12 ++++++------ .../flowey_hvlite/src/pipelines_shared/gh_pools.rs | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/openvmm-ci.yaml b/.github/workflows/openvmm-ci.yaml index 3f4a010b82..febdf9fbd6 100644 --- a/.github/workflows/openvmm-ci.yaml +++ b/.github/workflows/openvmm-ci.yaml @@ -349,7 +349,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job10-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -2832,7 +2832,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job18-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3366,7 +3366,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5277,7 +5277,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5595,7 +5595,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5814,7 +5814,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/.github/workflows/openvmm-pr-release.yaml b/.github/workflows/openvmm-pr-release.yaml index 3158c99992..6fb722f5ec 100644 --- a/.github/workflows/openvmm-pr-release.yaml +++ b/.github/workflows/openvmm-pr-release.yaml @@ -396,7 +396,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job10-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -2775,7 +2775,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job18-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3311,7 +3311,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5005,7 +5005,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5325,7 +5325,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5546,7 +5546,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/.github/workflows/openvmm-pr.yaml b/.github/workflows/openvmm-pr.yaml index 1113fffb56..e4085365fb 100644 --- a/.github/workflows/openvmm-pr.yaml +++ b/.github/workflows/openvmm-pr.yaml @@ -1060,7 +1060,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job12-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3439,7 +3439,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3656,7 +3656,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job20-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5734,7 +5734,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -6054,7 +6054,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -6275,7 +6275,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64 + - 1ES.ImageOverride=win-amd64-nvme - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs b/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs index 1f7fb92817..dffa3886a0 100644 --- a/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs +++ b/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs @@ -24,7 +24,7 @@ fn gh_pool_with_image_1es(pool: &str, image: &str) -> GhRunner { } pub fn windows_amd_1es() -> GhRunner { - gh_pool_with_image_1es(AMD_POOL_1ES, WINDOWS_IMAGE_AMD64) + gh_pool_with_image_1es(AMD_POOL_1ES, "win-amd64-nvme") } pub fn windows_intel_1es() -> GhRunner { From ddec48a51ff652daedb8a65e2fdf74def85d8008 Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Thu, 30 Apr 2026 18:26:04 -0700 Subject: [PATCH 07/15] trevor feedback + point back to win-amd64 image --- .github/workflows/openvmm-ci.yaml | 12 ++++++------ .github/workflows/openvmm-pr-release.yaml | 12 ++++++------ .github/workflows/openvmm-pr.yaml | 12 ++++++------ .../flowey_hvlite/src/pipelines_shared/gh_pools.rs | 2 +- petri/src/vm/hyperv/hyperv.psm1 | 10 ++++------ petri/src/vm/hyperv/powershell.rs | 14 +++----------- 6 files changed, 26 insertions(+), 36 deletions(-) diff --git a/.github/workflows/openvmm-ci.yaml b/.github/workflows/openvmm-ci.yaml index febdf9fbd6..3f4a010b82 100644 --- a/.github/workflows/openvmm-ci.yaml +++ b/.github/workflows/openvmm-ci.yaml @@ -349,7 +349,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job10-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -2832,7 +2832,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job18-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3366,7 +3366,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5277,7 +5277,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5595,7 +5595,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5814,7 +5814,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/.github/workflows/openvmm-pr-release.yaml b/.github/workflows/openvmm-pr-release.yaml index 6fb722f5ec..3158c99992 100644 --- a/.github/workflows/openvmm-pr-release.yaml +++ b/.github/workflows/openvmm-pr-release.yaml @@ -396,7 +396,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job10-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -2775,7 +2775,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job18-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3311,7 +3311,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5005,7 +5005,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5325,7 +5325,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5546,7 +5546,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/.github/workflows/openvmm-pr.yaml b/.github/workflows/openvmm-pr.yaml index e4085365fb..1113fffb56 100644 --- a/.github/workflows/openvmm-pr.yaml +++ b/.github/workflows/openvmm-pr.yaml @@ -1060,7 +1060,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job12-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3439,7 +3439,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job2-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -3656,7 +3656,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job20-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -5734,7 +5734,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job3-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -6054,7 +6054,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job4-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read @@ -6275,7 +6275,7 @@ jobs: runs-on: - self-hosted - 1ES.Pool=openvmm-gh-amd-westus3 - - 1ES.ImageOverride=win-amd64-nvme + - 1ES.ImageOverride=win-amd64 - JobId=job5-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} permissions: contents: read diff --git a/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs b/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs index dffa3886a0..1f7fb92817 100644 --- a/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs +++ b/flowey/flowey_hvlite/src/pipelines_shared/gh_pools.rs @@ -24,7 +24,7 @@ fn gh_pool_with_image_1es(pool: &str, image: &str) -> GhRunner { } pub fn windows_amd_1es() -> GhRunner { - gh_pool_with_image_1es(AMD_POOL_1ES, "win-amd64-nvme") + gh_pool_with_image_1es(AMD_POOL_1ES, WINDOWS_IMAGE_AMD64) } pub fn windows_intel_1es() -> GhRunner { diff --git a/petri/src/vm/hyperv/hyperv.psm1 b/petri/src/vm/hyperv/hyperv.psm1 index 8a9325cbd8..9430977c36 100644 --- a/petri/src/vm/hyperv/hyperv.psm1 +++ b/petri/src/vm/hyperv/hyperv.psm1 @@ -226,10 +226,10 @@ function New-CustomVM # NvmeControllers => { # Vsid => { # Vtl, - # Drives => @( - # @{ Nsid; DiskPath }, + # Drives => [ + # DiskPath, # ... - # ) + # ] # }, # ... # } @@ -377,9 +377,7 @@ function New-CustomVM foreach ($controller in $NvmeControllers.GetEnumerator()) { $vsid = $controller.Name $targetVtl = $controller.Value["Vtl"] - $drives = $controller.Value["Drives"] - # Drives arrive pre-sorted by NSID from the Rust layer. - $vhdPaths = @($drives | ForEach-Object { $_["DiskPath"] }) + $vhdPaths = $controller.Value["Drives"] $resourceSettings += New-NvmeEmulatorRasd ` -VhdPaths $vhdPaths ` -TargetVtl $targetVtl ` diff --git a/petri/src/vm/hyperv/powershell.rs b/petri/src/vm/hyperv/powershell.rs index c9275566ad..5e565b9840 100644 --- a/petri/src/vm/hyperv/powershell.rs +++ b/petri/src/vm/hyperv/powershell.rs @@ -674,7 +674,7 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any }); // Serialize NVMe controllers as a hashtable keyed by VSID. - // Each value: @{ Vtl = N; Drives = @(@{Nsid = 1; DiskPath = "..."}, ...) } + // Each value: @{ Vtl = N; Drives = @("path1", "path2", ...) } // New-CustomVM imports HvlDeviceHost internally and calls New-NvmeEmulatorRasd. let nvme_controllers = if nvme_map.is_empty() { None @@ -707,16 +707,8 @@ pub async fn run_new_customvm(ps_mod: &Path, args: HyperVNewCustomVMArgs) -> any ( "Drives", ps::Value::new(ps::Array::new(sorted_drives.into_iter().map( - |(nsid, HyperVDrive { disk, .. })| { - ps::HashTable::new([ - ("Nsid", ps::Value::new(nsid)), - ( - "DiskPath", - ps::Value::new( - disk.expect("NVMe drives must have disk paths"), - ), - ), - ]) + |(_, HyperVDrive { disk, .. })| { + disk.expect("NVMe drives must have disk paths") }, ))), ), From dff13c3335915833e69cc163c1e0776cac06cedb Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Fri, 1 May 2026 03:41:57 +0000 Subject: [PATCH 08/15] workaround: register CLSID during every test --- petri/src/vm/hyperv/hyperv.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/petri/src/vm/hyperv/hyperv.psm1 b/petri/src/vm/hyperv/hyperv.psm1 index 9430977c36..486e3fadb1 100644 --- a/petri/src/vm/hyperv/hyperv.psm1 +++ b/petri/src/vm/hyperv/hyperv.psm1 @@ -374,6 +374,7 @@ function New-CustomVM "and the module is available on this host.") } Import-Module HvlDeviceHost -ErrorAction Stop + Register-HvlDeviceHostClsid $CLSID_FIOV_NVME foreach ($controller in $NvmeControllers.GetEnumerator()) { $vsid = $controller.Name $targetVtl = $controller.Value["Vtl"] From c262dcaa03531abb034e665df313ebb410dedecb Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Fri, 1 May 2026 06:29:38 +0000 Subject: [PATCH 09/15] explicitly grant VM access to the backing VHD --- petri/src/vm/hyperv/hyperv.psm1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/petri/src/vm/hyperv/hyperv.psm1 b/petri/src/vm/hyperv/hyperv.psm1 index 486e3fadb1..a458fd66f5 100644 --- a/petri/src/vm/hyperv/hyperv.psm1 +++ b/petri/src/vm/hyperv/hyperv.psm1 @@ -384,6 +384,12 @@ function New-CustomVM -TargetVtl $targetVtl ` -Vsid ([Guid]$vsid) ` | ConvertTo-CimEmbeddedString + + # For emulated NVMe drives, we must explicitly give the VM access to + # the backing VHD + foreach ($vhdPath in $controller.Value["Drives"]) { + icacls $vhdPath /grant "NT VIRTUAL MACHINE\${vmid}:(F)" | Out-Null + } } } From 84b4be94c0c5583ee45d7d81be6bd9cb45586f1a Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Mon, 4 May 2026 21:41:36 +0000 Subject: [PATCH 10/15] acl in rust, and fix comments --- petri/src/vm/hyperv/hyperv.psm1 | 7 ------- petri/src/vm/hyperv/mod.rs | 14 ++++++++++++++ vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs | 9 --------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/petri/src/vm/hyperv/hyperv.psm1 b/petri/src/vm/hyperv/hyperv.psm1 index a458fd66f5..9430977c36 100644 --- a/petri/src/vm/hyperv/hyperv.psm1 +++ b/petri/src/vm/hyperv/hyperv.psm1 @@ -374,7 +374,6 @@ function New-CustomVM "and the module is available on this host.") } Import-Module HvlDeviceHost -ErrorAction Stop - Register-HvlDeviceHostClsid $CLSID_FIOV_NVME foreach ($controller in $NvmeControllers.GetEnumerator()) { $vsid = $controller.Name $targetVtl = $controller.Value["Vtl"] @@ -384,12 +383,6 @@ function New-CustomVM -TargetVtl $targetVtl ` -Vsid ([Guid]$vsid) ` | ConvertTo-CimEmbeddedString - - # For emulated NVMe drives, we must explicitly give the VM access to - # the backing VHD - foreach ($vhdPath in $controller.Value["Drives"]) { - icacls $vhdPath /grant "NT VIRTUAL MACHINE\${vmid}:(F)" | Out-Null - } } } diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index 00ff1266cf..f4f7bcc772 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -401,6 +401,20 @@ impl PetriVmmBackend for HyperVPetriBackend { } } + // Grant the VM access to NVMe VHDs + for path in config + .vmbus_storage_controllers + .values() + .filter(|c| matches!(c.controller_type, crate::VmbusStorageType::Nvme)) + .flat_map(|c| c.drives.values()) + .filter_map(|drive| match &drive.disk { + Some(Disk::Persistent(path)) => Some(path), + _ => None, + }) + { + acl_read_for_vm(path, Some(*vm.vmid())).context("failed to set ACL for nvme VHD")?; + } + let serial_pipe_path = vm.get_vm_com_port_path(1); let serial_log_file = log_source.log_file("guest")?; log_tasks.push(driver.spawn( diff --git a/vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs b/vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs index b142d0cefa..cf5500cf21 100644 --- a/vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs +++ b/vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs @@ -397,15 +397,6 @@ async fn storvsp_nvme_hyperv( const SECTOR_SIZE: u64 = 512; const EXPECTED_NVME_DISK_SIZE_BYTES: u64 = NVME_DISK_SECTORS * SECTOR_SIZE; - // Assumptions made by test infra & routines: - // - // 1. Some test-infra added disks are 64MiB in size. Since we find disks by size, - // ensure that our test disks are a different size. - // 2. Disks under test need to be at least 100MiB for the IO tests (see [`test_storage_linux`]), - // with some arbitrary buffer (5MiB in this case). - static_assertions::const_assert_ne!(EXPECTED_NVME_DISK_SIZE_BYTES, 64 * 1024 * 1024); - static_assertions::const_assert!(EXPECTED_NVME_DISK_SIZE_BYTES > 105 * 1024 * 1024); - let mut vhd = tempfile::NamedTempFile::with_suffix("nvme.vhd").context("create temp nvme vhd")?; vhd.as_file() From f4ca0af73ee75d04a67bf38a7d41369c8ba5fcb8 Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Mon, 4 May 2026 23:05:03 +0000 Subject: [PATCH 11/15] CLSID import workaround still necessary on win-amd64 --- petri/src/vm/hyperv/hyperv.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/petri/src/vm/hyperv/hyperv.psm1 b/petri/src/vm/hyperv/hyperv.psm1 index 9430977c36..486e3fadb1 100644 --- a/petri/src/vm/hyperv/hyperv.psm1 +++ b/petri/src/vm/hyperv/hyperv.psm1 @@ -374,6 +374,7 @@ function New-CustomVM "and the module is available on this host.") } Import-Module HvlDeviceHost -ErrorAction Stop + Register-HvlDeviceHostClsid $CLSID_FIOV_NVME foreach ($controller in $NvmeControllers.GetEnumerator()) { $vsid = $controller.Name $targetVtl = $controller.Value["Vtl"] From 8557b491339c6284f3d51473ddf27c826b71797b Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Tue, 5 May 2026 00:44:00 +0000 Subject: [PATCH 12/15] add "acl_full_for_vm" --- petri/src/vm/hyperv/mod.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index f4f7bcc772..0f7ca45f6e 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -412,7 +412,7 @@ impl PetriVmmBackend for HyperVPetriBackend { _ => None, }) { - acl_read_for_vm(path, Some(*vm.vmid())).context("failed to set ACL for nvme VHD")?; + acl_full_for_vm(path, Some(*vm.vmid())).context("failed to set ACL for nvme VHD")?; } let serial_pipe_path = vm.get_vm_com_port_path(1); @@ -624,6 +624,28 @@ fn acl_read_for_vm(path: &Path, id: Option) -> anyhow::Result<()> { Ok(()) } +fn acl_full_for_vm(path: &Path, id: Option) -> anyhow::Result<()> { + let sid_arg = format!( + "NT VIRTUAL MACHINE\\{name}:F", + name = if let Some(id) = id { + format!("{id:X}") + } else { + "Virtual Machines".to_string() + } + ); + let output = std::process::Command::new("icacls.exe") + .arg(path) + .arg("/grant") + .arg(sid_arg) + .output() + .context("failed to run icacls")?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + anyhow::bail!("icacls failed: {stderr}"); + } + Ok(()) +} + async fn hyperv_serial_log_task( driver: DefaultDriver, serial_pipe_path: String, From be1ac0a6005d16cb1b20da4f7c38368def3666c5 Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Tue, 5 May 2026 00:56:49 +0000 Subject: [PATCH 13/15] modify perms in icacls --- petri/src/vm/hyperv/mod.rs | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index 0f7ca45f6e..8cd9e3a738 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -372,7 +372,7 @@ impl PetriVmmBackend for HyperVPetriBackend { let local_path = igvm_file.as_ref().unwrap(); fs_err::copy(config.firmware.openhcl_firmware().unwrap(), local_path) .context("failed to copy igvm file")?; - acl_read_for_vm(local_path, Some(*vm.vmid())) + acl_for_vm(local_path, Some(*vm.vmid()), false) .context("failed to set ACL for igvm file")?; let openhcl_log_file = log_source.log_file("openhcl")?; @@ -412,7 +412,7 @@ impl PetriVmmBackend for HyperVPetriBackend { _ => None, }) { - acl_full_for_vm(path, Some(*vm.vmid())).context("failed to set ACL for nvme VHD")?; + acl_for_vm(path, Some(*vm.vmid()), true).context("failed to set ACL for nvme VHD")?; } let serial_pipe_path = vm.get_vm_com_port_path(1); @@ -602,36 +602,15 @@ impl PetriVmRuntime for HyperVPetriRuntime { } } -fn acl_read_for_vm(path: &Path, id: Option) -> anyhow::Result<()> { +fn acl_for_vm(path: &Path, id: Option, write: bool) -> anyhow::Result<()> { let sid_arg = format!( - "NT VIRTUAL MACHINE\\{name}:R", + "NT VIRTUAL MACHINE\\{name}:{perm}", name = if let Some(id) = id { format!("{id:X}") } else { "Virtual Machines".to_string() - } - ); - let output = std::process::Command::new("icacls.exe") - .arg(path) - .arg("/grant") - .arg(sid_arg) - .output() - .context("failed to run icacls")?; - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("icacls failed: {stderr}"); - } - Ok(()) -} - -fn acl_full_for_vm(path: &Path, id: Option) -> anyhow::Result<()> { - let sid_arg = format!( - "NT VIRTUAL MACHINE\\{name}:F", - name = if let Some(id) = id { - format!("{id:X}") - } else { - "Virtual Machines".to_string() - } + }, + perm = if write { 'M' } else { 'R' } ); let output = std::process::Command::new("icacls.exe") .arg(path) From de86a42c72128d0c58cf82c61ce294bff69e4ea9 Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Tue, 5 May 2026 23:31:34 +0000 Subject: [PATCH 14/15] trevor feedback: petri_disk_to_hyperv --- petri/src/vm/hyperv/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index 8cd9e3a738..aac9b228d4 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -402,17 +402,17 @@ impl PetriVmmBackend for HyperVPetriBackend { } // Grant the VM access to NVMe VHDs - for path in config + for controller in config .vmbus_storage_controllers .values() .filter(|c| matches!(c.controller_type, crate::VmbusStorageType::Nvme)) - .flat_map(|c| c.drives.values()) - .filter_map(|drive| match &drive.disk { - Some(Disk::Persistent(path)) => Some(path), - _ => None, - }) { - acl_for_vm(path, Some(*vm.vmid()), true).context("failed to set ACL for nvme VHD")?; + for drive in controller.drives.values() { + if let Some(path) = petri_disk_to_hyperv(drive.disk.as_ref(), &temp_dir).await? { + acl_for_vm(&path, Some(*vm.vmid()), true) + .context("failed to set ACL for nvme VHD")?; + } + } } let serial_pipe_path = vm.get_vm_com_port_path(1); From b1b4d59a9bff98c383872ee5889987b3aee9fb8f Mon Sep 17 00:00:00 2001 From: Hadi Orabi Date: Tue, 5 May 2026 23:48:02 +0000 Subject: [PATCH 15/15] copilot feedback --- petri/src/vm/hyperv/mod.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index aac9b228d4..6395e8323e 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -296,6 +296,13 @@ impl PetriVmmBackend for HyperVPetriBackend { } } + let nvme_disk_paths: Vec = storage_controllers + .values() + .filter(|c| matches!(c.controller_type, powershell::HyperVVmbusStorageType::Nvme)) + .flat_map(|c| c.drives.values()) + .filter_map(|drive| drive.disk.clone()) + .collect(); + // Attempt to enable COM3 and use that to get KMSG logs, otherwise // fall back to use diag_client. let supports_com3 = { @@ -402,17 +409,8 @@ impl PetriVmmBackend for HyperVPetriBackend { } // Grant the VM access to NVMe VHDs - for controller in config - .vmbus_storage_controllers - .values() - .filter(|c| matches!(c.controller_type, crate::VmbusStorageType::Nvme)) - { - for drive in controller.drives.values() { - if let Some(path) = petri_disk_to_hyperv(drive.disk.as_ref(), &temp_dir).await? { - acl_for_vm(&path, Some(*vm.vmid()), true) - .context("failed to set ACL for nvme VHD")?; - } - } + for path in &nvme_disk_paths { + acl_for_vm(path, Some(*vm.vmid()), true).context("failed to set ACL for nvme VHD")?; } let serial_pipe_path = vm.get_vm_com_port_path(1);