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,
},
},
};
+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,
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,
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 " .
optional => 1,
type => 'string',
description => <<EODESCR,
-Note: this option is for experts only. It allows you to pass arbitrary arguments to kvm, for example:
+Arbitrary arguments passed to kvm, for example:
args: -no-reboot -no-hpet
+
+NOTE: this option is for experts only.
EODESCR
},
tablet => {
optional => 1,
description => "Emulated CPU type.",
type => 'string',
- format => $cpudesc,
+ format => $cpu_fmt,
},
parent => get_standard_option('pve-snapshot-name', {
optional => 1,
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=<id[-id],memory=<mb>[[,hostnodes=<id[-id]>] [,policy=<preferred|bind|interleave>]]",
+ type => 'string', format => $numa_fmt,
description => "numa topology",
};
PVE::JSONSchema::register_standard_option("pve-qm-numanode", $numadesc);
'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=<dev>][,queues=<nbqueues>][,rate=<mbps>] [,tag=<vlanid>][,trunks=<vlanid[;vlanid]>][,firewall=0|1],link_down=0|1]",
description => <<EODESCR,
Specify network devices.
$confdesc->{"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.",
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,
}
);
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,
},
);
$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');
%queues_fmt,
};
-my $usbformat = {
+my $usb_fmt = {
host => {
default_key => 1,
type => 'string', format => 'pve-qm-usb-device',
my $usbdesc = {
optional => 1,
- type => 'string', format => $usbformat,
+ type => 'string', format => $usb_fmt,
description => <<EODESCR,
Configure an USB device (n is 0 to 4). This can be used to
pass-through usb devices to the guest. HOSTUSBDEVICE syntax is:
You can use the 'lsusb -t' command to list existing usb devices.
-Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
The value 'spice' can be used to add a usb redirection devices for spice.
};
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
+# NOTE: the match-groups of this regex are used in parse_hostpci
+my $PCIRE = qr/([a-f0-9]{2}:[a-f0-9]{2})(?:\.([a-f0-9]))?/;
+my $hostpci_fmt = {
+ host => {
+ 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 => <<EODESCR,
Map host pci devices. HOSTPCIDEVICE syntax is:
The 'rombar' option determines whether or not the device's ROM will be visible in the guest's memory map (default is 'on').
-Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
Experimental: user reported problems with this option.
EODESCR
description => <<EODESCR,
Create a serial device inside the VM (n is 0 to 3), and pass through a host serial device (i.e. /dev/ttyS0), or create a unix socket on the host side (use 'qm terminal' to open a terminal connection).
-Note: If you pass through a host serial device, it is no longer possible to migrate such machines - use with special care.
+NOTE: If you pass through a host serial device, it is no longer possible to migrate such machines - use with special care.
Experimental: user reported problems with this option.
EODESCR
description => <<EODESCR,
Map host parallel devices (n is 0 to 2).
-Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
Experimental: user reported problems with this option.
EODESCR
}
-sub parse_numa {
- my ($data) = @_;
-
- my $res = {};
-
- foreach my $kvp (split(/,/, $data)) {
-
- if ($kvp =~ m/^memory=(\S+)$/) {
- $res->{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;
}
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;
}
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 {
}
# 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}',
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 {
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) = @_;
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;
}
$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') {
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};
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
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);
if !$devinfo->{$devname}->{virtdev};
}
- my $cfg = cfs_read_file('storage.cfg');
+ my $cfg = PVE::Storage::config();
# create empty/temp config
if ($oldconf) {
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;
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);
}
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);
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;
}
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);
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) {
# 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;
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) {
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);
}
}