\&parse_vm_config,
\&write_vm_config);
-PVE::JSONSchema::register_standard_option('skiplock', {
- description => "Ignore locks - only root is allowed to use this option.",
- type => 'boolean',
- optional => 1,
-});
-
PVE::JSONSchema::register_standard_option('pve-qm-stateuri', {
description => "Some command save/restore state from this location.",
type => 'string',
maxLength => 40,
});
+PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
+ type => 'string',
+ enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
+ description => "The drive's backing file's data format.",
+ optional => 1,
+});
+
#no warnings 'redefine';
sub cgroups_write {
'Haswell-noTSX' => 'GenuineIntel',
Broadwell => 'GenuineIntel',
'Broadwell-noTSX' => 'GenuineIntel',
+ 'Skylake-Client' => 'GenuineIntel',
# AMD CPUs
athlon => 'AuthenticAMD',
description => "Emulated CPU type.",
type => 'string',
enum => [ sort { "\L$a" cmp "\L$b" } keys %$cpu_vendor_list ],
- format_description => 'cputype',
default => 'kvm64',
default_key => 1,
},
ostype => {
optional => 1,
type => 'string',
- enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26 solaris)],
+ enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 win10 l24 l26 solaris)],
description => "Specify guest operating system.",
verbose_description => <<EODESC,
Specify guest operating system. This is used to enable special
cdrom => {
optional => 1,
type => 'string', format => 'pve-qm-ide',
- typetext => 'volume',
+ typetext => '<volume>',
description => "This is an alias for option -ide2",
},
cpu => {
model => {
type => 'string',
description => "Network Card Model. The 'virtio' model provides the best performance with very low CPU overhead. If your guest does not support this driver, it is usually best to use 'e1000'.",
- format_description => 'model',
enum => $nic_model_list,
default_key => 1,
},
description => "The drive's cache mode",
optional => 1,
},
- format => {
- type => 'string',
- format_description => 'image format',
- enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
- description => "The drive's backing file's data format.",
- optional => 1,
- },
+ format => get_standard_option('pve-qm-image-format'),
size => {
type => 'string',
format => 'disk-size',
}
);
+my %scsiblock_fmt = (
+ scsiblock => {
+ type => 'boolean',
+ description => "whether to use scsi-block for full passthrough of host block device\n\nWARNING: can lead to I/O errors in combination with low memory or high memory fragmentation on host",
+ optional => 1,
+ default => 0,
+ },
+);
+
my $add_throttle_desc = sub {
- my ($key, $type, $what, $unit, $longunit) = @_;
- $drivedesc_base{$key} = {
+ my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
+ my $d = {
type => $type,
format_description => $unit,
- description => "Maximum $what speed in $longunit per second.",
+ description => "Maximum $what in $longunit.",
optional => 1,
};
+ $d->{minimum} = $minimum if defined($minimum);
+ $drivedesc_base{$key} = $d;
};
# throughput: (leaky bucket)
-$add_throttle_desc->('bps', 'integer', 'r/w speed', 'bps', 'bytes');
-$add_throttle_desc->('bps_rd', 'integer', 'read speed', 'bps', 'bytes');
-$add_throttle_desc->('bps_wr', 'integer', 'write speed', 'bps', 'bytes');
-$add_throttle_desc->('mbps', 'number', 'r/w speed', 'mbps', 'megabytes');
-$add_throttle_desc->('mbps_rd', 'number', 'read speed', 'mbps', 'megabytes');
-$add_throttle_desc->('mbps_wr', 'number', 'write speed', 'mbps', 'megabytes');
-$add_throttle_desc->('iops', 'integer', 'r/w I/O', 'iops', 'operations');
-$add_throttle_desc->('iops_rd', 'integer', 'read I/O', 'iops', 'operations');
-$add_throttle_desc->('iops_wr', 'integer', 'write I/O', 'iops', 'operations');
+$add_throttle_desc->('bps', 'integer', 'r/w speed', 'bps', 'bytes per second');
+$add_throttle_desc->('bps_rd', 'integer', 'read speed', 'bps', 'bytes per second');
+$add_throttle_desc->('bps_wr', 'integer', 'write speed', 'bps', 'bytes per second');
+$add_throttle_desc->('mbps', 'number', 'r/w speed', 'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_rd', 'number', 'read speed', 'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_wr', 'number', 'write speed', 'mbps', 'megabytes per second');
+$add_throttle_desc->('iops', 'integer', 'r/w I/O', 'iops', 'operations per second');
+$add_throttle_desc->('iops_rd', 'integer', 'read I/O', 'iops', 'operations per second');
+$add_throttle_desc->('iops_wr', 'integer', 'write I/O', 'iops', 'operations per second');
# pools: (pool of IO before throttling starts taking effect)
-$add_throttle_desc->('mbps_max', 'number', 'unthrottled r/w pool', 'mbps', 'megabytes');
-$add_throttle_desc->('mbps_rd_max', 'number', 'unthrottled read pool', 'mbps', 'megabytes');
-$add_throttle_desc->('mbps_wr_max', 'number', 'unthrottled write pool', 'mbps', 'megabytes');
-$add_throttle_desc->('iops_max', 'integer', 'unthrottled r/w I/O pool', 'iops', 'operations');
-$add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool', 'iops', 'operations');
-$add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations');
+$add_throttle_desc->('mbps_max', 'number', 'unthrottled r/w pool', 'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_rd_max', 'number', 'unthrottled read pool', 'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_wr_max', 'number', 'unthrottled write pool', 'mbps', 'megabytes per second');
+$add_throttle_desc->('iops_max', 'integer', 'unthrottled r/w I/O pool', 'iops', 'operations per second');
+$add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool', 'iops', 'operations per second');
+$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);
my $ide_fmt = {
%drivedesc_base,
%drivedesc_base,
%iothread_fmt,
%queues_fmt,
+ %scsiblock_fmt,
};
my $scsidesc = {
optional => 1,
%iothread_fmt,
%model_fmt,
%queues_fmt,
+ %scsiblock_fmt,
};
my $efidisk_fmt = {
format_description => 'volume',
description => "The drive's backing volume.",
},
- format => {
- type => 'string',
- format_description => 'image format',
- enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
- description => "The drive's backing file's data format.",
- optional => 1,
- },
+ format => get_standard_option('pve-qm-image-format'),
size => {
type => 'string',
format => 'disk-size',
optional => 1,
default => 1,
},
+ romfile => {
+ type => 'string',
+ pattern => '[^,;]+',
+ format_description => 'string',
+ description => "Custom pci device rom filename (must be located in /usr/share/kvm/).",
+ optional => 1,
+ },
pcie => {
type => 'boolean',
description => "Choose the PCI-express bus (needs the 'q35' machine model).",
wvista => 'Windows Vista',
win7 => 'Windows 7',
win8 => 'Windows 8/2012',
+ win10 => 'Windows 10/2016',
l24 => 'Linux 2.4',
l26 => 'Linux 2.6',
};
$res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
}
}
+
+ # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
+ for my $requirement (
+ [bps_max_length => 'mbps_max'],
+ [bps_rd_max_length => 'mbps_rd_max'],
+ [bps_wr_max_length => 'mbps_wr_max'],
+ [iops_max_length => 'iops_max'],
+ [iops_rd_max_length => 'iops_rd_max'],
+ [iops_wr_max_length => 'iops_wr_max']) {
+ my ($option, $requires) = @$requirement;
+ if ($res->{$option} && !$res->{$requires}) {
+ warn "$option requires $requires\n";
+ ++$error;
+ }
+ }
+
return undef if $error;
return undef if $res->{mbps_rd} && $res->{mbps};
if ($drive->{file} =~ m|^/|) {
$path = $drive->{file};
if (my $info = path_is_scsi($path)) {
- if ($info->{type} == 0) {
+ if ($info->{type} == 0 && $drive->{scsiblock}) {
$devicetype = 'block';
} elsif ($info->{type} == 1) { # tape
$devicetype = 'generic';
my ($path, $owner) = PVE::Storage::path($storecfg, $volid);
return if !$path || !$owner || ($owner != $vmid);
- PVE::Storage::vdisk_free($storecfg, $volid);
+ eval {
+ PVE::Storage::vdisk_free($storecfg, $volid);
+ };
+ warn "Could not remove disk '$volid', check manually: $@" if $@;
+
});
if ($keep_empty_config) {
$qmpclient->queue_cmd($vmid, $statuscb, 'query-status');
}
- $qmpclient->queue_execute(undef, 1);
+ $qmpclient->queue_execute(undef, 2);
foreach my $vmid (keys %$list) {
next if $opt_vmid && ($vmid ne $opt_vmid);
my $kvmver = kvm_user_version();
my $vernum = 0; # unknown
my $ostype = $conf->{ostype};
+ my $winversion = windows_version($ostype);
+
if ($kvmver =~ m/^(\d+)\.(\d+)$/) {
$vernum = $1*1000000+$2*1000;
} elsif ($kvmver =~ m/^(\d+)\.(\d+)\.(\d+)$/) {
$vga = 'qxl' if $qxlnum;
if (!$vga) {
- if ($conf->{ostype} && ($conf->{ostype} eq 'win8' ||
- $conf->{ostype} eq 'win7' ||
- $conf->{ostype} eq 'w2k8')) {
- $vga = 'std';
- } else {
- $vga = 'cirrus';
- }
+ $vga = $winversion >= 6 ? 'std' : 'cirrus';
}
# enable absolute mouse coordinates (needed by vnc)
push @$devices, '-device', print_tabletdevice_full($conf) if $tablet;
my $kvm_off = 0;
+ my $gpu_passthrough;
+
# host pci devices
for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
my $d = parse_hostpci($conf->{"hostpci$i"});
}
my $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : '';
+ my $romfile = $d->{romfile};
+
my $xvga = '';
if ($d->{'x-vga'}) {
$xvga = ',x-vga=on';
$kvm_off = 1;
$vga = 'none';
- if ($ostype eq 'win7' || $ostype eq 'win8' || $ostype eq 'w2k8') {
- push @$cpuFlags , 'hv_vendor_id=proxmox';
- }
+ $gpu_passthrough = 1;
+
if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
$xvga = "";
}
if($j == 0){
$devicestr .= "$rombar$xvga";
$devicestr .= ",multifunction=on" if $multifunction;
+ $devicestr .= ",romfile=/usr/share/kvm/$romfile" if $romfile;
}
push @$devices, '-device', $devicestr;
my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
my $useLocaltime = $conf->{localtime};
- if ($ostype) {
- # other, wxp, w2k, w2k3, w2k8, wvista, win7, win8, l24, l26, solaris
-
- if ($ostype =~ m/^w/) { # windows
- $useLocaltime = 1 if !defined($conf->{localtime});
-
- # use time drift fix when acpi is enabled
- if (!(defined($conf->{acpi}) && $conf->{acpi} == 0)) {
- $tdf = 1 if !defined($conf->{tdf});
- }
- }
-
- if ($ostype eq 'win7' || $ostype eq 'win8' || $ostype eq 'w2k8' ||
- $ostype eq 'wvista') {
- push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
- push @$cmd, '-no-hpet';
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
- push @$cpuFlags , 'hv_spinlocks=0x1fff' if !$nokvm;
- push @$cpuFlags , 'hv_vapic' if !$nokvm;
- push @$cpuFlags , 'hv_time' if !$nokvm;
-
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) {
- push @$cpuFlags , 'hv_reset' if !$nokvm;
- push @$cpuFlags , 'hv_vpindex' if !$nokvm;
- push @$cpuFlags , 'hv_runtime' if !$nokvm;
- }
+ if ($winversion >= 5) { # windows
+ $useLocaltime = 1 if !defined($conf->{localtime});
- } else {
- push @$cpuFlags , 'hv_spinlocks=0xffff' if !$nokvm;
- }
+ # use time drift fix when acpi is enabled
+ if (!(defined($conf->{acpi}) && $conf->{acpi} == 0)) {
+ $tdf = 1 if !defined($conf->{tdf});
}
+ }
- if ($ostype eq 'win7' || $ostype eq 'win8') {
- push @$cpuFlags , 'hv_relaxed' if !$nokvm;
- }
+ if ($winversion >= 6) {
+ push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
+ push @$cmd, '-no-hpet';
}
push @$rtcFlags, 'driftfix=slew' if $tdf;
push @$cpuFlags , '+kvm_pv_eoi' if !$nokvm;
}
+ add_hyperv_enlighments($cpuFlags, $winversion, $machine_type, $kvmver, $nokvm, $conf->{bios}, $gpu_passthrough);
+
push @$cpuFlags, 'enforce' if $cpu ne 'host' && !$nokvm;
push @$cpuFlags, 'kvm=off' if $kvm_off;
if ($qxlnum) {
if ($qxlnum > 1) {
- if ($conf->{ostype} && $conf->{ostype} =~ m/^w/){
+ 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";
my $pfamily = PVE::Tools::get_host_address_family($nodename);
$spice_port = PVE::Tools::next_spice_port($pfamily);
- push @$devices, '-spice', "tls-port=${spice_port},addr=localhost,tls-ciphers=DES-CBC3-SHA,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";
sub qemu_block_set_io_throttle {
my ($vmid, $deviceid,
$bps, $bps_rd, $bps_wr, $iops, $iops_rd, $iops_wr,
- $bps_max, $bps_rd_max, $bps_wr_max, $iops_max, $iops_rd_max, $iops_wr_max) = @_;
+ $bps_max, $bps_rd_max, $bps_wr_max, $iops_max, $iops_rd_max, $iops_wr_max,
+ $bps_max_length, $bps_rd_max_length, $bps_wr_max_length,
+ $iops_max_length, $iops_rd_max_length, $iops_wr_max_length) = @_;
return if !check_running($vmid) ;
bps_wr_max => int($bps_wr_max),
iops_max => int($iops_max),
iops_rd_max => int($iops_rd_max),
- iops_wr_max => int($iops_wr_max)
+ iops_wr_max => int($iops_wr_max),
+ bps_max_length => int($bps_max_length),
+ bps_rd_max_length => int($bps_rd_max_length),
+ bps_wr_max_length => int($bps_wr_max_length),
+ iops_max_length => int($iops_max_length),
+ iops_rd_max_length => int($iops_rd_max_length),
+ iops_wr_max_length => int($iops_wr_max_length),
);
}
my $running = check_running($vmid);
- return if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
+ $size = 0 if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
return if !$running;
my $running = check_running($vmid);
- return if !PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
-
- return if !$running;
-
- vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
+ if ($running && do_snapshots_with_qemu($storecfg, $volid)){
+ vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
+ } else {
+ PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
+ }
}
sub set_migration_caps {
# update existing disk
# skip non hotpluggable value
- if (&$safe_num_ne($drive->{discard}, $old_drive->{discard}) ||
+ if (&$safe_string_ne($drive->{discard}, $old_drive->{discard}) ||
&$safe_string_ne($drive->{iothread}, $old_drive->{iothread}) ||
&$safe_string_ne($drive->{queues}, $old_drive->{queues}) ||
&$safe_string_ne($drive->{cache}, $old_drive->{cache})) {
&$safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max}) ||
&$safe_num_ne($drive->{iops_max}, $old_drive->{iops_max}) ||
&$safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max}) ||
- &$safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max})) {
+ &$safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max}) ||
+ &$safe_num_ne($drive->{bps_max_length}, $old_drive->{bps_max_length}) ||
+ &$safe_num_ne($drive->{bps_rd_max_length}, $old_drive->{bps_rd_max_length}) ||
+ &$safe_num_ne($drive->{bps_wr_max_length}, $old_drive->{bps_wr_max_length}) ||
+ &$safe_num_ne($drive->{iops_max_length}, $old_drive->{iops_max_length}) ||
+ &$safe_num_ne($drive->{iops_rd_max_length}, $old_drive->{iops_rd_max_length}) ||
+ &$safe_num_ne($drive->{iops_wr_max_length}, $old_drive->{iops_wr_max_length})) {
qemu_block_set_io_throttle($vmid,"drive-$opt",
($drive->{mbps} || 0)*1024*1024,
($drive->{mbps_wr_max} || 0)*1024*1024,
$drive->{iops_max} || 0,
$drive->{iops_rd_max} || 0,
- $drive->{iops_wr_max} || 0);
+ $drive->{iops_wr_max} || 0,
+ $drive->{bps_max_length} || 1,
+ $drive->{bps_rd_max_length} || 1,
+ $drive->{bps_wr_max_length} || 1,
+ $drive->{iops_max_length} || 1,
+ $drive->{iops_rd_max_length} || 1,
+ $drive->{iops_wr_max_length} || 1);
}
sub vm_start {
my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused,
- $forcemachine, $spice_ticket, $migration_network, $migration_type) = @_;
+ $forcemachine, $spice_ticket, $migration_network, $migration_type, $targetstorage) = @_;
PVE::QemuConfig->lock_config($vmid, sub {
my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
# set environment variable useful inside network script
$ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
+ my $local_volumes = {};
+
+ if ($targetstorage) {
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+
+ return if drive_is_cdrom($drive);
+
+ my $volid = $drive->{file};
+
+ return if !$volid;
+
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
+
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ return if $scfg->{shared};
+ $local_volumes->{$ds} = [$volid, $storeid, $volname];
+ });
+
+ my $format = undef;
+
+ foreach my $opt (sort keys %$local_volumes) {
+
+ my ($volid, $storeid, $volname) = @{$local_volumes->{$opt}};
+ my $drive = parse_drive($opt, $conf->{$opt});
+
+ #if remote storage is specified, use default format
+ if ($targetstorage && $targetstorage ne "1") {
+ $storeid = $targetstorage;
+ my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
+ $format = $defFormat;
+ } else {
+ #else we use same format than original
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ $format = qemu_img_format($scfg, $volid);
+ }
+
+ my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, ($drive->{size}/1024));
+ my $newdrive = $drive;
+ $newdrive->{format} = $format;
+ $newdrive->{file} = $newvolid;
+ my $drivestr = PVE::QemuServer::print_drive($vmid, $newdrive);
+ $local_volumes->{$opt} = $drivestr;
+ #pass drive to conf for command line
+ $conf->{$opt} = $drivestr;
+ }
+ }
+
my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
my $migrate_port = 0;
my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
my $nodename = PVE::INotify::nodename();
+ if (!defined($migration_type)) {
+ if (defined($datacenterconf->{migration}->{type})) {
+ $migration_type = $datacenterconf->{migration}->{type};
+ } else {
+ $migration_type = 'secure';
+ }
+ }
+
if ($migration_type eq 'insecure') {
my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
if ($migrate_network_addr) {
warn $@ if $@;
}
- if ($migratedfrom) {
+ #start nbd server for storage migration
+ if ($targetstorage) {
+ my $nodename = PVE::INotify::nodename();
+ my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
+ my $localip = $migrate_network_addr ? $migrate_network_addr : PVE::Cluster::remote_node_ip($nodename, 1);
+ my $pfamily = PVE::Tools::get_host_address_family($nodename);
+ $migrate_port = PVE::Tools::next_migrate_port($pfamily);
+
+ vm_mon_cmd_nocheck($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${migrate_port}" } } );
+
+ $localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
+ foreach my $opt (sort keys %$local_volumes) {
+ my $volid = $local_volumes->{$opt};
+ vm_mon_cmd_nocheck($vmid, "nbd-server-add", device => "drive-$opt", writable => JSON::true );
+ my $migrate_storage_uri = "nbd:${localip}:${migrate_port}:exportname=drive-$opt";
+ print "storage migration listens on $migrate_storage_uri volume:$volid\n";
+ }
+ }
+
+ if ($migratedfrom) {
eval {
set_migration_caps($vmid);
};
}
} else {
-
if (!$statefile && (!defined($conf->{balloon}) || $conf->{balloon})) {
vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
if $conf->{balloon};
}
sub qemu_drive_mirror {
- my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized) = @_;
+ my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete, $qga) = @_;
- my $storecfg = PVE::Storage::config();
- my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid);
+ $jobs = {} if !$jobs;
- my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+ my $qemu_target;
+ 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;
+
+ $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);
- my $format = qemu_img_format($dst_scfg, $dst_volname);
+ my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
- my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+ $format = qemu_img_format($dst_scfg, $dst_volname);
- my $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path;
+ my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+
+ $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path;
+ }
my $opts = { timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $qemu_target };
$opts->{format} = $format if $format;
- print "drive mirror is starting (scanning bitmap) : this step can take some minutes/hours, depend of disk size and storage speed\n";
+ print "drive mirror is starting for drive-$drive\n";
- my $finish_job = sub {
- while (1) {
- my $stats = vm_mon_cmd($vmid, "query-block-jobs");
- my $stat = @$stats[0];
- last if !$stat;
- sleep 1;
- }
- };
+ eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); }; #if a job already run for this device,it's throw an error
+
+ if (my $err = $@) {
+ eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
+ die "mirroring error: $err";
+ }
+
+ qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete, $qga);
+}
+
+sub qemu_drive_mirror_monitor {
+ my ($vmid, $vmiddst, $jobs, $skipcomplete, $qga) = @_;
eval {
- vm_mon_cmd($vmid, "drive-mirror", %$opts);
+ my $err_complete = 0;
+
while (1) {
+ die "storage migration timed out\n" if $err_complete > 300;
+
my $stats = vm_mon_cmd($vmid, "query-block-jobs");
- my $stat = @$stats[0];
- die "mirroring job seem to have die. Maybe do you have bad sectors?" if !$stat;
- die "error job is not mirroring" if $stat->{type} ne "mirror";
- my $busy = $stat->{busy};
- my $ready = $stat->{ready};
+ my $running_mirror_jobs = {};
+ foreach my $stat (@$stats) {
+ next if $stat->{type} ne 'mirror';
+ $running_mirror_jobs->{$stat->{device}} = $stat;
+ }
+
+ my $readycounter = 0;
+
+ foreach my $job (keys %$jobs) {
+
+ if(defined($jobs->{$job}->{complete}) && !defined($running_mirror_jobs->{$job})) {
+ print "$job : finished\n";
+ delete $jobs->{$job};
+ next;
+ }
- if (my $total = $stat->{len}) {
- my $transferred = $stat->{offset} || 0;
- my $remaining = $total - $transferred;
- my $percent = sprintf "%.2f", ($transferred * 100 / $total);
+ die "$job: mirroring has been cancelled\n" if !defined($running_mirror_jobs->{$job});
- print "transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
+ my $busy = $running_mirror_jobs->{$job}->{busy};
+ my $ready = $running_mirror_jobs->{$job}->{ready};
+ if (my $total = $running_mirror_jobs->{$job}->{len}) {
+ my $transferred = $running_mirror_jobs->{$job}->{offset} || 0;
+ my $remaining = $total - $transferred;
+ my $percent = sprintf "%.2f", ($transferred * 100 / $total);
+
+ 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};
}
+ last if scalar(keys %$jobs) == 0;
+
+ if ($readycounter == scalar(keys %$jobs)) {
+ print "all mirroring jobs are ready \n";
+ last if $skipcomplete; #do the complete later
+
+ if ($vmiddst && $vmiddst != $vmid) {
+ if ($qga) {
+ print "freeze filesystem\n";
+ eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); };
+ } else {
+ print "suspend vm\n";
+ eval { PVE::QemuServer::vm_suspend($vmid, 1); };
+ }
- if ($stat->{ready} eq 'true') {
+ # if we clone a disk for a new target vm, we don't switch the disk
+ PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs);
- last if $vmiddst != $vmid;
+ if ($qga) {
+ print "unfreeze filesystem\n";
+ eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); };
+ } else {
+ print "resume vm\n";
+ eval { PVE::QemuServer::vm_resume($vmid, 1, 1); };
+ }
- # try to switch the disk if source and destination are on the same guest
- eval { vm_mon_cmd($vmid, "block-job-complete", device => "drive-$drive") };
- if (!$@) {
- &$finish_job();
last;
+ } else {
+
+ foreach my $job (keys %$jobs) {
+ # try to switch the disk if source and destination are on the same guest
+ print "$job: Completing block job...\n";
+
+ eval { vm_mon_cmd($vmid, "block-job-complete", device => $job) };
+ if ($@ =~ m/cannot be completed/) {
+ print "$job: Block job cannot be completed, try again.\n";
+ $err_complete++;
+ }else {
+ print "$job: Completed successfully.\n";
+ $jobs->{$job}->{complete} = 1;
+ eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ;
+ }
+ }
}
- die $@ if $@ !~ m/cannot be completed/;
}
sleep 1;
}
-
-
};
my $err = $@;
- my $cancel_job = sub {
- vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive");
- &$finish_job();
- };
-
if ($err) {
- eval { &$cancel_job(); };
+ eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
die "mirroring error: $err";
}
- if ($vmiddst != $vmid) {
- # if we clone a disk for a new target vm, we don't switch the disk
- &$cancel_job(); # so we call block-job-cancel
+}
+
+sub qemu_blockjobs_cancel {
+ my ($vmid, $jobs) = @_;
+
+ foreach my $job (keys %$jobs) {
+ print "$job: Cancelling block job\n";
+ eval { vm_mon_cmd($vmid, "block-job-cancel", device => $job); };
+ $jobs->{$job}->{cancel} = 1;
+ }
+
+ while (1) {
+ my $stats = vm_mon_cmd($vmid, "query-block-jobs");
+
+ my $running_jobs = {};
+ foreach my $stat (@$stats) {
+ $running_jobs->{$stat->{device}} = $stat;
+ }
+
+ foreach my $job (keys %$jobs) {
+
+ 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};
+ }
+ }
+
+ last if scalar(keys %$jobs) == 0;
+
+ sleep 1;
}
}
+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) = @_;
+ $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga) = @_;
my $newvolid;
$newvolid = PVE::Storage::vdisk_clone($storecfg, $drive->{file}, $newvmid, $snapname);
push @$newvollist, $newvolid;
} else {
+
my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
$storeid = $storage if $storage;
if $drive->{iothread};
}
- qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit);
+ qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete, $qga);
}
}
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) = @_;
+
+ return if $nokvm;
+ return if $winversion < 6;
+ return if $bios && $bios eq 'ovmf' && $winversion < 8;
+
+ push @$cpuFlags , 'hv_vendor_id=proxmox' if $gpu_passthrough;
+
+ if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
+ push @$cpuFlags , 'hv_spinlocks=0x1fff';
+ push @$cpuFlags , 'hv_vapic';
+ push @$cpuFlags , 'hv_time';
+ } else {
+ push @$cpuFlags , 'hv_spinlocks=0xffff';
+ }
+
+ if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) {
+ push @$cpuFlags , 'hv_reset';
+ push @$cpuFlags , 'hv_vpindex';
+ push @$cpuFlags , 'hv_runtime';
+ }
+
+ if ($winversion >= 7) {
+ push @$cpuFlags , 'hv_relaxed';
+ }
+}
+
+sub windows_version {
+ my ($ostype) = @_;
+
+ return 0 if !$ostype;
+
+ my $winversion = 0;
+
+ if($ostype eq 'wxp' || $ostype eq 'w2k3' || $ostype eq 'w2k') {
+ $winversion = 5;
+ } elsif($ostype eq 'w2k8' || $ostype eq 'wvista') {
+ $winversion = 6;
+ } elsif ($ostype =~ m/^win(\d+)$/) {
+ $winversion = $1;
+ }
+
+ return $winversion;
+}
+
# bash completion helper
sub complete_backup_archives {
return $res;
}
+sub nbd_stop {
+ my ($vmid) = @_;
+
+ vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
1;