Skip to content

Commit 6c69039

Browse files
committed
wip
1 parent 56f7ac8 commit 6c69039

File tree

5 files changed

+136
-48
lines changed

5 files changed

+136
-48
lines changed

petri/src/vm/hyperv/hyperv.psm1

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -479,33 +479,34 @@ function Set-Vtl2Settings {
479479
[Parameter(Mandatory = $true)]
480480
[string]$SettingsFile,
481481

482-
[string]$ClientName = 'Petri',
483-
484-
[int]$ConvertDepth = 8
482+
[string]$ClientName = 'Petri'
485483
)
486484

487-
# TODO: Support protobuf in addition to JSON formatted settings.
488-
# (Should be easy, right? Just have the serde code write the pb output directly and read it
489-
# into a byte array here instead of reading the JSON file.)
490-
$settingsJson = Get-Content -Raw -Path $SettingsFile| ConvertTo-Json -Depth $ConvertDepth -Compress
485+
$settingsContent = Get-Content -Raw -Path $SettingsFile
491486

492487
$guestManagement = Get-VmGuestManagementService
493488

494489
$options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions
495490
$options.SetCustomOption("ClientName", $ClientName, $false)
496491

492+
Write-Host "aaaa"
493+
497494
# Parameter - VmId
498495
$p1 = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("VmId", $VmId.ToString(), [Microsoft.Management.Infrastructure.cimtype]::String, [Microsoft.Management.Infrastructure.CimFlags]::In)
499496

497+
Write-Host "bbbb"
498+
500499
# Parameter - Namespace
501500
$p2 = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("Namespace", $Namespace, [Microsoft.Management.Infrastructure.cimtype]::String, [Microsoft.Management.Infrastructure.CimFlags]::In)
502501

502+
Write-Host "cccc"
503+
503504
# Parameter - Settings
504505
# The input is a byte buffer with the size prepended.
505506
# Size is a uint32 in network byte order (i.e. Big Endian)
506-
# Size includes the itself and the payload.
507+
# Size includes the size itself and the payload.
507508

508-
$bytes = [system.Text.Encoding]::UTF8.GetBytes($settingsJson)
509+
$bytes = [system.Text.Encoding]::UTF8.GetBytes($settingsContent)
509510

