Skip to content

Commit a508c30

Browse files
authored
petri: add support for Hyper-V + SCSI-to-SCSI relay, use it (#2171)
In doing #2137, I realized that I had done almost all the work required to do scsi-to-scsi relay for the Hyper-V backend. petri: - Add infra to set VTL2 settings for a Hyper-V test backend. - Return the SCSI controller VSID when one is added (required to reference that controller in VTL2 settings...). - Add a `hyperv_test` macro, so that the test can get access to the Hyper-V Petri backend (like `openvmm_test`). vmm_tests: - Write a test that uses this infra.
1 parent aa2697a commit a508c30

File tree

8 files changed

+437
-17
lines changed

8 files changed

+437
-17
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9286,6 +9286,7 @@ version = "0.0.0"
92869286
dependencies = [
92879287
"anyhow",
92889288
"disk_backend_resources",
9289+
"disk_vhd1",
92899290
"futures",
92909291
"get_resources",
92919292
"guid",

petri/src/vm/hyperv/hyperv.psm1

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,25 @@ function Get-VmCommandLine
181181
[System.Text.Encoding]::UTF8.GetString($vssd.FirmwareParameters)
182182
}
183183

184+
function Get-VmScsiControllerProperties
185+
{
186+
[CmdletBinding()]
187+
Param (
188+
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
189+
[System.Object]
190+
$Controller
191+
)
192+
193+
$vm = Get-VM -Id $Controller.VMId;
194+
$ControllerNumber = $Controller.ControllerNumber;
195+
196+
$vssd = Get-VmSystemSettings $Vm;
197+
$rasds = $vssd | Get-CimAssociatedInstance -ResultClassName "Msvm_ResourceAllocationSettingData" | Where-Object { $_.ResourceSubType -eq "Microsoft:Hyper-V:Synthetic SCSI Controller" };
198+
$rasd = $rasds[$ControllerNumber];
199+
200+
return "$ControllerNumber,$($rasd.VirtualSystemIdentifiers[0])"
201+
}
202+
184203
function Set-VmScsiControllerTargetVtl
185204
{
186205
[CmdletBinding()]
@@ -464,4 +483,64 @@ function Get-GuestStateFile
464483
$guestStateFile = $vssd.GuestStateFile
465484

466485
return "$guestStateDataRoot\$guestStateFile"
467-
}
486+
}
487+
488+
function Set-Vtl2Settings {
489+
[CmdletBinding()]
490+
param (
491+
[Parameter(Position = 0, Mandatory = $true)]
492+
[Guid] $VmId,
493+
494+
[Parameter(Mandatory = $true)]
495+
[ValidateNotNullOrEmpty()]
496+
[string]$Namespace,
497+
498+
[Parameter(Mandatory = $true)]
499+
[string]$SettingsFile,
500+
501+
[string]$ClientName = 'Petri'
502+
)
503+
504+
$settingsContent = Get-Content -Raw -Path $SettingsFile
505+
506+
$guestManagement = Get-VmGuestManagementService
507+
508+
$options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions
509+
$options.SetCustomOption("ClientName", $ClientName, $false)
510+
511+
# Parameter - VmId
512+
$p1 = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("VmId", $VmId.ToString(), [Microsoft.Management.Infrastructure.cimtype]::String, [Microsoft.Management.Infrastructure.CimFlags]::In)
513+
514+
# Parameter - Namespace
515+
$p2 = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("Namespace", $Namespace, [Microsoft.Management.Infrastructure.cimtype]::String, [Microsoft.Management.Infrastructure.CimFlags]::In)
516+
517+
# Parameter - Settings
518+
# The input is a byte buffer with the size prepended.
519+
# Size is a uint32 in network byte order (i.e. Big Endian)
520+
# Size includes the size itself and the payload.
521+
522+
$bytes = [system.Text.Encoding]::UTF8.GetBytes($settingsContent)
523+
524+
$header = [System.BitConverter]::GetBytes([uint32]($bytes.Length + 4))
525+
if ([System.BitConverter]::IsLittleEndian) {
526+
[System.Array]::Reverse($header)
527+
}
528+
$bytes = $header + $bytes
529+
530+
$p3 = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("Settings", $bytes, [Microsoft.Management.Infrastructure.cimtype]::UInt8Array, [Microsoft.Management.Infrastructure.CimFlags]::In)
531+
532+
$result = $guestManagement | Invoke-CimMethod -MethodName GetManagementVtlSettings -Arguments @{"VmId" = $VmId.ToString(); "Namespace" = $Namespace } |
533+
Trace-CimMethodExecution -CimInstance $guestManagement -MethodName "GetManagementVtlSettings"
534+
$updateId = $result.CurrentUpdateId
535+
536+
$p4 = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("CurrentUpdateId", $updateId, [Microsoft.Management.Infrastructure.cimtype]::UInt64, [Microsoft.Management.Infrastructure.CimFlags]::In)
537+
538+
$params = New-Object Microsoft.Management.Infrastructure.CimMethodParametersCollection
539+
$params.Add($p1); $params.Add($p2); $params.Add($p3); $params.Add($p4)
540+
541+
$cimSession = New-CimSession
542+
$cimSession.InvokeMethod("root\virtualization\v2", $guestManagement, "SetManagementVtlSettings", $params, $options) |
543+
Trace-CimMethodExecution -CimInstance $guestManagement -MethodName "SetManagementVtlSettings" | Out-Null
544+
545+
$cimSession | Remove-CimSession | Out-Null
546+
}

