]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
d/control: bump versioned (build-)dependency of libpve-common-perl
[qemu-server.git] / PVE / QemuServer.pm
index e0a89aa4b96b0aab780c05cb9004d0d4d3c6c30d..8c1aa30a0c5794ea9c0ece34ecd34ad989321bb5 100644 (file)
@@ -119,25 +119,10 @@ PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
        optional => 1,
 });
 
-
-sub map_storage {
-    my ($map, $source) = @_;
-
-    return $source if !defined($map);
-
-    return $map->{entries}->{$source}
-       if $map->{entries} && defined($map->{entries}->{$source});
-
-    return $map->{default} if $map->{default};
-
-    # identity (fallback)
-    return $source;
-}
-
 PVE::JSONSchema::register_standard_option('pve-targetstorage', {
     description => "Mapping from source to target storages. Providing only a single storage ID maps all source storages to that storage. Providing the special value '1' will map each source storage to itself.",
     type => 'string',
-    format => 'storagepair-list',
+    format => 'storage-pair-list',
     optional => 1,
 });
 
@@ -286,6 +271,21 @@ my $rng_fmt = {
     },
 };
 
+my $meta_info_fmt = {
+    'ctime' => {
+       type => 'integer',
+       description => "The guest creation timestamp as UNIX epoch time",
+       minimum => 0,
+       optional => 1,
+    },
+    'creation-qemu' => {
+       type => 'string',
+       description => "The QEMU (machine) version from the time this VM was created.",
+       pattern => '\d+(\.\d+)+',
+       optional => 1,
+    },
+};
+
 my $confdesc = {
     onboot => {
        optional => 1,
@@ -707,6 +707,12 @@ EODESCR
        description => "Configure a VirtIO-based Random Number Generator.",
        optional => 1,
     },
+    meta => {
+       type => 'string',
+       format => $meta_info_fmt,
+       description => "Some (read-only) meta-information about this guest.",
+       optional => 1,
+    },
 };
 
 my $cicustom_fmt = {
@@ -734,6 +740,14 @@ my $cicustom_fmt = {
        format => 'pve-volume-id',
        format_description => 'volume',
     },
+    vendor => {
+    type => 'string',
+    optional => 1,
+    description => 'Specify a custom file containing all vendor data passed to the VM via'
+     .' cloud-init.',
+    format => 'pve-volume-id',
+    format_description => 'volume',
+    },
 };
 PVE::JSONSchema::register_format('pve-qm-cicustom', $cicustom_fmt);
 
