From 0fc15d74eed757b1d4aca65b34b990510d9ba1cc Mon Sep 17 00:00:00 2001 From: Clint Branham Date: Fri, 20 Mar 2026 15:56:03 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20OVA=20import=20=E2=80=94=20scsi=20bus,?= =?UTF-8?q?=20boot=20order,=20NIC=20model=20from=20OVF,=20metadata=20verif?= =?UTF-8?q?ication?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Locally verified against PVE 9.1 with Ubuntu 24.04 cloud OVA: - Use scsi bus (not sata) for imported disks, matching PVE UI behavior - Set boot=order=scsi0 for first disk - Parse NIC ResourceSubType from OVF (VmxNet3, E1000, E1000e, etc.) and map to PVE network model instead of hardcoding virtio - Add MapNicModel() to OvfMetadata with fallback to e1000 for unknown types - Integration test verifies name, cores, memory, ostype, scsi0, net0, boot qmpstatus fix (from previous local testing): - WaitForStatusTransition polls status/current endpoint for qmpstatus - PVE reports status=running but qmpstatus=paused for suspended VMs - SDN subnet ID uses zone prefix: {zone}-{ip}-{prefix} Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Models/Vms/OvfMetadata.cs | 34 ++++++++++++++++++- .../Cmdlets/Vms/ImportPveOvaCmdlet.cs | 18 +++++++--- .../Integration/Integration.Tests.ps1 | 13 +++++-- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/PSProxmoxVE.Core/Models/Vms/OvfMetadata.cs b/src/PSProxmoxVE.Core/Models/Vms/OvfMetadata.cs index cded707..b9f8364 100644 --- a/src/PSProxmoxVE.Core/Models/Vms/OvfMetadata.cs +++ b/src/PSProxmoxVE.Core/Models/Vms/OvfMetadata.cs @@ -28,6 +28,9 @@ namespace PSProxmoxVE.Core.Models.Vms /// The connection name (e.g. "VM Network", "bridged"). public string ConnectionName { get; set; } = string.Empty; + + /// The OVF ResourceSubType (e.g. "E1000", "vmxnet3", "VirtualE1000e"). + public string ResourceSubType { get; set; } = string.Empty; } /// @@ -227,10 +230,12 @@ namespace PSProxmoxVE.Core.Models.Vms ?? item.SelectSingleNode("rasd:Caption", nsm)?.InnerText ?? "Network adapter"; var connection = item.SelectSingleNode("rasd:Connection", nsm)?.InnerText ?? string.Empty; + var nicSubType = item.SelectSingleNode("rasd:ResourceSubType", nsm)?.InnerText ?? string.Empty; metadata.NetworkAdapters.Add(new OvfNetworkAdapter { AdapterName = adapterName, - ConnectionName = connection + ConnectionName = connection, + ResourceSubType = nicSubType }); break; } @@ -323,6 +328,33 @@ namespace PSProxmoxVE.Core.Models.Vms return (int)value; } + /// + /// Maps an OVF ResourceSubType for a NIC to a PVE network model string. + /// + public static string MapNicModel(string resourceSubType) + { + if (string.IsNullOrEmpty(resourceSubType)) + return "virtio"; + + var lower = resourceSubType.ToLowerInvariant(); + + if (lower.Contains("vmxnet3")) + return "vmxnet3"; + if (lower.Contains("e1000e") || lower.Contains("virtuale1000e")) + return "e1000e"; + if (lower.Contains("e1000") || lower.Contains("virtuale1000")) + return "e1000"; + if (lower.Contains("vmxnet2") || lower.Contains("vmxnet")) + return "vmxnet3"; // PVE doesn't support vmxnet2, use vmxnet3 + if (lower.Contains("pcnet")) + return "e1000"; // pcnet not supported on PVE, fallback to e1000 + if (lower.Contains("virtio")) + return "virtio"; + + // Unknown model — default to e1000 (widely compatible, no driver install needed) + return "e1000"; + } + private static string MapOsType(string osDescription) { if (string.IsNullOrEmpty(osDescription)) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/ImportPveOvaCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/ImportPveOvaCmdlet.cs index 694d95b..506e77f 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/ImportPveOvaCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/ImportPveOvaCmdlet.cs @@ -223,20 +223,30 @@ namespace PSProxmoxVE.Cmdlets.Vms if (!string.IsNullOrEmpty(metadata.OsTypeHint) && metadata.OsTypeHint != "other") vmConfig["ostype"] = metadata.OsTypeHint; - // Add disk import-from parameters + // Add disk import-from parameters (use scsi bus like PVE UI) + string? firstDisk = null; for (int i = 0; i < metadata.Disks.Count; i++) { var disk = metadata.Disks[i]; - var diskSlot = $"{disk.BusType}{i}"; + var diskSlot = $"scsi{i}"; var importFrom = $"{Storage}:import/{fileName}/{disk.FileName}"; vmConfig[diskSlot] = $"{TargetStorage}:0,import-from={importFrom}"; + firstDisk ??= diskSlot; WriteVerbose($" Disk {diskSlot}: import-from={importFrom} -> {TargetStorage}"); } - // Add network adapters + // Set boot order to the first disk + if (firstDisk != null) + vmConfig["boot"] = $"order={firstDisk}"; + + // Set sockets (PVE default) + vmConfig["sockets"] = 1; + + // Add network adapters with model from OVF for (int i = 0; i < metadata.NetworkAdapters.Count; i++) { - vmConfig[$"net{i}"] = "virtio,bridge=vmbr0"; + var nicModel = OvfMetadata.MapNicModel(metadata.NetworkAdapters[i].ResourceSubType); + vmConfig[$"net{i}"] = $"{nicModel},bridge=vmbr0"; } var createTask = vmService.CreateVm(session, Node, vmConfig); diff --git a/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1 b/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1 index a933470..8be1073 100644 --- a/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1 +++ b/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1 @@ -1214,7 +1214,7 @@ Describe 'Integration Tests' -Tag 'Integration' { $metadata.Disks.Count | Should -BeGreaterThan 0 } - It 'Should import OVA as a VM (Import-PveOva)' { + It 'Should import OVA as a VM with correct metadata (Import-PveOva)' { if (Skip-IfNoTarget) { return } if (-not $script:OvaPath -or -not (Test-Path $script:OvaPath)) { Set-ItResult -Skipped -Because 'PVETEST_OVA_PATH not set or file not found' @@ -1228,9 +1228,18 @@ Describe 'Integration Tests' -Tag 'Integration' { $vm | Should -Not -BeNullOrEmpty $script:CreatedVmIds.Add($vm.VmId) - # Verify the VM exists + # Verify VM exists with correct name $found = Get-PveVm -Node $script:Node -Name 'pester-ova-vm' | Select-Object -First 1 $found | Should -Not -BeNullOrEmpty + $found.Name | Should -Be 'pester-ova-vm' + + # Verify config matches OVF metadata + $config = Get-PveVmConfig -Node $script:Node -VmId $vm.VmId + $config.Memory | Should -BeGreaterThan 0 + $config.Cores | Should -BeGreaterThan 0 + $config.OsType | Should -Be 'l26' + $config.Scsi0 | Should -Not -BeNullOrEmpty -Because 'disk should be imported to scsi0' + $config.Net0 | Should -Not -BeNullOrEmpty -Because 'network adapter should be configured' } It 'Should start the OVA-imported VM' {