package PVE::QemuServer;
use strict;
+use warnings;
use POSIX;
use IO::Handle;
use IO::Select;
ostype => {
optional => 1,
type => 'string',
- enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26)],
+ enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26 solaris)],
description => <<EODESC,
Used to enable special optimization/features for specific
operating systems:
win8 => Microsoft Windows 8/2012
l24 => Linux 2.4 Kernel
l26 => Linux 2.6/3.X Kernel
+solaris => solaris/opensolaris/openindiania kernel
-other|l24|l26 ... no special behaviour
+other|l24|l26|solaris ... no special behaviour
wxp|w2k|w2k3|w2k8|wvista|win7|win8 ... use --localtime switch
EODESC
},
vga => {
optional => 1,
type => 'string',
- description => "Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and 'cirrur' for other OS types",
- enum => [qw(std cirrus vmware)],
+ description => "Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and 'cirrur' for other OS types. Option 'qxl' enables the SPICE display sever. You can also run without any graphic card using a serial devive as terminal.",
+ enum => [qw(std cirrus vmware qxl serial0 serial1 serial2 serial3 qxl2 qxl3 qxl4)],
},
watchdog => {
optional => 1,
optional => 1,
type => 'boolean',
default => 1,
- description => "Enable/disable the usb tablet device. This device is usually needed to allow absolute mouse positioning. Else the mouse runs out of sync with normal vnc clients. If you're running lots of console-only guests on one host, you may consider disabling this to save some context switches.",
+ description => "Enable/disable the usb tablet device. This device is usually needed to allow absolute mouse positioning with VNC. Else the mouse runs out of sync with normal VNC clients. If you're running lots of console-only guests on one host, you may consider disabling this to save some context switches. This is turned of by default if you use spice (vga=qxl).",
},
migrate_speed => {
optional => 1,
my $usbdesc = {
optional => 1,
type => 'string', format => 'pve-qm-usb-device',
- typetext => 'host=HOSTUSBDEVICE',
+ typetext => 'host=HOSTUSBDEVICE|spice',
description => <<EODESCR,
Configure an USB device (n is 0 to 4). This can be used to
pass-through usb devices to the guest. HOSTUSBDEVICE syntax is:
Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+The value 'spice' can be used to add a usb redirection devices for spice.
+
EODESCR
};
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
my $serialdesc = {
optional => 1,
type => 'string',
- pattern => '/dev/ttyS\d+',
+ pattern => '(/dev/ttyS\d+|socket)',
description => <<EODESCR,
-Map host serial devices (n is 0 to 3).
+Create a serial device inside the VM (n is 0 to 3), and pass through a host serial device, or create a unix socket on the host side (use 'qm terminal' to open a terminal connection).
Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
my $paralleldesc= {
optional => 1,
type => 'string',
- pattern => '/dev/parport\d+',
+ pattern => '/dev/parport\d+|/dev/usb/lp\d+',
description => <<EODESCR,
Map host parallel devices (n is 0 to 2).
return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
return undef if $res->{aio} && $res->{aio} !~ m/^(native|threads)$/;
-
+
return undef if $res->{mbps_rd} && $res->{mbps};
return undef if $res->{mbps_wr} && $res->{mbps};
if ($res->{size}) {
- return undef if !defined($res->{size} = &$parse_size($res->{size}));
+ return undef if !defined($res->{size} = &$parse_size($res->{size}));
}
if ($res->{media} && ($res->{media} eq 'cdrom')) {
my $buf = "\x00" x 36;
my $sensebuf = "\x00" x 8;
- my $cmd = pack("C x3 C x11", 0x12, 36);
+ my $cmd = pack("C x3 C x1", 0x12, 36);
# see /usr/include/scsi/sg.h
my $sg_io_hdr_t = "i i C C s I P P P I I i P C C C C S S i I I";
}
my $res = {};
- ($res->{device}, $res->{removable}, $res->{venodor},
+ (my $byte0, my $byte1, $res->{vendor},
$res->{product}, $res->{revision}) = unpack("C C x6 A8 A16 A4", $buf);
+ $res->{removable} = $byte1 & 128 ? 1 : 0;
+ $res->{type} = $byte0 & 31;
+
return $res;
}
my $path = '';
if (drive_is_cdrom($drive)) {
$devicetype = 'cd';
- } else {
+ } else {
if ($drive->{file} =~ m|^/|) {
$path = $drive->{file};
} else {
}
if($path =~ m/^iscsi\:\/\//){
- $devicetype = 'generic';
- }
- else {
- $devicetype = 'block' if path_is_scsi($path);
+ $devicetype = 'generic';
+ } else {
+ if (my $info = path_is_scsi($path)) {
+ if ($info->{type} == 0) {
+ $devicetype = 'block';
+ } elsif ($info->{type} == 1) { # tape
+ $devicetype = 'generic';
+ }
+ }
}
}
$found = 1;
$res->{hostbus} = $1;
$res->{hostport} = $2;
+ } elsif ($v =~ m/^spice$/) {
+ $found = 1;
+ $res->{spice} = 1;
} else {
return undef;
}
my @lines = split(/\n/, $raw);
foreach my $line (@lines) {
next if $line =~ m/^\s*$/;
-
+
if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
my $snapname = $1;
$conf->{description} = $descr if $descr;
$descr = '';
- $conf = $res->{snapshots}->{$snapname} = {};
+ $conf = $res->{snapshots}->{$snapname} = {};
next;
}
delete $conf->{$key};
}
}
-
+
my $generate_raw_config = sub {
my ($conf) = @_;
$loc_res = 1 if $conf->{hostpci}; # old syntax
foreach my $k (keys %$conf) {
+ next if $k =~ m/^usb/ && ($conf->{$k} eq 'spice');
$loc_res = 1 if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
}
my $nodelist = PVE::Cluster::get_nodelist();
my $nodehash = { map { $_ => 1 } @$nodelist };
my $nodename = PVE::INotify::nodename();
-
+
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
my $info = $resp->{'return'};
return if !$info->{max_mem};
-
+
my $d = $res->{$vmid};
# use memory assigned to VM
$d->{maxmem} = $info->{max_mem};
$d->{balloon} = $info->{actual};
-
+
if (defined($info->{total_mem}) && defined($info->{free_mem})) {
$d->{mem} = $info->{total_mem} - $info->{free_mem};
$d->{freemem} = $info->{free_mem};
sub foreach_volid {
my ($conf, $func) = @_;
-
+
my $volhash = {};
my $test_volid = sub {
my ($volid, $is_cdrom) = @_;
return if !$volid;
-
+
$volhash->{$volid} = $is_cdrom || 0;
};
}
foreach my $volid (keys %$volhash) {
- &$func($volid, $volhash->{$volid});
+ &$func($volid, $volhash->{$volid});
}
}
+sub vga_conf_has_spice {
+ my ($vga) = @_;
+
+ return 0 if !$vga || $vga !~ m/^qxl([234])?$/;
+
+ return $1 || 1;
+}
+
sub config_to_command {
- my ($storecfg, $vmid, $conf, $defaults) = @_;
+ my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_;
my $cmd = [];
my $globalFlags = [];
my $machineFlags = [];
my $rtcFlags = [];
+ my $cpuFlags = [];
my $devices = [];
my $pciaddr = '';
my $bridges = {};
# include usb device config
push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2;
+ my $vga = $conf->{vga};
+
+ my $qxlnum = vga_conf_has_spice($vga);
+ $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';
+ }
+ }
+
# enable absolute mouse coordinates (needed by vnc)
- my $tablet = defined($conf->{tablet}) ? $conf->{tablet} : $defaults->{tablet};
+ my $tablet;
+ if (defined($conf->{tablet})) {
+ $tablet = $conf->{tablet};
+ } 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)
+ }
+
push @$devices, '-device', 'usb-tablet,id=tablet,bus=uhci.0,port=1' if $tablet;
# host pci devices
push @$devices, '-device', "usb-host,vendorid=0x$d->{vendorid},productid=0x$d->{productid}";
} elsif (defined($d->{hostbus}) && defined($d->{hostport})) {
push @$devices, '-device', "usb-host,hostbus=$d->{hostbus},hostport=$d->{hostport}";
+ } elsif ($d->{spice}) {
+ # usb redir support for spice
+ push @$devices, '-chardev', "spicevmc,id=usbredirchardev$i,name=usbredir";
+ push @$devices, '-device', "usb-redir,chardev=usbredirchardev$i,id=usbredirdev$i,bus=ehci.0";
}
}
# serial devices
for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) {
if (my $path = $conf->{"serial$i"}) {
- die "no such serial device\n" if ! -c $path;
- push @$devices, '-chardev', "tty,id=serial$i,path=$path";
- push @$devices, '-device', "isa-serial,chardev=serial$i";
+ if ($path eq 'socket') {
+ my $socket = "/var/run/qemu-server/${vmid}.serial$i";
+ push @$devices, '-chardev', "socket,id=serial$i,path=$socket,server,nowait";
+ push @$devices, '-device', "isa-serial,chardev=serial$i";
+ } else {
+ die "no such serial device\n" if ! -c $path;
+ push @$devices, '-chardev', "tty,id=serial$i,path=$path";
+ push @$devices, '-device', "isa-serial,chardev=serial$i";
+ }
}
}
for (my $i = 0; $i < $MAX_PARALLEL_PORTS; $i++) {
if (my $path = $conf->{"parallel$i"}) {
die "no such parallel device\n" if ! -c $path;
- push @$devices, '-chardev', "parport,id=parallel$i,path=$path";
+ my $devtype = $path =~ m!^/dev/usb/lp! ? 'tty' : 'parport';
+ push @$devices, '-chardev', "$devtype,id=parallel$i,path=$path";
push @$devices, '-device', "isa-parallel,chardev=parallel$i";
}
}
$sockets = $conf->{sockets} if $conf->{sockets};
my $cores = $conf->{cores} || 1;
-
push @$cmd, '-smp', "sockets=$sockets,cores=$cores";
- push @$cmd, '-cpu', $conf->{cpu} if $conf->{cpu};
-
push @$cmd, '-nodefaults';
my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
push @$cmd, '-no-reboot' if defined($conf->{reboot}) && $conf->{reboot} == 0;
- my $vga = $conf->{vga};
- if (!$vga) {
- if ($conf->{ostype} && ($conf->{ostype} eq 'win8' || $conf->{ostype} eq 'win7' || $conf->{ostype} eq 'w2k8')) {
- $vga = 'std';
- } else {
- $vga = 'cirrus';
- }
- }
-
- push @$cmd, '-vga', $vga if $vga; # for kvm 77 and later
+ push @$cmd, '-vga', $vga if $vga && $vga !~ m/^serial\d+$/; # for kvm 77 and later
# time drift fix
my $tdf = defined($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf};
my $useLocaltime = $conf->{localtime};
if (my $ost = $conf->{ostype}) {
- # other, wxp, w2k, w2k3, w2k8, wvista, win7, win8, l24, l26
+ # other, wxp, w2k, w2k3, w2k8, wvista, win7, win8, l24, l26, solaris
if ($ost =~ m/^w/) { # windows
$useLocaltime = 1 if !defined($conf->{localtime});
}
}
- if ($ost eq 'win7' || $ost eq 'win8' || $ost eq 'w2k8' ||
+ if ($ost eq 'win7' || $ost eq 'win8' || $ost eq 'w2k8' ||
$ost eq 'wvista') {
push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
push @$cmd, '-no-hpet';
+ #push @$cpuFlags , 'hv_vapic" if !$nokvm; #fixme, my win2008R2 hang at boot with this
+ push @$cpuFlags , 'hv_spinlocks=0xffff' if !$nokvm;
+ }
+
+ if ($ost eq 'win7' || $ost eq 'win8') {
+ push @$cpuFlags , 'hv_relaxed' if !$nokvm;
}
}
die "No accelerator found!\n" if !$cpuinfo->{hvm};
}
- if ($conf->{machine}) {
- push @$machineFlags, "type=$conf->{machine}";
+ my $machine_type = $forcemachine || $conf->{machine};
+ if ($machine_type) {
+ push @$machineFlags, "type=${machine_type}";
}
if ($conf->{startdate}) {
push @$rtcFlags, 'base=localtime';
}
+ my $cpu = $nokvm ? "qemu64" : "kvm64";
+ $cpu = $conf->{cpu} if $conf->{cpu};
+
+ push @$cpuFlags , '+x2apic' if !$nokvm && $conf->{ostype} ne 'solaris';
+
+ push @$cpuFlags , '-x2apic' if $conf->{ostype} eq 'solaris';
+
+ push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
+
+ $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
+
+ push @$cmd, '-cpu', $cpu;
+
push @$cmd, '-S' if $conf->{freeze};
# set keyboard layout
push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
}
+ my $spice_port;
+
+ if ($qxlnum) {
+ if ($qxlnum > 1) {
+ if ($conf->{ostype} && $conf->{ostype} =~ m/^w/){
+ 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";
+ }
+ } else {
+ # assume other OS works like Linux
+ push @$cmd, '-global', 'qxl-vga.ram_size=134217728';
+ push @$cmd, '-global', 'qxl-vga.vram_size=67108864';
+ }
+ }
+
+ my $pciaddr = print_pci_addr("spice", $bridges);
+
+ $spice_port = PVE::Tools::next_unused_port(61000, 61099);
+
+ push @$cmd, '-spice', "tls-port=${spice_port},addr=127.0.0.1,tls-ciphers=DES-CBC3-SHA,seamless-migration=on";
+
+ push @$cmd, '-device', "virtio-serial,id=spice$pciaddr";
+ push @$cmd, '-chardev', "spicevmc,id=vdagent,name=vdagent";
+ push @$cmd, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0";
+ }
+
# enable balloon by default, unless explicitly disabled
if (!defined($conf->{balloon}) || $conf->{balloon}) {
$pciaddr = print_pci_addr("balloon0", $bridges);
}
push @$cmd, @$devices;
- push @$cmd, '-rtc', join(',', @$rtcFlags)
+ push @$cmd, '-rtc', join(',', @$rtcFlags)
if scalar(@$rtcFlags);
- push @$cmd, '-machine', join(',', @$machineFlags)
+ push @$cmd, '-machine', join(',', @$machineFlags)
if scalar(@$machineFlags);
push @$cmd, '-global', join(',', @$globalFlags)
if scalar(@$globalFlags);
- return wantarray ? ($cmd, $vollist) : $cmd;
+ return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
}
sub vnc_socket {
return "${var_run_tmpdir}/$vmid.vnc";
}
+sub spice_port {
+ my ($vmid) = @_;
+
+ my $res = vm_mon_cmd($vmid, 'query-spice');
+
+ return $res->{'tls-port'} || $res->{'port'} || die "no spice port\n";
+}
+
sub qmp_socket {
my ($vmid) = @_;
return "${var_run_tmpdir}/$vmid.qmp";
while (my ($k, $v) = each %$bridges) {
$bridgeid = $k;
}
- return if $bridgeid < 1;
+ return if !$bridgeid || $bridgeid < 1;
my $bridge = "pci.$bridgeid";
my $devices_list = vm_devices_list($vmid);
return if !check_running($vmid) ;
- $bps = 0 if !$bps;
- $bps_rd = 0 if !$bps_rd;
- $bps_wr = 0 if !$bps_wr;
- $iops = 0 if !$iops;
- $iops_rd = 0 if !$iops_rd;
- $iops_wr = 0 if !$iops_wr;
-
vm_mon_cmd($vmid, "block_set_io_throttle", device => $deviceid, bps => int($bps), bps_rd => int($bps_rd), bps_wr => int($bps_wr), iops => int($iops), iops_rd => int($iops_rd), iops_wr => int($iops_wr));
}
}
sub vm_start {
- my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused) = @_;
+ my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused, $forcemachine, $spice_ticket) = @_;
lock_config($vmid, sub {
my $conf = load_config($vmid, $migratedfrom);
# set environment variable useful inside network script
$ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
- my ($cmd, $vollist) = config_to_command($storecfg, $vmid, $conf, $defaults);
+ my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
my $migrate_port = 0;
-
+ my $migrate_uri;
if ($statefile) {
if ($statefile eq 'tcp') {
+ my $localip = "localhost";
+ my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
+ if ($datacenterconf->{migration_unsecure}) {
+ my $nodename = PVE::INotify::nodename();
+ $localip = PVE::Cluster::remote_node_ip($nodename, 1);
+ }
$migrate_port = PVE::Tools::next_migrate_port();
- my $migrate_uri = "tcp:localhost:${migrate_port}";
+ $migrate_uri = "tcp:${localip}:${migrate_port}";
push @$cmd, '-incoming', $migrate_uri;
push @$cmd, '-S';
} else {
my $err = $@;
die "start failed: $err" if $err;
- print "migration listens on port $migrate_port\n" if $migrate_port;
+ print "migration listens on $migrate_uri\n" if $migrate_uri;
if ($statefile && $statefile ne 'tcp') {
eval { vm_mon_cmd_nocheck($vmid, "cont"); };
warn $@ if $@;
}
- if($migratedfrom) {
+ if ($migratedfrom) {
my $capabilities = {};
$capabilities->{capability} = "xbzrle";
$capabilities->{state} = JSON::true;
eval { vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); };
- }
- else{
+ warn $@ if $@;
+
+ if ($spice_port) {
+ print "spice listens on port $spice_port\n";
+ if ($spice_ticket) {
+ PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "set_password", protocol => 'spice', password => $spice_ticket);
+ PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "expire_password", protocol => 'spice', time => "+30");
+ }
+ }
+
+ } else {
if (!$statefile && (!defined($conf->{balloon}) || $conf->{balloon})) {
- vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
+ vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
if $conf->{balloon};
- vm_mon_cmd_nocheck($vmid, 'qom-set',
- path => "machine/peripheral/balloon0",
- property => "guest-stats-polling-interval",
+ vm_mon_cmd_nocheck($vmid, 'qom-set',
+ path => "machine/peripheral/balloon0",
+ property => "guest-stats-polling-interval",
value => 2);
}
}
$timeout = $cmd->{arguments}->{timeout};
delete $cmd->{arguments}->{timeout};
}
-
+
eval {
die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
my $sname = qmp_socket($vmid);
scsihw1 => { bus => 0, addr => 6 },
ahci0 => { bus => 0, addr => 7 },
qga0 => { bus => 0, addr => 8 },
+ spice => { bus => 0, addr => 9 },
virtio0 => { bus => 0, addr => 10 },
virtio1 => { bus => 0, addr => 11 },
virtio2 => { bus => 0, addr => 12 },
net3 => { bus => 0, addr => 21 },
net4 => { bus => 0, addr => 22 },
net5 => { bus => 0, addr => 23 },
+ vga1 => { bus => 0, addr => 24 },
+ vga2 => { bus => 0, addr => 25 },
+ vga3 => { bus => 0, addr => 26 },
#addr29 : usb-host (pve-usb.cfg)
'pci.1' => { bus => 0, addr => 30 },
'pci.2' => { bus => 0, addr => 31 },
sub update_disksize {
my ($vmid, $conf, $volid_hash) = @_;
-
+
my $changes;
my $used = {};
# to the same path).
my $usedpath = {};
-
+
# update size info
foreach my $opt (keys %$conf) {
if (valid_drivename($opt)) {
next if !$volid;
$used->{$volid} = 1;
- if ($volid_hash->{$volid} &&
+ if ($volid_hash->{$volid} &&
(my $path = $volid_hash->{$volid}->{path})) {
$usedpath->{$path} = 1;
}
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 ($used->{$volid} || ($path && $usedpath->{$path})) {
$changes = 1;
delete $conf->{$opt};
}
my ($vmid) = @_;
my $conf = load_config($vmid);
-
+
check_lock($conf);
my $vm_volids = {};
&$updatefn($vmid);
} else {
lock_config($vmid, $updatefn, $vmid);
- }
+ }
}
}
}
} else {
die "unknown compression method '$comp'\n";
}
-
+
}
my $tmpdir = "/var/tmp/vzdumptmp$$";
$devinfo->{$devname}->{format} = $format;
$devinfo->{$devname}->{storeid} = $storeid;
- # check permission on storage
+ # check permission on storage
my $pool = $opts->{pool}; # todo: do we need that?
if ($user ne 'root@pam') {
$rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
}
foreach my $devname (keys %$devinfo) {
- die "found no device mapping information for device '$devname'\n"
- if !$devinfo->{$devname}->{virtdev};
+ die "found no device mapping information for device '$devname'\n"
+ if !$devinfo->{$devname}->{virtdev};
}
my $cfg = cfs_read_file('storage.cfg');
# create empty/temp config
- if ($oldconf) {
+ if ($oldconf) {
PVE::Tools::file_set_contents($conffile, "memory: 128\n");
foreach_drive($oldconf, sub {
my ($ds, $drive) = @_;
my $write_zeros = 1;
# fixme: what other storages types initialize volumes with zero?
- if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' ||
+ if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' || $scfg->{type} eq 'glusterfs' ||
$scfg->{type} eq 'sheepdog' || $scfg->{type} eq 'rbd') {
$write_zeros = 0;
}
my $cookie = { netcount => 0 };
while (defined(my $line = <$fh>)) {
- restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
+ restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
}
$fh->close();
close($fifofh);
}
};
-
+
print "restore vma archive: $cmd\n";
run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
};
my $cookie = { netcount => 0 };
while (defined (my $line = <$srcfd>)) {
- restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
+ restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
}
$srcfd->close();
next if $k eq 'digest';
next if $k eq 'description';
next if $k =~ m/^unused\d+$/;
-
+
$dest->{$k} = $source->{$k};
}
};
my $volid = $drive->{file};
my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
- $sidhash->{$sid} = $sid if $sid;
+ $sidhash->{$sid} = $sid if $sid;
}
foreach my $sid (sort keys %$sidhash) {
my $alloc_vmstate_volid = sub {
my ($storecfg, $vmid, $conf, $snapname) = @_;
-
+
# Note: we try to be smart when selecting a $target storage
my $target;
my $conf = load_config($vmid);
- die "you can't take a snapshot if it's a template\n"
+ die "you can't take a snapshot if it's a template\n"
if is_template($conf);
check_lock($conf);
$conf->{lock} = 'snapshot';
- die "snapshot name '$snapname' already used\n"
- if defined($conf->{snapshots}->{$snapname});
+ die "snapshot name '$snapname' already used\n"
+ if defined($conf->{snapshots}->{$snapname});
my $storecfg = PVE::Storage::config();
die "snapshot feature is not available" if !has_feature('snapshot', $conf, $storecfg);
$snap->{snaptime} = time();
$snap->{description} = $comment if $comment;
+ # always overwrite machine if we save vmstate. This makes sure we
+ # can restore it later using correct machine type
+ $snap->{machine} = get_current_qemu_machine($vmid) if $snap->{vmstate};
+
update_config_nolock($vmid, $conf, 1);
};
my $conf = load_config($vmid);
- die "missing snapshot lock\n"
- if !($conf->{lock} && $conf->{lock} eq 'snapshot');
+ die "missing snapshot lock\n"
+ if !($conf->{lock} && $conf->{lock} eq 'snapshot');
my $snap = $conf->{snapshots}->{$snapname};
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+ die "wrong snapshot state\n"
+ if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
- die "wrong snapshot state\n"
- if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
-
delete $snap->{snapstate};
delete $conf->{lock};
my $prepare = 1;
my $storecfg = PVE::Storage::config();
-
+
my $updatefn = sub {
my $conf = load_config($vmid);
$snap = $conf->{snapshots}->{$snapname};
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
- die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
+ die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
if $snap->{snapstate};
if ($prepare) {
delete $conf->{lock};
}
+ my $forcemachine;
+
if (!$prepare) {
+ my $has_machine_config = defined($conf->{machine});
+
# copy snapshot config to current config
$conf = &$snapshot_apply_config($conf, $snap);
$conf->{parent} = $snapname;
+
+ # Note: old code did not store 'machine', so we try to be smart
+ # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).
+ $forcemachine = $conf->{machine} || 'pc-i440fx-1.4';
+ # we remove the 'machine' configuration if not explicitly specified
+ # in the original config.
+ delete $conf->{machine} if $snap->{vmstate} && !$has_machine_config;
}
update_config_nolock($vmid, $conf, 1);
if (!$prepare && $snap->{vmstate}) {
my $statefile = PVE::Storage::path($storecfg, $snap->{vmstate});
- vm_start($storecfg, $vmid, $statefile);
+ vm_start($storecfg, $vmid, $statefile, undef, undef, undef, $forcemachine);
}
};
lock_config($vmid, $updatefn);
-
+
foreach_drive($snap, sub {
my ($ds, $drive) = @_;
if ($running) {
if ($snap->{vmstate}) {
- my $path = PVE::Storage::path($storecfg, $snap->{vmstate});
+ my $path = PVE::Storage::path($storecfg, $snap->{vmstate});
vm_mon_cmd($vmid, "savevm-start", statefile => $path);
&$savevm_wait($vmid);
} else {
};
qga_freezefs($vmid) if $running && $freezefs;
-
+
foreach_drive($snap, sub {
my ($ds, $drive) = @_;
}
}
};
-
+
my $updatefn = sub {
my ($remove_drive) = @_;
if (!$drivehash) {
check_lock($conf);
- die "you can't delete a snapshot if vm is a template\n"
+ die "you can't delete a snapshot if vm is a template\n"
if is_template($conf);
}
$snap = $conf->{snapshots}->{$snapname};
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
# remove parent refs
&$unlink_parent($conf, $snap->{parent});
return $1;
} elsif ($scfg->{type} eq 'iscsi') {
return "host_device";
- } else {
+ } else {
return "raw";
}
}
my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
if ($format) {
- #fixme : sometime drive-mirror timeout, but works fine after.
+ #fixme : sometime drive-mirror timeout, but works fine after.
# (I have see the problem with big volume > 200GB), so we need to eval
- eval { vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing",
+ eval { vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing",
sync => "full", target => $dst_path, format => $format); };
} else {
- eval { vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing",
+ eval { vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing",
sync => "full", target => $dst_path); };
}
$old_len = $stat->{offset};
sleep 1;
}
-
+
if ($vmiddst == $vmid) {
- # switch the disk if source and destination are on the same guest
+ # switch the disk if source and destination are on the same guest
vm_mon_cmd($vmid, "block-job-complete", device => "drive-$drive");
}
};
}
sub clone_disk {
- my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
+ my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
$newvmid, $storage, $format, $full, $newvollist) = @_;
my $newvolid;
qemu_img_convert($drive->{file}, $newvolid, $size, $snapname);
} else {
qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid);
- }
+ }
}
my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
return $disk;
}
+# this only works if VM is running
+sub get_current_qemu_machine {
+ my ($vmid) = @_;
+
+ my $cmd = { execute => 'query-machines', arguments => {} };
+ my $res = PVE::QemuServer::vm_qmp_command($vmid, $cmd);
+
+ my ($current, $default);
+ foreach my $e (@$res) {
+ $default = $e->{name} if $e->{'is-default'};
+ $current = $e->{name} if $e->{'is-current'};
+ }
+
+ # fallback to the default machine if current is not supported by qemu
+ return $current || $default || 'pc';
+}
+
+sub read_x509_subject_spice {
+ my ($filename) = @_;
+
+ # read x509 subject
+ my $bio = Net::SSLeay::BIO_new_file($filename, 'r');
+ my $x509 = Net::SSLeay::PEM_read_bio_X509($bio);
+ Net::SSLeay::BIO_free($bio);
+ my $nameobj = Net::SSLeay::X509_get_subject_name($x509);
+ my $subject = Net::SSLeay::X509_NAME_oneline($nameobj);
+ Net::SSLeay::X509_free($x509);
+
+ # remote-viewer wants comma as seperator (not '/')
+ $subject =~ s!^/!!;
+ $subject =~ s!/(\w+=)!,$1!g;
+
+ return $subject;
+}
1;