]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
fix #947: reenable disk/cdrom passthrough
[qemu-server.git] / PVE / QemuServer.pm
index 4d16036bdb593dcc7ea9356d854499c927f5a209..cd53978cd4cd2f7b4022399d01f3163369631ea6 100644 (file)
@@ -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 => <<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 => {
@@ -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=<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);
@@ -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=<dev>][,queues=<nbqueues>][,rate=<mbps>] [,tag=<vlanid>][,trunks=<vlanid[;vlanid]>][,firewall=0|1],link_down=0|1]",
     description => <<EODESCR,
 Specify network devices.
 
@@ -515,13 +619,30 @@ for (my $i = 0; $i < $MAX_NETS; $i++)  {
     $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.",
@@ -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 => <<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:
@@ -787,10 +908,37 @@ EODESCR
 };
 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:
 
@@ -1455,28 +1603,26 @@ sub drive_is_cdrom {
 
 }
 
-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;
 }
 
@@ -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) {