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,
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 => {
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 => 'string',
- format => 'pve-volume-id',
+ format => 'pve-volume-id-or-qm-path',
default_key => 1,
format_description => 'volume',
description => "The drive's backing volume.",
};
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:
}
-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 {
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 $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
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) {