510511
$header = [System.BitConverter]::GetBytes([uint32]($bytes.Length + 4))
511512
if ([System.BitConverter]::IsLittleEndian) {

petri/src/vm/hyperv/mod.rs

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,21 @@ pub struct HyperVPetriRuntime {
6969
is_isolated: bool,
7070
}
7171

72+
pub struct HyperVPetriConfig {
73+
pub initial_vtl2_settings: Option<vtl2_settings_proto::Vtl2Settings>,
74+
}
75+
7276
impl HyperVPetriRuntime {
7377
pub async fn add_openhcl_nvme_storage<P: AsRef<Path>>(
7478
&self,
7579
instance_id: Option<&guid::Guid>,
7680
vhd_paths: &[P],
7781
) -> anyhow::Result<()> {
82+
for vhd_path in vhd_paths {
83+
acl_for_vm(vhd_path, Some(*self.vm.vmid()), VmFileAccess::FullControl)
84+
.context("grant VM access to VHD")?;
85+
}
86+
7887
self.vm
7988
.add_openhcl_nvme_storage(instance_id, vhd_paths)
8089
.await
@@ -90,7 +99,7 @@ impl HyperVPetriRuntime {
9099

91100
#[async_trait]
92101
impl PetriVmmBackend for HyperVPetriBackend {
93-
type VmmConfig = ();
102+
type VmmConfig = HyperVPetriConfig;
94103
type VmRuntime = HyperVPetriRuntime;
95104

96105
fn check_compat(firmware: &Firmware, arch: MachineArch) -> bool {
@@ -113,10 +122,6 @@ impl PetriVmmBackend for HyperVPetriBackend {
113122
modify_vmm_config: Option<impl FnOnce(Self::VmmConfig) -> Self::VmmConfig + Send>,
114123
resources: &PetriVmResources,
115124
) -> anyhow::Result<Self::VmRuntime> {
116-
if modify_vmm_config.is_some() {
117-
panic!("specified modify_vmm_config, but that is not supported for hyperv");
118-
}
119-
120125
let PetriVmConfig {
121126
name,
122127
arch,
@@ -413,7 +418,7 @@ impl PetriVmmBackend for HyperVPetriBackend {
413418
// Hyper-V (e.g., if it is in a WSL filesystem).
414419
let igvm_file = temp_dir.path().join("igvm.bin");
415420
fs_err::copy(src_igvm_file, &igvm_file).context("failed to copy igvm file")?;
416-
acl_read_for_vm(&igvm_file, Some(*vm.vmid()))
421+
acl_for_vm(&igvm_file, Some(*vm.vmid()), VmFileAccess::Read)
417422
.context("failed to set ACL for igvm file")?;
418423

419424
// TODO: only increase VTL2 memory on debug builds
@@ -511,6 +516,21 @@ impl PetriVmmBackend for HyperVPetriBackend {
511516
hyperv_serial_log_task(driver.clone(), serial_pipe_path, serial_log_file),
512517
));
513518

519+
// todo mattkur
520+
let initial_vtl2_settings = if let Some(f) = modify_vmm_config {
521+
f(HyperVPetriConfig {
522+
initial_vtl2_settings: None,
523+
})
524+
.initial_vtl2_settings
525+
} else {
526+
None
527+
};
528+
529+
if let Some(settings) = initial_vtl2_settings {
530+
tracing::info!(?settings, "applying initial VTL2 settings");
531+
vm.set_base_vtl2_settings(&settings).await?;
532+
}
533+
514534
vm.start().await?;
515535

516536
Ok(HyperVPetriRuntime {
@@ -635,17 +655,36 @@ impl PetriVmRuntime for HyperVPetriRuntime {
635655
}
636656
}
637657

638-
fn acl_read_for_vm(path: &Path, id: Option<guid::Guid>) -> anyhow::Result<()> {
658+
/// The type of access to grant the VM for a file.
659+
#[expect(missing_docs)]
660+
enum VmFileAccess {
661+
Read,
662+
FullControl,
663+
}
664+
665+
/// The Hyper-V security model requires that the VM be granted explicit access to any
666+
/// resources assigned. Hyper-V takes care of this when you use the admin facing
667+
/// PowerShell cmdlets and some WMI flows. But, some tests use lower level APIs that don't
668+
/// do this automatically.
669+
fn acl_for_vm<P: AsRef<Path>>(
670+
path: P,
671+
id: Option<guid::Guid>,
672+
access: VmFileAccess,
673+
) -> anyhow::Result<()> {
639674
let sid_arg = format!(
640-
"NT VIRTUAL MACHINE\\{name}:R",
675+
"NT VIRTUAL MACHINE\\{name}:{perm}",
641676
name = if let Some(id) = id {
642677
format!("{id:X}")
643678
} else {
644679
"Virtual Machines".to_string()
680+
},
681+
perm = match access {
682+
VmFileAccess::Read => "R",
683+
VmFileAccess::FullControl => "F",
645684
}
646685
);
647686
let output = std::process::Command::new("icacls.exe")
648-
.arg(path)
687+
.arg(path.as_ref())
649688
.arg("/grant")
650689
.arg(sid_arg)
651690
.output()
@@ -654,6 +693,7 @@ fn acl_read_for_vm(path: &Path, id: Option<guid::Guid>) -> anyhow::Result<()> {
654693
let stderr = String::from_utf8_lossy(&output.stderr);
655694
anyhow::bail!("icacls failed: {stderr}");
656695
}
696+
657697
Ok(())
658698
}
659699

petri/src/vm/hyperv/powershell.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,10 @@ pub async fn run_check_vm_host_supports_hyperv_storage() -> anyhow::Result<bool>
11281128
///
11291129
/// This is true if there are Microsoft-internal utilties installed on the
11301130
/// host that support this feature.
1131+
///
1132+
/// TODO: Check that the resource is successfully added to the VM.
1133+
/// e.g. if there is a signature issue, it seems that this call completes
1134+
/// yet there was an error.
11311135
pub async fn run_configure_microsoft_internal_nvme_storage<P: AsRef<Path>>(
11321136
vmid: &Guid,
11331137
instance_id: Option<&Guid>,
@@ -1171,6 +1175,8 @@ pub async fn run_set_base_vtl2_settings(
11711175
.write_all(serde_json::to_string(vtl2_settings)?.as_bytes())
11721176
.context("writing settings to tempfile")?;
11731177

1178+
tracing::info!(?tempfile, ?vtl2_settings, ?vmid, "set base vtl2 settings");
1179+
11741180
run_host_cmd(
11751181
PowerShellBuilder::new()
11761182
.cmdlet("Import-Module")

petri/src/vm/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ impl<T: PetriVmmBackend> PetriVmBuilder<T> {
323323
let mut tasks = Vec::new();
324324

325325
{
326-
const TIMEOUT_DURATION_MINUTES: u64 = 7;
326+
const TIMEOUT_DURATION_MINUTES: u64 = 20; // DO NOT MERGE
327327
const TIMER_DURATION: Duration = Duration::from_secs(TIMEOUT_DURATION_MINUTES * 60);
328328
let log_source = resources.log_source.clone();
329329
let inspect_task =

vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs

Lines changed: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use mesh::rpc::RpcSend;
1818
use nvme_resources::NamespaceDefinition;
1919
use nvme_resources::NvmeControllerHandle;
2020
use petri::PetriVmBuilder;
21+
#[cfg(windows)]
2122
use petri::hyperv::HyperVPetriBackend;
2223
use petri::openvmm::OpenVmmPetriBackend;
2324
use petri::pipette::PipetteClient;
@@ -37,10 +38,11 @@ use storvsp_resources::ScsiControllerHandle;
3738
use storvsp_resources::ScsiDeviceAndPath;
3839
use storvsp_resources::ScsiPath;
3940
use vm_resource::IntoResource;
41+
#[cfg(windows)]
4042
use vmm_test_macros::hyperv_test;
4143
use vmm_test_macros::openvmm_test;
44+
#[cfg(windows)]
4245
use vtl2_settings_proto::Vtl2Settings;
43-
use vtl2_settings_proto::Vtl2SettingsFixed;
4446

4547
/// Create a VPCI device config for an NVMe controller assigned to VTL2, with a single namespace.
4648
/// The namespace will be backed by either a file or a ramdisk, depending on whether
@@ -624,12 +626,16 @@ async fn openhcl_linux_storvsp_dvd_nvme(
624626
Ok(())
625627
}
626628

629+
#[cfg(windows)]
627630
#[hyperv_test(openhcl_uefi_x64(vhd(ubuntu_2504_server_x64)))]
628631
pub async fn add_openhcl_nvme_storage(
629632
config: PetriVmBuilder<HyperVPetriBackend>,
630633
) -> anyhow::Result<()> {
631-
let instance_id = Guid::new_random();
634+
let vtl0_instance_id = Guid::new_random();
635+
let vtl2_instance_id = Guid::new_random();
636+
const VHD_SIZE: u64 = 200 * 1024 * 1024; // 200 MiB
632637

638+
// todo: generate a better temp file name
633639
let temp_dir = tempfile::tempdir()?;
634640
let vhd_path = temp_dir.path().join("test.vhd");
635641
let file = File::options()
@@ -641,43 +647,78 @@ pub async fn add_openhcl_nvme_storage(
641647
.open(&vhd_path)
642648
.context("create file")?;
643649

644-
file.set_len(32 * 1024 * 1024).context("set file length")?;
650+
file.set_len(VHD_SIZE).context("set file length")?;
645651
disk_vhd1::Vhd1Disk::make_fixed(&file).context("make fixed")?;
646652

647-
let (mut vm, agent) = config.run().await?;
648-
vm.backend()
649-
.add_openhcl_nvme_storage(Some(&instance_id), &[vhd_path.to_str().unwrap()])
650-
.await?;
651-
652-
let mut vtl2_settings = Vtl2Settings {
653+
let initial_vtl2_settings = Vtl2Settings {
653654
version: vtl2_settings_proto::vtl2_settings_base::Version::V1.into(),
654-
dynamic: Some(Default::default()),
655-
fixed: Some(Default::default()),
655+
dynamic: Some(vtl2_settings_proto::Vtl2SettingsDynamic {
656+
storage_controllers: vec![
657+
Vtl2StorageControllerBuilder::scsi()
658+
.with_instance_id(vtl0_instance_id)
659+
.build(),
660+
],
661+
..Default::default()
662+
}),
663+
fixed: None,
656664
namespace_settings: Default::default(),
657665
};
658-
vtl2_settings
659-
.dynamic
660-
.as_mut()
661-
.unwrap()
662-
.storage_controllers
666+
667+
// This is a bit ugly; ideally the HyperV backend would munge and set any
668+
// VTL2 settings to the modify call (just like happens for OpenVMM).
669+
// For now, just take the expedient path to get this test stood up.
670+
let mut vtl2_settings = initial_vtl2_settings.clone();
671+
672+
let (mut vm, agent) = config
673+
.with_vmbus_redirect(true)
674+
.modify_backend(move |b| {
675+
assert!(b.initial_vtl2_settings.is_none());
676+
petri::hyperv::HyperVPetriConfig {
677+
initial_vtl2_settings: Some(initial_vtl2_settings),
678+
}
679+
})
680+
.run()
681+
.await?;
682+
683+
// Runtime add the NVMe device and update the VTL2 settings to
684+
// include it.
685+
//
686+
// This _could_ happen before the VM is started, but do it at runtime
687+
// for test simplicity.
688+
689+
vm.backend()
690+
.add_openhcl_nvme_storage(Some(&vtl2_instance_id), &[vhd_path.to_str().unwrap()])
691+
.await?;
692+
693+
vtl2_settings.dynamic.as_mut().unwrap().storage_controllers[0]
694+
.luns
663695
.push(
664-
Vtl2StorageControllerBuilder::scsi()
665-
.with_instance_id(Guid::new_random())
666-
.add_lun(
667-
Vtl2LunBuilder::disk()
668-
.with_location(0)
669-
.with_physical_device(Vtl2StorageBackingDeviceBuilder::new(
670-
ControllerType::Nvme,
671-
instance_id,
672-
1,
673-
)),
674-
)
696+
Vtl2LunBuilder::disk()
697+
.with_location(0)
698+
.with_physical_device(Vtl2StorageBackingDeviceBuilder::new(
699+
ControllerType::Nvme,
700+
vtl2_instance_id,
701+
1,
702+
))
675703
.build(),
676704
);
677705
vm.backend().set_base_vtl2_settings(&vtl2_settings).await?;
678706

679-
// todo: configure vtl2 settings
680-
// todo: make sure the disk shows up in the VTL0 guest
707+
match vm.inspect_openhcl("vm/nvme/devices", None, None).await {
708+
Err(e) => tracing::error!(?e, "Failed to inspect NVMe devices"),
709+
Ok(devices) => tracing::info!(devices = %devices.json(), "NVMe devices"),
710+
}
711+
712+
test_storage_linux(
713+
&agent,
714+
vec![ExpectedGuestDevice {
715+
controller_guid: vtl0_instance_id,
716+
lun: 0,
717+
disk_size_sectors: (VHD_SIZE / 512) as usize,
718+
friendly_name: "nvme".to_string(),
719+
}],
720+
)
721+
.await?;
681722

682723
agent.power_off().await?;
683724
vm.wait_for_clean_teardown().await?;

0 commit comments

Comments
 (0)