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;
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 $OVMF_IMG = '/usr/share/kvm/OVMF-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
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',
Opteron_G3 => 'AuthenticAMD',
Opteron_G4 => 'AuthenticAMD',
Opteron_G5 => 'AuthenticAMD',
+ EPYC => 'AuthenticAMD',
+ 'EPYC-IBPB' => 'AuthenticAMD',
# generic types, use vendor from host node
host => 'default',
kvm64 => 'default',
qemu32 => 'default',
qemu64 => 'default',
+ max => 'default',
};
+my $cpu_flag = qr/[+-](pcid|spec-ctrl)/;
+
my $cpu_fmt = {
cputype => {
description => "Emulated CPU type.",
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 = {
optional => 1,
type => 'integer',
description => "CPU weight for a VM.",
- verbose_description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
- minimum => 0,
- maximum => 500000,
+ verbose_description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.",
+ minimum => 2,
+ maximum => 262144,
default => 1024,
},
memory => {
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,
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 => 'en-us',
+ default => undef,
},
name => {
optional => 1,
w2k8;; Microsoft Windows 2008
wvista;; Microsoft Windows Vista
win7;; Microsoft Windows 7
-win8;; Microsoft Windows 8/2012
+win8;; Microsoft Windows 8/2012/2012r2
+win10;; Microsoft Windows 10/2016
l24;; Linux 2.4 Kernel
l26;; Linux 2.6/3.X Kernel
solaris;; Solaris/OpenSolaris/OpenIndiania kernel
type => 'string', format => 'pve-volume-id',
description => "Reference to a volume which stores the VM state. This is used internally for snapshots.",
},
+ vmstatestorage => get_standard_option('pve-storage-id', {
+ description => "Default storage for VM state volumes/files.",
+ optional => 1,
+ }),
machine => {
description => "Specific the Qemu machine type.",
type => 'string',
maxLength => 256,
optional => 1,
},
- replica => {
+ protection => {
optional => 1,
- description => "Storage replica for local storage.",
type => 'boolean',
+ description => "Sets the protection flag of the VM. This will disable the remove VM and remove disk operations.",
default => 0,
},
- replica_rate_limit => {
+ bios => {
optional => 1,
- description => "Storage replica rate limit in KBytes/s.",
- type => 'integer',
- minimum => 1,
+ type => 'string',
+ enum => [ qw(seabios ovmf) ],
+ description => "Select BIOS implementation.",
+ default => 'seabios',
},
- replica_interval => {
+};
+
+my $confdesc_cloudinit = {
+ citype => {
optional => 1,
- description => "Storage replica sync interval",
- type => 'integer',
- minimum => 1,
- maximum => 1440,
- default => 15,
+ 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'],
},
- replica_target => {
+ ciuser => {
optional => 1,
- description => "Storage replica taget node.",
type => 'string',
+ description => "cloud-init: User name to change ssh keys and password for instead of the image's configured default user.",
},
- protection => {
+ cipassword => {
optional => 1,
- type => 'boolean',
- description => "Sets the protection flag of the VM. This will disable the remove VM and remove disk operations.",
- default => 0,
+ 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.',
},
- bios => {
+ searchdomain => {
optional => 1,
type => 'string',
- enum => [ qw(seabios ovmf) ],
- description => "Select BIOS implementation.",
- default => 'seabios',
+ 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).",
},
};
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);
},
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 => {
description => "Whether the drive should be included when making backups.",
optional => 1,
},
- replica => {
+ replicate => {
type => 'boolean',
- description => 'Will include this drive to an storage replical job.',
+ description => 'Whether the drive should considered for replication jobs.',
optional => 1,
default => 1,
},
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,
}
);
$add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations per second');
# burst lengths
-$add_throttle_desc->('bps_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
-$add_throttle_desc->('bps_rd_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
-$add_throttle_desc->('bps_wr_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
-$add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
-$add_throttle_desc->('iops_rd_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
-$add_throttle_desc->('iops_wr_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
+
+# legacy support
+$drivedesc_base{'bps_rd_length'} = { alias => 'bps_rd_max_length' };
+$drivedesc_base{'bps_wr_length'} = { alias => 'bps_wr_max_length' };
+$drivedesc_base{'iops_rd_length'} = { alias => 'iops_rd_max_length' };
+$drivedesc_base{'iops_wr_length'} = { alias => 'iops_wr_max_length' };
my $ide_fmt = {
%drivedesc_base,
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|/|;
# can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
for my $requirement (
+ [mbps_max => 'mbps'],
+ [mbps_rd_max => 'mbps_rd'],
+ [mbps_wr_max => 'mbps_wr'],
+ [miops_max => 'miops'],
+ [miops_rd_max => 'miops_rd'],
+ [miops_wr_max => 'miops_wr'],
[bps_max_length => 'mbps_max'],
[bps_rd_max_length => 'mbps_rd_max'],
[bps_wr_max_length => 'mbps_wr_max'],
$device .= ",bootindex=$drive->{bootindex}" if $drive->{bootindex};
+ if (my $serial = $drive->{serial}) {
+ $serial = URI::Escape::uri_unescape($serial);
+ $device .= ",serial=$serial";
+ }
+
+
return $device;
}
}
my $opts = '';
- my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr iops_max iops_rd_max iops_wr_max);
+ 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};
- }
- if (my $serial = $drive->{serial}) {
- $serial = URI::Escape::uri_unescape($serial);
- $opts .= ",serial=$serial";
+ $opts .= ",$o=$drive->{$o}" if defined($drive->{$o});
}
- $opts .= ",format=$format" if $format && !$drive->{format};
+ # snapshot only accepts on|off
+ if (defined($drive->{snapshot})) {
+ my $v = $drive->{snapshot} ? 'on' : 'off';
+ $opts .= ",snapshot=$v";
+ }
- foreach my $o (qw(bps bps_rd bps_wr)) {
- my $v = $drive->{"m$o"};
- $opts .= ",$o=" . int($v*1024*1024) if $v;
+ foreach my $type (['', '-total'], [_rd => '-read'], [_wr => '-write']) {
+ my ($dir, $qmpname) = @$type;
+ if (my $v = $drive->{"mbps$dir"}) {
+ $opts .= ",throttling.bps$qmpname=".int($v*1024*1024);
+ }
+ if (my $v = $drive->{"mbps${dir}_max"}) {
+ $opts .= ",throttling.bps$qmpname-max=".int($v*1024*1024);
+ }
+ if (my $v = $drive->{"bps${dir}_max_length"}) {
+ $opts .= ",throttling.bps$qmpname-max-length=$v";
+ }
+ if (my $v = $drive->{"iops${dir}"}) {
+ $opts .= ",throttling.iops$qmpname=$v";
+ }
+ if (my $v = $drive->{"iops${dir}_max"}) {
+ $opts .= ",throttling.iops$qmpname-max=$v";
+ }
+ if (my $v = $drive->{"iops${dir}_max_length"}) {
+ $opts .= ",throttling.iops$qmpname-max-length=$v";
+ }
}
+ $opts .= ",format=$format" if $format && !$drive->{format};
+
my $cache_direct = 0;
if (my $cache = $drive->{cache}) {
sub print_cpu_device {
my ($conf, $id) = @_;
- my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
- my $cpu = $nokvm ? "qemu64" : "kvm64";
+ my $kvm = $conf->{kvm} // 1;
+ my $cpu = $kvm ? "kvm64" : "qemu64";
if (my $cputype = $conf->{cpu}) {
my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype)
or die "Cannot parse cpu description: $cputype\n";
$cpu = $cpuconf->{cputype};
}
- my $sockets = 1;
- $sockets = $conf->{sockets} if $conf->{sockets};
my $cores = $conf->{cores} || 1;
my $current_core = ($id - 1) % $cores;
- my $current_socket = int(($id - $current_core)/$cores);
+ my $current_socket = int(($id - 1 - $current_core)/$cores);
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');
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;
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);
return $prop;
}
+# return copy of $confdesc_cloudinit to generate documentation
+sub cloudinit_config_properties {
+
+ return dclone($confdesc_cloudinit);
+}
+
sub check_type {
my ($key, $value) = @_;
PVE::QemuConfig->check_lock($conf) if !$skiplock;
+ if ($conf->{template}) {
+ # check if any base image is still used by a linked clone
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+
+ return if drive_is_cdrom($drive);
+
+ my $volid = $drive->{file};
+
+ return if !$volid || $volid =~ m|^/|;
+
+ die "base volume '$volid' is still in use by linked cloned\n"
+ if PVE::Storage::volume_is_base_and_used($storecfg, $volid);
+
+ });
+ }
+
# only remove disks owned by this 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};
} 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); };
}
}
- my $conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
- $res->{keyboard} = $conf->{keyboard} if $conf->{keyboard};
-
return $res;
}
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;
$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;
$d->{template} = PVE::QemuConfig->is_template($conf);
+ $d->{serial} = 1 if conf_has_serial($conf);
+
$res->{$vmid} = $d;
}
my $volhash = {};
my $test_volid = sub {
- my ($volid, $is_cdrom) = @_;
+ my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_;
return if !$volid;
- $volhash->{$volid} = $is_cdrom || 0;
+ $volhash->{$volid}->{cdrom} //= 1;
+ $volhash->{$volid}->{cdrom} = 0 if !$is_cdrom;
+
+ $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);
+
+ $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1
+ if defined($snapname);
};
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
- &$test_volid($drive->{file}, drive_is_cdrom($drive));
+ $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef);
});
foreach my $snapname (keys %{$conf->{snapshots}}) {
my $snap = $conf->{snapshots}->{$snapname};
- &$test_volid($snap->{vmstate}, 0);
+ $test_volid->($snap->{vmstate}, 0, 1, $snapname);
foreach_drive($snap, sub {
my ($ds, $drive) = @_;
- &$test_volid($drive->{file}, drive_is_cdrom($drive));
+ $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, $snapname);
});
}
}
}
+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) = @_;
my $vernum = 0; # unknown
my $ostype = $conf->{ostype};
my $winversion = windows_version($ostype);
+ my $kvm = $conf->{kvm} // 1;
+
+ die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n" if (!$cpuinfo->{hvm} && $kvm);
if ($kvmver =~ m/^(\d+)\.(\d+)$/) {
$vernum = $1*1000000+$2*1000;
push @$cmd, '-id', $vmid;
+ my $vmname = $conf->{name} || "vm$vmid";
+
+ push @$cmd, '-name', $vmname;
+
my $use_virtio = 0;
my $qmpsocket = qmp_socket($vmid);
}
if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
- my $ovmfbase;
-
- # prefer the OVMF_CODE variant
- if (-f $OVMF_CODE) {
- $ovmfbase = $OVMF_CODE;
- } elsif (-f $OVMF_IMG) {
- $ovmfbase = $OVMF_IMG;
- }
-
- die "no uefi base img found\n" if !$ovmfbase;
- push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$ovmfbase";
+ die "uefi base image not found\n" if ! -f $OVMF_CODE;
- if (defined($conf->{efidisk0}) && ($ovmfbase eq $OVMF_CODE)) {
- my $d = PVE::JSONSchema::parse_property_string($efidisk_fmt, $conf->{efidisk0});
- my $format = $d->{format} // 'raw';
- my $path;
+ my $path;
+ 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});
- my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
- $format = qemu_img_format($scfg, $volname);
+ if (!defined($format)) {
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ $format = qemu_img_format($scfg, $volname);
+ }
} else {
$path = $d->{file};
- $format = "raw";
+ die "efidisk format must be specified\n"
+ if !defined($format);
}
- push @$cmd, '-drive', "if=pflash,unit=1,id=drive-efidisk0,format=$format,file=$path";
- } elsif ($ovmfbase eq $OVMF_CODE) {
- warn "using uefi without permanent efivars disk\n";
- my $ovmfvar_dst = "/tmp/$vmid-ovmf.fd";
- PVE::Tools::file_copy($OVMF_VARS, $ovmfvar_dst, 256*1024);
- push @$cmd, '-drive', "if=pflash,unit=1,format=raw,file=$ovmfvar_dst";
} else {
- # if the base img is not OVMF_CODE, we do not have to bother
- # to create/use a vars image, since it will not be used anyway
- # this can only happen if someone manually deletes the OVMF_CODE image
- # or has an old pve-qemu-kvm version installed.
- # both should not happen, but we ignore it here
+ 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";
+ push @$cmd, '-drive', "if=pflash,unit=1,format=$format,id=drive-efidisk0,file=$path";
}
$vga = 'qxl' if $qxlnum;
if (!$vga) {
- $vga = $winversion >= 6 ? 'std' : 'cirrus';
+ if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
+ $vga = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
+ } else {
+ $vga = ($winversion >= 6) ? 'std' : 'cirrus';
+ }
}
# enable absolute mouse coordinates (needed by vnc)
}
}
- my $vmname = $conf->{name} || "vm$vmid";
-
- push @$cmd, '-name', $vmname;
my $sockets = 1;
$sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
# time drift fix
my $tdf = defined($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf};
- my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
my $useLocaltime = $conf->{localtime};
if ($winversion >= 5) { # windows
push @$rtcFlags, 'driftfix=slew' if $tdf;
- if ($nokvm) {
+ if (!$kvm) {
push @$machineFlags, 'accel=tcg';
- } else {
- die "No accelerator found!\n" if !$cpuinfo->{hvm};
}
if ($machine_type) {
push @$rtcFlags, 'base=localtime';
}
- my $cpu = $nokvm ? "qemu64" : "kvm64";
+ my $cpu = $kvm ? "kvm64" : "qemu64";
if (my $cputype = $conf->{cpu}) {
my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype)
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';
if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
- push @$cpuFlags , '+kvm_pv_unhalt' if !$nokvm;
- push @$cpuFlags , '+kvm_pv_eoi' if !$nokvm;
+ push @$cpuFlags , '+kvm_pv_unhalt' if $kvm;
+ push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
}
- add_hyperv_enlighments($cpuFlags, $winversion, $machine_type, $kvmver, $nokvm, $conf->{bios}, $gpu_passthrough);
+ add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough) if $kvm;
- push @$cpuFlags, 'enforce' if $cpu ne 'host' && !$nokvm;
+ push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm;
push @$cpuFlags, 'kvm=off' if $kvm_off;
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};
my $nodename = PVE::INotify::nodename();
my $pfamily = PVE::Tools::get_host_address_family($nodename);
- $spice_port = PVE::Tools::next_spice_port($pfamily);
+ my @nodeaddrs = PVE::Tools::getaddrinfo_all('localhost', family => $pfamily);
+ die "failed to get an ip address of type $pfamily for 'localhost'\n" if !@nodeaddrs;
+ my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
+ $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
- push @$devices, '-spice', "tls-port=${spice_port},addr=localhost,tls-ciphers=HIGH,seamless-migration=on";
+ push @$devices, '-spice', "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
push @$devices, '-device', "virtio-serial,id=spice$pciaddr";
push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent";
push @$vollist, $drive->{file};
}
+ # ignore efidisk here, already added in bios/fw handling code above
+ return if $drive->{interface} eq 'efidisk';
+
$use_virtio = 1 if $ds =~ m/^virtio/;
if (drive_is_cdrom ($drive)) {
$ahcicontroller->{$controller}=1;
}
- if ($drive->{interface} eq 'efidisk') {
- # this will be added somewhere else
- return;
- }
-
my $drive_cmd = print_drive_full($storecfg, $vmid, $drive);
push @$devices, '-drive',$drive_cmd;
push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges);
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');
} 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);
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) = @_;
'startup' => 1,
'description' => 1,
'protection' => 1,
+ 'vmstatestorage' => 1,
};
# hotplug changes in [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+)$/) {
}
}
+ 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};
$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
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
$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
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',
}
$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 {
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;
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 = $@) {
}
} 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+$/;
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";
}
my $vollist = [];
foreach_volid($conf, sub {
- my ($volid, $is_cdrom) = @_;
+ my ($volid, $attr) = @_;
return if $volid =~ m|^/|;
} 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;
}
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) {
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);
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;
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$$";
rmtree $tmpdir;
# disable interrupts (always do cleanups)
- local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
- warn "got interrupt - ignored\n";
- };
+ local $SIG{INT} =
+ local $SIG{TERM} =
+ local $SIG{QUIT} =
+ local $SIG{HUP} = sub { warn "got interrupt - ignored\n"; };
my $mapfifo = "/var/tmp/vzdumptmp$$.fifo";
POSIX::mkfifo($mapfifo, 0600);
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;
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 = {};
$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");
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;
$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;
eval {
# enable interrupts
- local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
- die "interrupted by signal\n";
- };
+ local $SIG{INT} =
+ local $SIG{TERM} =
+ local $SIG{QUIT} =
+ local $SIG{HUP} =
+ local $SIG{PIPE} = sub { die "interrupted by signal\n"; };
local $SIG{ALRM} = sub { die "got timeout\n"; };
$oldtimeout = alarm($timeout);
}
};
- 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 = $@;
push @$vollist, $volid if $volid;
}
- my $cfg = PVE::Storage::config();
PVE::Storage::deactivate_volumes($cfg, $vollist);
unlink $mapfifo;
my $tmpfn = "$conffile.$$.tmp";
# disable interrupts (always do cleanups)
- local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
- print STDERR "got interrupt - ignored\n";
- };
+ local $SIG{INT} =
+ local $SIG{TERM} =
+ local $SIG{QUIT} =
+ local $SIG{HUP} = sub { print STDERR "got interrupt - ignored\n"; };
eval {
# enable interrupts
- local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
- die "interrupted by signal\n";
- };
+ local $SIG{INT} =
+ local $SIG{TERM} =
+ local $SIG{QUIT} =
+ local $SIG{HUP} =
+ local $SIG{PIPE} = sub { die "interrupted by signal\n"; };
if ($archive eq '-') {
print "extracting archive from STDIN\n";
warn $@ if $@;
};
-sub foreach_writable_storage {
+sub foreach_storage_used_by_vm {
my ($conf, $func) = @_;
my $sidhash = {};
- foreach my $ds (keys %$conf) {
- next if !is_valid_drivename($ds);
-
- my $drive = parse_drive($ds, $conf->{$ds});
- next if !$drive;
- next if drive_is_cdrom($drive);
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+ return if drive_is_cdrom($drive);
my $volid = $drive->{file};
my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
$sidhash->{$sid} = $sid if $sid;
- }
+ });
foreach my $sid (sort keys %$sidhash) {
&$func($sid);
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;
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";
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";
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);
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 {
# 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 {
}else {
print "$job: Completed successfully.\n";
$jobs->{$job}->{complete} = 1;
- eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ;
}
}
}
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};
}
}
}
}
-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) = @_;
my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
$storeid = $storage if $storage;
- my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
- if (!$format) {
- my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
- $format = qemu_img_format($scfg, $volname);
- }
-
- # test if requested format is supported - else use default
- my $supported = grep { $_ eq $format } @$validFormats;
- $format = $defFormat if !$supported;
-
+ my $dst_format = resolve_dst_disk_format($storecfg, $storeid, $volname, $format);
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, $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]);
$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;
return ($use_old_bios_files, $machine_type);
}
+sub create_efidisk {
+ my ($storecfg, $storeid, $vmid, $fmt) = @_;
+
+ die "EFI vars default image not found\n" if ! -f $OVMF_VARS;
+
+ my $vars_size = PVE::Tools::convert_size(-s $OVMF_VARS, 'b' => 'kb');
+ my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
+ PVE::Storage::activate_volumes($storecfg, [$volid]);
+
+ my $path = PVE::Storage::path($storecfg, $volid);
+ eval {
+ run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $OVMF_VARS, $path]);
+ };
+ die "Copying EFI vars image failed: $@" if $@;
+
+ return ($volid, $vars_size);
+}
+
sub lspci {
my $devices = {};
push @{$devices->{$id}}, $res;
});
+ # Entries should be sorted by functions.
+ foreach my $id (keys %$devices) {
+ my $dev = $devices->{$id};
+ $devices->{$id} = [ sort { $a->{function} <=> $b->{function} } @$dev ];
+ }
+
return $devices;
}
return ($maxdev, $controller, $controller_prefix);
}
-sub add_hyperv_enlighments {
- my ($cpuFlags, $winversion, $machine_type, $kvmver, $nokvm, $bios, $gpu_passthrough) = @_;
+sub add_hyperv_enlightenments {
+ my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough) = @_;
- return if $nokvm;
return if $winversion < 6;
return if $bios && $bios eq 'ovmf' && $winversion < 8;
return $winversion;
}
+sub resolve_dst_disk_format {
+ my ($storecfg, $storeid, $src_volname, $format) = @_;
+ my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
+
+ if (!$format) {
+ # if no target format is specified, use the source disk format as hint
+ if ($src_volname) {
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ $format = qemu_img_format($scfg, $src_volname);
+ } else {
+ return $defFormat;
+ }
+ }
+
+ # test if requested format is supported - else use default
+ my $supported = grep { $_ eq $format } @$validFormats;
+ $format = $defFormat if !$supported;
+ return $format;
+}
+
+sub resolve_first_disk {
+ my $conf = shift;
+ my @disks = PVE::QemuServer::valid_drive_names();
+ my $firstdisk;
+ foreach my $ds (reverse @disks) {
+ next if !$conf->{$ds};
+ my $disk = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
+ next if PVE::QemuServer::drive_is_cdrom($disk);
+ $firstdisk = $ds;
+ }
+ return $firstdisk;
+}
+
+sub generate_smbios1_uuid {
+ my ($uuid, $uuid_str);
+ UUID::generate($uuid);
+ UUID::unparse($uuid, $uuid_str);
+ return "uuid=$uuid_str";
+}
+
# bash completion helper
sub complete_backup_archives {