From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 4AADA6D216 for ; Wed, 31 Mar 2021 17:13:13 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 32F8B119E0 for ; Wed, 31 Mar 2021 17:12:43 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id E08F8119D3 for ; Wed, 31 Mar 2021 17:12:38 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 9FCBA40270 for ; Wed, 31 Mar 2021 17:12:38 +0200 (CEST) Date: Wed, 31 Mar 2021 17:12:28 +0200 From: Fabian =?iso-8859-1?q?Gr=FCnbichler?= To: Proxmox VE development discussion References: <20210326123227.137243-1-d.jaeger@proxmox.com> <20210326123227.137243-2-d.jaeger@proxmox.com> In-Reply-To: <20210326123227.137243-2-d.jaeger@proxmox.com> MIME-Version: 1.0 User-Agent: astroid/0.15.0 (https://github.com/astroidmail/astroid) Message-Id: <1617202583.o619p5czdy.astroid@nora.none> Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-SPAM-LEVEL: Spam detection results: 0 AWL 0.027 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [proxmox.com, agent.pm, cpu.pm, machine.pm, qemu.pm, ovf.pm, qemuserver.pm] Subject: Re: [pve-devel] [PATCH v7 qemu-server] Add API for import wizards X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 31 Mar 2021 15:13:13 -0000 this is starting to shape up nicely. as promised, I now took a stab at=20 (roughly!) integrating this into our regular flow (see diff below): - IMPORT_DISK_RE now uses -1, as 0 actually can be mismatched by=20 NEW_DISK_RE - the actual import happens in create_disks - only the basic checks (match of "marked for import" with "import=20 sources") happen early on - the target storage and source volume are permission checked now - importvm is dropped in favor of calling create_vm with import_sources - update_vm_async now also supports import_sources the last two IMHO have some nice benefits: - we can now easily mix and match importing and newly allocating blank=20 disks in a single create call (allowing us to use a single GUI wizard=20 as well if we want) - create_vm and importvm don't have to duplicate all the surrounding=20 logic (or have strange contortions to make one call the other) - we could likely drop the separate import_disk API call, and let the=20 `importdisk` CLI command prepare parameters for a regular VM config=20 update I'm sure I've missed some corner cases, as I've only tested create_vm=20 with importing, and not the other newly exposed APIs. stat shows how much boilerplace/duplication this removes, although there=20 is probably even more potential here since `create_vm` partly duplicates=20 the `update_vm_api` sub that ends up calling `create_disks`: PVE/API2/Qemu.pm | 481 ++++++++++++++++++++-------------------------------= ---- 1 file changed, 171 insertions(+), 310 deletions(-) the original patch was +381 for that file, so in total we are now at=20 +242 instead. could you take a look and see if I missed anything fundamental? -----8<----- diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 2f50f38..41e1ab7 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -60,8 +60,19 @@ my $resolve_cdrom_alias =3D sub { } }; =20 +my $parse_import_sources =3D sub { + my $param =3D shift; + my $import =3D {}; + foreach my $pair (PVE::Tools::split_list($param)) { + my ($device, $diskimage) =3D split('=3D', $pair); + $import->{$device} =3D $diskimage; + } + + return $import; +}; + my $NEW_DISK_RE =3D qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!; -my $IMPORT_DISK_RE =3D qr!^([^/:\s]+):0$!; +my $IMPORT_DISK_RE =3D qr!^([^/:\s]+):-1$!; my $check_storage_access =3D sub { my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = =3D @_; =20 @@ -84,6 +95,12 @@ my $check_storage_access =3D sub { my $scfg =3D PVE::Storage::storage_config($storecfg, $storeid); raise_param_exc({ storage =3D> "storage '$storeid' does not support v= m images"}) if !$scfg->{content}->{images}; + } elsif ($volid =3D~ $IMPORT_DISK_RE) { + my $storeid =3D $1; + $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSp= ace']); + my $scfg =3D PVE::Storage::storage_config($storecfg, $storeid); + raise_param_exc({ storage =3D> "storage '$storeid' does not support v= m images"}) + if !$scfg->{content}->{images}; } else { PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmi= d, $volid); } @@ -91,6 +108,13 @@ my $check_storage_access =3D sub { =20 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Dat= astore.AllocateSpace']) if defined($settings->{vmstatestorage}); + + if (defined($settings->{import_sources})) { + my $images =3D $parse_import_sources->($settings->{import_sources}); + foreach my $source_image (values %$images) { + PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmi= d, $source_image); + } + } }; =20 my $check_storage_access_clone =3D sub { @@ -133,10 +157,25 @@ my $check_storage_access_clone =3D sub { return $sharedvm; }; =20 +# Raise exception if $format is not supported by $storeid +my $check_format_is_supported =3D sub { + my ($format, $storeid, $storecfg) =3D @_; + die "storage ID parameter must be passed to the sub" if !$storeid; + die "storage configuration must be passed to the sub" if !$storecfg; + + return if !$format; + + my (undef, $valid_formats) =3D PVE::Storage::storage_default_format($s= torecfg, $storeid); + my $supported =3D grep { $_ eq $format } @$valid_formats; + + die "format '$format' is not supported on storage $storeid" if !$suppo= rted; +}; + + # Note: $pool is only needed when creating a VM, because pool permissions # are automatically inherited if VM already exists inside a pool. my $create_disks =3D sub { - my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settin= gs, $default_storage) =3D @_; + my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settin= gs, $default_storage, $import) =3D @_; =20 my $vollist =3D []; =20 @@ -192,6 +231,69 @@ my $create_disks =3D sub { $disk->{size} =3D PVE::Tools::convert_size($size, 'kb' =3D> 'b'); delete $disk->{format}; # no longer needed $res->{$ds} =3D PVE::QemuServer::print_drive($disk); + } elsif ($volid =3D~ $IMPORT_DISK_RE) { + my $target_storage =3D $1; + + my $source =3D $import->{$ds}; + die "cannot import '$ds', no import source defined\n" if !$source; + $source =3D PVE::Storage::abs_filesystem_path($storecfg, $source, 1); + my $src_size =3D PVE::Storage::file_size_info($source); + die "Could not get file size of $source" if !defined($src_size); + + $check_format_is_supported->($disk->{format}, $storeid, $storecfg); + + my $dst_format =3D PVE::QemuServer::resolve_dst_disk_format( + $storecfg, + $storeid, + undef, + $disk->{format}, + ); + my $dst_volid =3D PVE::Storage::vdisk_alloc( + $storecfg, + $storeid, + $vmid, + $dst_format, + undef, + PVE::Tools::convert_size($src_size, 'b' =3D> 'kb'), + ); + + print "Importing disk image '$source' as '$dst_volid'...\n"; + eval { + local $SIG{INT} =3D + local $SIG{TERM} =3D + local $SIG{QUIT} =3D + local $SIG{HUP} =3D + local $SIG{PIPE} =3D sub { die "Interrupted by signal $!\n"; }; + + my $zeroinit =3D PVE::Storage::volume_has_feature( + $storecfg, + 'sparseinit', + $dst_volid, + ); + PVE::Storage::activate_volumes($storecfg, [$dst_volid]); + PVE::QemuServer::qemu_img_convert( + $source, + $dst_volid, + $src_size, + undef, + $zeroinit, + ); + PVE::Storage::deactivate_volumes($storecfg, [$dst_volid]); + + }; + if (my $err =3D $@) { + eval { PVE::Storage::vdisk_free($storecfg, $dst_volid) }; + warn "Cleanup of $dst_volid failed: $@ \n" if $@; + + die "Importing disk '$source' failed: $err\n" if $err; + } + push @$vollist, $dst_volid; + $disk->{file} =3D $dst_volid; + if ($ds !~ /^unused\d+$/) { + $disk->{size} =3D $src_size; + delete $disk->{format}; # no longer needed + } + $res->{$ds} =3D PVE::QemuServer::print_drive($disk); } else { =20 PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmi= d, $volid); @@ -218,7 +320,7 @@ my $create_disks =3D sub { } }; =20 - eval { PVE::QemuConfig->foreach_volume($settings, $code); }; + eval { PVE::QemuConfig->foreach_volume_full($settings, { include_unuse= d =3D> 1 }, $code); }; =20 # free allocated images on error if (my $err =3D $@) { @@ -544,6 +646,13 @@ __PACKAGE__->register_method({ default =3D> 0, description =3D> "Start VM after it was created successfully.", }, + import_sources =3D> { + description =3D> "\\0 delimited mapping of devices to disk images to= import." . + "For example, scsi0=3D/mnt/nfs/image1.vmdk", + type =3D> 'string', + format =3D> 'device-image-pair-alist', + optional =3D> 1, + }, }), }, returns =3D> { @@ -608,21 +717,34 @@ __PACKAGE__->register_method({ =20 &$check_cpu_model_access($rpcenv, $authuser, $param); =20 + my $import_devices =3D $parse_import_sources->($param->{import_source= s}); + foreach my $opt (keys %$param) { if (PVE::QemuServer::is_valid_drivename($opt)) { my $drive =3D PVE::QemuServer::parse_drive($opt, $param->{$opt}); raise_param_exc({ $opt =3D> "unable to parse drive options" }) if !$= drive; + raise_param_exc({ $opt =3D> "not marked for import, but import sourc= e defined" }) + if $drive->{file} !~ $IMPORT_DISK_RE && $import_devices->{$opt}; + raise_param_exc({ $opt =3D> "marked for import, but no import source= defined" }) + if $drive->{file} =3D~ $IMPORT_DISK_RE && !$import_devices->{$opt}; =20 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); $param->{$opt} =3D PVE::QemuServer::print_drive($drive); } } + foreach my $opt (keys %$import_devices) { + raise_param_exc({ import_sources =3D> "$opt not marked for import, but i= mport source defined" }) + if !defined($param->{$opt}); +=20 + } =20 PVE::QemuServer::add_random_macs($param); } else { my $keystr =3D join(' ', keys %$param); raise_param_exc({ archive =3D> "option conflicts with other options (= $keystr)"}) if $keystr; =20 + raise_param_exc({ import_sources =3D> "cannot import existing disk an= d restore backup." }) if $param->{import_sources}; + if ($archive eq '-') { die "pipe requires cli environment\n" if $rpcenv->{type} ne 'cli'; @@ -690,10 +812,11 @@ __PACKAGE__->register_method({ my $realcmd =3D sub { my $conf =3D $param; my $arch =3D PVE::QemuServer::get_vm_arch($conf); + my $import =3D $parse_import_sources->(extract_param($param, "import_sou= rces")); =20 my $vollist =3D []; eval { - $vollist =3D &$create_disks($rpcenv, $authuser, $conf, $arch, $store= cfg, $vmid, $pool, $param, $storage); + $vollist =3D &$create_disks($rpcenv, $authuser, $conf, $arch, $store= cfg, $vmid, $pool, $param, $storage, $import); =20 if (!$conf->{boot}) { my $devs =3D PVE::QemuServer::get_default_bootdevices($conf); @@ -1163,11 +1286,17 @@ my $update_vm_api =3D sub { die "cannot add non-replicatable volume to a replicated VM\n"; }; =20 + my $import_devices =3D $parse_import_sources->($param->{import_sources= }); + foreach my $opt (keys %$param) { if (PVE::QemuServer::is_valid_drivename($opt)) { # cleanup drive path my $drive =3D PVE::QemuServer::parse_drive($opt, $param->{$opt}); raise_param_exc({ $opt =3D> "unable to parse drive options" }) if !$d= rive; + raise_param_exc({ $opt =3D> "not marked for import, but import source= defined" }) + if $drive->{file} !~ $IMPORT_DISK_RE && $import_devices->{$opt}; + raise_param_exc({ $opt =3D> "marked for import, but no import source = defined" }) + if $drive->{file} =3D~ $IMPORT_DISK_RE && !$import_devices->{$opt}; PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); $check_replication->($drive); $param->{$opt} =3D PVE::QemuServer::print_drive($drive); @@ -1185,12 +1314,20 @@ my $update_vm_api =3D sub { } } =20 + foreach my $opt (keys %$import_devices) { + raise_param_exc({ import_sources =3D> "$opt not marked for import, but im= port source defined" }) + if !defined($param->{$opt}); + + } + &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@dele= te]); =20 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys = %$param]); =20 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param); =20 + delete $param->{import_sources}; + my $updatefn =3D sub { =20 my $conf =3D PVE::QemuConfig->load_config($vmid); @@ -1332,7 +1469,7 @@ my $update_vm_api =3D sub { PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $c= onf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt})) if defined($conf->{pending}->{$opt}); =20 - &$create_disks($rpcenv, $authuser, $conf->{pending}, $arch, $storecf= g, $vmid, undef, {$opt =3D> $param->{$opt}}); + &$create_disks($rpcenv, $authuser, $conf->{pending}, $arch, $storecf= g, $vmid, undef, {$opt =3D> $param->{$opt}}, {$opt =3D> $import_devices->{$= opt}}); } elsif ($opt =3D~ m/^serial\d+/) { if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param= ->{$opt} eq 'socket') { $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']); @@ -1476,6 +1613,13 @@ __PACKAGE__->register_method({ optional =3D> 1, requires =3D> 'delete', }, + import_sources =3D> { + description =3D> "\\0 delimited mapping of devices to disk images to= import." . + "For example, scsi0=3D/mnt/nfs/image1.vmdk", + type =3D> 'string', + format =3D> 'device-image-pair-alist', + optional =3D> 1, + }, digest =3D> { type =3D> 'string', description =3D> 'Prevent changes if current configuration file has = different SHA1 digest. This can be used to prevent concurrent modifications= .', @@ -4377,111 +4521,6 @@ __PACKAGE__->register_method({ return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param->{= vmid}, $param->{type}); }}); =20 -# Raise exception if $format is not supported by $storeid -my $check_format_is_supported =3D sub { - my ($format, $storeid, $storecfg) =3D @_; - die "storage ID parameter must be passed to the sub" if !$storeid; - die "storage configuration must be passed to the sub" if !$storecfg; - - return if !$format; - - my (undef, $valid_formats) =3D PVE::Storage::storage_default_format($s= torecfg, $storeid); - my $supported =3D grep { $_ eq $format } @$valid_formats; - - die "format '$format' is not supported on storage $storeid" if !$suppo= rted; -}; - -# storecfg ... PVE::Storage::config() -# vmid ... target VM ID -# vmconf ... target VM configuration -# source ... source image (volid or absolute path) -# target ... hash with -# storeid =3D> storage ID -# format =3D> disk format (optional) -# options =3D> hash with device options (may or may not contain :0) -# device =3D> device where the disk is attached (for example, scsi3) (o= ptional) -# -# returns ... volid of the allocated disk image (e.g. local-lvm:vm-100-dis= k-2) -my $import_disk_image =3D sub { - my ($storecfg, $vmid, $vmconf, $source, $target) =3D @_; - my $requested_format =3D $target->{format}; - my $storeid =3D $target->{storeid}; - - die "Source parameter is undefined!" if !defined $source; - $source =3D PVE::Storage::abs_filesystem_path($storecfg, $source, 1); - - eval { PVE::Storage::storage_config($storecfg, $storeid) }; - die "Error while importing disk image $source: $@\n" if $@; - - my $src_size =3D PVE::Storage::file_size_info($source); - die "Could not get file size of $source" if !defined($src_size); - - $check_format_is_supported->($requested_format, $storeid, $storecfg); - - my $dst_format =3D PVE::QemuServer::resolve_dst_disk_format( - $storecfg, - $storeid, - undef, - $requested_format, - ); - my $dst_volid =3D PVE::Storage::vdisk_alloc( - $storecfg, - $storeid, - $vmid, - $dst_format, - undef, - $src_size / 102, - ); - - print "Importing disk image '$source' as '$dst_volid'...\n"; - eval { - local $SIG{INT} =3D - local $SIG{TERM} =3D - local $SIG{QUIT} =3D - local $SIG{HUP} =3D - local $SIG{PIPE} =3D sub { die "Interrupted by signal $!\n"; }; - - my $zeroinit =3D PVE::Storage::volume_has_feature( - $storecfg, - 'sparseinit', - $dst_volid, - ); - PVE::Storage::activate_volumes($storecfg, [$dst_volid]); - PVE::QemuServer::qemu_img_convert( - $source, - $dst_volid, - $src_size, - undef, - $zeroinit, - ); - PVE::Storage::deactivate_volumes($storecfg, [$dst_volid]); - - }; - if (my $err =3D $@) { - eval { PVE::Storage::vdisk_free($storecfg, $dst_volid) }; - warn "Cleanup of $dst_volid failed: $@ \n" if $@; - - die "Importing disk '$source' failed: $err\n" if $err; - } - - $target->{options}->{file} =3D $dst_volid; - my $options_string =3D PVE::QemuServer::print_drive($target->{options}= ); - $target->{device} =3D PVE::QemuConfig->add_unused_volume($vmconf, $dst= _volid) - if !$target->{device}; - - $update_vm_api->( - { - vmid =3D> $vmid, - $target->{device} =3D> $options_string, - skiplock =3D> 1, - digest =3D> $vmconf->{digest}, - }, - 1, - ); - - return $dst_volid; -}; - __PACKAGE__->register_method ({ name =3D> 'importdisk', path =3D> '{vmid}/importdisk', @@ -4544,22 +4583,25 @@ __PACKAGE__->register_method ({ PVE::Storage::storage_config($storecfg, $storeid); =20 =20 - if ($device_options) { - # $device_options may or may not contain :0 - my $parsed =3D PVE::QemuServer::Drive::parse_drive($device, $device_o= ptions); - if ($parsed) { - raise_param_exc({$device_options =3D> "Invalid import syntax"}) - if !($parsed->{file} =3D~ $IMPORT_DISK_RE); - } else { - my $fake =3D "$storeid:0,$device_options"; - $parsed =3D PVE::QemuServer::Drive::parse_drive($device, $fake); - } - delete $parsed->{file}; - delete $parsed->{interface}; - delete $parsed->{index}; - $device_options =3D $parsed; + if (!$device_options) { + $device_options =3D "$storeid:0"; + } + + # $device_options may or may not contain :0 + my $parsed =3D PVE::QemuServer::Drive::parse_drive($device, $device_optio= ns); + + if ($parsed) { + raise_param_exc({$device_options =3D> "Invalid import syntax"}) + if !($parsed->{file} =3D~ $IMPORT_DISK_RE); + } else { + my $fake =3D "$storeid:0,$device_options"; + $parsed =3D PVE::QemuServer::Drive::parse_drive($device, $fake); } =20 + delete $parsed->{interface}; + delete $parsed->{index}; + $device_options =3D $parsed; + # Format can be set explicitly "--format vmdk" # or as part of device options "--device_options discard=3Don,format=3Dvm= dk" my $format =3D extract_param($param, 'format'); @@ -4570,192 +4612,11 @@ __PACKAGE__->register_method ({ } $check_format_is_supported->($format, $storeid, $storecfg); =20 - # quick checks before fork + lock - my $conf =3D PVE::QemuConfig->load_config($vmid); - PVE::QemuConfig->check_lock($conf); - PVE::Tools::assert_if_modified($conf->{digest}, $digest); - if ($device && $conf->{$device}) { - die "Could not import because device $device is already in ". - "use in VM $vmid. Choose a different device!"; - } - - my $worker =3D sub { - PVE::QemuConfig->lock_config($vmid, sub { - $conf =3D PVE::QemuConfig->load_config($vmid); - PVE::QemuConfig->check_lock($conf); - - PVE::Tools::assert_if_modified($conf->{digest}, $digest); - PVE::QemuConfig->set_lock($vmid, 'import'); - $conf =3D PVE::QemuConfig->load_config($vmid); - }); - - my $target =3D { - node =3D> $node, - storeid =3D> $storeid, - }; - $target->{format} =3D $format; - $target->{device} =3D $device; - $target->{options} =3D $device_options; - eval { $import_disk_image->($storecfg, $vmid, $conf, $source, $target= ) }; - my $err =3D $@; - eval { PVE::QemuConfig->remove_lock($vmid, 'import') }; - warn $@ if $@; - die $err if $err; - }; - return $rpcenv->fork_worker('importdisk', $vmid, $authuser, $worker); + return $update_vm_api->({ + $device =3D> PVE::QemuServer::Drive::print_drive($device_options), + import_sources =3D> "$device=3D$source", + digest =3D> $digest, + }); }}); =20 -__PACKAGE__->register_method({ - name =3D> 'importvm', - path =3D> '{vmid}/importvm', - method =3D> 'POST', - description =3D> "Import a VM from existing disk images.", - protected =3D> 1, - proxyto =3D> 'node', - parameters =3D> { - additionalProperties =3D> 0, - properties =3D> PVE::QemuServer::json_config_properties( - { - node =3D> get_standard_option('pve-node'), - vmid =3D> get_standard_option('pve-vmid', { completion =3D> - \&PVE::Cluster::complete_next_vmid }), - diskimage =3D> { - description =3D> "\\0 delimited mapping of devices to disk images. F= or " . - "example, scsi0=3D/mnt/nfs/image1.vmdk", - type =3D> 'string', - format =3D> 'device-image-pair-alist', - }, - start =3D> { - optional =3D> 1, - type =3D> 'boolean', - default =3D> 0, - description =3D> "Start VM after it was imported successfully.", - }, - }), - }, - returns =3D> { - type =3D> 'string', - }, - code =3D> sub { - my ($param) =3D @_; - my $node =3D extract_param($param, 'node'); - my $vmid =3D extract_param($param, 'vmid'); - my $diskimages_string =3D extract_param($param, 'diskimage'); - my $boot =3D extract_param($param, 'boot'); - my $start =3D extract_param($param, 'start'); - - my $rpcenv =3D PVE::RPCEnvironment::get(); - my $authuser =3D $rpcenv->get_user(); - my $storecfg =3D PVE::Storage::config(); - - PVE::Cluster::check_cfs_quorum(); - - my $import_param =3D {}; - foreach my $opt (keys %$param) { - next if $opt eq 'efidisk0'; - raise_param_exc({bootdisk =3D> "Deprecated: Use --boot order=3D inste= ad"}) - if $opt eq 'bootdisk'; - - if (PVE::QemuServer::Drive::is_valid_drivename($opt)) { - my $drive =3D PVE::QemuServer::parse_drive($opt, $param->{$opt}); - if ($drive->{file} =3D~ $IMPORT_DISK_RE) { - $import_param->{$opt} =3D $drive; - delete $param->{$opt}; - } - } - } - - my $diskimages =3D {}; - foreach my $pair (PVE::Tools::split_list($diskimages_string)) { - my ($device, $diskimage) =3D split('=3D', $pair); - $diskimages->{$device} =3D $diskimage; - raise_param_exc({ - $device =3D> "Device '$device' not marked for import, " . - "but import source '$diskimage' specified", - }) if !defined($import_param->{$device}); - PVE::Storage::abs_filesystem_path($storecfg, $diskimage, 1); - } - - foreach my $device (keys %$import_param) { - raise_param_exc({ - $device =3D> "Device '$device' marked for import, but no source given\n"= , - }) if !defined($diskimages->{$device}); - } - - eval { PVE::QemuConfig->create_and_lock_config($vmid, 0, 'import') }; - die "Unable to create config for VM import: $@" if $@; - - my $worker =3D sub { - my $reload_conf =3D sub { - my ($vmid) =3D @_; - my $conf =3D PVE::QemuConfig->load_config($vmid); - return $conf if PVE::QemuConfig->has_lock($conf, 'import'); - die "import lock in VM $vmid config file missing!"; - }; - - my $conf =3D $reload_conf->($vmid); - $update_vm_api->( - { - %$param, - node =3D> $node, - vmid =3D> $vmid, - skiplock =3D> 1, - digest =3D> $conf->{digest}, - }, - 1 - ); - - eval { - foreach my $device (keys %$import_param) { - $conf =3D $reload_conf->($vmid); - my $drive =3D $import_param->{$device}; - my $storeid =3D PVE::Storage::parse_volume_id($drive->{file}); - my $imported =3D $import_disk_image->( - $storecfg, - $vmid, - $conf, - $diskimages->{$device}, - { - storeid =3D> $storeid, - format =3D> $drive->{format}, - options =3D> $drive, - device =3D> $device, - }, - ); - } - }; - my $err =3D $@; - if ($err) { - eval { PVE::QemuServer::destroy_vm($storecfg, $vmid, 1) }; - warn "Could not destroy VM $vmid: $@" if "$@"; - - die "Import failed: $err"; - } - - $conf =3D $reload_conf->($vmid); - if (!$boot) { - my $bootdevs =3D PVE::QemuServer::get_default_bootdevices($conf); - $boot =3D PVE::QemuServer::print_bootorder($bootdevs); - } - $update_vm_api->( - { - node =3D> $node, - vmid =3D> $vmid, - boot =3D> $boot, - skiplock =3D> 1, - digest =3D> $conf->{digest}, - }, - 1, - ); - - eval { PVE::QemuConfig->remove_lock($vmid, 'import') }; - warn $@ if $@; - - PVE::QemuServer::vm_start($storecfg, $vmid) if $start; - }; - - return $rpcenv->fork_worker('importvm', $vmid, $authuser, $worker); - }}); - - 1; ----->8----- On March 26, 2021 1:32 pm, Dominic J=C3=A4ger wrote: > Extend qm importdisk/importovf functionality to the API. >=20 > Signed-off-by: Dominic J=C3=A4ger >=20 > --- > v6->v7: Feedback by Fabian G > - Introduce a regex for the import syntax :0 > - Use parameter list instead of hash for import helper > - More parsing, less string magic > - More VM config digest checking > - Create a schema format for diskimage source mapping > - Preliminarily remove some boot parameter handling > - Dare to really edit schema format subs for a cleaner solution > - Whitespace, variable names, ... >=20 > PVE/API2/Qemu.pm | 383 ++++++++++++++++++++++++++++++++++++++++- > PVE/API2/Qemu/Makefile | 2 +- > PVE/API2/Qemu/OVF.pm | 68 ++++++++ > PVE/QemuServer.pm | 52 +++++- > PVE/QemuServer/OVF.pm | 10 +- > 5 files changed, 502 insertions(+), 13 deletions(-) > create mode 100644 PVE/API2/Qemu/OVF.pm >=20 > diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm > index e95ab13..2f50f38 100644 > --- a/PVE/API2/Qemu.pm > +++ b/PVE/API2/Qemu.pm > @@ -45,7 +45,6 @@ BEGIN { > } > } > =20 > -use Data::Dumper; # fixme: remove > =20 > use base qw(PVE::RESTHandler); > =20 > @@ -62,6 +61,7 @@ my $resolve_cdrom_alias =3D sub { > }; > =20 > my $NEW_DISK_RE =3D qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!; > +my $IMPORT_DISK_RE =3D qr!^([^/:\s]+):0$!; > my $check_storage_access =3D sub { > my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage= ) =3D @_; > =20 > @@ -4377,4 +4377,385 @@ __PACKAGE__->register_method({ > return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param-= >{vmid}, $param->{type}); > }}); > =20 > +# Raise exception if $format is not supported by $storeid > +my $check_format_is_supported =3D sub { > + my ($format, $storeid, $storecfg) =3D @_; > + die "storage ID parameter must be passed to the sub" if !$storeid; > + die "storage configuration must be passed to the sub" if !$storecfg; > + > + return if !$format; > + > + my (undef, $valid_formats) =3D PVE::Storage::storage_default_format(= $storecfg, $storeid); > + my $supported =3D grep { $_ eq $format } @$valid_formats; > + > + die "format '$format' is not supported on storage $storeid" if !$sup= ported; > +}; > + > +# storecfg ... PVE::Storage::config() > +# vmid ... target VM ID > +# vmconf ... target VM configuration > +# source ... source image (volid or absolute path) > +# target ... hash with > +# storeid =3D> storage ID > +# format =3D> disk format (optional) > +# options =3D> hash with device options (may or may not contain :0) > +# device =3D> device where the disk is attached (for example, scsi3) = (optional) > +# > +# returns ... volid of the allocated disk image (e.g. local-lvm:vm-100-d= isk-2) > +my $import_disk_image =3D sub { > + my ($storecfg, $vmid, $vmconf, $source, $target) =3D @_; > + my $requested_format =3D $target->{format}; > + my $storeid =3D $target->{storeid}; > + > + die "Source parameter is undefined!" if !defined $source; > + $source =3D PVE::Storage::abs_filesystem_path($storecfg, $source, 1)= ; > + > + eval { PVE::Storage::storage_config($storecfg, $storeid) }; > + die "Error while importing disk image $source: $@\n" if $@; > + > + my $src_size =3D PVE::Storage::file_size_info($source); > + die "Could not get file size of $source" if !defined($src_size); > + > + $check_format_is_supported->($requested_format, $storeid, $storecfg)= ; > + > + my $dst_format =3D PVE::QemuServer::resolve_dst_disk_format( > + $storecfg, > + $storeid, > + undef, > + $requested_format, > + ); > + my $dst_volid =3D PVE::Storage::vdisk_alloc( > + $storecfg, > + $storeid, > + $vmid, > + $dst_format, > + undef, > + $src_size / 102, > + ); > + > + print "Importing disk image '$source' as '$dst_volid'...\n"; > + eval { > + local $SIG{INT} =3D > + local $SIG{TERM} =3D > + local $SIG{QUIT} =3D > + local $SIG{HUP} =3D > + local $SIG{PIPE} =3D sub { die "Interrupted by signal $!\n"; }; > + > + my $zeroinit =3D PVE::Storage::volume_has_feature( > + $storecfg, > + 'sparseinit', > + $dst_volid, > + ); > + PVE::Storage::activate_volumes($storecfg, [$dst_volid]); > + PVE::QemuServer::qemu_img_convert( > + $source, > + $dst_volid, > + $src_size, > + undef, > + $zeroinit, > + ); > + PVE::Storage::deactivate_volumes($storecfg, [$dst_volid]); > + > + }; > + if (my $err =3D $@) { > + eval { PVE::Storage::vdisk_free($storecfg, $dst_volid) }; > + warn "Cleanup of $dst_volid failed: $@ \n" if $@; > + > + die "Importing disk '$source' failed: $err\n" if $err; > + } > + > + $target->{options}->{file} =3D $dst_volid; > + my $options_string =3D PVE::QemuServer::print_drive($target->{option= s}); > + $target->{device} =3D PVE::QemuConfig->add_unused_volume($vmconf, $d= st_volid) > + if !$target->{device}; > + > + $update_vm_api->( > + { > + vmid =3D> $vmid, > + $target->{device} =3D> $options_string, > + skiplock =3D> 1, > + digest =3D> $vmconf->{digest}, > + }, > + 1, > + ); > + > + return $dst_volid; > +}; > + > +__PACKAGE__->register_method ({ > + name =3D> 'importdisk', > + path =3D> '{vmid}/importdisk', > + method =3D> 'POST', > + proxyto =3D> 'node', > + protected =3D> 1, > + description =3D> "Import an external disk image into a VM. The image= format ". > + "has to be supported by qemu-img.", > + parameters =3D> { > + additionalProperties =3D> 0, > + properties =3D> { > + node =3D> get_standard_option('pve-node'), > + vmid =3D> get_standard_option('pve-vmid', > + {completion =3D> \&PVE::QemuServer::complete_vmid}), > + source =3D> { > + description =3D> "Disk image to import. Can be a volid ". > + "(local:99/imageToImport.raw) or an absolute path on the server.", > + type =3D> 'string', > + }, > + device =3D> { > + type =3D> 'string', > + description =3D> "Bus/Device type of the new disk (e.g. 'ide0', ". > + "'scsi2'). Will add the image as unused disk if omitted.", > + enum =3D> [PVE::QemuServer::Drive::valid_drive_names()], > + optional =3D> 1, > + }, > + device_options =3D> { > + type =3D> 'string', > + description =3D> "Options to set for the new disk (e.g. 'discard=3Don,= backup=3D0')", > + optional =3D> 1, > + requires =3D> 'device', > + }, > + storage =3D> get_standard_option('pve-storage-id', { > + description =3D> "The storage to which the image will be imported to."= , > + completion =3D> \&PVE::QemuServer::complete_storage, > + }), > + format =3D> { > + type =3D> 'string', > + description =3D> 'Target format.', > + enum =3D> [ 'raw', 'qcow2', 'vmdk' ], > + optional =3D> 1, > + }, > + digest =3D> get_standard_option('pve-config-digest'), > + }, > + }, > + returns =3D> { type =3D> 'string'}, > + code =3D> sub { > + my ($param) =3D @_; > + my $vmid =3D extract_param($param, 'vmid'); > + my $node =3D extract_param($param, 'node'); > + my $source =3D extract_param($param, 'source'); > + my $digest =3D extract_param($param, 'digest'); > + my $device_options =3D extract_param($param, 'device_options'); > + my $device =3D extract_param($param, 'device'); > + my $storeid =3D extract_param($param, 'storage'); > + > + my $rpcenv =3D PVE::RPCEnvironment::get(); > + my $authuser =3D $rpcenv->get_user(); > + my $storecfg =3D PVE::Storage::config(); > + PVE::Storage::storage_config($storecfg, $storeid); > + > + > + if ($device_options) { > + # $device_options may or may not contain :0 > + my $parsed =3D PVE::QemuServer::Drive::parse_drive($device, $device= _options); > + if ($parsed) { > + raise_param_exc({$device_options =3D> "Invalid import syntax"}) > + if !($parsed->{file} =3D~ $IMPORT_DISK_RE); > + } else { > + my $fake =3D "$storeid:0,$device_options"; > + $parsed =3D PVE::QemuServer::Drive::parse_drive($device, $fake); > + } > + delete $parsed->{file}; > + delete $parsed->{interface}; > + delete $parsed->{index}; > + $device_options =3D $parsed; > + } > + > + # Format can be set explicitly "--format vmdk" > + # or as part of device options "--device_options discard=3Don,format=3D= vmdk" > + my $format =3D extract_param($param, 'format'); > + if ($device_options) { > + raise_param_exc({format =3D> "Format already specified in device_op= tions!"}) > + if $format && $device_options->{format}; > + $format =3D $format || $device_options->{format}; # may be undefine= d > + } > + $check_format_is_supported->($format, $storeid, $storecfg); > + > + # quick checks before fork + lock > + my $conf =3D PVE::QemuConfig->load_config($vmid); > + PVE::QemuConfig->check_lock($conf); > + PVE::Tools::assert_if_modified($conf->{digest}, $digest); > + if ($device && $conf->{$device}) { > + die "Could not import because device $device is already in ". > + "use in VM $vmid. Choose a different device!"; > + } > + > + my $worker =3D sub { > + PVE::QemuConfig->lock_config($vmid, sub { > + $conf =3D PVE::QemuConfig->load_config($vmid); > + PVE::QemuConfig->check_lock($conf); > + > + PVE::Tools::assert_if_modified($conf->{digest}, $digest); > + PVE::QemuConfig->set_lock($vmid, 'import'); > + $conf =3D PVE::QemuConfig->load_config($vmid); > + }); > + > + my $target =3D { > + node =3D> $node, > + storeid =3D> $storeid, > + }; > + $target->{format} =3D $format; > + $target->{device} =3D $device; > + $target->{options} =3D $device_options; > + eval { $import_disk_image->($storecfg, $vmid, $conf, $source, $targ= et) }; > + my $err =3D $@; > + eval { PVE::QemuConfig->remove_lock($vmid, 'import') }; > + warn $@ if $@; > + die $err if $err; > + }; > + return $rpcenv->fork_worker('importdisk', $vmid, $authuser, $worker); > + }}); > + > +__PACKAGE__->register_method({ > + name =3D> 'importvm', > + path =3D> '{vmid}/importvm', > + method =3D> 'POST', > + description =3D> "Import a VM from existing disk images.", > + protected =3D> 1, > + proxyto =3D> 'node', > + parameters =3D> { > + additionalProperties =3D> 0, > + properties =3D> PVE::QemuServer::json_config_properties( > + { > + node =3D> get_standard_option('pve-node'), > + vmid =3D> get_standard_option('pve-vmid', { completion =3D> > + \&PVE::Cluster::complete_next_vmid }), > + diskimage =3D> { > + description =3D> "\\0 delimited mapping of devices to disk images.= For " . > + "example, scsi0=3D/mnt/nfs/image1.vmdk", > + type =3D> 'string', > + format =3D> 'device-image-pair-alist', > + }, > + start =3D> { > + optional =3D> 1, > + type =3D> 'boolean', > + default =3D> 0, > + description =3D> "Start VM after it was imported successfully.", > + }, > + }), > + }, > + returns =3D> { > + type =3D> 'string', > + }, > + code =3D> sub { > + my ($param) =3D @_; > + my $node =3D extract_param($param, 'node'); > + my $vmid =3D extract_param($param, 'vmid'); > + my $diskimages_string =3D extract_param($param, 'diskimage'); > + my $boot =3D extract_param($param, 'boot'); > + my $start =3D extract_param($param, 'start'); > + > + my $rpcenv =3D PVE::RPCEnvironment::get(); > + my $authuser =3D $rpcenv->get_user(); > + my $storecfg =3D PVE::Storage::config(); > + > + PVE::Cluster::check_cfs_quorum(); > + > + my $import_param =3D {}; > + foreach my $opt (keys %$param) { > + next if $opt eq 'efidisk0'; > + raise_param_exc({bootdisk =3D> "Deprecated: Use --boot order=3D ins= tead"}) > + if $opt eq 'bootdisk'; > + > + if (PVE::QemuServer::Drive::is_valid_drivename($opt)) { > + my $drive =3D PVE::QemuServer::parse_drive($opt, $param->{$opt}); > + if ($drive->{file} =3D~ $IMPORT_DISK_RE) { > + $import_param->{$opt} =3D $drive; > + delete $param->{$opt}; > + } > + } > + } > + > + my $diskimages =3D {}; > + foreach my $pair (PVE::Tools::split_list($diskimages_string)) { > + my ($device, $diskimage) =3D split('=3D', $pair); > + $diskimages->{$device} =3D $diskimage; > + raise_param_exc({ > + $device =3D> "Device '$device' not marked for import, " . > + "but import source '$diskimage' specified", > + }) if !defined($import_param->{$device}); > + PVE::Storage::abs_filesystem_path($storecfg, $diskimage, 1); > + } > + > + foreach my $device (keys %$import_param) { > + raise_param_exc({ > + $device =3D> "Device '$device' marked for import, but no source given\= n", > + }) if !defined($diskimages->{$device}); > + } > + > + eval { PVE::QemuConfig->create_and_lock_config($vmid, 0, 'import') }; > + die "Unable to create config for VM import: $@" if $@; > + > + my $worker =3D sub { > + my $reload_conf =3D sub { > + my ($vmid) =3D @_; > + my $conf =3D PVE::QemuConfig->load_config($vmid); > + return $conf if PVE::QemuConfig->has_lock($conf, 'import'); > + die "import lock in VM $vmid config file missing!"; > + }; > + > + my $conf =3D $reload_conf->($vmid); > + $update_vm_api->( > + { > + %$param, > + node =3D> $node, > + vmid =3D> $vmid, > + skiplock =3D> 1, > + digest =3D> $conf->{digest}, > + }, > + 1 > + ); > + > + eval { > + foreach my $device (keys %$import_param) { > + $conf =3D $reload_conf->($vmid); > + my $drive =3D $import_param->{$device}; > + my $storeid =3D PVE::Storage::parse_volume_id($drive->{file}); > + my $imported =3D $import_disk_image->( > + $storecfg, > + $vmid, > + $conf, > + $diskimages->{$device}, > + { > + storeid =3D> $storeid, > + format =3D> $drive->{format}, > + options =3D> $drive, > + device =3D> $device, > + }, > + ); > + } > + }; > + my $err =3D $@; > + if ($err) { > + eval { PVE::QemuServer::destroy_vm($storecfg, $vmid, 1) }; > + warn "Could not destroy VM $vmid: $@" if "$@"; > + > + die "Import failed: $err"; > + } > + > + $conf =3D $reload_conf->($vmid); > + if (!$boot) { > + my $bootdevs =3D PVE::QemuServer::get_default_bootdevices($conf); > + $boot =3D PVE::QemuServer::print_bootorder($bootdevs); > + } > + $update_vm_api->( > + { > + node =3D> $node, > + vmid =3D> $vmid, > + boot =3D> $boot, > + skiplock =3D> 1, > + digest =3D> $conf->{digest}, > + }, > + 1, > + ); > + > + eval { PVE::QemuConfig->remove_lock($vmid, 'import') }; > + warn $@ if $@; > + > + PVE::QemuServer::vm_start($storecfg, $vmid) if $start; > + }; > + > + return $rpcenv->fork_worker('importvm', $vmid, $authuser, $worker); > + }}); > + > + > 1; > diff --git a/PVE/API2/Qemu/Makefile b/PVE/API2/Qemu/Makefile > index 5d4abda..bdd4762 100644 > --- a/PVE/API2/Qemu/Makefile > +++ b/PVE/API2/Qemu/Makefile > @@ -1,4 +1,4 @@ > -SOURCES=3DAgent.pm CPU.pm Machine.pm > +SOURCES=3DAgent.pm CPU.pm Machine.pm OVF.pm > =20 > .PHONY: install > install: > diff --git a/PVE/API2/Qemu/OVF.pm b/PVE/API2/Qemu/OVF.pm > new file mode 100644 > index 0000000..bd6e90b > --- /dev/null > +++ b/PVE/API2/Qemu/OVF.pm > @@ -0,0 +1,68 @@ > +package PVE::API2::Qemu::OVF; > + > +use strict; > +use warnings; > + > +use PVE::RESTHandler; > +use PVE::JSONSchema qw(get_standard_option); > +use PVE::QemuServer::OVF; > + > +use base qw(PVE::RESTHandler); > + > +__PACKAGE__->register_method ({ > + name =3D> 'index', > + path =3D> '', > + method =3D> 'GET', > + proxyto =3D> 'node', > + description =3D> "Read an .ovf manifest.", > + parameters =3D> { > + additionalProperties =3D> 0, > + properties =3D> { > + node =3D> get_standard_option('pve-node'), > + manifest =3D> { > + description =3D> ".ovf manifest", > + type =3D> 'string', > + }, > + }, > + }, > + returns =3D> { > + description =3D> "VM config according to .ovf manifest and digest of ma= nifest", > + type =3D> "object", > + }, > + returns =3D> { > + type =3D> 'object', > + additionalProperties =3D> 1, > + properties =3D> PVE::QemuServer::json_ovf_properties({ > + name =3D> { > + type =3D> 'string', > + optional =3D> 1, > + }, > + cores =3D> { > + type =3D> 'integer', > + optional =3D> 1, > + }, > + memory =3D> { > + type =3D> 'integer', > + optional =3D> 1, > + }, > + }), > + }, > + code =3D> sub { > + my ($param) =3D @_; > + > + my $manifest =3D $param->{manifest}; > + die "$manifest: non-existent or non-regular file\n" if (! -f $manifest)= ; > + > + my $parsed =3D PVE::QemuServer::OVF::parse_ovf($manifest, 0, 1); > + my $result; > + $result->{cores} =3D $parsed->{qm}->{cores}; > + $result->{name} =3D $parsed->{qm}->{name}; > + $result->{memory} =3D $parsed->{qm}->{memory}; > + my $disks =3D $parsed->{disks}; > + foreach my $disk (@$disks) { > + $result->{$disk->{disk_address}} =3D $disk->{backing_file}; > + } > + return $result; > + }}); > + > +1; > \ No newline at end of file > diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm > index 1c0b5c2..131c0b6 100644 > --- a/PVE/QemuServer.pm > +++ b/PVE/QemuServer.pm > @@ -300,7 +300,7 @@ my $confdesc =3D { > optional =3D> 1, > type =3D> 'string', > description =3D> "Lock/unlock the VM.", > - enum =3D> [qw(backup clone create migrate rollback snapshot snapshot-de= lete suspending suspended)], > + enum =3D> [qw(backup clone create migrate rollback snapshot snapshot-de= lete suspending suspended import)], > }, > cpulimit =3D> { > optional =3D> 1, > @@ -985,19 +985,41 @@ PVE::JSONSchema::register_format('pve-volume-id-or-= qm-path', \&verify_volume_id_ > sub verify_volume_id_or_qm_path { > my ($volid, $noerr) =3D @_; > =20 > - if ($volid eq 'none' || $volid eq 'cdrom' || $volid =3D~ m|^/|) { > - return $volid; > - } > + return $volid eq 'none' || $volid eq 'cdrom' ? > + $volid : > + verify_volume_id_or_absolute_path($volid, $noerr); > +} > + > +PVE::JSONSchema::register_format('pve-volume-id-or-absolute-path', \&ver= ify_volume_id_or_absolute_path); > +sub verify_volume_id_or_absolute_path { > + my ($volid, $noerr) =3D @_; > + > + return $volid if $volid =3D~ m|^/|; > =20 > - # if its neither 'none' nor 'cdrom' nor a path, check if its a volum= e-id > $volid =3D eval { PVE::JSONSchema::check_format('pve-volume-id', $vo= lid, '') }; > if ($@) { > - return if $noerr; > + return undef if $noerr; > die $@; > } > return $volid; > } > =20 > +PVE::JSONSchema::register_format('device-image-pair', \&verify_device_im= age_pair); > +sub verify_device_image_pair { > + my ($pair, $noerr) =3D @_; > + > + my $error =3D sub { > + return undef if $noerr; > + die $@; > + }; > + > + my ($device, $image) =3D split('=3D', $pair); > + $error->("Invalid device '$device'") if !PVE::QemuServer::Drive::is_= valid_drivename($device); > + $error->("Invalid image '$image'") if !verify_volume_id_or_absolute_= path($image); > + > + return $pair; > +} > + > my $usb_fmt =3D { > host =3D> { > default_key =3D> 1, > @@ -2030,6 +2052,22 @@ sub json_config_properties { > return $prop; > } > =20 > +# Properties that we can read from an OVF file > +sub json_ovf_properties { > + my $prop =3D shift; > + > + foreach my $device ( PVE::QemuServer::Drive::valid_drive_names()) { > + $prop->{$device} =3D { > + type =3D> 'string', > + format =3D> 'pve-volume-id-or-absolute-path', > + description =3D> "Disk image that gets imported to $device", > + optional =3D> 1, > + }; > + } > + > + return $prop; > +} > + > # return copy of $confdesc_cloudinit to generate documentation > sub cloudinit_config_properties { > =20 > @@ -6748,7 +6786,7 @@ sub qemu_img_convert { > $src_path =3D PVE::Storage::path($storecfg, $src_volid, $snapname); > $src_is_iscsi =3D ($src_path =3D~ m|^iscsi://|); > $cachemode =3D 'none' if $src_scfg->{type} eq 'zfspool'; > - } elsif (-f $src_volid) { > + } elsif (-f $src_volid || -b $src_volid) { > $src_path =3D $src_volid; > if ($src_path =3D~ m/\.($PVE::QemuServer::Drive::QEMU_FORMAT_RE)$/) { > $src_format =3D $1; > diff --git a/PVE/QemuServer/OVF.pm b/PVE/QemuServer/OVF.pm > index c76c199..48146e9 100644 > --- a/PVE/QemuServer/OVF.pm > +++ b/PVE/QemuServer/OVF.pm > @@ -87,7 +87,7 @@ sub id_to_pve { > =20 > # returns two references, $qm which holds qm.conf style key/values, and = \@disks > sub parse_ovf { > - my ($ovf, $debug) =3D @_; > + my ($ovf, $debug, $manifest_only) =3D @_; > =20 > my $dom =3D XML::LibXML->load_xml(location =3D> $ovf, no_blanks =3D>= 1); > =20 > @@ -220,9 +220,11 @@ ovf:Item[rasd:InstanceID=3D'%s']/rasd:ResourceType",= $controller_id); > die "error parsing $filepath, file seems not to exist at $backing_f= ile_path\n"; > } > =20 > - my $virtual_size; > - if ( !($virtual_size =3D PVE::Storage::file_size_info($backing_file_pat= h)) ) { > - die "error parsing $backing_file_path, size seems to be $virtual_si= ze\n"; > + my $virtual_size =3D undef; > + if (!$manifest_only) { # Not possible if manifest is uploaded in web gu= i > + if ( !($virtual_size =3D PVE::Storage::file_size_info($backing_file= _path)) ) { > + die "error parsing $backing_file_path: Could not get file size info: $= @\n"; > + } > } > =20 > $pve_disk =3D { > --=20 > 2.20.1 >=20 >=20 > _______________________________________________ > pve-devel mailing list > pve-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel >=20 =