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,
});
},
};
+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,
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 = {
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);
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 {
# 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) {
}
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;
}
}
sub print_keyboarddevice_full {
- my ($conf, $arch, $machine) = @_;
+ my ($conf, $arch) = @_;
return if $arch ne 'aarch64';
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) = @_;
vmstate => 1,
runningmachine => 1,
runningcpu => 1,
+ meta => 1,
};
foreach my $opt (keys %$confdesc) {
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) = @_;
}
}
+ if (defined(my $fixups = qemu_created_version_fixups($conf, $forcemachine, $kvmver))) {
+ push @$cmd, $fixups->@*;
+ }
+
if ($conf->{vmgenid}) {
push @$devices, '-device', 'vmgenid,guid='.$conf->{vmgenid};
}
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+$//;
} 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
}
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";
}
}
+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) = @_;
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);
+ return if $ds eq 'tpmstate0';
my $volid = $drive->{file};
# 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);
# 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) = @_;
# 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
$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);
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);
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);
}
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
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;
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);