]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
enable cluster mapped USB devices for guests
[qemu-server.git] / PVE / QemuServer.pm
index ed361a526a4b1e3a7e73a5218cb2d607e52e5a36..9341b1ae2867f1dcc78ced47df9ea7f5535fbd8a 100644 (file)
@@ -56,7 +56,7 @@ use PVE::QemuServer::Machine;
 use PVE::QemuServer::Memory;
 use PVE::QemuServer::Monitor qw(mon_cmd);
 use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
-use PVE::QemuServer::USB qw(parse_usb_device);
+use PVE::QemuServer::USB;
 
 my $have_sdn;
 eval {
@@ -558,7 +558,7 @@ EODESC
        verbose_description => <<EODESCR,
 Arbitrary arguments passed to kvm, for example:
 
-args: -no-reboot -no-hpet
+args: -no-reboot -smbios 'type=0,vendor=FOO'
 
 NOTE: this option is for experts only.
 EODESCR
@@ -780,6 +780,11 @@ my $confdesc_cloudinit = {
            .' recommended. Use ssh keys instead. Also note that older cloud-init versions do not'
            .' support hashed passwords.',
     },
+    ciupgrade => {
+       optional => 1,
+       type => 'boolean',
+       description => 'cloud-init: do an automatic package upgrade after the first boot.'
+    },
     cicustom => {
        optional => 1,
        type => 'string',
@@ -830,7 +835,6 @@ while (my ($k, $v) = each %$confdesc) {
     PVE::JSONSchema::register_standard_option("pve-qm-$k", $v);
 }
 
-my $MAX_USB_DEVICES = 14;
 my $MAX_NETS = 32;
 my $MAX_SERIAL_PORTS = 4;
 my $MAX_PARALLEL_PORTS = 3;
@@ -1076,44 +1080,6 @@ sub verify_volume_id_or_absolute_path {
     return $volid;
 }
 
-my $usb_fmt = {
-    host => {
-       default_key => 1,
-       type => 'string', format => 'pve-qm-usb-device',
-       format_description => 'HOSTUSBDEVICE|spice',
-        description => <<EODESCR,
-The Host USB device or port or the value 'spice'. HOSTUSBDEVICE syntax is:
-
- 'bus-port(.port)*' (decimal numbers) or
- 'vendor_id:product_id' (hexadeciaml numbers) or
- 'spice'
-
-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.
-
-The value 'spice' can be used to add a usb redirection devices for spice.
-EODESCR
-    },
-    usb3 => {
-       optional => 1,
-       type => 'boolean',
-       description => "Specifies whether if given host option is a USB3 device or port."
-           ." For modern guests (machine version >= 7.1 and ostype l26 and windows > 7), this flag"
-           ." is irrelevant (all devices are plugged into a xhci controller).",
-        default => 0,
-    },
-};
-
-my $usbdesc = {
-    optional => 1,
-    type => 'string', format => $usb_fmt,
-    description => "Configure an USB device (n is 0 to 4, for machine version >= 7.1 and ostype"
-       ." l26 or windows > 7, n can be up to 14).",
-};
-PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
-
 my $serialdesc = {
        optional => 1,
        type => 'string',
@@ -1162,8 +1128,8 @@ for my $key (keys %{$PVE::QemuServer::Drive::drivedesc_hash}) {
     $confdesc->{$key} = $PVE::QemuServer::Drive::drivedesc_hash->{$key};
 }
 
-for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
-    $confdesc->{"usb$i"} = $usbdesc;
+for (my $i = 0; $i < $PVE::QemuServer::USB::MAX_USB_DEVICES; $i++)  {
+    $confdesc->{"usb$i"} = $PVE::QemuServer::USB::usbdesc;
 }
 
 my $boot_fmt = {
@@ -2239,17 +2205,6 @@ sub qemu_created_version_fixups {
     return;
 }
 
-PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
-sub verify_usb_device {
-    my ($value, $noerr) = @_;
-
-    return $value if parse_usb_device($value);
-
-    return if $noerr;
-
-    die "unable to parse usb device\n";
-}
-
 # add JSON properties for create and set function
 sub json_config_properties {
     my ($prop, $with_disk_alloc) = @_;
@@ -2867,7 +2822,7 @@ our $vmstatus_return_properties = {
        optional => 1,
     },
     qmpstatus => {
-       description => "QEMU QMP agent status.",
+       description => "VM run state from the 'query-status' QMP monitor command.",
        type => 'string',
        optional => 1,
     },
@@ -3543,7 +3498,9 @@ sub query_understood_cpu_flags {
 # Since commit 277d33454f77ec1d1e0bc04e37621e4dd2424b67 in pve-qemu, smm is not off by default
 # anymore. But smm=off seems to be required when using SeaBIOS and serial display.
 my sub should_disable_smm {
-    my ($conf, $vga) = @_;
+    my ($conf, $vga, $machine) = @_;
+
+    return if $machine =~ m/^virt/; # there is no smm flag that could be disabled
 
     return (!defined($conf->{bios}) || $conf->{bios} eq 'seabios') &&
        $vga->{type} && $vga->{type} =~ m/^(serial\d+|none)$/;
@@ -3733,7 +3690,7 @@ sub config_to_command {
 
     # add usb controllers
     my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers(
-       $conf, $bridges, $arch, $machine_type, $usbdesc->{format}, $MAX_USB_DEVICES, $machine_version);
+       $conf, $bridges, $arch, $machine_type, $machine_version);
     push @$devices, @usbcontrollers if @usbcontrollers;
     my $vga = parse_vga($conf->{vga});
 
@@ -3775,7 +3732,7 @@ sub config_to_command {
     $usb_dev_features->{spice_usb3} = 1 if min_version($machine_version, 4, 0);
 
     my @usbdevices = PVE::QemuServer::USB::get_usb_devices(
-       $conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features, $bootorder, $machine_version);
+       $conf, $usb_dev_features, $bootorder, $machine_version);
     push @$devices, @usbdevices if @usbdevices;
 
     # serial devices
@@ -3794,7 +3751,7 @@ sub config_to_command {
            }
        } else {
            die "no such serial device\n" if ! -c $path;
-           push @$devices, '-chardev', "tty,id=serial$i,path=$path";
+           push @$devices, '-chardev', "serial,id=serial$i,path=$path";
            push @$devices, '-device', "isa-serial,chardev=serial$i";
        }
     }
@@ -3803,7 +3760,7 @@ sub config_to_command {
     for (my $i = 0; $i < $MAX_PARALLEL_PORTS; $i++)  {
        if (my $path = $conf->{"parallel$i"}) {
            die "no such parallel device\n" if ! -c $path;
-           my $devtype = $path =~ m!^/dev/usb/lp! ? 'tty' : 'parport';
+           my $devtype = $path =~ m!^/dev/usb/lp! ? 'serial' : 'parallel';
            push @$devices, '-chardev', "$devtype,id=parallel$i,path=$path";
            push @$devices, '-device', "isa-parallel,chardev=parallel$i";
        }
@@ -3846,7 +3803,7 @@ sub config_to_command {
 
     push @$cmd, '-boot', "menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg";
 
-    push @$cmd, '-no-acpi' if defined($conf->{acpi}) && $conf->{acpi} == 0;
+    push $machineFlags->@*, 'acpi=off' if defined($conf->{acpi}) && $conf->{acpi} == 0;
 
     push @$cmd, '-no-reboot' if  defined($conf->{reboot}) && $conf->{reboot} == 0;
 
@@ -3878,7 +3835,7 @@ sub config_to_command {
 
     if ($winversion >= 6) {
        push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
-       push @$cmd, '-no-hpet';
+       push @$machineFlags, 'hpet=off';
     }
 
     push @$rtcFlags, 'driftfix=slew' if $tdf;
@@ -4155,7 +4112,7 @@ sub config_to_command {
        push @$machineFlags, 'accel=tcg';
     }
 
-    push @$machineFlags, 'smm=off' if should_disable_smm($conf, $vga);
+    push @$machineFlags, 'smm=off' if should_disable_smm($conf, $vga, $machine_type);
 
     my $machine_type_min = $machine_type;
     if ($add_pve_version) {
@@ -4646,12 +4603,8 @@ sub qemu_usb_hotplug {
        qemu_deviceadd($vmid, PVE::QemuServer::USB::print_qemu_xhci_controller($pciaddr));
     }
 
-    # print_usbdevice_full expects the parsed device
-    my $d = parse_usb_device($device->{host});
-    $d->{usb3} = $device->{usb3};
-
     # add the new one
-    vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $d, $arch, $machine_type);
+    vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type);
 }
 
 sub qemu_cpu_hotplug {
@@ -4766,7 +4719,7 @@ sub qemu_block_resize {
 
     my $running = check_running($vmid);
 
-    $size = 0 if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
+    PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
 
     return if !$running;
 
@@ -4906,16 +4859,18 @@ sub foreach_volid {
 }
 
 my $fast_plug_option = {
+    'description' => 1,
+    'hookscript' => 1,
     'lock' => 1,
+    'migrate_downtime' => 1,
+    'migrate_speed' => 1,
     'name' => 1,
     'onboot' => 1,
+    'protection' => 1,
     'shares' => 1,
     'startup' => 1,
-    'description' => 1,
-    'protection' => 1,
-    'vmstatestorage' => 1,
-    'hookscript' => 1,
     'tags' => 1,
+    'vmstatestorage' => 1,
 };
 
 for my $opt (keys %$confdesc_cloudinit) {
@@ -5111,9 +5066,9 @@ sub vmconfig_hotplug_pending {
            } elsif ($opt =~ m/^usb(\d+)$/) {
                my $index = $1;
                die "skip\n" if !$usb_hotplug;
-               my $d = eval { parse_property_string($usbdesc->{format}, $value) };
+               my $d = eval { parse_property_string('pve-qm-usb', $value) };
                my $id = $opt;
-               if ($d->{host} eq 'spice')  {
+               if ($d->{host} =~ m/^spice$/i)  {
                    $id = "usbredirdev$index";
                }
                qemu_usb_hotplug($storecfg, $conf, $vmid, $id, $d, $arch, $machine_type);
@@ -5190,7 +5145,7 @@ sub vmconfig_hotplug_pending {
     # unplug xhci controller if no usb device is left
     if ($usb_hotplug) {
        my $has_usb = 0;
-       for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) {
+       for (my $i = 0; $i < $PVE::QemuServer::USB::MAX_USB_DEVICES; $i++) {
            next if !defined($conf->{"usb$i"});
            $has_usb = 1;
            last;
@@ -5326,6 +5281,7 @@ sub vmconfig_update_net {
        if (safe_string_ne($oldnet->{model}, $newnet->{model}) ||
            safe_string_ne($oldnet->{macaddr}, $newnet->{macaddr}) ||
            safe_num_ne($oldnet->{queues}, $newnet->{queues}) ||
+           safe_num_ne($oldnet->{mtu}, $newnet->{mtu}) ||
            !($newnet->{bridge} && $oldnet->{bridge})) { # bridge/nat mode change
 
             # for non online change, we try to hot-unplug
@@ -5850,9 +5806,14 @@ sub vm_start_nolock {
            for my $dev ($d->{pciid}->@*) {
                my $info = PVE::QemuServer::PCI::prepare_pci_device($vmid, $dev->{id}, $id, $d->{mdev});
 
-               # nvidia grid needs the uuid of the mdev as qemu parameter
+               # nvidia grid needs the qemu parameter '-uuid' set
+               # use smbios uuid or mdev uuid as fallback for that
                if ($d->{mdev} && !defined($uuid) && $info->{vendor} eq '10de') {
-                   $uuid = PVE::QemuServer::PCI::generate_mdev_uuid($vmid, $id);
+                   if (defined($conf->{smbios1})) {
+                       my $smbios_conf = parse_smbios1($conf->{smbios1});
+                       $uuid = $smbios_conf->{uuid} if defined($smbios_conf->{uuid});
+                   }
+                   $uuid = PVE::QemuServer::PCI::generate_mdev_uuid($vmid, $id) if !defined($uuid);
                }
            }
        }
@@ -6160,6 +6121,15 @@ sub cleanup_pci_devices {
            # NOTE: avoid PVE::SysFSTools::pci_cleanup_mdev_device as it requires PCI ID and we
            # don't want to break ABI just for this two liner
            my $dev_sysfs_dir = "/sys/bus/mdev/devices/$uuid";
+
+           # some nvidia vgpu driver versions want to clean the mdevs up themselves, and error
+           # out when we do it first. so wait for 10 seconds and then try it
+           my $pciid = $d->{pciid}->[0]->{id};
+           my $info = PVE::SysFSTools::pci_device_info("$pciid");
+           if ($info->{vendor} eq '10de') {
+               sleep 10;
+           }
+
            PVE::SysFSTools::file_write("$dev_sysfs_dir/remove", "1") if -e $dev_sysfs_dir;
        }
     }
@@ -6489,6 +6459,45 @@ sub vm_sendkey {
     });
 }
 
+sub check_bridge_access {
+    my ($rpcenv, $authuser, $conf) = @_;
+
+    return 1 if $authuser eq 'root@pam';
+
+    for my $opt (sort keys $conf->%*) {
+       next if $opt !~ m/^net\d+$/;
+       my $net = parse_net($conf->{$opt});
+       my ($bridge, $tag, $trunks) = $net->@{'bridge', 'tag', 'trunks'};
+       PVE::GuestHelpers::check_vnet_access($rpcenv, $authuser, $bridge, $tag, $trunks);
+    }
+    return 1;
+};
+
+sub check_mapping_access {
+    my ($rpcenv, $user, $conf) = @_;
+
+    for my $opt (keys $conf->%*) {
+       if ($opt =~ m/^usb\d+$/) {
+           my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $conf->{$opt});
+           if (my $host = $device->{host}) {
+               die "only root can set '$opt' config for real devices\n"
+                   if $host !~ m/^spice$/i && $user ne 'root@pam';
+           } elsif ($device->{mapping}) {
+               $rpcenv->check_full($user, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
+           } else {
+               die "either 'host' or 'mapping' must be set.\n";
+           }
+       }
+   }
+};
+
+# FIXME: improve checks on restore by checking before actually extracing and
+# merging the new config
+sub check_restore_permissions {
+    my ($rpcenv, $user, $conf) = @_;
+    check_bridge_access($rpcenv, $user, $conf);
+    check_mapping_access($rpcenv, $user, $conf);
+}
 # vzdump restore implementaion
 
 sub tar_archive_read_firstfile {
@@ -7133,6 +7142,7 @@ sub restore_proxmox_backup_archive {
     }
 
     my $new_conf = $restore_merge_config->($conffile, $new_conf_raw, $options->{override_conf});
+    check_restore_permissions($rpcenv, $user, $new_conf);
     PVE::QemuConfig->write_config($vmid, $new_conf);
 
     eval { rescan($vmid, 1); };
@@ -7446,6 +7456,7 @@ sub restore_vma_archive {
     }
 
     my $new_conf = $restore_merge_config->($conffile, $new_conf_raw, $opts->{override_conf});
+    check_restore_permissions($rpcenv, $user, $new_conf);
     PVE::QemuConfig->write_config($vmid, $new_conf);
 
     eval { rescan($vmid, 1); };
@@ -7660,7 +7671,7 @@ sub convert_iscsi_path {
 }
 
 sub qemu_img_convert {
-    my ($src_volid, $dst_volid, $size, $snapname, $is_zero_initialized) = @_;
+    my ($src_volid, $dst_volid, $size, $snapname, $is_zero_initialized, $bwlimit) = @_;
 
     my $storecfg = PVE::Storage::config();
     my ($src_storeid, $src_volname) = PVE::Storage::parse_volume_id($src_volid, 1);
@@ -7700,6 +7711,7 @@ sub qemu_img_convert {
        if $snapname && $src_format && $src_format eq "qcow2";
     push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
     push @$cmd, '-T', $cachemode if defined($cachemode);
+    push @$cmd, '-r', "${bwlimit}K" if defined($bwlimit);
 
     if ($src_is_iscsi) {
        push @$cmd, '--image-opts';
@@ -8061,7 +8073,7 @@ sub clone_disk {
        } else {
            clone_disk_check_io_uring($drive, $storecfg, $src_storeid, $storeid, $use_drive_mirror);
 
-           ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 10);
+           $size = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 10);
        }
        $newvolid = PVE::Storage::vdisk_alloc(
            $storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024)
@@ -8085,7 +8097,6 @@ sub clone_disk {
            qemu_drive_mirror($vmid, $src_drivename, $newvolid, $newvmid, $sparseinit, $jobs,
                $completion, $qga, $bwlimit);
        } else {
-           # TODO: handle bwlimits
            if ($dst_drivename eq 'efidisk0') {
                # the relevant data on the efidisk may be smaller than the source
                # e.g. on RBD/ZFS, so we use dd to copy only the amount
@@ -8108,13 +8119,13 @@ sub clone_disk {
                push $cmd->@*, "bs=$bs", "osize=$size", "if=$src_path", "of=$dst_path";
                run_command($cmd);
            } else {
-               qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit);
+               qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit, $bwlimit);
            }
        }
     }
 
 no_data_clone:
-    my ($size) = eval { PVE::Storage::volume_size_info($storecfg, $newvolid, 10) };
+    my $size = eval { PVE::Storage::volume_size_info($storecfg, $newvolid, 10) };
 
     my $disk = dclone($drive);
     delete $disk->{format};
@@ -8193,7 +8204,7 @@ sub create_efidisk($$$$$$$) {
     PVE::Storage::activate_volumes($storecfg, [$volid]);
 
     qemu_img_convert($ovmf_vars, $volid, $vars_size_b, undef, 0);
-    my ($size) = PVE::Storage::volume_size_info($storecfg, $volid, 3);
+    my $size = PVE::Storage::volume_size_info($storecfg, $volid, 3);
 
     return ($volid, $size/1024);
 }
@@ -8559,6 +8570,10 @@ sub add_nets_bridge_fdb {
        }
 
        my $bridge = $net->{bridge};
+       if (!$bridge) {
+           log_warn("Interface '$iface' not attached to any bridge.");
+           next;
+       }
        if ($have_sdn) {
            PVE::Network::SDN::Zones::add_bridge_fdb($iface, $mac, $bridge, $net->{firewall});
        } elsif (-d "/sys/class/net/$bridge/bridge") { # avoid fdb management with OVS for now