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 $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 => {
keyboard => {
optional => 1,
type => 'string',
- description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.conf' configuration file.",
+ description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.conf' configuration file.".
+ "It should not be necessary to set it.",
enum => PVE::Tools::kvmkeymaplist(),
- default => 'en-us',
+ default => undef,
},
name => {
optional => 1,
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',
description => "Select BIOS implementation.",
default => 'seabios',
},
+ 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).",
+ },
+ hostname => {
+ optional => 1,
+ description => "cloud-init: Hostname to use instead of the vm-name + search-domain.",
+ type => 'string', format => 'dns-name',
+ maxLength => 255,
+ },
};
# what about other qemu settings ?
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->{"ipconfig$i"} = $ipconfigdesc;
}
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,
},
+ replicate => {
+ type => 'boolean',
+ description => 'Whether the drive should considered for replication jobs.',
+ optional => 1,
+ default => 1,
+ },
+ rerror => {
+ type => 'string',
+ enum => [qw(ignore report stop)],
+ description => 'Read error action.',
+ optional => 1,
+ },
werror => {
type => 'string',
enum => [qw(enospc ignore report stop)],
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,
- }
-);
-
-my %rerror_fmt = (
- rerror => {
- type => 'string',
- enum => [qw(ignore report stop)],
- description => 'Read error action.',
- 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,
+ }
);
my %iothread_fmt = ( iothread => {
$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,
- %rerror_fmt,
%model_fmt,
};
PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
my $sata_fmt = {
%drivedesc_base,
- %rerror_fmt,
};
my $satadesc = {
optional => 1,
my $virtio_fmt = {
%drivedesc_base,
%iothread_fmt,
- %rerror_fmt,
};
my $virtiodesc = {
optional => 1,
my $alldrive_fmt = {
%drivedesc_base,
- %rerror_fmt,
%iothread_fmt,
%model_fmt,
%queues_fmt,
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'],
}
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};
+ $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"}) {
+ $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";
+ }
+ }
+
if (my $serial = $drive->{serial}) {
$serial = URI::Escape::uri_unescape($serial);
$opts .= ",serial=$serial";
$opts .= ",format=$format" if $format && !$drive->{format};
- foreach my $o (qw(bps bps_rd bps_wr)) {
- my $v = $drive->{"m$o"};
- $opts .= ",$o=" . int($v*1024*1024) if $v;
- }
-
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 {
- my ($drive) = @_;
+ my ($drive, $exclude_cloudinit) = @_;
+
+ return 0 if $exclude_cloudinit && $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
$res->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
}
+ $res->{macaddr} = PVE::Tools::random_ether_addr() if !defined($res->{macaddr});
+ return $res;
+}
+
+# ipconfigX ip=cidr,gw=ip,ip6=cidr,gw6=ip
+sub parse_ipconfig {
+ my ($data) = @_;
+
+ my $res = eval { PVE::JSONSchema::parse_property_string($ipconfig_fmt, $data) };
+ if ($@) {
+ warn $@;
+ return undef;
+ }
+
+ if ($res->{gw} && !$res->{ip}) {
+ warn 'gateway specified without specifying an IP address';
+ return undef;
+ }
+ if ($res->{gw6} && !$res->{ip6}) {
+ warn 'IPv6 gateway specified without specifying an IPv6 address';
+ return undef;
+ }
+ if ($res->{gw} && $res->{ip} eq 'dhcp') {
+ warn 'gateway specified together with DHCP';
+ return undef;
+ }
+ if ($res->{gw6} && $res->{ip6} !~ /^$IPV6RE/) {
+ # gw6 + auto/dhcp
+ warn "IPv6 gateway specified together with $res->{ip6} address";
+ return undef;
+ }
+
+ if (!$res->{ip} && !$res->{ip6}) {
+ return { ip => 'dhcp', ip6 => 'dhcp' };
+ }
+
return $res;
}
sub vmconfig_register_unused_drive {
my ($storecfg, $vmid, $conf, $drive) = @_;
- if (!drive_is_cdrom($drive)) {
+ if (!drive_is_cdrom($drive, 1)) {
my $volid = $drive->{file};
if (vm_is_volid_owner($storecfg, $vmid, $volid)) {
PVE::QemuConfig->add_unused_volume($conf, $volid, $vmid);
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 $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;
}
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)
# 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;
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);
} 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);
'startup' => 1,
'description' => 1,
'protection' => 1,
+ 'vmstatestorage' => 1,
};
# hotplug changes in [PENDING]
$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
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',
print "migration listens on $migrate_uri\n" if $migrate_uri;
- if ($statefile && $statefile ne 'tcp') {
+ if ($statefile && $statefile ne 'tcp' && $statefile ne 'unix') {
eval { vm_mon_cmd_nocheck($vmid, "cont"); };
warn $@ if $@;
}
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;
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);
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);
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;
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);
print "$job: transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
}
- $readycounter++ if $running_mirror_jobs->{$job}->{ready} eq 'true';
+ $readycounter++ if $running_mirror_jobs->{$job}->{ready};
}
last if scalar(keys %$jobs) == 0;
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));
+ $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, undef, ($size/1024));
push @$newvollist, $newvolid;
PVE::Storage::activate_volumes($storecfg, [$newvolid]);
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;
}
my $maxdev = 0;
- if ($conf->{scsihw} && ($conf->{scsihw} =~ m/^lsi/)) {
+ if (!$conf->{scsihw} || ($conf->{scsihw} =~ m/^lsi/)) {
$maxdev = 7;
} elsif ($conf->{scsihw} && ($conf->{scsihw} eq 'virtio-scsi-single')) {
$maxdev = 1;
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 {
return $res;
}
-sub nbd_stop {
- my ($vmid) = @_;
-
- vm_mon_cmd($vmid, 'nbd-server-stop');
-}
-
1;