X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=541d7b099ed3cdea1c7de82aae785d999ea4523c;hb=29004a20ca4a9b75433e6a2cccbfd2fdea8cf790;hp=42c412a72e4aafc53cfbfbff37de7f48cb7f9b75;hpb=3618ee99aa4db4487b606ab90f6ac11ed9240245;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 42c412a..541d7b0 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,22 @@ 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 PVE::Systemd; 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 @@ -75,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 { @@ -108,17 +121,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 +151,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 +160,11 @@ my $cpu_vendor_list = { kvm64 => 'default', qemu32 => 'default', qemu64 => 'default', + max => 'default', }; +my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb)/; + my $cpu_fmt = { cputype => { description => "Emulated CPU type.", @@ -150,6 +179,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', 'ibpb', 'ssbd', 'virt-ssbd', 'amd-ssbd', 'amd-no-ssb', 'pdpe1gb'.", + format_description => '+FLAG[;-FLAG...]', + type => 'string', + pattern => qr/$cpu_flag(;$cpu_flag)*/, + optional => 1, + }, }; my $watchdog_fmt = { @@ -170,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, @@ -235,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, @@ -243,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, @@ -350,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, @@ -388,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, @@ -483,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', @@ -509,6 +559,59 @@ 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 = { + 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 ? @@ -668,8 +771,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); @@ -730,7 +889,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 => { @@ -794,6 +955,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, } ); @@ -1243,7 +1411,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|/|; @@ -1560,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; } @@ -1599,10 +1773,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"}) { @@ -1625,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; @@ -1761,8 +1937,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'); @@ -1832,6 +2015,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; @@ -1900,7 +2118,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); @@ -2028,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) = @_; @@ -2044,13 +2278,19 @@ 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}; } return $prop; } +# return copy of $confdesc_cloudinit to generate documentation +sub cloudinit_config_properties { + + return dclone($confdesc_cloudinit); +} + sub check_type { my ($key, $value) = @_; @@ -2134,7 +2374,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}; @@ -2239,7 +2479,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); }; @@ -2386,9 +2626,6 @@ sub load_defaults { } } - my $conf = PVE::Cluster::cfs_read_file('datacenter.cfg'); - $res->{keyboard} = $conf->{keyboard} if $conf->{keyboard}; - return $res; } @@ -2578,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 @@ -2603,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? @@ -2645,6 +2929,8 @@ sub vmstatus { $d->{template} = PVE::QemuConfig->is_template($conf); + $d->{serial} = 1 if conf_has_serial($conf); + $res->{$vmid} = $d; } @@ -2803,7 +3089,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; @@ -2813,6 +3099,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); @@ -2822,7 +3111,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}}) { @@ -2830,7 +3119,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); }); } @@ -2839,6 +3128,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) = @_; @@ -2889,6 +3190,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); @@ -2904,24 +3209,35 @@ 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; 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"; @@ -3038,9 +3354,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 @@ -3136,6 +3449,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'; @@ -3173,16 +3490,14 @@ 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}; #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"; @@ -3197,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 @@ -3389,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'); @@ -3575,10 +3891,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); @@ -3957,81 +4269,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) = @_; @@ -4051,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); } @@ -4062,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); } @@ -4163,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+)$/) { @@ -4195,6 +4445,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}; @@ -4236,6 +4502,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 @@ -4498,6 +4768,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 @@ -4533,6 +4806,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 @@ -4658,16 +4933,18 @@ 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} : $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', @@ -4680,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 { @@ -4689,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; @@ -4704,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 = $@) { @@ -4758,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+$/; @@ -4810,10 +5085,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"; } @@ -4936,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); @@ -5012,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); @@ -5019,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); } }); } @@ -5298,6 +5576,20 @@ 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); + 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; } @@ -5365,6 +5657,7 @@ sub update_disksize { my ($vmid, $conf, $volid_hash) = @_; my $changes; + my $prefix = "VM $vmid:"; # used and unused disks my $referenced = {}; @@ -5396,6 +5689,7 @@ sub update_disksize { if ($new ne $conf->{$opt}) { $changes = 1; $conf->{$opt} = $new; + print "$prefix update disk '$opt' information.\n"; } } } @@ -5406,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}; } @@ -5421,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) } @@ -5429,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 { @@ -5450,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)) { @@ -5474,21 +5777,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$$"; @@ -5508,7 +5839,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; @@ -5524,6 +5855,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 = {}; @@ -5562,17 +5895,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"); @@ -5615,14 +5955,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; @@ -5635,7 +5981,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; @@ -5688,8 +6034,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 = $@; @@ -5701,7 +6047,6 @@ sub restore_vma_archive { push @$vollist, $volid if $volid; } - my $cfg = PVE::Storage::config(); PVE::Storage::deactivate_volumes($cfg, $vollist); unlink $mapfifo; @@ -5888,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; @@ -5941,7 +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, '-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"; @@ -5970,7 +6317,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"; @@ -5986,32 +6333,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); @@ -6123,7 +6447,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}) } ; } } } @@ -6161,7 +6484,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}; } } @@ -6172,25 +6494,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) = @_; @@ -6210,7 +6513,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]); @@ -6281,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 { @@ -6291,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; @@ -6418,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'; + } } } @@ -6472,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