mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-28 00:38:57 +00:00
fix: OVA import uses single create call with inline disk import
PVE handles OVA disk extraction + import as part of qmcreate when
import-from is specified in the create parameters. The previous
approach (create VM, then POST config with import-from) caused lock
timeouts because the config update conflicted with the VM lock.
Now Import-PveOva passes disk import-from and network config directly
in the POST /nodes/{node}/qemu create call, matching how the PVE UI
handles OVA imports.
Verified locally against PVE 9.1 with Ubuntu 24.04 cloud OVA (571 MB).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -207,8 +207,10 @@ namespace PSProxmoxVE.Cmdlets.Vms
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Create the VM
|
||||
WriteVerbose($"Creating VM {vmId} on node '{Node}'...");
|
||||
// Step 5: Create VM with disks and network in a single API call.
|
||||
// PVE handles OVA disk extraction + import as part of qmcreate when
|
||||
// import-from is specified in the create parameters.
|
||||
WriteVerbose($"Creating VM {vmId} with disk import on node '{Node}'...");
|
||||
var vmConfig = new Dictionary<string, object>
|
||||
{
|
||||
["vmid"] = vmId
|
||||
@@ -221,68 +223,37 @@ namespace PSProxmoxVE.Cmdlets.Vms
|
||||
if (!string.IsNullOrEmpty(metadata.OsTypeHint) && metadata.OsTypeHint != "other")
|
||||
vmConfig["ostype"] = metadata.OsTypeHint;
|
||||
|
||||
var createTask = vmService.CreateVm(session, Node, vmConfig);
|
||||
|
||||
if (!string.IsNullOrEmpty(createTask.Upid))
|
||||
{
|
||||
WriteVerbose("Waiting for VM creation task to complete...");
|
||||
var completedCreate = taskService.WaitForTask(session, Node, createTask.Upid, null, null, null);
|
||||
if (completedCreate.ExitStatus != null && completedCreate.ExitStatus != "OK")
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new InvalidOperationException($"VM creation task failed with status: {completedCreate.ExitStatus}"),
|
||||
"VmCreationFailed",
|
||||
ErrorCategory.InvalidResult,
|
||||
createTask.Upid));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Import each disk from the OVA
|
||||
var importProgressRecord = new ProgressRecord(2,
|
||||
$"Importing OVA disks to VM {vmId}",
|
||||
"Importing disks...");
|
||||
|
||||
// Add disk import-from parameters
|
||||
for (int i = 0; i < metadata.Disks.Count; i++)
|
||||
{
|
||||
var disk = metadata.Disks[i];
|
||||
var diskSlot = $"{disk.BusType}{i}";
|
||||
var importFrom = $"{Storage}:import/{fileName}/{disk.FileName}";
|
||||
|
||||
importProgressRecord.PercentComplete = (int)((i * 100L) / metadata.Disks.Count);
|
||||
importProgressRecord.StatusDescription = $"Importing disk {i + 1}/{metadata.Disks.Count}: {disk.FileName} -> {diskSlot}";
|
||||
WriteProgress(importProgressRecord);
|
||||
|
||||
WriteVerbose($"Importing disk: {disk.FileName} -> VM {vmId} slot {diskSlot} (from {importFrom})");
|
||||
|
||||
var diskTask = vmService.ImportDisk(session, Node, vmId, diskSlot, TargetStorage, importFrom);
|
||||
|
||||
if (Wait.IsPresent && !string.IsNullOrEmpty(diskTask.Upid))
|
||||
{
|
||||
WriteVerbose($"Waiting for disk import task to complete: {diskSlot}...");
|
||||
var completedDisk = taskService.WaitForTask(session, Node, diskTask.Upid, null, null, null);
|
||||
if (completedDisk.ExitStatus != null && completedDisk.ExitStatus != "OK")
|
||||
{
|
||||
WriteWarning($"Disk import for {diskSlot} completed with status: {completedDisk.ExitStatus}");
|
||||
}
|
||||
}
|
||||
vmConfig[diskSlot] = $"{TargetStorage}:0,import-from={importFrom}";
|
||||
WriteVerbose($" Disk {diskSlot}: import-from={importFrom} -> {TargetStorage}");
|
||||
}
|
||||
|
||||
importProgressRecord.RecordType = ProgressRecordType.Completed;
|
||||
WriteProgress(importProgressRecord);
|
||||
|
||||
// Step 7: Configure network adapters
|
||||
if (metadata.NetworkAdapters.Count > 0)
|
||||
// Add network adapters
|
||||
for (int i = 0; i < metadata.NetworkAdapters.Count; i++)
|
||||
{
|
||||
var netConfig = new Dictionary<string, object>();
|
||||
for (int i = 0; i < metadata.NetworkAdapters.Count; i++)
|
||||
{
|
||||
// Use virtio model with default bridge vmbr0
|
||||
netConfig[$"net{i}"] = "virtio,bridge=vmbr0";
|
||||
}
|
||||
vmConfig[$"net{i}"] = "virtio,bridge=vmbr0";
|
||||
}
|
||||
|
||||
WriteVerbose($"Configuring {metadata.NetworkAdapters.Count} network adapter(s)...");
|
||||
vmService.SetVmConfig(session, Node, vmId, netConfig);
|
||||
var createTask = vmService.CreateVm(session, Node, vmConfig);
|
||||
|
||||
if (Wait.IsPresent && !string.IsNullOrEmpty(createTask.Upid))
|
||||
{
|
||||
WriteVerbose("Waiting for VM creation + disk import to complete...");
|
||||
var completedCreate = taskService.WaitForTask(session, Node, createTask.Upid, null, null, null);
|
||||
if (completedCreate.ExitStatus != null && completedCreate.ExitStatus != "OK")
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new InvalidOperationException($"OVA import task failed with status: {completedCreate.ExitStatus}"),
|
||||
"OvaImportFailed",
|
||||
ErrorCategory.InvalidResult,
|
||||
createTask.Upid));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 8: Output the created VM
|
||||
|
||||
Reference in New Issue
Block a user