X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=648a5f8096f4f779882760463fb615e538c84c6b;hb=51153f86ce68572e522a8c474c2106028e95ae5d;hp=6237cbc0adaa4b4e5e0bbc46ec2a6c72618ca6af;hpb=7b42f951424daba842889d241a69cf18a5119f3e;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 6237cbc..648a5f8 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -38,8 +38,9 @@ use Time::HiRes qw(gettimeofday); use File::Copy qw(copy); use URI::Escape; -my $OVMF_CODE = '/usr/share/kvm/OVMF_CODE-pure-efi.fd'; -my $OVMF_VARS = '/usr/share/kvm/OVMF_VARS-pure-efi.fd'; +my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/'; +my $OVMF_CODE = "$EDK2_FW_BASE/OVMF_CODE.fd"; +my $OVMF_VARS = "$EDK2_FW_BASE/OVMF_VARS.fd"; my $qemu_snap_storage = {rbd => 1, sheepdog => 1}; @@ -263,7 +264,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, @@ -543,7 +544,7 @@ my $confdesc_cloudinit = { citype => { optional => 1, type => 'string', - description => 'Specifies the cloud-init configuration format.', + description => 'Specifies the cloud-init configuration format. The default depends on the configured operating system type (`ostype`. We use the `nocloud` format for Linux, and `configdrive2` for windows.', enum => ['configdrive2', 'nocloud'], }, ciuser => { @@ -554,8 +555,7 @@ my $confdesc_cloudinit = { cipassword => { optional => 1, type => 'string', - description => 'cloud-init: Password to assign the user. Using this is generally not recommended. Use ssh keys instead. ' - . 'Also note that older cloud-init versions do not support hashed passwords.', + description => 'cloud-init: Password to assign the user. Using this is generally not recommended. Use ssh keys instead. Also note that older cloud-init versions do not support hashed passwords.', }, searchdomain => { optional => 1, @@ -571,14 +571,7 @@ my $confdesc_cloudinit = { optional => 1, type => 'string', format => 'urlencoded', - description => "cloud-init : Setup public SSH keys (one key per line, " . - "OpenSSH format).", - }, - hostname => { - optional => 1, - description => "cloud-init: Hostname to use instead of the vm-name + search-domain.", - type => 'string', format => 'dns-name', - maxLength => 255, + description => "cloud-init: Setup public SSH keys (one key per line, OpenSSH format).", }, }; @@ -1696,6 +1689,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; } @@ -1768,11 +1767,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; @@ -1979,7 +1973,6 @@ sub parse_net { my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg'); $res->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix}); } - $res->{macaddr} = PVE::Tools::random_ether_addr() if !defined($res->{macaddr}); return $res; } @@ -2240,6 +2233,12 @@ sub json_config_properties { return $prop; } +# return copy of $confdesc_cloudinit to generate documentation +sub cloudinit_config_properties { + + return dclone($confdesc_cloudinit); +} + sub check_type { my ($key, $value) = @_; @@ -2575,9 +2574,6 @@ sub load_defaults { } } - my $conf = PVE::Cluster::cfs_read_file('datacenter.cfg'); - $res->{keyboard} = $conf->{keyboard} if $conf->{keyboard}; - return $res; } @@ -3095,6 +3091,10 @@ sub config_to_command { push @$cmd, '-id', $vmid; + my $vmname = $conf->{name} || "vm$vmid"; + + push @$cmd, '-name', $vmname; + my $use_virtio = 0; my $qmpsocket = qmp_socket($vmid); @@ -3251,9 +3251,6 @@ sub config_to_command { } } - my $vmname = $conf->{name} || "vm$vmid"; - - push @$cmd, '-name', $vmname; my $sockets = 1; $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused @@ -3390,9 +3387,7 @@ sub config_to_command { push @$cmd, '-S' if $conf->{freeze}; - # set keyboard layout - my $kb = $conf->{keyboard} || $defaults->{keyboard}; - push @$cmd, '-k', $kb if $kb; + push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard}); # enable sound #my $soundhw = $conf->{soundhw} || $defaults->{soundhw}; @@ -3606,21 +3601,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'); @@ -4170,81 +4166,6 @@ sub __read_avail { return $res; } -# old code, only used to shutdown old VM after update -sub vm_monitor_command { - my ($vmid, $cmdstr, $nocheck) = @_; - - my $res; - - eval { - die "VM $vmid not running\n" if !check_running($vmid, $nocheck); - - my $sname = "${var_run_tmpdir}/$vmid.mon"; - - my $sock = IO::Socket::UNIX->new( Peer => $sname ) || - die "unable to connect to VM $vmid socket - $!\n"; - - my $timeout = 3; - - # hack: migrate sometime blocks the monitor (when migrate_downtime - # is set) - if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) { - $timeout = 60*60; # 1 hour - } - - # read banner; - my $data = __read_avail($sock, $timeout); - - if ($data !~ m/^QEMU\s+(\S+)\s+monitor\s/) { - die "got unexpected qemu monitor banner\n"; - } - - my $sel = new IO::Select; - $sel->add($sock); - - if (!scalar(my @ready = $sel->can_write($timeout))) { - die "monitor write error - timeout"; - } - - my $fullcmd = "$cmdstr\r"; - - # syslog('info', "VM $vmid monitor command: $cmdstr"); - - my $b; - if (!($b = $sock->syswrite($fullcmd)) || ($b != length($fullcmd))) { - die "monitor write error - $!"; - } - - return if ($cmdstr eq 'q') || ($cmdstr eq 'quit'); - - $timeout = 20; - - if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) { - $timeout = 60*60; # 1 hour - } elsif ($cmdstr =~ m/^(eject|change)/) { - $timeout = 60; # note: cdrom mount command is slow - } - if ($res = __read_avail($sock, $timeout)) { - - my @lines = split("\r?\n", $res); - - shift @lines if $lines[0] !~ m/^unknown command/; # skip echo - - $res = join("\n", @lines); - $res .= "\n"; - } - }; - - my $err = $@; - - if ($err) { - syslog("err", "VM $vmid monitor command failed - $err"); - die $err; - } - - return $res; -} - sub qemu_block_resize { my ($vmid, $deviceid, $storecfg, $volid, $size) = @_; @@ -4376,7 +4297,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+)$/) { @@ -4896,10 +4820,11 @@ sub vm_start { PVE::Storage::activate_volumes($storecfg, $vollist); - if (!check_running($vmid, 1) && -d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") { - my $cmd = []; - push @$cmd, '/bin/systemctl', 'stop', "$vmid.scope"; - eval { run_command($cmd); }; + if (!check_running($vmid, 1)) { + eval { + run_command(['/bin/systemctl', 'stop', "$vmid.scope"], + outfunc => sub {}, errfunc => sub {}); + }; } my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits} @@ -4919,6 +4844,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::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties); + run_command($cmd, %run_params); + }; + }; + if ($conf->{hugepages}) { my $code = sub { @@ -4928,11 +4860,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; @@ -4943,10 +4871,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 = $@) { @@ -4997,10 +4922,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+$/; @@ -5049,10 +4972,6 @@ sub vm_qmp_command { my $qmpclient = PVE::QMPClient->new(); $res = $qmpclient->cmd($vmid, $cmd, $timeout); - } elsif (-e "${var_run_tmpdir}/$vmid.mon") { - die "can't execute complex command on old monitor - stop/start your vm to fix the problem\n" - if scalar(%{$cmd->{arguments}}); - vm_monitor_command($vmid, $cmd->{execute}, $nocheck); } else { die "unable to open monitor socket\n"; } @@ -5720,21 +5639,49 @@ sub rescan { sub restore_vma_archive { my ($archive, $vmid, $user, $opts, $comp) = @_; - my $input = $archive eq '-' ? "<&STDIN" : undef; my $readfrom = $archive; - my $uncomp = ''; - if ($comp) { + my $cfg = PVE::Storage::config(); + my $commands = []; + my $bwlimit = $opts->{bwlimit}; + + my $dbg_cmdstring = ''; + my $add_pipe = sub { + my ($cmd) = @_; + push @$commands, $cmd; + $dbg_cmdstring .= ' | ' if length($dbg_cmdstring); + $dbg_cmdstring .= PVE::Tools::cmd2string($cmd); $readfrom = '-'; - my $qarchive = PVE::Tools::shellquote($archive); + }; + + my $input = undef; + if ($archive eq '-') { + $input = '<&STDIN'; + } else { + # If we use a backup from a PVE defined storage we also consider that + # storage's rate limit: + my (undef, $volid) = PVE::Storage::path_to_volume_id($cfg, $archive); + if (defined($volid)) { + my ($sid, undef) = PVE::Storage::parse_volume_id($volid); + my $readlimit = PVE::Storage::get_bandwidth_limit('restore', [$sid], $bwlimit); + if ($readlimit) { + print STDERR "applying read rate limit: $readlimit\n"; + my $cstream = ['cstream', '-t', $readlimit*1024, '--', $readfrom]; + $add_pipe->($cstream); + } + } + } + + if ($comp) { + my $cmd; if ($comp eq 'gzip') { - $uncomp = "zcat $qarchive|"; + $cmd = ['zcat', $readfrom]; } elsif ($comp eq 'lzop') { - $uncomp = "lzop -d -c $qarchive|"; + $cmd = ['lzop', '-d', '-c', $readfrom]; } else { die "unknown compression method '$comp'\n"; } - + $add_pipe->($cmd); } my $tmpdir = "/var/tmp/vzdumptmp$$"; @@ -5754,7 +5701,7 @@ sub restore_vma_archive { open($fifofh, '>', $mapfifo) || die $!; }; - my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir"; + $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]); my $oldtimeout; my $timeout = 5; @@ -5770,6 +5717,8 @@ sub restore_vma_archive { my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid); my $oldconf = PVE::Cluster::cfs_read_file($cfs_path); + my %storage_limits; + my $print_devmap = sub { my $virtdev_hash = {}; @@ -5808,17 +5757,24 @@ sub restore_vma_archive { $rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']); } + $storage_limits{$storeid} = $bwlimit; + $virtdev_hash->{$virtdev} = $devinfo->{$devname}; } } + foreach my $key (keys %storage_limits) { + my $limit = PVE::Storage::get_bandwidth_limit('restore', [$key], $bwlimit); + next if !$limit; + print STDERR "rate limit for storage $key: $limit KiB/s\n"; + $storage_limits{$key} = $limit * 1024; + } + foreach my $devname (keys %$devinfo) { die "found no device mapping information for device '$devname'\n" if !$devinfo->{$devname}->{virtdev}; } - my $cfg = PVE::Storage::config(); - # create empty/temp config if ($oldconf) { PVE::Tools::file_set_contents($conffile, "memory: 128\n"); @@ -5861,14 +5817,20 @@ sub restore_vma_archive { foreach my $virtdev (sort keys %$virtdev_hash) { my $d = $virtdev_hash->{$virtdev}; my $alloc_size = int(($d->{size} + 1024 - 1)/1024); - my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid}); + my $storeid = $d->{storeid}; + my $scfg = PVE::Storage::storage_config($cfg, $storeid); + + my $map_opts = ''; + if (my $limit = $storage_limits{$storeid}) { + $map_opts .= "throttling.bps=$limit:throttling.group=$storeid:"; + } # test if requested format is supported - my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $d->{storeid}); + my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $storeid); my $supported = grep { $_ eq $d->{format} } @$validFormats; $d->{format} = $defFormat if !$supported; - my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid, + my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid, $d->{format}, undef, $alloc_size); print STDERR "new volume ID is '$volid'\n"; $d->{volid} = $volid; @@ -5881,7 +5843,7 @@ sub restore_vma_archive { $write_zeros = 0; } - print $fifofh "format=$d->{format}:${write_zeros}:$d->{devname}=$path\n"; + print $fifofh "${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\n"; print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n"; $map->{$virtdev} = $volid; @@ -5934,8 +5896,8 @@ sub restore_vma_archive { } }; - print "restore vma archive: $cmd\n"; - run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo); + print "restore vma archive: $dbg_cmdstring\n"; + run_command($commands, input => $input, outfunc => $parser, afterfork => $openfifo); }; my $err = $@; @@ -5947,7 +5909,6 @@ sub restore_vma_archive { push @$vollist, $volid if $volid; } - my $cfg = PVE::Storage::config(); PVE::Storage::deactivate_volumes($cfg, $vollist); unlink $mapfifo; @@ -6188,6 +6149,8 @@ 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' 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"; @@ -6503,13 +6466,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; @@ -6764,4 +6722,10 @@ sub complete_storage { return $res; } +sub nbd_stop { + my ($vmid) = @_; + + vm_mon_cmd($vmid, 'nbd-server-stop'); +} + 1;