@@ -1235,7 +1249,7 @@ sub kvm_user_version {
 my sub extract_version {
     my ($machine_type, $version) = @_;
     $version = kvm_user_version() if !defined($version);
-    PVE::QemuServer::Machine::extract_version($machine_type, $version)
+    return PVE::QemuServer::Machine::extract_version($machine_type, $version)
 }
 
 sub kernel_has_vhost_net {
@@ -1384,9 +1398,9 @@ sub scsi_inquiry {
     # see /usr/include/scsi/sg.h
     my $sg_io_hdr_t = "i i C C s I P P P I I i P C C C C S S i I I";
 
-    my $packet = pack($sg_io_hdr_t, ord('S'), -3, length($cmd),
-                     length($sensebuf), 0, length($buf), $buf,
-                     $cmd, $sensebuf, 6000);
+    my $packet = pack(
+       $sg_io_hdr_t, ord('S'), -3, length($cmd), length($sensebuf), 0, length($buf), $buf, $cmd, $sensebuf, 6000
+    );
 
     $ret = ioctl($fh, $SG_IO, $packet);
     if (!$ret) {
@@ -1401,11 +1415,10 @@ sub scsi_inquiry {
     }
 
     my $res = {};
-    (my $byte0, my $byte1, $res->{vendor},
-     $res->{product}, $res->{revision}) = unpack("C C x6 A8 A16 A4", $buf);
+    $res->@{qw(type removable vendor product revision)} = unpack("C C x6 A8 A16 A4", $buf);
 
-    $res->{removable} = $byte1 & 128 ? 1 : 0;
-    $res->{type} = $byte0 & 31;
+    $res->{removable} = $res->{removable} & 128 ? 1 : 0;
+    $res->{type} &= 0x1F;
 
     return $res;
 }
@@ -1437,7 +1450,7 @@ sub print_tabletdevice_full {
 }
 
 sub print_keyboarddevice_full {
-    my ($conf, $arch, $machine) = @_;
+    my ($conf, $arch) = @_;
 
     return if $arch ne 'aarch64';
 
@@ -2108,6 +2121,53 @@ sub parse_rng {
     return $res;
 }
 
+sub parse_meta_info {
+    my ($value) = @_;
+
+    return if !$value;
+
+    my $res = eval { parse_property_string($meta_info_fmt, $value) };
+    warn $@ if $@;
+    return $res;
+}
+
+sub new_meta_info_string {
+    my () = @_; # for now do not allow to override any value
+
+    return PVE::JSONSchema::print_property_string(
+       {
+           'creation-qemu' => kvm_user_version(),
+           ctime => "". int(time()),
+       },
+       $meta_info_fmt
+    );
+}
+
+sub qemu_created_version_fixups {
+    my ($conf, $forcemachine, $kvmver) = @_;
+
+    my $meta = parse_meta_info($conf->{meta}) // {};
+    my $forced_vers = PVE::QemuServer::Machine::extract_version($forcemachine);
+
+    # check if we need to apply some handling for VMs that always use the latest machine version but
+    # had a machine version transition happen that affected HW such that, e.g., an OS config change
+    # would be required (we do not want to pin machine version for non-windows OS type)
+    if (
+       (!defined($conf->{machine}) || $conf->{machine} =~ m/^(?:pc|q35|virt)$/) # non-versioned machine
+       && (!defined($meta->{'creation-qemu'}) || !min_version($meta->{'creation-qemu'}, 6, 1)) # created before 6.1
+       && (!$forced_vers || min_version($forced_vers, 6, 1)) # handle snapshot-rollback/migrations
+       && min_version($kvmver, 6, 1) # only need to apply the change since 6.1
+    ) {
+       my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
+       if ($q35 && $conf->{ostype} && $conf->{ostype} eq 'l26') {
+           # this changed to default-on in Q 6.1 for q35 machines, it will mess with PCI slot view
+           # and thus with the predictable interface naming of systemd
+           return ['-global', 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off'];
+       }
+    }
+    return;
+}
+
 PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
 sub verify_usb_device {
     my ($value, $noerr) = @_;
@@ -2129,6 +2189,7 @@ sub json_config_properties {
        vmstate => 1,
        runningmachine => 1,
        runningcpu => 1,
+       meta => 1,
     };
 
     foreach my $opt (keys %$confdesc) {
@@ -3327,6 +3388,16 @@ my sub get_cpuunits {
     my ($conf) = @_;
     return $conf->{cpuunits} // (PVE::CGroup::cgroup_mode() == 2 ? 100 : 1024);
 }
+
+# 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) = @_;
+
+    return (!defined($conf->{bios}) || $conf->{bios} eq 'seabios') &&
+       $vga->{type} && $vga->{type} =~ m/^(serial\d+|none)$/;
+}
+
 sub config_to_command {
     my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu,
         $pbs_backing) = @_;
@@ -3503,6 +3574,10 @@ sub config_to_command {
        }
     }
 
+    if (defined(my $fixups = qemu_created_version_fixups($conf, $forcemachine, $kvmver))) {
+       push @$cmd, $fixups->@*;
+    }
+
     if ($conf->{vmgenid}) {
        push @$devices, '-device', 'vmgenid,guid='.$conf->{vmgenid};
     }
@@ -3922,6 +3997,8 @@ sub config_to_command {
        push @$machineFlags, 'accel=tcg';
     }
 
+    push @$machineFlags, 'smm=off' if should_disable_smm($conf, $vga);
+
     my $machine_type_min = $machine_type;
     if ($add_pve_version) {
        $machine_type_min =~ s/\+pve\d+$//;
@@ -4845,6 +4922,8 @@ sub vmconfig_hotplug_pending {
            } elsif ($opt eq 'cpulimit') {
                my $cpulimit = $conf->{pending}->{$opt} == 0 ? -1 : int($conf->{pending}->{$opt} * 100000);
                $cgroup->change_cpu_quota($cpulimit, 100000);
+           } elsif ($opt eq 'agent') {
+               vmconfig_update_agent($conf, $opt, $value);
            } else {
                die "skip\n";  # skip non-hot-pluggable options
            }
@@ -4904,6 +4983,8 @@ sub vmconfig_delete_or_detach_drive {
 sub vmconfig_apply_pending {
     my ($vmid, $conf, $storecfg, $errors) = @_;
 
+    return if !scalar(keys %{$conf->{pending}});
+
     my $add_apply_error = sub {
        my ($opt, $msg) = @_;
        my $err_msg = "unable to apply pending change $opt : $msg";
@@ -5004,6 +5085,29 @@ sub vmconfig_update_net {
     }
 }
 
+sub vmconfig_update_agent {
+    my ($conf, $opt, $value) = @_;
+
+    die "skip\n" if !$conf->{$opt};
+
+    my $hotplug_options = { fstrim_cloned_disks => 1 };
+
+    my $old_agent = parse_guest_agent($conf);
+    my $agent = parse_guest_agent({$opt => $value});
+
+    for my $option (keys %$agent) { # added/changed options
+       next if defined($hotplug_options->{$option});
+       die "skip\n" if safe_string_ne($agent->{$option}, $old_agent->{$option});
+    }
+
+    for my $option (keys %$old_agent) { # removed options
+       next if defined($hotplug_options->{$option});
+       die "skip\n" if safe_string_ne($old_agent->{$option}, $agent->{$option});
+    }
+
+    return; # either no actual change (e.g., format string reordered) or just hotpluggable changes
+}
+
 sub vmconfig_update_disk {
     my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $arch, $machine_type) = @_;
 
@@ -5121,6 +5225,7 @@ sub vm_migrate_get_nbd_disks {
        my ($ds, $drive) = @_;
 
        return if drive_is_cdrom($drive);
+       return if $ds eq 'tpmstate0';
 
        my $volid = $drive->{file};
 
@@ -5159,7 +5264,7 @@ sub vm_migrate_alloc_nbd_disks {
        # volume is not available there, fall back to the default format.
        # Otherwise use the same format as the original.
        if (!$storagemap->{identity}) {
-           $storeid = map_storage($storagemap, $storeid);
+           $storeid = PVE::JSONSchema::map_id($storagemap, $storeid);
            my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
            my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
            my $fileFormat = qemu_img_format($scfg, $volname);
@@ -5252,7 +5357,8 @@ sub vm_start {
 #   network => CIDR of migration network
 #   type => secure/insecure - tunnel over encrypted connection or plain-text
 #   nbd_proto_version => int, 0 for TCP, 1 for UNIX
-#   replicated_volumes = which volids should be re-used with bitmaps for nbd migration
+#   replicated_volumes => which volids should be re-used with bitmaps for nbd migration
+#   tpmstate_vol => new volid of tpmstate0, not yet contained in config
 sub vm_start_nolock {
     my ($storecfg, $vmid, $conf, $params, $migrate_opts) = @_;
 
@@ -5277,6 +5383,13 @@ sub vm_start_nolock {
     # this way we can reuse the old ISO with the correct config
     PVE::QemuServer::Cloudinit::generate_cloudinitconfig($conf, $vmid) if !$migratedfrom;
 
+    # override TPM state vol if migrated, conf is out of date still
+    if (my $tpmvol = $migrate_opts->{tpmstate_vol}) {
+        my $parsed = parse_drive("tpmstate0", $conf->{tpmstate0});
+        $parsed->{file} = $tpmvol;
+        $conf->{tpmstate0} = print_drive($parsed);
+    }
+
     my $defaults = load_defaults();
 
     # set environment variable useful inside network script
@@ -5384,7 +5497,12 @@ sub vm_start_nolock {
        $pci_devices->{$i} = parse_hostpci($dev);
     }
 
-    my $pci_id_list = [ map { $_->{id} } map { $_->{pciid}->@* } values $pci_devices->%* ];
+    # do not reserve pciid for mediated devices, sysfs will error out for duplicate assignment
+    my $real_pci_devices = [ grep { !(defined($_->{mdev}) && scalar($_->{pciid}->@*) == 1) } values $pci_devices->%* ];
+
+    # map to a flat list of pci ids
+    my $pci_id_list = [ map { $_->{id} } map { $_->{pciid}->@* } $real_pci_devices->@* ];
+
     # reserve all PCI IDs before actually doing anything with them
     PVE::QemuServer::PCI::reserve_pci_usage($pci_id_list, $vmid, $start_timeout);
 
@@ -5606,9 +5724,8 @@ sub vm_commandline {
     my ($storecfg, $vmid, $snapname) = @_;
 
     my $conf = PVE::QemuConfig->load_config($vmid);
-    my $forcemachine;
-    my $forcecpu;
 
+    my ($forcemachine, $forcecpu);
     if ($snapname) {
        my $snapshot = $conf->{snapshots}->{$snapname};
        die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
@@ -5624,8 +5741,7 @@ sub vm_commandline {
 
     my $defaults = load_defaults();
 
-    my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults,
-       $forcemachine, $forcecpu);
+    my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu);
 
     return PVE::Tools::cmd2string($cmd);
 }
@@ -7335,9 +7451,11 @@ sub qemu_drive_mirror_monitor {
                    if ($agent_running) {
                        print "freeze filesystem\n";
                        eval { mon_cmd($vmid, "guest-fsfreeze-freeze"); };
+                       warn $@ if $@;
                    } else {
                        print "suspend vm\n";
                        eval { PVE::QemuServer::vm_suspend($vmid, 1); };
+                       warn $@ if $@;
                    }
 
                    # if we clone a disk for a new target vm, we don't switch the disk
@@ -7346,9 +7464,11 @@ sub qemu_drive_mirror_monitor {
                    if ($agent_running) {
                        print "unfreeze filesystem\n";
                        eval { mon_cmd($vmid, "guest-fsfreeze-thaw"); };
+                       warn $@ if $@;
                    } else {
                        print "resume vm\n";
-                       eval {  PVE::QemuServer::vm_resume($vmid, 1, 1); };
+                       eval { PVE::QemuServer::vm_resume($vmid, 1, 1); };
+                       warn $@ if $@;
                    }
 
                    last;
@@ -7507,8 +7627,8 @@ sub clone_disk {
 no_data_clone:
     my ($size) = eval { PVE::Storage::volume_size_info($storecfg, $newvolid, 10) };
 
-    my $disk = $drive;
-    $disk->{format} = undef;
+    my $disk = dclone($drive);
+    delete $disk->{format};
     $disk->{file} = $newvolid;
     $disk->{size} = $size if defined($size);