X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=cd53978cd4cd2f7b4022399d01f3163369631ea6;hb=ffa42b860d6f605db1368a64d35ceb44aca34d5c;hp=0d0b65bf1f738bb32d700ada75ff5e8691648bcf;hpb=46630a5fd44666cbc98da52d75f055a2ec6d8798;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 0d0b65b..cd53978 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -92,11 +92,12 @@ mkdir $lock_dir; my $pcisysfs = "/sys/bus/pci"; -my $cpudesc = { +my $cpu_fmt = { cputype => { description => "Emulated CPU type.", type => 'string', enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom Conroe Penryn Nehalem Westmere SandyBridge IvyBridge Haswell Haswell-noTSX Broadwell Broadwell-noTSX Opteron_G1 Opteron_G2 Opteron_G3 Opteron_G4 Opteron_G5 host) ], + format_description => 'cputype', default => 'kvm64', default_key => 1, }, @@ -108,6 +109,24 @@ my $cpudesc = { }, }; +my $watchdog_fmt = { + model => { + default_key => 1, + type => 'string', + enum => [qw(i6300esb ib700)], + description => "Watchdog type to emulate.", + default => 'i6300esb', + optional => 1, + }, + action => { + type => 'string', + enum => [qw(reset shutdown poweroff pause debug none)], + description => "The action to perform if after activation the guest fails to poll the watchdog in time.", + optional => 1, + }, +}; +PVE::JSONSchema::register_format('pve-qm-watchdog', $watchdog_fmt); + my $confdesc = { onboot => { optional => 1, @@ -142,7 +161,7 @@ my $confdesc = { cpulimit => { optional => 1, type => 'number', - description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.", + description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.", minimum => 0, maximum => 128, default => 0, @@ -321,7 +340,6 @@ EODESC watchdog => { optional => 1, type => 'string', format => 'pve-qm-watchdog', - typetext => '[[model=]i6300esb|ib700] [,[action=]reset|shutdown|poweroff|pause|debug|none]', description => "Create a virtual hardware watchdog device. Once enabled" . " (by a guest action), the watchdog must be periodically polled " . "by an agent inside the guest or else the watchdog will reset " . @@ -346,9 +364,11 @@ EODESC optional => 1, type => 'string', description => < { @@ -386,7 +406,7 @@ EODESCR optional => 1, description => "Emulated CPU type.", type => 'string', - format => $cpudesc, + format => $cpu_fmt, }, parent => get_standard_option('pve-snapshot-name', { optional => 1, @@ -466,10 +486,38 @@ my $MAX_NUMA = 8; my $MAX_MEM = 4194304; my $STATICMEM = 1024; +my $numa_fmt = { + cpus => { + type => "string", + pattern => qr/\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*/, + description => "CPUs accessing this numa node.", + format_description => "id[-id];...", + }, + memory => { + type => "number", + description => "Amount of memory this numa node provides.", + format_description => "mb", + optional => 1, + }, + hostnodes => { + type => "string", + pattern => qr/\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*/, + description => "host numa nodes to use", + format_description => "id[-id];...", + optional => 1, + }, + policy => { + type => 'string', + enum => [qw(preferred bind interleave)], + format_description => 'preferred|bind|interleave', + description => "numa allocation policy.", + optional => 1, + }, +}; +PVE::JSONSchema::register_format('pve-qm-numanode', $numa_fmt); my $numadesc = { optional => 1, - type => 'string', format => 'pve-qm-numanode', - typetext => "cpus=[[,hostnodes=] [,policy=]]", + type => 'string', format => $numa_fmt, description => "numa topology", }; PVE::JSONSchema::register_standard_option("pve-qm-numanode", $numadesc); @@ -483,10 +531,66 @@ my $nic_model_list = ['rtl8139', 'ne2k_pci', 'e1000', 'pcnet', 'virtio', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em']; my $nic_model_list_txt = join(' ', sort @$nic_model_list); +my $net_fmt = { + macaddr => { + type => 'string', + pattern => qr/[0-9a-f]{2}(?::[0-9a-f]{2}){5}/i, + description => "MAC address", + format_description => "XX:XX:XX:XX:XX:XX", + optional => 1, + }, + model => { alias => 'macaddr', default_key => 1 }, + (map { $_ => { group => 'model' } } @$nic_model_list), + bridge => { + type => 'string', + description => 'Bridge to attach the network device to.', + format_description => 'bridge', + optional => 1, + }, + queues => { + type => 'integer', + minimum => 0, maximum => 16, + description => 'Number of packet queues to be used on the device.', + format_description => 'number', + optional => 1, + }, + rate => { + type => 'number', + minimum => 0, + description => 'Rate limit in mbps as floating point number.', + format_description => 'mbps', + optional => 1, + }, + tag => { + type => 'integer', + minimum => 2, maximum => 4094, + description => 'VLAN tag to apply to packets on this interface.', + format_description => 'vlanid', + optional => 1, + }, + trunks => { + type => 'string', + pattern => qr/\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*/, + description => 'VLAN trunks to pass through this interface.', + format_description => 'id;id...', + optional => 1, + }, + firewall => { + type => 'boolean', + description => 'Whether this interface should be protected by the firewall.', + format_description => '0|1', + optional => 1, + }, + link_down => { + type => 'boolean', + description => 'Whether this interface should be DISconnected (like pulling the plug).', + format_description => '0|1', + optional => 1, + }, +}; my $netdesc = { optional => 1, type => 'string', format => 'pve-qm-net', - typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=][,queues=][,rate=] [,tag=][,trunks=][,firewall=0|1],link_down=0|1]", description => <{"net$i"} = $netdesc; } +PVE::JSONSchema::register_format('pve-volume-id-or-qm-path', \&verify_volume_id_or_qm_path); +sub verify_volume_id_or_qm_path { + my ($volid, $noerr) = @_; + + if ($volid eq 'none' || $volid eq 'cdrom' || $volid =~ m|^/|) { + return $volid; + } + + # if its neither 'none' nor 'cdrom' nor a path, check if its a volume-id + $volid = eval { PVE::JSONSchema::check_format('pve-volume-id', $volid, '') }; + if ($@) { + return undef if $noerr; + die $@; + } + return $volid; +} + my $drivename_hash; my %drivedesc_base = ( volume => { alias => 'file' }, file => { - type => 'pve-volume-id', + type => 'string', + format => 'pve-volume-id-or-qm-path', default_key => 1, format_description => 'volume', description => "The drive's backing volume.", @@ -620,7 +742,8 @@ my %drivedesc_base = ( type => 'string', format => 'urlencoded', format_description => 'serial', - description => "The drive's reported serial number, url-encoded.", + maxLength => 20*3, # *3 since it's %xx url enoded + description => "The drive's reported serial number, url-encoded, up to 20 bytes long.", optional => 1, } ); @@ -647,7 +770,8 @@ my %model_fmt = ( type => 'string', format => 'urlencoded', format_description => 'model', - description => "The drive's reported model name, url-encoded.", + maxLength => 40*3, # *3 since it's %xx url enoded + description => "The drive's reported model name, url-encoded, up to 40 bytes long.", optional => 1, }, ); @@ -675,17 +799,17 @@ my $add_throttle_desc = sub { $add_throttle_desc->('bps', 'integer', 'r/w speed', 'bps', 'bytes'); $add_throttle_desc->('bps_rd', 'integer', 'read speed', 'bps', 'bytes'); $add_throttle_desc->('bps_wr', 'integer', 'write speed', 'bps', 'bytes'); -$add_throttle_desc->('mbps', 'float', 'r/w speed', 'mbps', 'megabytes'); -$add_throttle_desc->('mbps_rd', 'float', 'read speed', 'mbps', 'megabytes'); -$add_throttle_desc->('mbps_wr', 'float', 'write speed', 'mbps', 'megabytes'); +$add_throttle_desc->('mbps', 'number', 'r/w speed', 'mbps', 'megabytes'); +$add_throttle_desc->('mbps_rd', 'number', 'read speed', 'mbps', 'megabytes'); +$add_throttle_desc->('mbps_wr', 'number', 'write speed', 'mbps', 'megabytes'); $add_throttle_desc->('iops', 'integer', 'r/w I/O', 'iops', 'operations'); $add_throttle_desc->('iops_rd', 'integer', 'read I/O', 'iops', 'operations'); $add_throttle_desc->('iops_wr', 'integer', 'write I/O', 'iops', 'operations'); # pools: (pool of IO before throttling starts taking effect) -$add_throttle_desc->('mbps_max', 'float', 'unthrottled r/w pool', 'mbps', 'megabytes'); -$add_throttle_desc->('mbps_rd_max', 'float', 'unthrottled read pool', 'mbps', 'megabytes'); -$add_throttle_desc->('mbps_wr_max', 'float', 'unthrottled write pool', 'mbps', 'megabytes'); +$add_throttle_desc->('mbps_max', 'number', 'unthrottled r/w pool', 'mbps', 'megabytes'); +$add_throttle_desc->('mbps_rd_max', 'number', 'unthrottled read pool', 'mbps', 'megabytes'); +$add_throttle_desc->('mbps_wr_max', 'number', 'unthrottled write pool', 'mbps', 'megabytes'); $add_throttle_desc->('iops_max', 'integer', 'unthrottled r/w I/O pool', 'iops', 'operations'); $add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool', 'iops', 'operations'); $add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations'); @@ -746,7 +870,7 @@ my $alldrive_fmt = { %queues_fmt, }; -my $usbformat = { +my $usb_fmt = { host => { default_key => 1, type => 'string', format => 'pve-qm-usb-device', @@ -763,7 +887,7 @@ my $usbformat = { my $usbdesc = { optional => 1, - type => 'string', format => $usbformat, + type => 'string', format => $usb_fmt, description => < { + default_key => 1, + type => 'string', + pattern => qr/$PCIRE(;$PCIRE)*/, + format_description => 'HOSTPCIID[;HOSTPCIID2...]', + description => "The PCI ID of a host's PCI device or a list of PCI virtual functions of the host.", + }, + rombar => { + type => 'boolean', + optional => 1, + default => 1, + }, + pcie => { + type => 'boolean', + optional => 1, + default => 0, + }, + 'x-vga' => { + type => 'boolean', + optional => 1, + default => 0, + }, +}; +PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt); + my $hostpcidesc = { optional => 1, type => 'string', format => 'pve-qm-hostpci', - typetext => "[host=]HOSTPCIDEVICE [,rombar=on|off] [,pcie=0|1] [,x-vga=on|off]", description => < < <{memory} = $1; - } elsif ($kvp =~ m/^policy=(preferred|bind|interleave)$/) { - $res->{policy} = $1; - } elsif ($kvp =~ m/^cpus=(\d+)(-(\d+))?$/) { - $res->{cpus}->{start} = $1; - $res->{cpus}->{end} = $3; - } elsif ($kvp =~ m/^hostnodes=(\d+)(-(\d+))?$/) { - $res->{hostnodes}->{start} = $1; - $res->{hostnodes}->{end} = $3; +sub parse_number_sets { + my ($set) = @_; + my $res = []; + foreach my $part (split(/;/, $set)) { + if ($part =~ /^\s*(\d+)(?:-(\d+))?\s*$/) { + die "invalid range: $part ($2 < $1)\n" if defined($2) && $2 < $1; + push @$res, [ $1, $2 ]; } else { - return undef; + die "invalid range: $part\n"; } } + return $res; +} + +sub parse_numa { + my ($data) = @_; + my $res = PVE::JSONSchema::parse_property_string($numa_fmt, $data); + $res->{cpus} = parse_number_sets($res->{cpus}) if defined($res->{cpus}); + $res->{hostnodes} = parse_number_sets($res->{hostnodes}) if defined($res->{hostnodes}); return $res; } @@ -1482,35 +1631,18 @@ sub parse_hostpci { return undef if !$value; + my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value); - my @list = split(/,/, $value); - my $found; - - my $res = {}; - foreach my $kv (@list) { - - if ($kv =~ m/^(host=)?([a-f0-9]{2}:[a-f0-9]{2})(\.([a-f0-9]))?$/) { - $found = 1; - if(defined($4)){ - push @{$res->{pciid}}, { id => $2 , function => $4}; - - }else{ - my $pcidevices = lspci($2); - $res->{pciid} = $pcidevices->{$2}; - } - } elsif ($kv =~ m/^rombar=(on|off)$/) { - $res->{rombar} = $1; - } elsif ($kv =~ m/^x-vga=(on|off)$/) { - $res->{'x-vga'} = $1; - } elsif ($kv =~ m/^pcie=(\d+)$/) { - $res->{pcie} = 1 if $1 == 1; + my @idlist = split(/;/, $res->{host}); + delete $res->{host}; + foreach my $id (@idlist) { + if ($id =~ /^$PCIRE$/) { + push @{$res->{pciid}}, { id => $1, function => ($2//'0') }; } else { - warn "unknown hostpci setting '$kv'\n"; + # should have been caught by parse_property_string already + die "failed to parse PCI id: $id\n"; } } - - return undef if !$found; - return $res; } @@ -1518,54 +1650,19 @@ sub parse_hostpci { sub parse_net { my ($data) = @_; - my $res = {}; - - foreach my $kvp (split(/,/, $data)) { - - if ($kvp =~ m/^(ne2k_pci|e1000|e1000-82540em|e1000-82544gc|e1000-82545em|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er|vmxnet3)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) { - my $model = lc($1); - my $mac = defined($3) ? uc($3) : PVE::Tools::random_ether_addr(); - $res->{model} = $model; - $res->{macaddr} = $mac; - } elsif ($kvp =~ m/^bridge=(\S+)$/) { - $res->{bridge} = $1; - } elsif ($kvp =~ m/^queues=(\d+)$/) { - $res->{queues} = $1; - } elsif ($kvp =~ m/^rate=(\d+(\.\d+)?)$/) { - $res->{rate} = $1; - } elsif ($kvp =~ m/^tag=(\d+)$/) { - $res->{tag} = $1; - } elsif ($kvp =~ m/^trunks=([0-9;]+)$/) { - $res->{trunks} = $1; - } elsif ($kvp =~ m/^firewall=([01])$/) { - $res->{firewall} = $1; - } elsif ($kvp =~ m/^link_down=([01])$/) { - $res->{link_down} = $1; - } else { - return undef; - } - + my $res = eval { PVE::JSONSchema::parse_property_string($net_fmt, $data) }; + if ($@) { + warn $@; + return undef; } - - return undef if !$res->{model}; - + $res->{macaddr} = PVE::Tools::random_ether_addr() if !defined($res->{macaddr}); return $res; } sub print_net { my $net = shift; - my $res = "$net->{model}"; - $res .= "=$net->{macaddr}" if $net->{macaddr}; - $res .= ",bridge=$net->{bridge}" if $net->{bridge}; - $res .= ",rate=$net->{rate}" if $net->{rate}; - $res .= ",tag=$net->{tag}" if $net->{tag}; - $res .= ",trunks=$net->{trunks}" if $net->{trunks}; - $res .= ",firewall=1" if $net->{firewall}; - $res .= ",link_down=1" if $net->{link_down}; - $res .= ",queues=$net->{queues}" if $net->{queues}; - - return $res; + return PVE::JSONSchema::print_property_string($net, $net_fmt); } sub add_random_macs { @@ -1670,7 +1767,7 @@ sub vmconfig_cleanup_pending { } # smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str] -my $smbios1_desc = { +my $smbios1_fmt = { uuid => { type => 'string', pattern => '[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}', @@ -1718,17 +1815,17 @@ my $smbios1_desc = { sub parse_smbios1 { my ($data) = @_; - my $res = eval { PVE::JSONSchema::parse_property_string($smbios1_desc, $data) }; + my $res = eval { PVE::JSONSchema::parse_property_string($smbios1_fmt, $data) }; warn $@ if $@; return $res; } sub print_smbios1 { my ($smbios1) = @_; - return PVE::JSONSchema::print_property_string($smbios1, $smbios1_desc); + return PVE::JSONSchema::print_property_string($smbios1, $smbios1_fmt); } -PVE::JSONSchema::register_format('pve-qm-smbios1', $smbios1_desc); +PVE::JSONSchema::register_format('pve-qm-smbios1', $smbios1_fmt); PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk); sub verify_bootdisk { @@ -1741,17 +1838,6 @@ sub verify_bootdisk { die "invalid boot disk '$value'\n"; } -PVE::JSONSchema::register_format('pve-qm-numanode', \&verify_numa); -sub verify_numa { - my ($value, $noerr) = @_; - - return $value if parse_numa($value); - - return undef if $noerr; - - die "unable to parse numa options\n"; -} - PVE::JSONSchema::register_format('pve-qm-net', \&verify_net); sub verify_net { my ($value, $noerr) = @_; @@ -1763,47 +1849,13 @@ sub verify_net { die "unable to parse network options\n"; } -PVE::JSONSchema::register_format('pve-qm-hostpci', \&verify_hostpci); -sub verify_hostpci { - my ($value, $noerr) = @_; - - return $value if parse_hostpci($value); - - return undef if $noerr; - - die "unable to parse pci id\n"; -} - -PVE::JSONSchema::register_format('pve-qm-watchdog', \&verify_watchdog); -sub verify_watchdog { - my ($value, $noerr) = @_; - - return $value if parse_watchdog($value); - - return undef if $noerr; - - die "unable to parse watchdog options\n"; -} - sub parse_watchdog { my ($value) = @_; return undef if !$value; - my $res = {}; - - foreach my $p (split(/,/, $value)) { - next if $p =~ m/^\s*$/; - - if ($p =~ m/^(model=)?(i6300esb|ib700)$/) { - $res->{model} = $2; - } elsif ($p =~ m/^(action=)?(reset|shutdown|poweroff|pause|debug|none)$/) { - $res->{action} = $2; - } else { - return undef; - } - } - + my $res = eval { PVE::JSONSchema::parse_property_string($watchdog_fmt, $value) }; + warn $@ if $@; return $res; } @@ -2809,9 +2861,10 @@ sub config_to_command { $pciaddr = print_pci_addr("hostpci$i", $bridges); } - my $rombar = $d->{rombar} && $d->{rombar} eq 'off' ? ",rombar=0" : ""; - my $xvga = $d->{'x-vga'} && $d->{'x-vga'} eq 'on' ? ",x-vga=on" : ""; - if ($xvga && $xvga ne '') { + my $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : ''; + my $xvga = ''; + if ($d->{'x-vga'}) { + $xvga = ',x-vga=on'; $kvm_off = 1; $vga = 'none'; if ($ostype eq 'win7' || $ostype eq 'win8' || $ostype eq 'w2k8') { @@ -2998,7 +3051,7 @@ sub config_to_command { my $cpu = $nokvm ? "qemu64" : "kvm64"; if (my $cputype = $conf->{cpu}) { - my $cpuconf = PVE::JSONSchema::parse_property_string($cpudesc, $cputype) + my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype) or die "Cannot parse cpu description: $cputype\n"; $cpu = $cpuconf->{cputype}; $kvm_off = 1 if $cpuconf->{hidden}; @@ -3059,28 +3112,26 @@ sub config_to_command { my $numa_object = "memory-backend-ram,id=ram-node$i,size=${numa_memory}M"; # cpus - my $cpus_start = $numa->{cpus}->{start}; - die "missing numa node$i cpus\n" if !defined($cpus_start); - my $cpus_end = $numa->{cpus}->{end} if defined($numa->{cpus}->{end}); - my $cpus = $cpus_start; - if (defined($cpus_end)) { - $cpus .= "-$cpus_end"; - die "numa node$i : cpu range $cpus is incorrect\n" if $cpus_end <= $cpus_start; - } + my $cpulists = $numa->{cpus}; + die "missing numa node$i cpus\n" if !defined($cpulists); + my $cpus = join(',', map { + my ($start, $end) = @$_; + defined($end) ? "$start-$end" : $start + } @$cpulists); # hostnodes - my $hostnodes_start = $numa->{hostnodes}->{start}; - if (defined($hostnodes_start)) { - my $hostnodes_end = $numa->{hostnodes}->{end} if defined($numa->{hostnodes}->{end}); - my $hostnodes = $hostnodes_start; - if (defined($hostnodes_end)) { - $hostnodes .= "-$hostnodes_end"; - die "host node $hostnodes range is incorrect\n" if $hostnodes_end <= $hostnodes_start; - } - - my $hostnodes_end_range = defined($hostnodes_end) ? $hostnodes_end : $hostnodes_start; - for (my $i = $hostnodes_start; $i <= $hostnodes_end_range; $i++ ) { - die "host numa node$i don't exist\n" if ! -d "/sys/devices/system/node/node$i/"; + my $hostnodelists = $numa->{hostnodes}; + if (defined($hostnodelists)) { + my $hostnodes; + foreach my $hostnoderange (@$hostnodelists) { + my ($start, $end) = @$hostnoderange; + $hostnodes .= ',' if $hostnodes; + $hostnodes .= $start; + $hostnodes .= "-$end" if defined($end); + $end //= $start; + for (my $i = $start; $i <= $end; ++$i ) { + die "host numa node$i don't exist\n" if ! -d "/sys/devices/system/node/node$i/"; + } } # policy @@ -5319,7 +5370,7 @@ sub update_disksize { sub rescan { my ($vmid, $nolock) = @_; - my $cfg = PVE::Cluster::cfs_read_file("storage.cfg"); + my $cfg = PVE::Storage::config(); my $volid_hash = scan_volids($cfg, $vmid); @@ -5458,7 +5509,7 @@ sub restore_vma_archive { if !$devinfo->{$devname}->{virtdev}; } - my $cfg = cfs_read_file('storage.cfg'); + my $cfg = PVE::Storage::config(); # create empty/temp config if ($oldconf) { @@ -5583,7 +5634,7 @@ sub restore_vma_archive { push @$vollist, $volid if $volid; } - my $cfg = cfs_read_file('storage.cfg'); + my $cfg = PVE::Storage::config(); PVE::Storage::deactivate_volumes($cfg, $vollist); unlink $mapfifo; @@ -5628,7 +5679,7 @@ sub restore_tar_archive { if $firstfile ne 'qemu-server.conf'; } - my $storecfg = cfs_read_file('storage.cfg'); + my $storecfg = PVE::Storage::config(); # destroy existing data - keep empty config my $vmcfgfn = PVE::QemuConfig->config_file($vmid); @@ -5802,7 +5853,7 @@ sub template_create { } sub qemu_img_convert { - my ($src_volid, $dst_volid, $size, $snapname) = @_; + my ($src_volid, $dst_volid, $size, $snapname, $is_zero_initialized) = @_; my $storecfg = PVE::Storage::config(); my ($src_storeid, $src_volname) = PVE::Storage::parse_volume_id($src_volid, 1); @@ -5824,7 +5875,12 @@ sub qemu_img_convert { my $cmd = []; push @$cmd, '/usr/bin/qemu-img', 'convert', '-t', 'writeback', '-p', '-n'; push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2"); - push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path, $dst_path; + push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path; + if ($is_zero_initialized) { + push @$cmd, "zeroinit:$dst_path"; + } else { + push @$cmd, $dst_path; + } my $parser = sub { my $line = shift; @@ -5855,7 +5911,7 @@ sub qemu_img_format { } sub qemu_drive_mirror { - my ($vmid, $drive, $dst_volid, $vmiddst) = @_; + my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized) = @_; my $storecfg = PVE::Storage::config(); my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid); @@ -5866,11 +5922,22 @@ sub qemu_drive_mirror { my $dst_path = PVE::Storage::path($storecfg, $dst_volid); - my $opts = { timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $dst_path }; + my $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path; + + my $opts = { timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $qemu_target }; $opts->{format} = $format if $format; print "drive mirror is starting (scanning bitmap) : this step can take some minutes/hours, depend of disk size and storage speed\n"; + my $finish_job = sub { + while (1) { + my $stats = vm_mon_cmd($vmid, "query-block-jobs"); + my $stat = @$stats[0]; + last if !$stat; + sleep 1; + } + }; + eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); while (1) { @@ -5897,7 +5964,10 @@ sub qemu_drive_mirror { # try to switch the disk if source and destination are on the same guest eval { vm_mon_cmd($vmid, "block-job-complete", device => "drive-$drive") }; - last if !$@; + if (!$@) { + &$finish_job(); + last; + } die $@ if $@ !~ m/cannot be completed/; } sleep 1; @@ -5909,12 +5979,7 @@ sub qemu_drive_mirror { my $cancel_job = sub { vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive"); - while (1) { - my $stats = vm_mon_cmd($vmid, "query-block-jobs"); - my $stat = @$stats[0]; - last if !$stat; - sleep 1; - } + &$finish_job(); }; if ($err) { @@ -5960,10 +6025,11 @@ sub clone_disk { PVE::Storage::activate_volumes($storecfg, $newvollist); + my $sparseinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $newvolid); if (!$running || $snapname) { - qemu_img_convert($drive->{file}, $newvolid, $size, $snapname); + qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit); } else { - qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid); + qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit); } }