petri/src/vm/hyperv/mod.rs

Lines changed: 151 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@ use vmgs_resources::GuestStateEncryptionPolicy;
5959
/// The Hyper-V Petri backend
6060
pub struct HyperVPetriBackend {}
6161

62+
/// Represents a SCSI Controller addeded to a VM.
63+
#[derive(Debug)]
64+
pub struct HyperVScsiController {
65+
/// An identifier provided by the test to identify this controller.
66+
pub test_id: String,
67+
68+
/// The controller number assigned by Hyper-V.
69+
pub controller_number: u32,
70+
71+
/// The target VTL this controller is mapped to (supplied by test).
72+
pub target_vtl: u32,
73+
74+
/// The VSID assigned by Hyper-V for this controller.
75+
pub vsid: guid::Guid,
76+
}
77+
6278
/// Resources needed at runtime for a Hyper-V Petri VM
6379
pub struct HyperVPetriRuntime {
6480
vm: HyperVVM,
@@ -68,11 +84,63 @@ pub struct HyperVPetriRuntime {
6884

6985
is_openhcl: bool,
7086
is_isolated: bool,
87+
88+
/// Last VTL2 settings set on this VM.
89+
vtl2_settings: Option<vtl2_settings_proto::Vtl2Settings>,
90+
91+
/// Test-added SCSI controllers.
92+
/// TODO (future PR): push this into `PetriVmConfig` and use in
93+
/// openvmm as well.
94+
additional_scsi_controllers: Vec<HyperVScsiController>,
95+
}
96+
97+
/// Additional configuration for a Hyper-V VM.
98+
#[derive(Default, Debug)]
99+
pub struct HyperVPetriConfig {
100+
/// VTL2 settings to configure on the VM before petri powers it on.
101+
initial_vtl2_settings: Option<vtl2_settings_proto::Vtl2Settings>,
102+
103+
/// Test-added SCSI controllers (targeting specific VTLs).
104+
/// A tuple if test-identifier and targetvtl. Test-identifier
105+
/// is used so that the test can find a specific controller, if that
106+
/// is important to the test. These are resolved into a list of
107+
/// [`HyperVScsiController`] objects stored in the runtime.
108+
additional_scsi_controllers: Vec<(String, u32)>,
109+
}
110+
111+
impl HyperVPetriConfig {
112+
/// Add custom VTL 2 settings.
113+
// TODO: At some point we want to replace uses of this with nicer with_disk,
114+
// with_nic, etc. methods. And unify this with the same function definition
115+
// as the openvmm backend.
116+
pub fn with_custom_vtl2_settings(
117+
mut self,
118+
f: impl FnOnce(&mut vtl2_settings_proto::Vtl2Settings),
119+
) -> Self {
120+
if self.initial_vtl2_settings.is_none() {
121+
self.initial_vtl2_settings = Some(vtl2_settings_proto::Vtl2Settings {
122+
version: vtl2_settings_proto::vtl2_settings_base::Version::V1.into(),
123+
fixed: None,
124+
dynamic: Some(Default::default()),
125+
namespace_settings: Default::default(),
126+
});
127+
}
128+
129+
f(self.initial_vtl2_settings.as_mut().unwrap());
130+
self
131+
}
132+
133+
/// Add an additional SCSI controller to the VM.
134+
/// Will be added before the VM starts.
135+
pub fn with_additional_scsi_controller(mut self, test_id: String, target_vtl: u32) -> Self {
136+
self.additional_scsi_controllers.push((test_id, target_vtl));
137+
self
138+
}
71139
}
72140

73141
#[async_trait]
74142
impl PetriVmmBackend for HyperVPetriBackend {
75-
type VmmConfig = ();
143+
type VmmConfig = HyperVPetriConfig;
76144
type VmRuntime = HyperVPetriRuntime;
77145

78146
fn check_compat(firmware: &Firmware, arch: MachineArch) -> bool {
@@ -95,10 +163,6 @@ impl PetriVmmBackend for HyperVPetriBackend {
95163
modify_vmm_config: Option<impl FnOnce(Self::VmmConfig) -> Self::VmmConfig + Send>,
96164
resources: &PetriVmResources,
97165
) -> anyhow::Result<Self::VmRuntime> {
98-
if modify_vmm_config.is_some() {
99-
panic!("specified modify_vmm_config, but that is not supported for hyperv");
100-
}
101-
102166
let PetriVmConfig {
103167
name,
104168
arch,
@@ -327,7 +391,7 @@ impl PetriVmmBackend for HyperVPetriBackend {
327391
BootDeviceType::Ide => Some((powershell::ControllerType::Ide, 0)),
328392
BootDeviceType::Scsi => Some((
329393
powershell::ControllerType::Scsi,
330-
vm.add_scsi_controller(0).await?,
394+
vm.add_scsi_controller(0).await?.0,
331395
)),
332396
BootDeviceType::Nvme => todo!("NVMe boot device not yet supported for Hyper-V"),
333397
} {
@@ -384,7 +448,7 @@ impl PetriVmmBackend for HyperVPetriBackend {
384448
vm.set_imc(&imc_hive).await?;
385449
}
386450

387-
let controller_number = vm.add_scsi_controller(0).await?;
451+
let controller_number = vm.add_scsi_controller(0).await?.0;
388452
vm.add_vhd(
389453
&agent_disk_path,
390454
powershell::ControllerType::Scsi,
@@ -437,7 +501,7 @@ impl PetriVmmBackend for HyperVPetriBackend {
437501
if build_and_persist_agent_image(&agent_image, &agent_disk_path)
438502
.context("vtl2 agent disk")?
439503
{
440-
let controller_number = vm.add_scsi_controller(2).await?;
504+
let controller_number = vm.add_scsi_controller(2).await?.0;
441505
vm.add_vhd(
442506
&agent_disk_path,
443507
powershell::ControllerType::Scsi,
@@ -507,6 +571,32 @@ impl PetriVmmBackend for HyperVPetriBackend {
507571
hyperv_serial_log_task(driver.clone(), serial_pipe_path, serial_log_file),
508572
));
509573

574+
let mut added_controllers = Vec::new();
575+
let mut vtl2_settings = None;
576+
577+
// TODO: If OpenHCL is being used, then translate storage through it.
578+
// (requires changes above where VHDs are added)
579+
if let Some(modify_vmm_config) = modify_vmm_config {
580+
let config = modify_vmm_config(HyperVPetriConfig::default());
581+
582+
tracing::debug!(?config, "additional hyper-v config");
583+
584+
for (test_id, target_vtl) in config.additional_scsi_controllers {
585+
let (controller_number, vsid) = vm.add_scsi_controller(target_vtl).await?;
586+
added_controllers.push(HyperVScsiController {
587+
test_id,
588+
controller_number,
589+
target_vtl,
590+
vsid,
591+
});
592+
}
593+
594+
if let Some(settings) = &config.initial_vtl2_settings {
595+
vm.set_base_vtl2_settings(settings).await?;
596+
vtl2_settings = Some(settings.clone());
597+
}
598+
}
599+
510600
vm.start().await?;
511601

512602
Ok(HyperVPetriRuntime {
@@ -516,10 +606,63 @@ impl PetriVmmBackend for HyperVPetriBackend {
516606
driver: driver.clone(),
517607
is_openhcl: openhcl_config.is_some(),
518608
is_isolated: firmware.isolation().is_some(),
609+
additional_scsi_controllers: added_controllers,
610+
vtl2_settings,
519611
})
520612
}
521613
}
522614

615+
impl HyperVPetriRuntime {
616+
/// Gets the VTL2 settings in the `Base` namespace.
617+
///
618+
/// TODO: For now, this is just a copy of whatever was last set via petri.
619+
/// The function signature (`async`, return `Result`) is to allow for
620+
/// future changes to actually query the VM for the current settings.
621+
pub async fn get_base_vtl2_settings(
622+
&self,
623+
) -> anyhow::Result<Option<vtl2_settings_proto::Vtl2Settings>> {
624+
Ok(self.vtl2_settings.clone())
625+
}
626+
627+
/// Get the list of additional SCSI controllers added to this VM (those
628+
/// configured to be added by the test, as opposed to the petri framework).
629+
pub fn get_additional_scsi_controllers(&self) -> &[HyperVScsiController] {
630+
&self.additional_scsi_controllers
631+
}
632+
633+
/// Set the VTL2 settings in the `Base` namespace (fixed settings, storage
634+
/// settings, etc).
635+
pub async fn set_base_vtl2_settings(
636+
&mut self,
637+
settings: &vtl2_settings_proto::Vtl2Settings,
638+
) -> anyhow::Result<()> {
639+
let r = self.vm.set_base_vtl2_settings(settings).await;
640+
if r.is_ok() {
641+
self.vtl2_settings = Some(settings.clone());
642+
}
643+
r
644+
}
645+
646+
/// Adds a VHD with the optionally specified location (a.k.a LUN) to the
647+
/// optionally specified controller.
648+
pub async fn add_vhd(
649+
&mut self,
650+
vhd: impl AsRef<Path>,
651+
controller_type: powershell::ControllerType,
652+
controller_location: Option<u32>,
653+
controller_number: Option<u32>,
654+
) -> anyhow::Result<()> {
655+
self.vm
656+
.add_vhd(
657+
vhd.as_ref(),
658+
controller_type,
659+
controller_location,
660+
controller_number,
661+
)
662+
.await
663+
}
664+
}
665+
523666
#[async_trait]
524667
impl PetriVmRuntime for HyperVPetriRuntime {
525668
type VmInspector = NoPetriVmInspector;

0 commit comments

Comments
 (0)