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 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
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 {
Opteron_G3 => 'AuthenticAMD',
Opteron_G4 => 'AuthenticAMD',
Opteron_G5 => 'AuthenticAMD',
+ EPYC => 'AuthenticAMD',
+ 'EPYC-IBPB' => 'AuthenticAMD',
# generic types, use vendor from host node
host => 'default',
max => 'default',
};
-my $cpu_flag = qr/[+-](pcid|spec-ctrl)/;
+my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb)/;
my $cpu_fmt = {
cputype => {
flags => {
description => "List of additional CPU flags separated by ';'."
. " Use '+FLAG' to enable, '-FLAG' to disable a flag."
- . " Currently supported flags: 'pcid', 'spec-ctrl'.",
+ . " 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)*/,
};
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 $vga_fmt = {
+ type => {
+ description => "Select the VGA type.",
+ type => 'string',
+ default => 'std',
+ optional => 1,
+ default_key => 1,
+ enum => [qw(cirrus qxl qxl2 qxl3 qxl4 serial0 serial1 serial2 serial3 std virtio vmware)],
+ },
+ memory => {
+ description => "Sets the VGA memory (in MiB). Has no effect with serial display.",
+ type => 'integer',
+ optional => 1,
+ minimum => 4,
+ maximum => 512,
+ },
+};
+
my $confdesc = {
onboot => {
optional => 1,
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 => undef,
},
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,
},
vga => {
optional => 1,
- type => 'string',
- description => "Select the VGA type.",
- verbose_description => "Select the VGA type. If you want to use high resolution" .
- " modes (>= 1280x1024x16) then you should use the options " .
- "'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and " .
- "'cirrus' for other OS types. The 'qxl' option enables the SPICE " .
- "display sever. For win* OS you can select how many independent " .
- "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)],
+ type => 'string', format => $vga_fmt,
+ description => "Configure the VGA hardware.",
+ verbose_description => "Configure the VGA Hardware. If you want to use ".
+ "high resolution modes (>= 1280x1024x16) you may need to increase " .
+ "the vga memory option. Since QEMU 2.9 the default VGA display type " .
+ "is 'std' for all OS types besides some Windows versions (XP and " .
+ "older) which use 'cirrus'. The 'qxl' option enables the SPICE " .
+ "display server. For win* OS you can select how many independent " .
+ "displays you want, Linux guests can add displays them self.\n".
+ "You can also run without any graphic card, using a serial device as terminal.",
},
watchdog => {
optional => 1,
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',
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 ?
my $MAX_SATA_DISKS = 6;
my $MAX_USB_DEVICES = 5;
my $MAX_NETS = 32;
-my $MAX_UNUSED_DISKS = 8;
+my $MAX_UNUSED_DISKS = 256;
my $MAX_HOSTPCI_DEVICES = 4;
my $MAX_SERIAL_PORTS = 4;
my $MAX_PARALLEL_PORTS = 3;
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);
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,
}
);
},
);
+my %ssd_fmt = (
+ ssd => {
+ type => 'boolean',
+ description => "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
+ optional => 1,
+ },
+);
+
my $add_throttle_desc = sub {
my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
my $d = {
my $ide_fmt = {
%drivedesc_base,
%model_fmt,
+ %ssd_fmt,
};
PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
%iothread_fmt,
%queues_fmt,
%scsiblock_fmt,
+ %ssd_fmt,
};
my $scsidesc = {
optional => 1,
my $sata_fmt = {
%drivedesc_base,
+ %ssd_fmt,
};
my $satadesc = {
optional => 1,
%model_fmt,
%queues_fmt,
%scsiblock_fmt,
+ %ssd_fmt,
};
my $efidisk_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|/|;
$device = "scsi-$devicetype,bus=$controller_prefix$controller.0,channel=0,scsi-id=0,lun=$drive->{index},drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
}
- } elsif ($drive->{interface} eq 'ide'){
- $maxdev = 2;
+ if ($drive->{ssd} && ($devicetype eq 'block' || $devicetype eq 'hd')) {
+ $device .= ",rotation_rate=1";
+ }
+
+ } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') {
+ my $maxdev = ($drive->{interface} eq 'sata') ? $MAX_SATA_DISKS : 2;
my $controller = int($drive->{index} / $maxdev);
my $unit = $drive->{index} % $maxdev;
my $devicetype = ($drive->{media} && $drive->{media} eq 'cdrom') ? "cd" : "hd";
- $device = "ide-$devicetype,bus=ide.$controller,unit=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
- if ($devicetype eq 'hd' && (my $model = $drive->{model})) {
- $model = URI::Escape::uri_unescape($model);
- $device .= ",model=$model";
+ $device = "ide-$devicetype";
+ if ($drive->{interface} eq 'ide') {
+ $device .= ",bus=ide.$controller,unit=$unit";
+ } else {
+ $device .= ",bus=ahci$controller.$unit";
+ }
+ $device .= ",drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
+
+ if ($devicetype eq 'hd') {
+ if (my $model = $drive->{model}) {
+ $model = URI::Escape::uri_unescape($model);
+ $device .= ",model=$model";
+ }
+ if ($drive->{ssd}) {
+ $device .= ",rotation_rate=1";
+ }
}
- } elsif ($drive->{interface} eq 'sata'){
- my $controller = int($drive->{index} / $MAX_SATA_DISKS);
- my $unit = $drive->{index} % $MAX_SATA_DISKS;
- $device = "ide-drive,bus=ahci$controller.$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
} elsif ($drive->{interface} eq 'usb') {
die "implement me";
# -device ide-drive,bus=ide.1,unit=0,drive=drive-ide0-1-0,id=ide0-1-0
$device .= ",bootindex=$drive->{bootindex}" if $drive->{bootindex};
+ if (my $serial = $drive->{serial}) {
+ $serial = URI::Escape::uri_unescape($serial);
+ $device .= ",serial=$serial";
+ }
+
+
return $device;
}
}
}
- 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;
return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
}
-sub drive_is_cdrom {
+my $vga_map = {
+ 'cirrus' => 'cirrus-vga',
+ 'std' => 'VGA',
+ 'vmware' => 'vmware-svga',
+ 'virtio' => 'virtio-vga',
+};
+
+sub print_vga_device {
+ my ($conf, $vga, $id, $qxlnum, $bridges) = @_;
+
+ my $type = $vga_map->{$vga->{type}};
+ my $vgamem_mb = $vga->{memory};
+ if ($qxlnum) {
+ $type = $id ? 'qxl' : 'qxl-vga';
+ }
+ die "no devicetype for $vga->{type}\n" if !$type;
+
+ my $memory = "";
+ if ($vgamem_mb) {
+ if ($vga->{type} eq 'virtio') {
+ my $bytes = PVE::Tools::convert_size($vgamem_mb, "mb" => "b");
+ $memory = ",max_hostmem=$bytes";
+ } elsif ($qxlnum) {
+ # from https://www.spice-space.org/multiple-monitors.html
+ $memory = ",vgamem_mb=$vga->{memory}";
+ my $ram = $vgamem_mb * 4;
+ my $vram = $vgamem_mb * 2;
+ $memory .= ",ram_size_mb=$ram,vram_size_mb=$vram";
+ } else {
+ $memory = ",vgamem_mb=$vga->{memory}";
+ }
+ } elsif ($qxlnum && $id) {
+ $memory = ",ram_size=67108864,vram_size=33554432";
+ }
+
+ my $q35 = machine_type_is_q35($conf);
+ my $vgaid = "vga" . ($id // '');
+ my $pciaddr;
+
+ if ($q35 && $vgaid eq 'vga') {
+ # the first display uses pcie.0 bus on q35 machines
+ $pciaddr = print_pcie_addr($vgaid, $bridges);
+ } else {
+ $pciaddr = print_pci_addr($vgaid, $bridges);
+ }
+
+ return "$type,id=${vgaid}${memory}${pciaddr}";
+}
+
+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 $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;
+}
+
+sub parse_vga {
+ my ($value) = @_;
+
+ return {} if !$value;
+ my $res = eval { PVE::JSONSchema::parse_property_string($vga_fmt, $value) };
+ warn $@ if $@;
+ return $res;
+}
+
PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
sub verify_usb_device {
my ($value, $noerr) = @_;
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) = @_;
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;
}
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
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?
my $volhash = {};
my $test_volid = sub {
- my ($volid, $is_cdrom, $replicate, $snapname) = @_;
+ my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_;
return if !$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);
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}}) {
$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);
});
}
sub vga_conf_has_spice {
my ($vga) = @_;
- return 0 if !$vga || $vga !~ m/^qxl([234])?$/;
+ my $vgaconf = parse_vga($vga);
+ my $vgatype = $vgaconf->{type};
+ return 0 if !$vgatype || $vgatype !~ m/^qxl([234])?$/;
return $1 || 1;
}
push @$cmd, '-id', $vmid;
+ my $vmname = $conf->{name} || "vm$vmid";
+
+ push @$cmd, '-name', $vmname;
+
my $use_virtio = 0;
my $qmpsocket = qmp_socket($vmid);
push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server,nowait";
push @$cmd, '-mon', "chardev=qmp,mode=control";
+ if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 12)) {
+ my $eventsocket = qmp_socket($vmid, 0, 'event');
+ push @$cmd, '-chardev', "socket,id=qmp-event,path=$eventsocket,server,nowait";
+ push @$cmd, '-mon', "chardev=qmp-event,mode=control";
+ }
push @$cmd, '-pidfile' , pidfile_name($vmid);
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;
# add usb controllers
my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $q35, $usbdesc->{format}, $MAX_USB_DEVICES);
push @$devices, @usbcontrollers if @usbcontrollers;
- my $vga = $conf->{vga};
+ my $vga = parse_vga($conf->{vga});
- my $qxlnum = vga_conf_has_spice($vga);
- $vga = 'qxl' if $qxlnum;
+ my $qxlnum = vga_conf_has_spice($conf->{vga});
+ $vga->{type} = 'qxl' if $qxlnum;
- if (!$vga) {
+ if (!$vga->{type}) {
if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
- $vga = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
+ $vga->{type} = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
} else {
- $vga = ($winversion >= 6) ? 'std' : 'cirrus';
+ $vga->{type} = ($winversion >= 6) ? 'std' : 'cirrus';
}
}
} else {
$tablet = $defaults->{tablet};
$tablet = 0 if $qxlnum; # disable for spice because it is not needed
- $tablet = 0 if $vga =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card)
+ $tablet = 0 if $vga->{type} =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card)
}
push @$devices, '-device', print_tabletdevice_full($conf) if $tablet;
if ($d->{'x-vga'}) {
$xvga = ',x-vga=on';
$kvm_off = 1;
- $vga = 'none';
+ $vga->{type} = 'none';
$gpu_passthrough = 1;
if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
}
}
- my $vmname = $conf->{name} || "vm$vmid";
-
- push @$cmd, '-name', $vmname;
my $sockets = 1;
$sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
push @$cmd, '-no-reboot' if defined($conf->{reboot}) && $conf->{reboot} == 0;
- push @$cmd, '-vga', $vga if $vga && $vga !~ m/^serial\d+$/; # for kvm 77 and later
-
- if ($vga && $vga !~ m/^serial\d+$/ && $vga ne 'none'){
+ if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none'){
+ push @$devices, '-device', print_vga_device($conf, $vga, undef, $qxlnum, $bridges);
my $socket = vnc_socket($vmid);
push @$cmd, '-vnc', "unix:$socket,x509,password";
} else {
+ push @$cmd, '-vga', 'none' if $vga->{type} eq 'none';
push @$cmd, '-nographic';
}
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";
if ($qxlnum > 1) {
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', print_vga_device($conf, $vga, $i, $qxlnum, $bridges);
}
} else {
# assume other OS works like Linux
- push @$cmd, '-global', 'qxl-vga.ram_size=134217728';
- push @$cmd, '-global', 'qxl-vga.vram_size=67108864';
+ my ($ram, $vram) = ("134217728", "67108864");
+ if ($vga->{memory}) {
+ $ram = PVE::Tools::convert_size($qxlnum*4*$vga->{memory}, 'mb' => 'b');
+ $vram = PVE::Tools::convert_size($qxlnum*2*$vga->{memory}, 'mb' => 'b');
+ }
+ push @$cmd, '-global', "qxl-vga.ram_size=$ram";
+ push @$cmd, '-global', "qxl-vga.vram_size=$vram";
}
}
}
sub qmp_socket {
- my ($vmid, $qga) = @_;
+ my ($vmid, $qga, $name) = @_;
my $sockettype = $qga ? 'qga' : 'qmp';
- return "${var_run_tmpdir}/$vmid.$sockettype";
+ my $ext = $name ? '-'.$name : '';
+ return "${var_run_tmpdir}/$vmid$ext.$sockettype";
}
sub pidfile_name {
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) = @_;
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);
}
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);
}
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}
}
$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 {
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";
}
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);
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);
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);
}
});
}
} 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);
my ($vmid, $conf, $volid_hash) = @_;
my $changes;
+ my $prefix = "VM $vmid:";
# used and unused disks
my $referenced = {};
if ($new ne $conf->{$opt}) {
$changes = 1;
$conf->{$opt} = $new;
+ print "$prefix update disk '$opt' information.\n";
}
}
}
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};
}
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)
}
}
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 {
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)) {
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$$";
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;
}
};
- 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;
}
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;
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";
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);
}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 ($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]);
$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 {
$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;
if ($winversion >= 7) {
push @$cpuFlags , 'hv_relaxed';
+
+ if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 12)) {
+ push @$cpuFlags , 'hv_synic';
+ push @$cpuFlags , 'hv_stimer';
+ }
}
}
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();
+}
+
+sub nbd_stop {
+ my ($vmid) = @_;
+
+ vm_mon_cmd($vmid, 'nbd-server-stop');
}
# bash completion helper
return $res;
}
-sub nbd_stop {
- my ($vmid) = @_;
-
- vm_mon_cmd($vmid, 'nbd-server-stop');
-}
-
1;