X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=541d7b099ed3cdea1c7de82aae785d999ea4523c;hb=29004a20ca4a9b75433e6a2cccbfd2fdea8cf790;hp=91a2bfc861524ea14af869cae9bee4c420f1a365;hpb=2715f9597082ad21931f662b630478b8299d385f;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 91a2bfc..541d7b0 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -34,6 +34,7 @@ use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr); use PVE::QemuServer::Memory; use PVE::QemuServer::USB qw(parse_usb_device); use PVE::QemuServer::Cloudinit; +use PVE::Systemd; use Time::HiRes qw(gettimeofday); use File::Copy qw(copy); use URI::Escape; @@ -79,6 +80,14 @@ PVE::JSONSchema::register_standard_option('pve-qm-image-format', { optional => 1, }); +PVE::JSONSchema::register_standard_option('pve-qemu-machine', { + description => "Specifies the Qemu machine type.", + type => 'string', + pattern => '(pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?)', + maxLength => 40, + optional => 1, +}); + #no warnings 'redefine'; sub cgroups_write { @@ -154,7 +163,7 @@ my $cpu_vendor_list = { max => 'default', }; -my $cpu_flag = qr/[+-](pcid|spec-ctrl)/; +my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb)/; my $cpu_fmt = { cputype => { @@ -173,7 +182,7 @@ my $cpu_fmt = { flags => { description => "List of additional CPU flags separated by ';'." . " Use '+FLAG' to enable, '-FLAG' to disable a flag." - . " Currently supported flags: 'pcid', 'spec-ctrl'.", + . " Currently supported flags: 'pcid', 'spec-ctrl', 'ibpb', 'ssbd', 'virt-ssbd', 'amd-ssbd', 'amd-no-ssb', 'pdpe1gb'.", format_description => '+FLAG[;-FLAG...]', type => 'string', pattern => qr/$cpu_flag(;$cpu_flag)*/, @@ -199,6 +208,21 @@ my $watchdog_fmt = { }; PVE::JSONSchema::register_format('pve-qm-watchdog', $watchdog_fmt); +my $agent_fmt = { + enabled => { + description => "Enable/disable Qemu GuestAgent.", + type => 'boolean', + default => 0, + default_key => 1, + }, + fstrim_cloned_disks => { + description => "Run fstrim after cloning/moving a disk.", + type => 'boolean', + optional => 1, + default => 0 + }, +}; + my $confdesc = { onboot => { optional => 1, @@ -264,7 +288,7 @@ my $confdesc = { shares => { optional => 1, type => 'integer', - description => "Amount of memory shares for auto-ballooning. The larger the number is, the more memory this VM gets. Number is relative to weights of all other running VMs. Using zero disables auto-ballooning", + description => "Amount of memory shares for auto-ballooning. The larger the number is, the more memory this VM gets. Number is relative to weights of all other running VMs. Using zero disables auto-ballooning. Auto-ballooning is done by pvestatd.", minimum => 0, maximum => 50000, default => 1000, @@ -272,7 +296,7 @@ my $confdesc = { keyboard => { optional => 1, type => 'string', - description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.conf' configuration file.". + description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.cfg' configuration file.". "It should not be necessary to set it.", enum => PVE::Tools::kvmkeymaplist(), default => undef, @@ -379,9 +403,9 @@ EODESC }, agent => { optional => 1, - type => 'boolean', - description => "Enable/disable Qemu GuestAgent.", - default => 0, + description => "Enable/disable Qemu GuestAgent and its properties.", + type => 'string', + format => $agent_fmt, }, kvm => { optional => 1, @@ -417,7 +441,7 @@ EODESC "displays you want, Linux guests can add displays them self. " . "You can also run without any graphic card, using a serial device" . " as terminal.", - enum => [qw(std cirrus vmware qxl serial0 serial1 serial2 serial3 qxl2 qxl3 qxl4)], + enum => [qw(cirrus qxl qxl2 qxl3 qxl4 serial0 serial1 serial2 serial3 std virtio vmware)], }, watchdog => { optional => 1, @@ -512,13 +536,10 @@ EODESCR description => "Default storage for VM state volumes/files.", optional => 1, }), - machine => { - description => "Specific the Qemu machine type.", - type => 'string', - pattern => '(pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?)', - maxLength => 40, - optional => 1, - }, + runningmachine => get_standard_option('pve-qemu-machine', { + description => "Specifies the Qemu machine type of the running vm. This is used internally for snapshots.", + }), + machine => get_standard_option('pve-qemu-machine'), smbios1 => { description => "Specify SMBIOS type 1 fields.", type => 'string', format => 'pve-qm-smbios1', @@ -538,6 +559,24 @@ EODESCR description => "Select BIOS implementation.", default => 'seabios', }, + vmgenid => { + type => 'string', + pattern => '(?:[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}|[01])', + format_description => 'UUID', + description => "Set VM Generation ID. Use '1' to autogenerate on create or update, pass '0' to disable explicitly.", + verbose_description => "The VM generation ID (vmgenid) device exposes a". + " 128-bit integer value identifier to the guest OS. This allows to". + " notify the guest operating system when the virtual machine is". + " executed with a different configuration (e.g. snapshot execution". + " or creation from a template). The guest operating system notices". + " the change, and is then able to react as appropriate by marking". + " its copies of distributed databases as dirty, re-initializing its". + " random number generator, etc.\n". + "Note that auto-creation only works when done throug API/CLI create". + " or update methods, but not when manually editing the config file.", + default => "1 (autogenerated)", + optional => 1, + }, }; my $confdesc_cloudinit = { @@ -1689,6 +1728,12 @@ sub print_drivedevice_full { $device .= ",bootindex=$drive->{bootindex}" if $drive->{bootindex}; + if (my $serial = $drive->{serial}) { + $serial = URI::Escape::uri_unescape($serial); + $device .= ",serial=$serial"; + } + + return $device; } @@ -1761,11 +1806,6 @@ sub print_drive_full { } } - if (my $serial = $drive->{serial}) { - $serial = URI::Escape::uri_unescape($serial); - $opts .= ",serial=$serial"; - } - $opts .= ",format=$format" if $format && !$drive->{format}; my $cache_direct = 0; @@ -2209,6 +2249,19 @@ sub parse_watchdog { return $res; } +sub parse_guest_agent { + my ($value) = @_; + + return {} if !defined($value->{agent}); + + my $res = eval { PVE::JSONSchema::parse_property_string($agent_fmt, $value->{agent}) }; + warn $@ if $@; + + # if the agent is disabled ignore the other potentially set properties + return {} if !$res->{enabled}; + return $res; +} + PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device); sub verify_usb_device { my ($value, $noerr) = @_; @@ -2225,7 +2278,7 @@ sub json_config_properties { my $prop = shift; foreach my $opt (keys %$confdesc) { - next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate'; + next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate' || $opt eq 'runningmachine'; $prop->{$opt} = $confdesc->{$opt}; } @@ -2762,6 +2815,53 @@ sub disksize { return $drive->{size}; } +our $vmstatus_return_properties = { + vmid => get_standard_option('pve-vmid'), + status => { + description => "Qemu process status.", + type => 'string', + enum => ['stopped', 'running'], + }, + maxmem => { + description => "Maximum memory in bytes.", + type => 'integer', + optional => 1, + renderer => 'bytes', + }, + maxdisk => { + description => "Root disk size in bytes.", + type => 'integer', + optional => 1, + renderer => 'bytes', + }, + name => { + description => "VM name.", + type => 'string', + optional => 1, + }, + qmpstatus => { + description => "Qemu QMP agent status.", + type => 'string', + optional => 1, + }, + pid => { + description => "PID of running qemu process.", + type => 'integer', + optional => 1, + }, + uptime => { + description => "Uptime.", + type => 'integer', + optional => 1, + renderer => 'duration', + }, + cpus => { + description => "Maximum usable CPUs.", + type => 'number', + optional => 1, + }, +}; + my $last_proc_pid_stat; # get VM status information @@ -2787,7 +2887,7 @@ sub vmstatus { my $cfspath = PVE::QemuConfig->cfs_config_path($vmid); my $conf = PVE::Cluster::cfs_read_file($cfspath) || {}; - my $d = {}; + my $d = { vmid => $vmid }; $d->{pid} = $list->{$vmid}->{pid}; # fixme: better status? @@ -3109,6 +3209,10 @@ sub config_to_command { push @$cmd, '-smbios', "type=1,$conf->{smbios1}"; } + if ($conf->{vmgenid}) { + push @$devices, '-device', 'vmgenid,guid='.$conf->{vmgenid}; + } + if ($conf->{bios} && $conf->{bios} eq 'ovmf') { die "uefi base image not found\n" if ! -f $OVMF_CODE; @@ -3393,7 +3497,7 @@ sub config_to_command { #push @$cmd, '-soundhw', 'es1370'; #push @$cmd, '-soundhw', $soundhw if $soundhw; - if($conf->{agent}) { + if (parse_guest_agent($conf)->{enabled}) { my $qgasocket = qmp_socket($vmid, 1); my $pciaddr = print_pci_addr("qga0", $bridges); push @$devices, '-chardev', "socket,path=$qgasocket,server,nowait,id=qga0"; @@ -3408,7 +3512,7 @@ sub config_to_command { if ($winversion){ for(my $i = 1; $i < $qxlnum; $i++){ my $pciaddr = print_pci_addr("vga$i", $bridges); - push @$cmd, '-device', "qxl,id=vga$i,ram_size=67108864,vram_size=33554432$pciaddr"; + push @$devices, '-device', "qxl,id=vga$i,ram_size=67108864,vram_size=33554432$pciaddr"; } } else { # assume other OS works like Linux @@ -3600,21 +3704,22 @@ sub vm_devices_list { my ($vmid) = @_; my $res = vm_mon_cmd($vmid, 'query-pci'); + my $devices_to_check = []; my $devices = {}; foreach my $pcibus (@$res) { - foreach my $device (@{$pcibus->{devices}}) { - next if !$device->{'qdev_id'}; - if ($device->{'pci_bridge'}) { - $devices->{$device->{'qdev_id'}} = 1; - foreach my $bridge_device (@{$device->{'pci_bridge'}->{devices}}) { - next if !$bridge_device->{'qdev_id'}; - $devices->{$bridge_device->{'qdev_id'}} = 1; - $devices->{$device->{'qdev_id'}}++; - } - } else { - $devices->{$device->{'qdev_id'}} = 1; - } + push @$devices_to_check, @{$pcibus->{devices}}, + } + + while (@$devices_to_check) { + my $to_check = []; + for my $d (@$devices_to_check) { + $devices->{$d->{'qdev_id'}} = 1 if $d->{'qdev_id'}; + next if !$d->{'pci_bridge'}; + + $devices->{$d->{'qdev_id'}} += scalar(@{$d->{'pci_bridge'}->{devices}}); + push @$to_check, @{$d->{'pci_bridge'}->{devices}}; } + $devices_to_check = $to_check; } my $resblock = vm_mon_cmd($vmid, 'query-block'); @@ -4183,7 +4288,7 @@ sub qemu_volume_snapshot { my $running = check_running($vmid); if ($running && do_snapshots_with_qemu($storecfg, $volid)){ - vm_mon_cmd($vmid, "snapshot-drive", device => $deviceid, name => $snap); + vm_mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap); } else { PVE::Storage::volume_snapshot($storecfg, $volid, $snap); } @@ -4194,8 +4299,18 @@ sub qemu_volume_snapshot_delete { my $running = check_running($vmid); + if($running) { + + $running = undef; + my $conf = PVE::QemuConfig->load_config($vmid); + foreach_drive($conf, sub { + my ($ds, $drive) = @_; + $running = 1 if $drive->{file} eq $volid; + }); + } + if ($running && do_snapshots_with_qemu($storecfg, $volid)){ - vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap); + vm_mon_cmd($vmid, 'blockdev-snapshot-delete-internal-sync', device => $deviceid, name => $snap); } else { PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running); } @@ -4295,7 +4410,10 @@ sub vmconfig_hotplug_pending { qemu_cpu_hotplug($vmid, $conf, undef); } elsif ($opt eq 'balloon') { # enable balloon device is not hotpluggable - die "skip\n" if !defined($conf->{balloon}) || $conf->{balloon}; + die "skip\n" if defined($conf->{balloon}) && $conf->{balloon} == 0; + # here we reset the ballooning value to memory + my $balloon = $conf->{memory} || $defaults->{memory}; + vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024); } elsif ($fast_plug_option->{$opt}) { # do nothing } elsif ($opt =~ m/^net(\d+)$/) { @@ -4839,6 +4957,13 @@ sub vm_start { } $properties{timeout} = 10 if $statefile; # setting up the scope shoul be quick + my $run_qemu = sub { + PVE::Tools::run_fork sub { + PVE::Systemd::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties); + run_command($cmd, %run_params); + }; + }; + if ($conf->{hugepages}) { my $code = sub { @@ -4848,11 +4973,7 @@ sub vm_start { PVE::QemuServer::Memory::hugepages_mount(); PVE::QemuServer::Memory::hugepages_allocate($hugepages_topology, $hugepages_host_topology); - eval { - PVE::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties); - run_command($cmd, %run_params); - }; - + eval { $run_qemu->() }; if (my $err = $@) { PVE::QemuServer::Memory::hugepages_reset($hugepages_host_topology); die $err; @@ -4863,10 +4984,7 @@ sub vm_start { eval { PVE::QemuServer::Memory::hugepages_update_locked($code); }; } else { - eval { - PVE::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties); - run_command($cmd, %run_params); - }; + eval { $run_qemu->() }; } if (my $err = $@) { @@ -4917,10 +5035,8 @@ sub vm_start { } } else { - if (!$statefile && (!defined($conf->{balloon}) || $conf->{balloon})) { - vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024) - if $conf->{balloon}; - } + vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024) + if !$statefile && $conf->{balloon}; foreach my $opt (keys %$conf) { next if $opt !~ m/^net\d+$/; @@ -5091,7 +5207,7 @@ sub vm_stop { eval { if ($shutdown) { - if (defined($conf) && $conf->{agent}) { + if (defined($conf) && parse_guest_agent($conf)->{enabled}) { vm_qmp_command($vmid, { execute => "guest-shutdown" }, $nocheck); } else { vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck); @@ -5167,6 +5283,13 @@ sub vm_resume { PVE::QemuConfig->lock_config($vmid, sub { + my $res = vm_mon_cmd($vmid, 'query-status'); + my $resume_cmd = 'cont'; + + if ($res->{status} && $res->{status} eq 'suspended') { + $resume_cmd = 'system_wakeup'; + } + if (!$nocheck) { my $conf = PVE::QemuConfig->load_config($vmid); @@ -5174,10 +5297,10 @@ sub vm_resume { PVE::QemuConfig->check_lock($conf) if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup')); - vm_mon_cmd($vmid, "cont"); + vm_mon_cmd($vmid, $resume_cmd); } else { - vm_mon_cmd_nocheck($vmid, "cont"); + vm_mon_cmd_nocheck($vmid, $resume_cmd); } }); } @@ -5453,6 +5576,13 @@ sub restore_update_config_line { } else { print $outfd $line; } + } elsif (($line =~ m/^vmgenid: (.*)/)) { + my $vmgenid = $1; + if ($vmgenid ne '0') { + # always generate a new vmgenid if there was a valid one setup + $vmgenid = generate_uuid(); + } + print $outfd "vmgenid: $vmgenid\n"; } elsif (($line =~ m/^(smbios1: )(.*)/) && $unique) { my ($uuid, $uuid_str); UUID::generate($uuid); @@ -5527,6 +5657,7 @@ sub update_disksize { my ($vmid, $conf, $volid_hash) = @_; my $changes; + my $prefix = "VM $vmid:"; # used and unused disks my $referenced = {}; @@ -5558,6 +5689,7 @@ sub update_disksize { if ($new ne $conf->{$opt}) { $changes = 1; $conf->{$opt} = $new; + print "$prefix update disk '$opt' information.\n"; } } } @@ -5568,6 +5700,7 @@ sub update_disksize { my $volid = $conf->{$opt}; my $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid}; if ($referenced->{$volid} || ($path && $referencedpath->{$path})) { + print "$prefix remove entry '$opt', its volume '$volid' is in use.\n"; $changes = 1; delete $conf->{$opt}; } @@ -5583,7 +5716,8 @@ sub update_disksize { next if !$path; # just to be sure next if $referencedpath->{$path}; $changes = 1; - PVE::QemuConfig->add_unused_volume($conf, $volid); + my $key = PVE::QemuConfig->add_unused_volume($conf, $volid); + print "$prefix add unreferenced volume '$volid' as '$key' to config.\n"; $referencedpath->{$path} = 1; # avoid to add more than once (aliases) } @@ -5591,10 +5725,17 @@ sub update_disksize { } sub rescan { - my ($vmid, $nolock) = @_; + my ($vmid, $nolock, $dryrun) = @_; my $cfg = PVE::Storage::config(); + # FIXME: Remove once our RBD plugin can handle CT and VM on a single storage + # see: https://pve.proxmox.com/pipermail/pve-devel/2018-July/032900.html + foreach my $stor (keys %{$cfg->{ids}}) { + delete($cfg->{ids}->{$stor}) if ! $cfg->{ids}->{$stor}->{content}->{images}; + } + + print "rescan volumes...\n"; my $volid_hash = scan_volids($cfg, $vmid); my $updatefn = sub { @@ -5612,7 +5753,7 @@ sub rescan { my $changes = update_disksize($vmid, $conf, $vm_volids); - PVE::QemuConfig->write_config($vmid, $conf) if $changes; + PVE::QemuConfig->write_config($vmid, $conf) if $changes && !$dryrun; }; if (defined($vmid)) { @@ -6092,11 +6233,11 @@ sub do_snapshots_with_qemu { } sub qga_check_running { - my ($vmid) = @_; + my ($vmid, $nowarn) = @_; eval { vm_mon_cmd($vmid, "guest-ping", timeout => 3); }; if ($@) { - warn "Qemu Guest Agent is not running - $@"; + warn "Qemu Guest Agent is not running - $@" if !$nowarn; return 0; } return 1; @@ -6145,8 +6286,9 @@ sub qemu_img_convert { my $cmd = []; push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n'; - push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2"); - push @$cmd, '-t', 'none', '-T', 'none'; + push @$cmd, '-l', "snapshot.name=$snapname" if($snapname && $src_format eq "qcow2"); + push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool'; + push @$cmd, '-T', 'none' if $src_scfg->{type} eq 'zfspool'; push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path; if ($is_zero_initialized) { push @$cmd, "zeroinit:$dst_path"; @@ -6452,9 +6594,9 @@ sub qemu_machine_feature_enabled { $current_minor = $2; } - return 1 if $current_major >= $version_major && $current_minor >= $version_minor; - - + return 1 if $current_major > $version_major || + ($current_major == $version_major && + $current_minor >= $version_minor); } sub qemu_machine_pxe { @@ -6462,13 +6604,8 @@ sub qemu_machine_pxe { $machine = PVE::QemuServer::get_current_qemu_machine($vmid) if !$machine; - foreach my $opt (keys %$conf) { - next if $opt !~ m/^net(\d+)$/; - my $net = PVE::QemuServer::parse_net($conf->{$opt}); - next if !$net; - my $romfile = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, 'qom-get', path => $opt, property => 'romfile'); - return $machine.".pxe" if $romfile =~ m/pxe/; - last; + if ($conf->{machine} && $conf->{machine} =~ m/\.pxe$/) { + $machine .= '.pxe'; } return $machine; @@ -6589,6 +6726,11 @@ sub add_hyperv_enlightenments { if ($winversion >= 7) { push @$cpuFlags , 'hv_relaxed'; + + if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 0)) { + push @$cpuFlags , 'hv_synic'; + push @$cpuFlags , 'hv_stimer'; + } } } @@ -6643,11 +6785,15 @@ sub resolve_first_disk { return $firstdisk; } -sub generate_smbios1_uuid { +sub generate_uuid { my ($uuid, $uuid_str); UUID::generate($uuid); UUID::unparse($uuid, $uuid_str); - return "uuid=$uuid_str"; + return $uuid_str; +} + +sub generate_smbios1_uuid { + return "uuid=".generate_uuid(); } # bash completion helper @@ -6723,4 +6869,10 @@ sub complete_storage { return $res; } +sub nbd_stop { + my ($vmid) = @_; + + vm_mon_cmd($vmid, 'nbd-server-stop'); +} + 1;