X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=628ca3359cf11140ebb1d1242dabfb1e8e1c9aba;hb=1d1c4e1c1ccab6be9ce60c82c696617a5458ec01;hp=2e822f37dae88991aae3fb4509edff9e5bd898f2;hpb=44c2a647ff8fbfb3da1287393eb95633836f6360;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 2e822f3..628ca33 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,6 +33,7 @@ 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; @@ -44,6 +45,8 @@ 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 @@ -108,17 +111,28 @@ my $cpu_vendor_list = { coreduo => 'GenuineIntel', core2duo => 'GenuineIntel', Conroe => 'GenuineIntel', - Penryn => 'GenuineIntel', + Penryn => 'GenuineIntel', Nehalem => 'GenuineIntel', + 'Nehalem-IBRS' => 'GenuineIntel', Westmere => 'GenuineIntel', + 'Westmere-IBRS' => 'GenuineIntel', SandyBridge => 'GenuineIntel', + 'SandyBridge-IBRS' => 'GenuineIntel', IvyBridge => 'GenuineIntel', + 'IvyBridge-IBRS' => 'GenuineIntel', Haswell => 'GenuineIntel', + 'Haswell-IBRS' => 'GenuineIntel', 'Haswell-noTSX' => 'GenuineIntel', + 'Haswell-noTSX-IBRS' => 'GenuineIntel', Broadwell => 'GenuineIntel', + 'Broadwell-IBRS' => 'GenuineIntel', 'Broadwell-noTSX' => 'GenuineIntel', + 'Broadwell-noTSX-IBRS' => 'GenuineIntel', 'Skylake-Client' => 'GenuineIntel', - + 'Skylake-Client-IBRS' => 'GenuineIntel', + 'Skylake-Server' => 'GenuineIntel', + 'Skylake-Server-IBRS' => 'GenuineIntel', + # AMD CPUs athlon => 'AuthenticAMD', phenom => 'AuthenticAMD', @@ -127,6 +141,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', @@ -134,8 +150,11 @@ my $cpu_vendor_list = { kvm64 => 'default', qemu32 => 'default', qemu64 => 'default', + max => 'default', }; +my $cpu_flag = qr/[+-](pcid|spec-ctrl)/; + my $cpu_fmt = { cputype => { description => "Emulated CPU type.", @@ -150,6 +169,15 @@ my $cpu_fmt = { optional => 1, default => 0 }, + flags => { + description => "List of additional CPU flags separated by ';'." + . " Use '+FLAG' to enable, '-FLAG' to disable a flag." + . " Currently supported flags: 'pcid', 'spec-ctrl'.", + format_description => '+FLAG[;-FLAG...]', + type => 'string', + pattern => qr/$cpu_flag(;$cpu_flag)*/, + optional => 1, + }, }; my $watchdog_fmt = { @@ -243,9 +271,10 @@ 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.conf' configuration file.". + "It should not be necessary to set it.", enum => PVE::Tools::kvmkeymaplist(), - default => 'en-us', + default => undef, }, name => { optional => 1, @@ -510,6 +539,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', @@ -667,8 +731,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); @@ -729,7 +849,9 @@ my %drivedesc_base = ( }, snapshot => { type => 'boolean', - description => "Whether the drive should be included when making snapshots.", + description => "Controls qemu's snapshot mode feature." + . " If activated, changes made to the disk are temporary and will" + . " be discarded when the VM is shutdown.", optional => 1, }, cache => { @@ -793,6 +915,13 @@ my %drivedesc_base = ( maxLength => 20*3, # *3 since it's %xx url enoded description => "The drive's reported serial number, url-encoded, up to 20 bytes long.", optional => 1, + }, + shared => { + type => 'boolean', + description => 'Mark this locally-managed volume as available on all nodes', + verbose_description => "Mark this locally-managed volume as available on all nodes.\n\nWARNING: This option does not share the volume automatically, it assumes it is shared already!", + optional => 1, + default => 0, } ); @@ -1242,7 +1371,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|/|; @@ -1598,10 +1727,17 @@ sub print_drive_full { } my $opts = ''; - my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard); + my @qemu_drive_options = qw(heads secs cyls trans media format cache rerror werror aio discard); foreach my $o (@qemu_drive_options) { - $opts .= ",$o=$drive->{$o}" if $drive->{$o}; + $opts .= ",$o=$drive->{$o}" if defined($drive->{$o}); + } + + # snapshot only accepts on|off + if (defined($drive->{snapshot})) { + my $v = $drive->{snapshot} ? 'on' : 'off'; + $opts .= ",snapshot=$v"; } + foreach my $type (['', '-total'], [_rd => '-read'], [_wr => '-write']) { my ($dir, $qmpname) = @$type; if (my $v = $drive->{"mbps$dir"}) { @@ -1760,8 +1896,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'); @@ -1828,6 +1971,42 @@ 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; +} + +# 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; } @@ -1899,7 +2078,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); @@ -2050,6 +2232,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) = @_; @@ -2133,7 +2321,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}; @@ -2238,7 +2426,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); }; @@ -2590,6 +2778,8 @@ sub vmstatus { my $storecfg = PVE::Storage::config(); my $list = vzlist(); + my $defaults = load_defaults(); + my ($uptime) = PVE::ProcFSTools::read_proc_uptime(1); my $cpucount = $cpuinfo->{cpus} || 1; @@ -2615,16 +2805,19 @@ sub vmstatus { $d->{maxdisk} = 0; } - $d->{cpus} = ($conf->{sockets} || 1) * ($conf->{cores} || 1); + $d->{cpus} = ($conf->{sockets} || $defaults->{sockets}) + * ($conf->{cores} || $defaults->{cores}); $d->{cpus} = $cpucount if $d->{cpus} > $cpucount; $d->{cpus} = $conf->{vcpus} if $conf->{vcpus}; $d->{name} = $conf->{name} || "VM $vmid"; - $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024) : 0; + $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024) + : $defaults->{memory}*(1024*1024); if ($conf->{balloon}) { $d->{balloon_min} = $conf->{balloon}*(1024*1024); - $d->{shares} = defined($conf->{shares}) ? $conf->{shares} : 1000; + $d->{shares} = defined($conf->{shares}) ? $conf->{shares} + : $defaults->{shares}; } $d->{uptime} = 0; @@ -2639,6 +2832,8 @@ sub vmstatus { $d->{template} = PVE::QemuConfig->is_template($conf); + $d->{serial} = 1 if conf_has_serial($conf); + $res->{$vmid} = $d; } @@ -2797,7 +2992,7 @@ sub foreach_volid { my $volhash = {}; my $test_volid = sub { - my ($volid, $is_cdrom, $replicate, $snapname) = @_; + my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_; return if !$volid; @@ -2807,6 +3002,9 @@ sub foreach_volid { $volhash->{$volid}->{replicate} //= 0; $volhash->{$volid}->{replicate} = 1 if $replicate; + $volhash->{$volid}->{shared} //= 0; + $volhash->{$volid}->{shared} = 1 if $shared; + $volhash->{$volid}->{referenced_in_config} //= 0; $volhash->{$volid}->{referenced_in_config} = 1 if !defined($snapname); @@ -2816,7 +3014,7 @@ sub foreach_volid { foreach_drive($conf, sub { my ($ds, $drive) = @_; - $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, undef); + $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef); }); foreach my $snapname (keys %{$conf->{snapshots}}) { @@ -2824,7 +3022,7 @@ sub foreach_volid { $test_volid->($snap->{vmstate}, 0, 1, $snapname); foreach_drive($snap, sub { my ($ds, $drive) = @_; - $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $snapname); + $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, $snapname); }); } @@ -2833,6 +3031,18 @@ sub foreach_volid { } } +sub conf_has_serial { + my ($conf) = @_; + + for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) { + if ($conf->{"serial$i"}) { + return 1; + } + } + + return 0; +} + sub vga_conf_has_spice { my ($vga) = @_; @@ -2902,20 +3112,27 @@ sub config_to_command { die "uefi base image not found\n" if ! -f $OVMF_CODE; my $path; - my $format = 'raw'; + my $format; if (my $efidisk = $conf->{efidisk0}) { my $d = PVE::JSONSchema::parse_property_string($efidisk_fmt, $efidisk); my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1); + $format = $d->{format}; if ($storeid) { $path = PVE::Storage::path($storecfg, $d->{file}); + if (!defined($format)) { + my $scfg = PVE::Storage::storage_config($storecfg, $storeid); + $format = qemu_img_format($scfg, $volname); + } } else { $path = $d->{file}; + die "efidisk format must be specified\n" + if !defined($format); } - $format = $d->{format} if $d->{format}; } else { warn "no efidisk configured! Using temporary efivars disk.\n"; $path = "/tmp/$vmid-ovmf.fd"; PVE::Tools::file_copy($OVMF_VARS, $path, -s $OVMF_VARS); + $format = 'raw'; } push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$OVMF_CODE"; @@ -3130,6 +3347,10 @@ sub config_to_command { or die "Cannot parse cpu description: $cputype\n"; $cpu = $cpuconf->{cputype}; $kvm_off = 1 if $cpuconf->{hidden}; + + if (defined(my $flags = $cpuconf->{flags})) { + push @$cpuFlags, split(";", $flags); + } } push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64'; @@ -3569,10 +3790,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); @@ -4189,6 +4406,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}; @@ -4230,6 +4463,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 @@ -4492,6 +4729,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 @@ -4527,6 +4767,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 @@ -4661,7 +4903,8 @@ sub vm_start { my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits} : $defaults->{cpuunits}; - my %run_params = (timeout => $statefile ? undef : 30, umask => 0077); + my $start_timeout = $conf->{hugepages} ? 300 : 30; + my %run_params = (timeout => $statefile ? undef : $start_timeout, umask => 0077); my %properties = ( Slice => 'qemu.slice', @@ -5292,6 +5535,13 @@ sub restore_update_config_line { } else { print $outfd $line; } + } elsif (($line =~ m/^(smbios1: )(.*)/) && $unique) { + my ($uuid, $uuid_str); + UUID::generate($uuid); + UUID::unparse($uuid, $uuid_str); + my $smbios1 = parse_smbios1($2); + $smbios1->{uuid} = $uuid_str; + print $outfd $1.print_smbios1($smbios1)."\n"; } else { print $outfd $line; } @@ -5360,13 +5610,14 @@ sub update_disksize { my $changes; - my $used = {}; + # used and unused disks + my $referenced = {}; # Note: it is allowed to define multiple storages with same path (alias), so # we need to check both 'volid' and real 'path' (two different volid can point # to the same path). - my $usedpath = {}; + my $referencedpath = {}; # update size info foreach my $opt (keys %$conf) { @@ -5375,10 +5626,10 @@ sub update_disksize { my $volid = $drive->{file}; next if !$volid; - $used->{$volid} = 1; + $referenced->{$volid} = 1; if ($volid_hash->{$volid} && (my $path = $volid_hash->{$volid}->{path})) { - $usedpath->{$path} = 1; + $referencedpath->{$path} = 1; } next if drive_is_cdrom($drive); @@ -5398,21 +5649,24 @@ sub update_disksize { next if $opt !~ m/^unused\d+$/; my $volid = $conf->{$opt}; my $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid}; - if ($used->{$volid} || ($path && $usedpath->{$path})) { + if ($referenced->{$volid} || ($path && $referencedpath->{$path})) { $changes = 1; delete $conf->{$opt}; } + + $referenced->{$volid} = 1; + $referencedpath->{$path} = 1 if $path; } foreach my $volid (sort keys %$volid_hash) { next if $volid =~ m/vm-$vmid-state-/; - next if $used->{$volid}; + next if $referenced->{$volid}; my $path = $volid_hash->{$volid}->{path}; next if !$path; # just to be sure - next if $usedpath->{$path}; + next if $referencedpath->{$path}; $changes = 1; PVE::QemuConfig->add_unused_volume($conf, $volid); - $usedpath->{$path} = 1; # avoid to add more than once (aliases) + $referencedpath->{$path} = 1; # avoid to add more than once (aliases) } return $changes; @@ -5882,7 +6136,7 @@ sub qga_check_running { eval { vm_mon_cmd($vmid, "guest-ping", timeout => 3); }; if ($@) { - warn "Qemu Guest Agent are not running - $@"; + warn "Qemu Guest Agent is not running - $@"; return 0; } return 1; @@ -5960,7 +6214,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"; @@ -5976,32 +6230,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); @@ -6079,7 +6310,8 @@ sub qemu_drive_mirror_monitor { last if $skipcomplete; #do the complete later if ($vmiddst && $vmiddst != $vmid) { - if ($qga) { + my $agent_running = $qga && qga_check_running($vmid); + if ($agent_running) { print "freeze filesystem\n"; eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); }; } else { @@ -6090,7 +6322,7 @@ sub qemu_drive_mirror_monitor { # if we clone a disk for a new target vm, we don't switch the disk PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs); - if ($qga) { + if ($agent_running) { print "unfreeze filesystem\n"; eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); }; } else { @@ -6112,7 +6344,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}) } ; } } } @@ -6150,7 +6381,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}; } } @@ -6161,25 +6391,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) = @_; @@ -6199,7 +6410,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]); @@ -6541,10 +6762,4 @@ sub complete_storage { return $res; } -sub nbd_stop { - my ($vmid) = @_; - - vm_mon_cmd($vmid, 'nbd-server-stop'); -} - 1;