X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=cd53978cd4cd2f7b4022399d01f3163369631ea6;hb=ffa42b860d6f605db1368a64d35ceb44aca34d5c;hp=4d16036bdb593dcc7ea9356d854499c927f5a209;hpb=93c0971cec63791068a7239052642de81b18a890;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 4d16036..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, @@ -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 => 'string', - format => 'pve-volume-id', + format => 'pve-volume-id-or-qm-path', default_key => 1, format_description => 'volume', description => "The drive's backing volume.", @@ -749,7 +870,7 @@ my $alldrive_fmt = { %queues_fmt, }; -my $usbformat = { +my $usb_fmt = { host => { default_key => 1, type => 'string', format => 'pve-qm-usb-device', @@ -766,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; } @@ -1485,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; } @@ -1521,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 { @@ -1673,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}', @@ -1721,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 { @@ -1744,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) = @_; @@ -1766,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; } @@ -2812,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') { @@ -3001,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}; @@ -3062,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 @@ -5881,6 +5929,15 @@ sub qemu_drive_mirror { 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) { @@ -5907,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; @@ -5919,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) {