X-Git-Url: https://git.proxmox.com/?p=qemu-server.git;a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=277bc58309e3ae6d741854448f3169eacc1bee90;hp=ffbb436951b24dfc0a211c2438668361861094a4;hb=c2786bedc637c3cfa2702803c0ea0b75c2a03257;hpb=ec82e3eee4cc96d6c841056f16acfa14871739c3 diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index ffbb436..277bc58 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -22,7 +22,7 @@ use PVE::SafeSyslog; use Storable qw(dclone); 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); +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 PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file); use PVE::INotify; @@ -33,17 +33,21 @@ use PVE::RPCEnvironment; 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 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}; my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); +my $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/; + # Note about locking: we use flock on the config file protect # against concurent actions. # Aditionaly, we have a 'lock' setting in the config file. This @@ -138,6 +142,8 @@ my $cpu_vendor_list = { Opteron_G3 => 'AuthenticAMD', Opteron_G4 => 'AuthenticAMD', Opteron_G5 => 'AuthenticAMD', + EPYC => 'AuthenticAMD', + 'EPYC-IBPB' => 'AuthenticAMD', # generic types, use vendor from host node host => 'default', @@ -258,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, @@ -266,7 +272,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, @@ -534,6 +540,41 @@ EODESCR }, }; +my $confdesc_cloudinit = { + citype => { + optional => 1, + type => 'string', + 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 => { + optional => 1, + type => 'string', + description => "cloud-init: User name to change ssh keys and password for instead of the image's configured default user.", + }, + 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.', + }, + searchdomain => { + optional => 1, + type => 'string', + description => "cloud-init: Sets DNS search domains for a container. Create will automatically use the setting from the host if neither searchdomain nor nameserver are set.", + }, + nameserver => { + optional => 1, + type => 'string', format => 'address-list', + description => "cloud-init: Sets DNS server IP address for a container. Create will automatically use the setting from the host if neither searchdomain nor nameserver are set.", + }, + sshkeys => { + optional => 1, + type => 'string', + format => 'urlencoded', + description => "cloud-init: Setup public SSH keys (one key per line, OpenSSH format).", + }, +}; + # what about other qemu settings ? #cpu => 'string', #machine => 'string', @@ -691,8 +732,64 @@ my $netdesc = { PVE::JSONSchema::register_standard_option("pve-qm-net", $netdesc); +my $ipconfig_fmt = { + ip => { + type => 'string', + format => 'pve-ipv4-config', + format_description => 'IPv4Format/CIDR', + description => 'IPv4 address in CIDR format.', + optional => 1, + default => 'dhcp', + }, + gw => { + type => 'string', + format => 'ipv4', + format_description => 'GatewayIPv4', + description => 'Default gateway for IPv4 traffic.', + optional => 1, + requires => 'ip', + }, + ip6 => { + type => 'string', + format => 'pve-ipv6-config', + format_description => 'IPv6Format/CIDR', + description => 'IPv6 address in CIDR format.', + optional => 1, + default => 'dhcp', + }, + gw6 => { + type => 'string', + format => 'ipv6', + format_description => 'GatewayIPv6', + description => 'Default gateway for IPv6 traffic.', + optional => 1, + requires => 'ip6', + }, +}; +PVE::JSONSchema::register_format('pve-qm-ipconfig', $ipconfig_fmt); +my $ipconfigdesc = { + optional => 1, + type => 'string', format => 'pve-qm-ipconfig', + description => <<'EODESCR', +cloud-init: Specify IP addresses and gateways for the corresponding interface. + +IP addresses use CIDR notation, gateways are optional but need an IP of the same type specified. + +The special string 'dhcp' can be used for IP addresses to use DHCP, in which case no explicit gateway should be provided. +For IPv6 the special string 'auto' can be used to use stateless autoconfiguration. + +If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using dhcp on IPv4. +EODESCR +}; +PVE::JSONSchema::register_standard_option("pve-qm-ipconfig", $netdesc); + for (my $i = 0; $i < $MAX_NETS; $i++) { $confdesc->{"net$i"} = $netdesc; + $confdesc_cloudinit->{"ipconfig$i"} = $ipconfigdesc; +} + +foreach my $key (keys %$confdesc_cloudinit) { + $confdesc->{$key} = $confdesc_cloudinit->{$key}; } PVE::JSONSchema::register_format('pve-volume-id-or-qm-path', \&verify_volume_id_or_qm_path); @@ -1275,7 +1372,7 @@ sub get_iso_path { sub filename_to_volume_id { my ($vmid, $file, $media) = @_; - if (!($file eq 'none' || $file eq 'cdrom' || + if (!($file eq 'none' || $file eq 'cdrom' || $file =~ m|^/dev/.+| || $file =~ m/^([^:]+):(.+)$/)) { return undef if $file =~ m|/|; @@ -1592,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; } @@ -1664,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; @@ -1800,8 +1898,15 @@ sub print_cpu_device { return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0"; } -sub drive_is_cdrom { +sub drive_is_cloudinit { my ($drive) = @_; + return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@; +} + +sub drive_is_cdrom { + my ($drive, $exclude_cloudinit) = @_; + + return 0 if $exclude_cloudinit && drive_is_cloudinit($drive); return $drive && $drive->{media} && ($drive->{media} eq 'cdrom'); @@ -1871,6 +1976,41 @@ sub parse_net { return $res; } +# ipconfigX ip=cidr,gw=ip,ip6=cidr,gw6=ip +sub parse_ipconfig { + my ($data) = @_; + + my $res = eval { PVE::JSONSchema::parse_property_string($ipconfig_fmt, $data) }; + if ($@) { + warn $@; + return undef; + } + + if ($res->{gw} && !$res->{ip}) { + warn 'gateway specified without specifying an IP address'; + return undef; + } + if ($res->{gw6} && !$res->{ip6}) { + warn 'IPv6 gateway specified without specifying an IPv6 address'; + return undef; + } + if ($res->{gw} && $res->{ip} eq 'dhcp') { + warn 'gateway specified together with DHCP'; + return undef; + } + if ($res->{gw6} && $res->{ip6} !~ /^$IPV6RE/) { + # gw6 + auto/dhcp + warn "IPv6 gateway specified together with $res->{ip6} address"; + return undef; + } + + if (!$res->{ip} && !$res->{ip6}) { + return { ip => 'dhcp', ip6 => 'dhcp' }; + } + + return $res; +} + sub print_net { my $net = shift; @@ -1939,7 +2079,10 @@ sub vmconfig_undelete_pending_option { sub vmconfig_register_unused_drive { my ($storecfg, $vmid, $conf, $drive) = @_; - if (!drive_is_cdrom($drive)) { + if (drive_is_cloudinit($drive)) { + eval { PVE::Storage::vdisk_free($storecfg, $drive->{file}) }; + warn $@ if $@; + } elsif (!drive_is_cdrom($drive)) { my $volid = $drive->{file}; if (vm_is_volid_owner($storecfg, $vmid, $volid)) { PVE::QemuConfig->add_unused_volume($conf, $volid, $vmid); @@ -2090,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) = @_; @@ -2173,7 +2322,7 @@ sub destroy_vm { foreach_drive($conf, sub { my ($ds, $drive) = @_; - return if drive_is_cdrom($drive); + return if drive_is_cdrom($drive, 1); my $volid = $drive->{file}; @@ -2278,7 +2427,7 @@ sub parse_vm_config { } else { warn "vm $vmid - propertry 'delete' is only allowed in [PENDING]\n"; } - } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) { + } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) { my $key = $1; my $value = $2; eval { $value = check_type($key, $value); }; @@ -2425,9 +2574,6 @@ sub load_defaults { } } - my $conf = PVE::Cluster::cfs_read_file('datacenter.cfg'); - $res->{keyboard} = $conf->{keyboard} if $conf->{keyboard}; - return $res; } @@ -2945,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); @@ -3101,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 @@ -3240,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}; @@ -3456,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'); @@ -3642,10 +3788,6 @@ sub vm_deviceunplug { } elsif ($deviceid =~ m/^(scsi)(\d+)$/) { - #qemu 2.3 segfault on drive_del with virtioscsi + iothread - my $device = parse_drive($deviceid, $conf->{$deviceid}); - die "virtioscsi with iothread is not hot-unplugglable currently" if $device->{iothread}; - qemu_devicedel($vmid, $deviceid); qemu_drivedel($vmid, $deviceid); qemu_deletescsihw($conf, $vmid, $deviceid); @@ -4024,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) = @_; @@ -4230,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+)$/) { @@ -4262,6 +4332,22 @@ sub vmconfig_hotplug_pending { } } + my $apply_pending_cloudinit; + $apply_pending_cloudinit = sub { + my ($key, $value) = @_; + $apply_pending_cloudinit = sub {}; # once is enough + + my @cloudinit_opts = keys %$confdesc_cloudinit; + foreach my $opt (keys %{$conf->{pending}}) { + next if !grep { $_ eq $opt } @cloudinit_opts; + $conf->{$opt} = delete $conf->{pending}->{$opt}; + } + + my $new_conf = { %$conf }; + $new_conf->{$key} = $value; + PVE::QemuServer::Cloudinit::generate_cloudinitconfig($new_conf, $vmid); + }; + foreach my $opt (keys %{$conf->{pending}}) { next if $selection && !$selection->{$opt}; my $value = $conf->{pending}->{$opt}; @@ -4303,6 +4389,10 @@ sub vmconfig_hotplug_pending { $vmid, $opt, $value); } elsif (is_valid_drivename($opt)) { # some changes can be done without hotplug + my $drive = parse_drive($opt, $value); + if (drive_is_cloudinit($drive)) { + &$apply_pending_cloudinit($opt, $value); + } vmconfig_update_disk($storecfg, $conf, $hotplug_features->{disk}, $vmid, $opt, $value, 1); } elsif ($opt =~ m/^memory$/) { #dimms @@ -4565,6 +4655,9 @@ sub vmconfig_update_disk { if ($drive->{file} eq 'none') { vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt"); + if (drive_is_cloudinit($old_drive)) { + vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive); + } } else { my $path = get_iso_path($storecfg, $vmid, $drive->{file}); vm_mon_cmd($vmid, "eject", force => JSON::true,device => "drive-$opt"); # force eject if locked @@ -4600,6 +4693,8 @@ sub vm_start { $conf = PVE::QemuConfig->load_config($vmid); # update/reload } + PVE::QemuServer::Cloudinit::generate_cloudinitconfig($conf, $vmid); + my $defaults = load_defaults(); # set environment variable useful inside network script @@ -4725,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} @@ -4748,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 { @@ -4757,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; @@ -4772,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 = $@) { @@ -4826,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+$/; @@ -4878,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"; } @@ -5080,6 +5170,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); @@ -5087,10 +5184,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); } }); } @@ -5549,21 +5646,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$$"; @@ -5583,7 +5708,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; @@ -5599,6 +5724,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 = {}; @@ -5637,17 +5764,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"); @@ -5690,14 +5824,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; @@ -5710,7 +5850,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; @@ -5763,8 +5903,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 = $@; @@ -5776,7 +5916,6 @@ sub restore_vma_archive { push @$vollist, $volid if $volid; } - my $cfg = PVE::Storage::config(); PVE::Storage::deactivate_volumes($cfg, $vollist); unlink $mapfifo; @@ -5963,11 +6102,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; @@ -6017,6 +6156,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"; @@ -6045,7 +6186,7 @@ sub qemu_img_convert { sub qemu_img_format { my ($scfg, $volname) = @_; - if ($scfg->{path} && $volname =~ m/\.(raw|cow|qcow|qcow2|qed|vmdk|cloop)$/) { + if ($scfg->{path} && $volname =~ m/\.($QEMU_FORMAT_RE)$/) { return $1; } else { return "raw"; @@ -6061,32 +6202,9 @@ sub qemu_drive_mirror { my $format; $jobs->{"drive-$drive"} = {}; - if ($dst_volid =~ /^nbd:(localhost|[\d\.]+|\[[\d\.:a-fA-F]+\]):(\d+):exportname=(\S+)/) { - my $server = $1; - my $port = $2; - my $exportname = $3; - + if ($dst_volid =~ /^nbd:/) { + $qemu_target = $dst_volid; $format = "nbd"; - my $unixsocket = "/run/qemu-server/$vmid.mirror-drive-$drive"; - $qemu_target = "nbd+unix:///$exportname?socket=$unixsocket"; - my $cmd = ['socat', '-T30', "UNIX-LISTEN:$unixsocket,fork", "TCP:$server:$2,connect-timeout=5"]; - - my $pid = fork(); - if (!defined($pid)) { - die "forking socat tunnel failed\n"; - } elsif ($pid == 0) { - exec(@$cmd); - warn "exec failed: $!\n"; - POSIX::_exit(-1); - } - $jobs->{"drive-$drive"}->{pid} = $pid; - - my $timeout = 0; - while (!-S $unixsocket) { - die "nbd connection helper timed out\n" - if $timeout++ > 5; - sleep 1; - } } else { my $storecfg = PVE::Storage::config(); my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid); @@ -6198,7 +6316,6 @@ sub qemu_drive_mirror_monitor { }else { print "$job: Completed successfully.\n"; $jobs->{$job}->{complete} = 1; - eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ; } } } @@ -6236,7 +6353,6 @@ sub qemu_blockjobs_cancel { if (defined($jobs->{$job}->{cancel}) && !defined($running_jobs->{$job})) { print "$job: Done.\n"; - eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ; delete $jobs->{$job}; } } @@ -6247,25 +6363,6 @@ sub qemu_blockjobs_cancel { } } -sub qemu_blockjobs_finish_tunnel { - my ($vmid, $job, $cpid) = @_; - - return if !$cpid; - - for (my $i = 1; $i < 20; $i++) { - my $waitpid = waitpid($cpid, WNOHANG); - last if (defined($waitpid) && ($waitpid == $cpid)); - - if ($i == 10) { - kill(15, $cpid); - } elsif ($i >= 15) { - kill(9, $cpid); - } - sleep (1); - } - unlink "/run/qemu-server/$vmid.mirror-$job"; -} - sub clone_disk { my ($storecfg, $vmid, $running, $drivename, $drive, $snapname, $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga) = @_; @@ -6285,7 +6382,17 @@ sub clone_disk { my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3); print "create full clone of drive $drivename ($drive->{file})\n"; - $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, undef, ($size/1024)); + my $name = undef; + if (drive_is_cloudinit($drive)) { + $name = "vm-$newvmid-cloudinit"; + # cloudinit only supports raw and qcow2 atm: + if ($dst_format eq 'qcow2') { + $name .= '.qcow2'; + } elsif ($dst_format ne 'raw') { + die "clone: unhandled format for cloudinit image\n"; + } + } + $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024)); push @$newvollist, $newvolid; PVE::Storage::activate_volumes($storecfg, [$newvolid]); @@ -6366,13 +6473,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;