\&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',
'Haswell-noTSX' => 'GenuineIntel',
Broadwell => 'GenuineIntel',
'Broadwell-noTSX' => 'GenuineIntel',
+ 'Skylake-Client' => 'GenuineIntel',
# AMD CPUs
athlon => 'AuthenticAMD',
}
);
+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, $minimum) = @_;
my $d = {
%drivedesc_base,
%iothread_fmt,
%queues_fmt,
+ %scsiblock_fmt,
};
my $scsidesc = {
optional => 1,
%iothread_fmt,
%model_fmt,
%queues_fmt,
+ %scsiblock_fmt,
};
my $efidisk_fmt = {
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).",
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 $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : '';
+ my $romfile = $d->{romfile};
+
my $xvga = '';
if ($d->{'x-vga'}) {
$xvga = ',x-vga=on';
if($j == 0){
$devicestr .= "$rombar$xvga";
$devicestr .= ",multifunction=on" if $multifunction;
+ $devicestr .= ",romfile=/usr/share/kvm/$romfile" if $romfile;
}
push @$devices, '-device', $devicestr;
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";
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;
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;
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, $jobs, $skipcomplete) = @_;
+ my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete, $qga) = @_;
$jobs = {} if !$jobs;
my $qemu_target;
my $format;
+ $jobs->{"drive-$drive"} = {};
- if($dst_volid =~ /^nbd:(localhost|[\d\.]+|\[[\d\.:a-fA-F]+\]):(\d+)/) {
- $qemu_target = $dst_volid;
+ if ($dst_volid =~ /^nbd:(localhost|[\d\.]+|\[[\d\.:a-fA-F]+\]):(\d+):exportname=(\S+)/) {
my $server = $1;
my $port = $2;
+ my $exportname = $3;
+
$format = "nbd";
- die "can't connect remote nbd server $server:$port" if !PVE::Network::tcp_ping($server, $port, 2);
+ 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 "drive mirror is starting for drive-$drive\n";
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";
}
- $jobs->{"drive-$drive"} = {};
-
- qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete);
+ qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete, $qga);
}
sub qemu_drive_mirror_monitor {
- my ($vmid, $vmiddst, $jobs, $skipcomplete) = @_;
+ my ($vmid, $vmiddst, $jobs, $skipcomplete, $qga) = @_;
eval {
-
my $err_complete = 0;
while (1) {
next;
}
- die "$job: mirroring has been cancelled. Maybe do you have bad sectors?" if !defined($running_mirror_jobs->{$job});
+ die "$job: mirroring has been cancelled\n" if !defined($running_mirror_jobs->{$job});
my $busy = $running_mirror_jobs->{$job}->{busy};
my $ready = $running_mirror_jobs->{$job}->{ready};
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) {
+ 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 we clone a disk for a new target vm, we don't switch the disk
PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs);
+
+ 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); };
+ }
+
last;
} else {
foreach my $job (keys %$jobs) {
# try to switch the disk if source and destination are on the same guest
- print "$job : Try to complete block job\n";
+ 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 complete. Try again \n";
+ print "$job: Block job cannot be completed, try again.\n";
$err_complete++;
}else {
- print "$job : complete ok : flushing pending writes\n";
+ print "$job: Completed successfully.\n";
$jobs->{$job}->{complete} = 1;
+ eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ;
}
}
}
my ($vmid, $jobs) = @_;
foreach my $job (keys %$jobs) {
- print "$job: try to cancel block job\n";
+ print "$job: Cancelling block job\n";
eval { vm_mon_cmd($vmid, "block-job-cancel", device => $job); };
$jobs->{$job}->{cancel} = 1;
}
foreach my $job (keys %$jobs) {
- if(defined($jobs->{$job}->{cancel}) && !defined($running_jobs->{$job})) {
- print "$job : finished\n";
+ 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) = @_;
+ $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga) = @_;
my $newvolid;
if $drive->{iothread};
}
- qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete);
+ 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 $res;
}
+sub nbd_stop {
+ my ($vmid) = @_;
+
+ vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
1;