},
};
+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 => {
'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-none', \&verify_volume_id_or_none);
-sub verify_volume_id_or_none {
+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) = @_;
- return $volid if $volid eq 'none';
+ 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;
volume => { alias => 'file' },
file => {
type => 'string',
- format => 'pve-volume-id-or-none',
+ 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:
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 "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') {
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) {