X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=48657bf82a63a060d84a8c57d1c27d1514b89414;hb=5da072fb8418906fd58524a7a87d677d1f13cff9;hp=8a1168f100b0040f5c4b3480999474de506e013d;hpb=575d19dab5fca8757dc3621a3621c04f4d2ffc77;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 8a1168f..48657bf 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -3,45 +3,47 @@ package PVE::QemuServer; use strict; use warnings; -use POSIX; -use IO::Handle; -use IO::Select; -use IO::File; -use IO::Dir; -use IO::Socket::UNIX; +use Cwd 'abs_path'; +use Digest::SHA; +use Fcntl ':flock'; +use Fcntl; use File::Basename; +use File::Copy qw(copy); use File::Path; use File::stat; use Getopt::Long; -use Digest::SHA; -use Fcntl ':flock'; -use Cwd 'abs_path'; +use IO::Dir; +use IO::File; +use IO::Handle; +use IO::Select; +use IO::Socket::UNIX; use IPC::Open3; use JSON; -use Fcntl; -use PVE::SafeSyslog; -use Storable qw(dclone); use MIME::Base64; -use PVE::Exception qw(raise raise_param_exc); -use PVE::Storage; -use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE); -use PVE::JSONSchema qw(get_standard_option); +use POSIX; +use Storable qw(dclone); +use Time::HiRes qw(gettimeofday); +use URI::Escape; + use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file); +use PVE::Exception qw(raise raise_param_exc); +use PVE::GuestHelpers; use PVE::INotify; +use PVE::JSONSchema qw(get_standard_option); use PVE::ProcFSTools; -use PVE::QemuConfig; -use PVE::QMPClient; use PVE::RPCEnvironment; -use PVE::GuestHelpers; -use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port); -use PVE::QemuServer::Memory; -use PVE::QemuServer::USB qw(parse_usb_device); -use PVE::QemuServer::Cloudinit; +use PVE::SafeSyslog; +use PVE::Storage; use PVE::SysFSTools; use PVE::Systemd; -use Time::HiRes qw(gettimeofday); -use File::Copy qw(copy); -use URI::Escape; +use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE); + +use PVE::QMPClient; +use PVE::QemuConfig; +use PVE::QemuServer::Cloudinit; +use PVE::QemuServer::Memory; +use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port); +use PVE::QemuServer::USB qw(parse_usb_device); my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/'; my $OVMF = { @@ -146,6 +148,9 @@ my $cpu_vendor_list = { 'Skylake-Client-IBRS' => 'GenuineIntel', 'Skylake-Server' => 'GenuineIntel', 'Skylake-Server-IBRS' => 'GenuineIntel', + 'Cascadelake-Server' => 'GenuineIntel', + KnightsMill => 'GenuineIntel', + # AMD CPUs athlon => 'AuthenticAMD', @@ -1319,7 +1324,7 @@ EODESCR usb3 => { optional => 1, type => 'boolean', - description => "Specifies whether if given host option is a USB3 device or port (this does currently not work reliably with spice redirection and is then ignored).", + description => "Specifies whether if given host option is a USB3 device or port.", default => 0, }, }; @@ -2616,8 +2621,6 @@ sub touch_config { sub destroy_vm { my ($storecfg, $vmid, $keep_empty_config, $skiplock) = @_; - my $conffile = PVE::QemuConfig->config_file($vmid); - my $conf = PVE::QemuConfig->load_config($vmid); PVE::QemuConfig->check_lock($conf) if !$skiplock; @@ -2660,9 +2663,9 @@ sub destroy_vm { }); if ($keep_empty_config) { - PVE::Tools::file_set_contents($conffile, "memory: 128\n"); + PVE::QemuConfig->write_config($vmid, "memory: 128\n"); } else { - unlink $conffile; + PVE::QemuConfig->destroy_config($vmid); } # also remove unused disk @@ -2925,7 +2928,7 @@ sub check_local_resources { push @loc_res, "ivshmem" if $conf->{ivshmem}; foreach my $k (keys %$conf) { - next if $k =~ m/^usb/ && ($conf->{$k} eq 'spice'); + next if $k =~ m/^usb/ && ($conf->{$k} =~ m/^spice(?![^,])/); # sockets are safe: they will recreated be on the target side post-migrate next if $k =~ m/^serial/ && ($conf->{$k} eq 'socket'); push @loc_res, $k if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/; @@ -3620,8 +3623,6 @@ sub config_to_command { die "detected old qemu-kvm binary ($kvmver)\n" if $vernum < 15000; - my $have_ovz = -f '/proc/vz/vestat'; - my $q35 = machine_type_is_q35($conf); my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1'); my $use_old_bios_files = undef; @@ -3829,7 +3830,10 @@ sub config_to_command { } # usb devices - my @usbdevices = PVE::QemuServer::USB::get_usb_devices($conf, $usbdesc->{format}, $MAX_USB_DEVICES); + my $usb_dev_features = {}; + $usb_dev_features->{spice_usb3} = 1 if qemu_machine_feature_enabled($machine_type, $kvmver, 4, 0); + + my @usbdevices = PVE::QemuServer::USB::get_usb_devices($conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features); push @$devices, @usbdevices if @usbdevices; # serial devices for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) { @@ -4011,6 +4015,11 @@ sub config_to_command { my $pfamily = PVE::Tools::get_host_address_family($nodename); my @nodeaddrs = PVE::Tools::getaddrinfo_all('localhost', family => $pfamily); die "failed to get an ip address of type $pfamily for 'localhost'\n" if !@nodeaddrs; + + push @$devices, '-device', "virtio-serial,id=spice$pciaddr"; + push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent"; + push @$devices, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0"; + my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr}); $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost); @@ -4023,11 +4032,6 @@ sub config_to_command { my $spice_opts = "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on"; $spice_opts .= ",streaming-video=$spice_enhancement->{videostreaming}" if $spice_enhancement->{videostreaming}; push @$devices, '-spice', "$spice_opts"; - - push @$devices, '-device', "virtio-serial,id=spice$pciaddr"; - push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent"; - push @$devices, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0"; - } # enable balloon by default, unless explicitly disabled @@ -4163,7 +4167,7 @@ sub config_to_command { $bridges->{3} = 1 if $scsihw =~ m/^virtio-scsi-single/; - while (my ($k, $v) = each %$bridges) { + for my $k (sort {$b cmp $a} keys %$bridges) { $pciaddr = print_pci_addr("pci.$k", undef, $arch, $machine_type); unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0; } @@ -4179,7 +4183,7 @@ sub config_to_command { if (my $vmstate = $conf->{vmstate}) { my $statepath = PVE::Storage::path($storecfg, $vmstate); - PVE::Storage::activate_volumes($storecfg, [$vmstate]); + push @$vollist, $statepath; push @$cmd, '-loadstate', $statepath; } @@ -5414,7 +5418,6 @@ sub vm_start { my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine); - my $migrate_port = 0; my $migrate_uri; if ($statefile) { if ($statefile eq 'tcp') { @@ -5442,7 +5445,7 @@ sub vm_start { } my $pfamily = PVE::Tools::get_host_address_family($nodename); - $migrate_port = PVE::Tools::next_migrate_port($pfamily); + my $migrate_port = PVE::Tools::next_migrate_port($pfamily); $migrate_uri = "tcp:${localip}:${migrate_port}"; push @$cmd, '-incoming', $migrate_uri; push @$cmd, '-S'; @@ -5459,8 +5462,12 @@ sub vm_start { push @$cmd, '-incoming', $migrate_uri; push @$cmd, '-S'; - } else { + } elsif (-e $statefile) { push @$cmd, '-loadstate', $statefile; + } else { + my $statepath = PVE::Storage::path($storecfg, $statefile); + push @$vollist, $statepath; + push @$cmd, '-loadstate', $statepath; } } elsif ($paused) { push @$cmd, '-S'; @@ -5566,16 +5573,16 @@ sub vm_start { my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network); my $localip = $migrate_network_addr ? $migrate_network_addr : PVE::Cluster::remote_node_ip($nodename, 1); my $pfamily = PVE::Tools::get_host_address_family($nodename); - $migrate_port = PVE::Tools::next_migrate_port($pfamily); + my $storage_migrate_port = PVE::Tools::next_migrate_port($pfamily); - vm_mon_cmd_nocheck($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${migrate_port}" } } ); + vm_mon_cmd_nocheck($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${storage_migrate_port}" } } ); $localip = "[$localip]" if Net::IP::ip_is_ipv6($localip); foreach my $opt (sort keys %$local_volumes) { my $volid = $local_volumes->{$opt}; vm_mon_cmd_nocheck($vmid, "nbd-server-add", device => "drive-$opt", writable => JSON::true ); - my $migrate_storage_uri = "nbd:${localip}:${migrate_port}:exportname=drive-$opt"; + my $migrate_storage_uri = "nbd:${localip}:${storage_migrate_port}:exportname=drive-$opt"; print "storage migration listens on $migrate_storage_uri volume:$volid\n"; } } @@ -5870,6 +5877,22 @@ sub vm_stop { }); } +sub vm_reboot { + my ($vmid, $timeout) = @_; + + PVE::QemuConfig->lock_config($vmid, sub { + + # only reboot if running, as qmeventd starts it again on a stop event + return if !check_running($vmid); + + create_reboot_request($vmid); + + my $storecfg = PVE::Storage::config(); + _do_vm_stop($storecfg, $vmid, undef, undef, $timeout, 1); + + }); +} + sub vm_suspend { my ($vmid, $skiplock, $includestate, $statestorage) = @_; @@ -6490,7 +6513,7 @@ sub restore_vma_archive { foreach_drive($oldconf, sub { my ($ds, $drive) = @_; - return if !$drive->{is_cloudinit} && drive_is_cdrom($drive); + return if drive_is_cdrom($drive, 1); my $volid = $drive->{file}; return if !$volid || $volid =~ m|^/|; @@ -6865,66 +6888,77 @@ sub qemu_img_convert { my ($src_storeid, $src_volname) = PVE::Storage::parse_volume_id($src_volid, 1); my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1); - if ($src_storeid && $dst_storeid) { + die "destination '$dst_volid' is not a valid volid form qemu-img convert\n" if !$dst_storeid; - PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname); + my $cachemode; + my $src_path; + my $src_is_iscsi = 0; + my $src_format = 'raw'; + if ($src_storeid) { + PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname); my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid); - my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid); + $src_format = qemu_img_format($src_scfg, $src_volname); + $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname); + $src_is_iscsi = ($src_path =~ m|^iscsi://|); + $cachemode = 'none' if $src_scfg->{type} eq 'zfspool'; + } elsif (-f $src_volid) { + $src_path = $src_volid; + if ($src_path =~ m/\.($QEMU_FORMAT_RE)$/) { + $src_format = $1; + } + } - my $src_format = qemu_img_format($src_scfg, $src_volname); - my $dst_format = qemu_img_format($dst_scfg, $dst_volname); + die "source '$src_volid' is not a valid volid nor path for qemu-img convert\n" if !$src_path; - my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname); - my $dst_path = PVE::Storage::path($storecfg, $dst_volid); + my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid); + my $dst_format = qemu_img_format($dst_scfg, $dst_volname); + my $dst_path = PVE::Storage::path($storecfg, $dst_volid); + my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|); - my $src_is_iscsi = ($src_path =~ m|^iscsi://|); - my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|); + my $cmd = []; + push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n'; + 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', $cachemode if defined($cachemode); + + if ($src_is_iscsi) { + push @$cmd, '--image-opts'; + $src_path = convert_iscsi_path($src_path); + } else { + push @$cmd, '-f', $src_format; + } - my $cmd = []; - push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n'; - 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'; + if ($dst_is_iscsi) { + push @$cmd, '--target-image-opts'; + $dst_path = convert_iscsi_path($dst_path); + } else { + push @$cmd, '-O', $dst_format; + } - if ($src_is_iscsi) { - push @$cmd, '--image-opts'; - $src_path = convert_iscsi_path($src_path); - } else { - push @$cmd, '-f', $src_format; - } + push @$cmd, $src_path; - if ($dst_is_iscsi) { - push @$cmd, '--target-image-opts'; - $dst_path = convert_iscsi_path($dst_path); - } else { - push @$cmd, '-O', $dst_format; - } + if (!$dst_is_iscsi && $is_zero_initialized) { + push @$cmd, "zeroinit:$dst_path"; + } else { + push @$cmd, $dst_path; + } - push @$cmd, $src_path; + my $parser = sub { + my $line = shift; + if($line =~ m/\((\S+)\/100\%\)/){ + my $percent = $1; + my $transferred = int($size * $percent / 100); + my $remaining = $size - $transferred; - if (!$dst_is_iscsi && $is_zero_initialized) { - push @$cmd, "zeroinit:$dst_path"; - } else { - push @$cmd, $dst_path; + print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n"; } - my $parser = sub { - my $line = shift; - if($line =~ m/\((\S+)\/100\%\)/){ - my $percent = $1; - my $transferred = int($size * $percent / 100); - my $remaining = $size - $transferred; - - print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n"; - } - - }; + }; - eval { run_command($cmd, timeout => undef, outfunc => $parser); }; - my $err = $@; - die "copy failed: $err" if $err; - } + eval { run_command($cmd, timeout => undef, outfunc => $parser); }; + my $err = $@; + die "copy failed: $err" if $err; } sub qemu_img_format { @@ -7133,14 +7167,6 @@ sub clone_disk { print "create full clone of drive $drivename ($drive->{file})\n"; my $name = undef; - if (drive_is_cloudinit($drive)) { - $name = "vm-$newvmid-cloudinit"; - $snapname = undef; - # we only get here if it's supported by QEMU_FORMAT_RE, so just accept - if ($dst_format ne 'raw') { - $name .= ".$dst_format"; - } - } $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024)); push @$newvollist, $newvolid; @@ -7258,15 +7284,12 @@ sub create_efidisk($$$$$) { my (undef, $ovmf_vars) = get_ovmf_files($arch); die "EFI vars default image not found\n" if ! -f $ovmf_vars; - my $vars_size = PVE::Tools::convert_size(-s $ovmf_vars, 'b' => 'kb'); + my $vars_size_b = -s $ovmf_vars; + my $vars_size = PVE::Tools::convert_size($vars_size_b, 'b' => 'kb'); my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size); PVE::Storage::activate_volumes($storecfg, [$volid]); - my $path = PVE::Storage::path($storecfg, $volid); - eval { - run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $ovmf_vars, $path]); - }; - die "Copying EFI vars image failed: $@" if $@; + qemu_img_convert($ovmf_vars, $volid, $vars_size_b, undef, 0); return ($volid, $vars_size); }