use Storable qw(dclone);
use PVE::Exception qw(raise raise_param_exc);
use PVE::Storage;
-use PVE::Tools qw(run_command lock_file file_read_firstline);
+use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline);
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;
hotplug => {
optional => 1,
type => 'boolean',
- description => "Activate hotplug for disk and network device",
+ description => "Allow hotplug for disk and network device",
default => 0,
},
reboot => {
balloon => {
optional => 1,
type => 'integer',
- description => "Amount of target RAM for the VM in MB.",
- minimum => 16,
+ description => "Amount of target RAM for the VM in MB. Using zero disables the ballon driver.",
+ minimum => 0,
+ },
+ 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",
+ minimum => 0,
+ maximum => 50000,
+ default => 1000,
},
keyboard => {
optional => 1,
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.",
+ enum => [qw(std cirrus vmware qxl)],
},
watchdog => {
optional => 1,
typetext => '[[order=]\d+] [,up=\d+] [,down=\d+] ',
description => "Startup and shutdown behavior. Order is a non-negative number defining the general startup order. Shutdown in done with reverse ordering. Additionally you can set the 'up' or 'down' delay in seconds, which specifies a delay to wait before the next VM is started or stopped.",
},
+ template => {
+ optional => 1,
+ type => 'boolean',
+ description => "Enable/disable Template.",
+ default => 0,
+ },
args => {
optional => 1,
type => 'string',
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,
},
migrate_downtime => {
optional => 1,
- type => 'integer',
+ type => 'number',
description => "Set maximum tolerated downtime (in seconds) for migrations.",
minimum => 0,
- default => 1,
+ default => 0.1,
},
cdrom => {
optional => 1,
description => "Emulated CPU type.",
type => 'string',
enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom Conroe Penryn Nehalem Westmere SandyBridge Haswell Opteron_G1 Opteron_G2 Opteron_G3 Opteron_G4 Opteron_G5 host) ],
- default => 'qemu64',
+ default => 'kvm64',
},
parent => get_standard_option('pve-snapshot-name', {
optional => 1,
type => 'string', format => 'pve-volume-id',
description => "Reference to a volume which stores the VM state. This is used internally for snapshots.",
},
+ machine => {
+ description => "Specific the Qemu machine type.",
+ type => 'string',
+ pattern => '(pc|pc(-i440fx)?-\d+\.\d+|q35|pc-q35-\d+\.\d+)',
+ maxLength => 40,
+ optional => 1,
+ },
};
# what about other qemu settings ?
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);
return undef if !$res->{file};
+ if($res->{file} =~ m/\.(raw|cow|qcow|qcow2|vmdk|cloop)$/){
+ $res->{format} = $1;
+ }
+
return undef if $res->{cache} &&
$res->{cache} !~ m/^(off|none|writethrough|writeback|unsafe|directsync)$/;
return undef if $res->{snapshot} && $res->{snapshot} !~ m/^(on|off)$/;
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';
+ }
+ }
}
}
} else {
$path = PVE::Storage::path($storecfg, $volid);
}
- if (!$drive->{cache} && ($path =~ m|^/dev/| || $path =~ m|\.raw$|)) {
- $opts .= ",cache=none";
- }
}
+ $opts .= ",cache=none" if !$drive->{cache} && !drive_is_cdrom($drive);
+
my $pathinfo = $path ? "file=$path," : '';
return "${pathinfo}if=none,id=drive-$drive->{interface}$drive->{index}$opts";
if ($kvp =~ m/^(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) {
my $model = lc($1);
- my $mac = uc($3) || PVE::Tools::random_ether_addr();
+ my $mac = defined($3) ? uc($3) : PVE::Tools::random_ether_addr();
$res->{model} = $model;
$res->{macaddr} = $mac;
} elsif ($kvp =~ m/^bridge=(\S+)$/) {
$found = 1;
$res->{hostbus} = $1;
$res->{hostport} = $2;
+ } elsif ($v =~ m/^spice$/) {
+ $found = 1;
+ $res->{spice} = 1;
} else {
return undef;
}
} elsif ($type eq 'integer') {
return int($1) if $value =~ m/^(\d+)$/;
die "type check ('integer') failed - got '$value'\n";
+ } elsif ($type eq 'number') {
+ return $value if $value =~ m/^(\d+)(\.\d+)?$/;
+ die "type check ('number') failed - got '$value'\n";
} elsif ($type eq 'string') {
if (my $fmt = $confdesc->{$key}->{format}) {
if ($fmt eq 'pve-qm-drive') {
return $res;
}
+sub lock_config_mode {
+ my ($vmid, $timeout, $shared, $code, @param) = @_;
+
+ my $filename = config_file_lock($vmid);
+
+ my $res = lock_file_full($filename, $timeout, $shared, $code, @param);
+
+ die $@ if $@;
+
+ return $res;
+}
+
sub lock_config {
my ($vmid, $code, @param) = @_;
return if drive_is_cdrom($drive);
my $volid = $drive->{file};
+
return if !$volid || $volid =~ m|^/|;
my ($path, $owner) = PVE::Storage::path($storecfg, $volid);
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;
}
my $used_volids = {};
my $cleanup_config = sub {
- my ($cref) = @_;
+ my ($cref, $snapname) = @_;
foreach my $key (keys %$cref) {
next if $key eq 'digest' || $key eq 'description' || $key eq 'snapshots' ||
$cref->{$key} = $value;
- if (valid_drivename($key)) {
- my $drive = PVE::QemuServer::parse_drive($key, $value);
+ if (!$snapname && valid_drivename($key)) {
+ my $drive = parse_drive($key, $value);
$used_volids->{$drive->{file}} = 1 if $drive && $drive->{file};
}
}
&$cleanup_config($conf);
foreach my $snapname (keys %{$conf->{snapshots}}) {
- &$cleanup_config($conf->{snapshots}->{$snapname});
+ &$cleanup_config($conf->{snapshots}->{$snapname}, $snapname);
}
# remove 'unusedX' settings if we re-add a volume
delete $conf->{$key};
}
}
-
+
my $generate_raw_config = sub {
my ($conf) = @_;
return $loc_res;
}
-# check is used storages are available on all nodes (use by migrate)
+# check if used storages are available on all nodes (use by migrate)
sub check_storage_availability {
my ($storecfg, $conf, $node) = @_;
});
}
+# list nodes where all VM images are available (used by has_feature API)
+sub shared_nodes {
+ my ($conf, $storecfg) = @_;
+
+ my $nodelist = PVE::Cluster::get_nodelist();
+ my $nodehash = { map { $_ => 1 } @$nodelist };
+ my $nodename = PVE::INotify::nodename();
+
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+
+ my $volid = $drive->{file};
+ return if !$volid;
+
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+ if ($storeid) {
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ if ($scfg->{disable}) {
+ $nodehash = {};
+ } elsif (my $avail = $scfg->{nodes}) {
+ foreach my $node (keys %$nodehash) {
+ delete $nodehash->{$node} if !$avail->{$node};
+ }
+ } elsif (!$scfg->{shared}) {
+ foreach my $node (keys %$nodehash) {
+ delete $nodehash->{$node} if $node ne $nodename
+ }
+ }
+ }
+ });
+
+ return $nodehash
+}
+
sub check_lock {
my ($conf) = @_;
my @param = split(/\0/, $line);
my $cmd = $param[0];
- return if !$cmd || ($cmd !~ m|kvm$|);
+ return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m|qemu-system-x86_64$|);
for (my $i = 0; $i < scalar (@param); $i++) {
my $p = $param[$i];
$d->{name} = $conf->{name} || "VM $vmid";
$d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024) : 0;
+ if ($conf->{balloon}) {
+ $d->{balloon_min} = $conf->{balloon}*(1024*1024);
+ $d->{shares} = defined($conf->{shares}) ? $conf->{shares} : 1000;
+ }
+
$d->{uptime} = 0;
$d->{cpu} = 0;
$d->{mem} = 0;
$d->{diskread} = 0;
$d->{diskwrite} = 0;
+ $d->{template} = is_template($conf);
+
$res->{$vmid} = $d;
}
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;
};
- PVE::QemuServer::foreach_drive($conf, sub {
+ foreach_drive($conf, sub {
my ($ds, $drive) = @_;
&$test_volid($drive->{file}, drive_is_cdrom($drive));
});
foreach my $snapname (keys %{$conf->{snapshots}}) {
my $snap = $conf->{snapshots}->{$snapname};
&$test_volid($snap->{vmstate}, 0);
- PVE::QemuServer::foreach_drive($snap, sub {
+ foreach_drive($snap, sub {
my ($ds, $drive) = @_;
&$test_volid($drive->{file}, drive_is_cdrom($drive));
});
}
foreach my $volid (keys %$volhash) {
- &$func($volid, $volhash->{$volid});
+ &$func($volid, $volhash->{$volid});
}
}
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 = {};
push @$cmd, '-daemonize';
+ $pciaddr = print_pci_addr("piix3", $bridges);
+ push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2";
+
my $use_usb2 = 0;
for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) {
next if !$conf->{"usb$i"};
# include usb device config
push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2;
- # enable absolute mouse coordinates (needed by vnc)
- my $tablet = defined($conf->{tablet}) ? $conf->{tablet} : $defaults->{tablet};
- if ($tablet) {
- if ($use_usb2) {
- push @$devices, '-device', 'usb-tablet,bus=ehci.0,port=6';
+ my $vga = $conf->{vga};
+ if (!$vga) {
+ if ($conf->{ostype} && ($conf->{ostype} eq 'win8' ||
+ $conf->{ostype} eq 'win7' ||
+ $conf->{ostype} eq 'w2k8')) {
+ $vga = 'std';
} else {
- push @$devices, '-usbdevice', 'tablet';
+ $vga = 'cirrus';
}
}
+ # enable absolute mouse coordinates (needed by vnc)
+ my $tablet;
+ if (defined($conf->{tablet})) {
+ $tablet = $conf->{tablet};
+ } else {
+ $tablet = $defaults->{tablet};
+ $tablet = 0 if $vga eq 'qxl'; # disable for spice because it is not needed
+ }
+
+ push @$devices, '-device', 'usb-tablet,id=tablet,bus=uhci.0,port=1' if $tablet;
+
# host pci devices
for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
my $d = parse_hostpci($conf->{"hostpci$i"});
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";
}
}
$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
# time drift fix
}
}
- 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};
}
+ my $machine_type = $forcemachine || $conf->{machine};
+ if ($machine_type) {
+ push @$machineFlags, "type=${machine_type}";
+ }
+
if ($conf->{startdate}) {
push @$rtcFlags, "base=$conf->{startdate}";
} elsif ($useLocaltime) {
push @$rtcFlags, 'base=localtime';
}
+ my $cpu = $nokvm ? "qemu64" : "kvm64";
+ $cpu = $conf->{cpu} if $conf->{cpu};
+
+ push @$cpuFlags , '+x2apic' if !$nokvm;
+
+ 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';
}
- $pciaddr = print_pci_addr("balloon0", $bridges);
- push @$devices, '-device', "virtio-balloon-pci,id=balloon0$pciaddr" if $conf->{balloon};
+ if ($vga eq 'qxl') {
+ my $pciaddr = print_pci_addr("spice", $bridges);
+
+ my $port = PVE::Tools::next_unused_port(61000, 61099);
+
+ push @$cmd, '-spice', "tls-port=$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 @$devices, '-device', "virtio-balloon-pci,id=balloon0$pciaddr";
+ }
if ($conf->{watchdog}) {
my $wdopts = parse_watchdog($conf->{watchdog});
}
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 "${var_run_tmpdir}/$vmid.vnc";
}
+sub spice_port {
+ my ($vmid) = @_;
+
+ my $res = vm_mon_cmd_nocheck($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";
return "${var_run_tmpdir}/$vmid.pid";
}
-sub next_migrate_port {
-
- for (my $p = 60000; $p < 60010; $p++) {
-
- my $sock = IO::Socket::INET->new(Listen => 5,
- LocalAddr => 'localhost',
- LocalPort => $p,
- ReuseAddr => 1,
- Proto => 0);
-
- if ($sock) {
- close($sock);
- return $p;
- }
- }
-
- die "unable to find free migration port";
-}
-
sub vm_devices_list {
my ($vmid) = @_;
sub vm_deviceplug {
my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
- return 1 if !check_running($vmid) || !$conf->{hotplug};
+ return 1 if !check_running($vmid);
+
+ if ($deviceid eq 'tablet') {
+ my $devicefull = "usb-tablet,id=tablet,bus=uhci.0,port=1";
+ qemu_deviceadd($vmid, $devicefull);
+ return 1;
+ }
+
+ return 1 if !$conf->{hotplug};
my $devices_list = vm_devices_list($vmid);
return 1 if defined($devices_list->{$deviceid});
sub vm_deviceunplug {
my ($vmid, $conf, $deviceid) = @_;
- return 1 if !check_running ($vmid) || !$conf->{hotplug};
+ return 1 if !check_running ($vmid);
+
+ if ($deviceid eq 'tablet') {
+ qemu_devicedel($vmid, $deviceid);
+ return 1;
+ }
+
+ return 1 if !$conf->{hotplug};
my $devices_list = vm_devices_list($vmid);
return 1 if !defined($devices_list->{$deviceid});
die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
if ($deviceid =~ m/^(virtio)(\d+)$/) {
- return undef if !qemu_drivedel($vmid, $deviceid);
qemu_devicedel($vmid, $deviceid);
return undef if !qemu_devicedelverify($vmid, $deviceid);
+ return undef if !qemu_drivedel($vmid, $deviceid);
}
if ($deviceid =~ m/^(lsi)(\d+)$/) {
}
if ($deviceid =~ m/^(net)(\d+)$/) {
- return undef if !qemu_netdevdel($vmid, $deviceid);
qemu_devicedel($vmid, $deviceid);
return undef if !qemu_devicedelverify($vmid, $deviceid);
+ return undef if !qemu_netdevdel($vmid, $deviceid);
}
return 1;
sub qemu_deviceadd {
my ($vmid, $devicefull) = @_;
- my $ret = vm_human_monitor_command($vmid, "device_add $devicefull");
- $ret =~ s/^\s+//;
- # Otherwise, if the command succeeds, no output is sent. So any non-empty string shows an error
- return 1 if $ret eq "";
- syslog("err", "error on hotplug device : $ret");
- return undef;
+ $devicefull = "driver=".$devicefull;
+ my %options = split(/[=,]/, $devicefull);
+ vm_mon_cmd($vmid, "device_add" , %options);
+ return 1;
}
sub qemu_devicedel {
my($vmid, $deviceid) = @_;
-
- my $ret = vm_human_monitor_command($vmid, "device_del $deviceid");
- $ret =~ s/^\s+//;
- return 1 if $ret eq "";
- syslog("err", "detaching device $deviceid failed : $ret");
- return undef;
+ my $ret = vm_mon_cmd($vmid, "device_del", id => $deviceid);
+ return 1;
}
sub qemu_driveadd {
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);
my ($vmid, $conf, $device, $deviceid) = @_;
my $netdev = print_netdev_full($vmid, $conf, $device, $deviceid);
- my $ret = vm_human_monitor_command($vmid, "netdev_add $netdev");
- $ret =~ s/^\s+//;
+ my %options = split(/[=,]/, $netdev);
- #if the command succeeds, no output is sent. So any non-empty string shows an error
- return 1 if $ret eq "";
- syslog("err", "adding netdev failed: $ret");
- return undef;
+ vm_mon_cmd($vmid, "netdev_add", %options);
+ return 1;
}
sub qemu_netdevdel {
my ($vmid, $deviceid) = @_;
- my $ret = vm_human_monitor_command($vmid, "netdev_del $deviceid");
- $ret =~ s/^\s+//;
- #if the command succeeds, no output is sent. So any non-empty string shows an error
- return 1 if $ret eq "";
- syslog("err", "deleting netdev failed: $ret");
- return undef;
+ vm_mon_cmd($vmid, "netdev_del", id => $deviceid);
+ return 1;
}
sub qemu_block_set_io_throttle {
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 qemu_block_resize {
my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;
- my $running = PVE::QemuServer::check_running($vmid);
+ my $running = check_running($vmid);
return if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
sub qemu_volume_snapshot {
my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
- my $running = PVE::QemuServer::check_running($vmid);
+ my $running = check_running($vmid);
return if !PVE::Storage::volume_snapshot($storecfg, $volid, $snap, $running);
sub qemu_volume_snapshot_delete {
my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
- my $running = PVE::QemuServer::check_running($vmid);
+ my $running = check_running($vmid);
return if !PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
}
sub vm_start {
- my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused) = @_;
+ my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused, $forcemachine) = @_;
lock_config($vmid, sub {
my $conf = load_config($vmid, $migratedfrom);
+ die "you can't start a vm if it's a template\n" if is_template($conf);
+
check_lock($conf) if !$skiplock;
die "VM $vmid already running\n" if check_running($vmid, undef, $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) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
my $migrate_port = 0;
if ($statefile) {
if ($statefile eq 'tcp') {
- $migrate_port = next_migrate_port();
+ $migrate_port = PVE::Tools::next_migrate_port();
my $migrate_uri = "tcp:localhost:${migrate_port}";
push @$cmd, '-incoming', $migrate_uri;
push @$cmd, '-S';
print "migration listens on port $migrate_port\n" if $migrate_port;
if ($statefile && $statefile ne 'tcp') {
- eval { vm_mon_cmd($vmid, "cont"); };
+ eval { vm_mon_cmd_nocheck($vmid, "cont"); };
warn $@ if $@;
}
- # always set migrate speed (overwrite kvm default of 32m)
- # we set a very hight default of 8192m which is basically unlimited
- my $migrate_speed = $defaults->{migrate_speed} || 8192;
- $migrate_speed = $conf->{migrate_speed} || $migrate_speed;
- $migrate_speed = $migrate_speed * 1048576;
- eval {
- vm_mon_cmd($vmid, "migrate_set_speed", value => $migrate_speed);
- };
-
- my $migrate_downtime = $defaults->{migrate_downtime};
- $migrate_downtime = $conf->{migrate_downtime} if defined($conf->{migrate_downtime});
- if (defined($migrate_downtime)) {
- eval { vm_mon_cmd($vmid, "migrate_set_downtime", value => $migrate_downtime); };
- }
-
if($migratedfrom) {
my $capabilities = {};
$capabilities->{capability} = "xbzrle";
$capabilities->{state} = JSON::true;
- eval { PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); };
- } elsif ($conf->{balloon}) {
- vm_balloonset($vmid, $conf->{balloon});
- vm_mon_cmd($vmid, 'qom-set',
- path => "machine/peripheral/balloon0",
- property => "stats-polling-interval",
- value => 2);
+ eval { vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); };
+ if($conf->{vga} eq 'qxl'){
+ my $spice_port = PVE::QemuServer::spice_port($vmid);
+ print "spice listens on port $spice_port\n" if $spice_port;
+ if($spiceticket){
+ PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "set_password", protocol => 'spice', password => $spiceticket);
+ PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "expire_password", protocol => 'spice', time => "+5");
+ }
+ }
+
+ }
+ 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, '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 = PVE::QemuServer::qmp_socket($vmid);
+ my $sname = qmp_socket($vmid);
if (-e $sname) {
my $qmpclient = PVE::QMPClient->new();
my $conf = load_config($vmid);
- check_lock($conf) if !$skiplock;
+ check_lock($conf) if !($skiplock || ($conf->{lock} && $conf->{lock} eq 'backup'));
vm_mon_cmd($vmid, "stop");
});
my $conf = load_config($vmid);
- check_lock($conf) if !$skiplock;
+ check_lock($conf) if !($skiplock || ($conf->{lock} && $conf->{lock} eq 'backup'));
vm_mon_cmd($vmid, "cont");
});
my $res = '';
my $devices = {
- #addr1 : ide,parallel,serial (motherboard)
+ piix3 => { bus => 0, addr => 1 },
#addr2 : first videocard
balloon0 => { bus => 0, addr => 3 },
watchdog => { bus => 0, addr => 4 },
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 },
}
-sub vm_balloonset {
- my ($vmid, $value) = @_;
-
- vm_mon_cmd($vmid, "balloon", value => $value*1024*1024);
-}
-
# vzdump restore implementaion
-sub archive_read_firstfile {
+sub tar_archive_read_firstfile {
my $archive = shift;
die "ERROR: file '$archive' does not exist\n" if ! -f $archive;
return $firstfile;
}
-sub restore_cleanup {
- my $statfile = shift;
+sub tar_restore_cleanup {
+ my ($storecfg, $statfile) = @_;
print STDERR "starting cleanup\n";
if ($volid =~ m|^/|) {
unlink $volid || die 'unlink failed\n';
} else {
- my $cfg = cfs_read_file('storage.cfg');
- PVE::Storage::vdisk_free($cfg, $volid);
+ PVE::Storage::vdisk_free($storecfg, $volid);
}
print STDERR "temporary volume '$volid' sucessfuly removed\n";
};
return if $line =~ m/^lock:/;
return if $line =~ m/^unused\d+:/;
return if $line =~ m/^parent:/;
+ return if $line =~ m/^template:/; # restored VM is never a template
if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
# try to convert old 1.X settings
print $outfd "$id: $netstr\n";
} elsif ($line =~ m/^((ide|scsi|virtio|sata)\d+):\s*(\S+)\s*$/) {
my $virtdev = $1;
- my $value = $2;
+ my $value = $3;
if ($line =~ m/backup=no/) {
print $outfd "#$line";
} elsif ($virtdev && $map->{$virtdev}) {
- my $di = PVE::QemuServer::parse_drive($virtdev, $value);
+ my $di = parse_drive($virtdev, $value);
+ delete $di->{format}; # format can change on restore
$di->{file} = $map->{$virtdev};
- $value = PVE::QemuServer::print_drive($vmid, $di);
+ $value = print_drive($vmid, $di);
print $outfd "$virtdev: $value\n";
} else {
print $outfd $line;
foreach my $storeid (keys %$info) {
foreach my $item (@{$info->{$storeid}}) {
next if !($item->{volid} && $item->{size});
+ $item->{path} = PVE::Storage::path($cfg, $item->{volid});
$volid_hash->{$item->{volid}} = $item;
}
}
return $volid_hash;
}
+sub get_used_paths {
+ my ($vmid, $storecfg, $conf, $scan_snapshots, $skip_drive) = @_;
+
+ my $used_path = {};
+
+ my $scan_config = sub {
+ my ($cref, $snapname) = @_;
+
+ foreach my $key (keys %$cref) {
+ my $value = $cref->{$key};
+ if (valid_drivename($key)) {
+ next if $skip_drive && $key eq $skip_drive;
+ my $drive = parse_drive($key, $value);
+ next if !$drive || !$drive->{file} || drive_is_cdrom($drive);
+ if ($drive->{file} =~ m!^/!) {
+ $used_path->{$drive->{file}}++; # = 1;
+ } else {
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);
+ next if !$storeid;
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid, 1);
+ next if !$scfg;
+ my $path = PVE::Storage::path($storecfg, $drive->{file}, $snapname);
+ $used_path->{$path}++; # = 1;
+ }
+ }
+ }
+ };
+
+ &$scan_config($conf);
+
+ undef $skip_drive;
+
+ if ($scan_snapshots) {
+ foreach my $snapname (keys %{$conf->{snapshots}}) {
+ &$scan_config($conf->{snapshots}->{$snapname}, $snapname);
+ }
+ }
+
+ return $used_path;
+}
+
sub update_disksize {
my ($vmid, $conf, $volid_hash) = @_;
-
+
my $changes;
my $used = {};
+ # Note: it is allowed to define multiple storages with same path (alias), so
+ # we need to check both 'volid' and real 'path' (two different volid can point
+ # to the same path).
+
+ my $usedpath = {};
+
# update size info
foreach my $opt (keys %$conf) {
- if (PVE::QemuServer::valid_drivename($opt)) {
- my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
+ if (valid_drivename($opt)) {
+ my $drive = parse_drive($opt, $conf->{$opt});
my $volid = $drive->{file};
next if !$volid;
$used->{$volid} = 1;
+ if ($volid_hash->{$volid} &&
+ (my $path = $volid_hash->{$volid}->{path})) {
+ $usedpath->{$path} = 1;
+ }
- next if PVE::QemuServer::drive_is_cdrom($drive);
+ next if drive_is_cdrom($drive);
next if !$volid_hash->{$volid};
$drive->{size} = $volid_hash->{$volid}->{size};
+ my $new = print_drive($vmid, $drive);
+ if ($new ne $conf->{$opt}) {
+ $changes = 1;
+ $conf->{$opt} = $new;
+ }
+ }
+ }
+
+ # remove 'unusedX' entry if volume is used
+ foreach my $opt (keys %$conf) {
+ 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})) {
$changes = 1;
- $conf->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
+ delete $conf->{$opt};
}
}
foreach my $volid (sort keys %$volid_hash) {
next if $volid =~ m/vm-$vmid-state-/;
next if $used->{$volid};
+ my $path = $volid_hash->{$volid}->{path};
+ next if !$path; # just to be sure
+ next if $usedpath->{$path};
$changes = 1;
- PVE::QemuServer::add_unused_volume($conf, $volid);
+ add_unused_volume($conf, $volid);
+ $usedpath->{$path} = 1; # avoid to add more than once (aliases)
}
return $changes;
my $updatefn = sub {
my ($vmid) = @_;
- my $conf = PVE::QemuServer::load_config($vmid);
-
- PVE::QemuServer::check_lock($conf);
+ my $conf = load_config($vmid);
+
+ check_lock($conf);
+
+ my $vm_volids = {};
+ foreach my $volid (keys %$volid_hash) {
+ my $info = $volid_hash->{$volid};
+ $vm_volids->{$volid} = $info if $info->{vmid} && $info->{vmid} == $vmid;
+ }
- my $changes = PVE::QemuServer::update_disksize($vmid, $conf, $volid_hash);
+ my $changes = update_disksize($vmid, $conf, $vm_volids);
- PVE::QemuServer::update_config_nolock($vmid, $conf, 1) if $changes;
+ update_config_nolock($vmid, $conf, 1) if $changes;
};
if (defined($vmid)) {
if ($nolock) {
&$updatefn($vmid);
} else {
- PVE::QemuServer::lock_config($vmid, $updatefn, $vmid);
+ lock_config($vmid, $updatefn, $vmid);
}
} else {
my $vmlist = config_list();
if ($nolock) {
&$updatefn($vmid);
} else {
- PVE::QemuServer::lock_config($vmid, $updatefn, $vmid);
- }
+ lock_config($vmid, $updatefn, $vmid);
+ }
}
}
}
} else {
die "unknown compression method '$comp'\n";
}
-
+
}
my $tmpdir = "/var/tmp/vzdumptmp$$";
my $rpcenv = PVE::RPCEnvironment::get();
- my $conffile = PVE::QemuServer::config_file($vmid);
+ my $conffile = config_file($vmid);
my $tmpfn = "$conffile.$$.tmp";
+ # Note: $oldconf is undef if VM does not exists
+ my $oldconf = PVE::Cluster::cfs_read_file(cfs_config_path($vmid));
+
my $print_devmap = sub {
my $virtdev_hash = {};
$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 $map = {};
my $cfg = cfs_read_file('storage.cfg');
+
+ # create empty/temp config
+ if ($oldconf) {
+ PVE::Tools::file_set_contents($conffile, "memory: 128\n");
+ foreach_drive($oldconf, sub {
+ my ($ds, $drive) = @_;
+
+ return if drive_is_cdrom($drive);
+
+ my $volid = $drive->{file};
+
+ return if !$volid || $volid =~ m|^/|;
+
+ my ($path, $owner) = PVE::Storage::path($cfg, $volid);
+ return if !$path || !$owner || ($owner != $vmid);
+
+ # Note: only delete disk we want to restore
+ # other volumes will become unused
+ if ($virtdev_hash->{$ds}) {
+ PVE::Storage::vdisk_free($cfg, $volid);
+ }
+ });
+ }
+
+ my $map = {};
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});
+
+ # test if requested format is supported
+ my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $d->{storeid});
+ my $supported = grep { $_ eq $d->{format} } @$validFormats;
+ $d->{format} = $defFormat if !$supported;
+
my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
$d->{format}, undef, $alloc_size);
print STDERR "new volume ID is '$volid'\n";
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 '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);
};
}
rmtree $tmpdir;
-
- rename $tmpfn, $conffile ||
+
+ rename($tmpfn, $conffile) ||
die "unable to commit configuration file '$conffile'\n";
+ PVE::Cluster::cfs_update(); # make sure we read new file
+
eval { rescan($vmid, 1); };
warn $@ if $@;
}
my ($archive, $vmid, $user, $opts) = @_;
if ($archive ne '-') {
- my $firstfile = archive_read_firstfile($archive);
+ my $firstfile = tar_archive_read_firstfile($archive);
die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n"
if $firstfile ne 'qemu-server.conf';
}
+ my $storecfg = cfs_read_file('storage.cfg');
+
+ # destroy existing data - keep empty config
+ my $vmcfgfn = PVE::QemuServer::config_file($vmid);
+ destroy_vm($storecfg, $vmid, 1) if -f $vmcfgfn;
+
my $tocmd = "/usr/lib/qemu-server/qmextract";
$tocmd .= " --storage " . PVE::Tools::shellquote($opts->{storage}) if $opts->{storage};
local $ENV{VZDUMP_VMID} = $vmid;
local $ENV{VZDUMP_USER} = $user;
- my $conffile = PVE::QemuServer::config_file($vmid);
+ my $conffile = config_file($vmid);
my $tmpfn = "$conffile.$$.tmp";
# disable interrupts (always do cleanups)
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();
unlink $tmpfn;
- restore_cleanup("$tmpdir/qmrestore.stat") if !$opts->{info};
+ tar_restore_cleanup($storecfg, "$tmpdir/qmrestore.stat") if !$opts->{info};
die $err;
}
rename $tmpfn, $conffile ||
die "unable to commit configuration file '$conffile'\n";
+ PVE::Cluster::cfs_update(); # make sure we read new file
+
eval { rescan($vmid, 1); };
warn $@ if $@;
};
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"
+ 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();
-
- foreach_drive($conf, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
- my $volid = $drive->{file};
-
- my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
- if ($storeid) {
- my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
- die "can't snapshot volume '$volid'\n"
- if !(($scfg->{path} && $volname =~ m/\.qcow2$/) ||
- ($scfg->{type} eq 'nexenta') ||
- ($scfg->{type} eq 'rbd') ||
- ($scfg->{type} eq 'sheepdog'));
- } elsif ($volid =~ m|^(/.+)$| && -e $volid) {
- die "snapshot device '$volid' is not possible\n";
- } else {
- die "can't snapshot volume '$volid'\n";
- }
- });
-
+ die "snapshot feature is not available" if !has_feature('snapshot', $conf, $storecfg);
$snap = $conf->{snapshots}->{$snapname} = {};
$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);
+ die "you can't rollback 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);
- 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) = @_;
my ($vmid) = @_;
for(;;) {
- my $stat = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-savevm");
+ my $stat = vm_mon_cmd_nocheck($vmid, "query-savevm");
if (!$stat->{status}) {
die "savevm not active\n";
} elsif ($stat->{status} eq 'active') {
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 $err = $@;
- eval { gqa_unfreezefs($vmid) if $running && $freezefs; };
+ eval { qga_unfreezefs($vmid) if $running && $freezefs; };
warn $@ if $@;
eval { vm_mon_cmd($vmid, "savevm-end") if $running; };
}
}
};
-
+
my $updatefn = sub {
my ($remove_drive) = @_;
my $conf = load_config($vmid);
- check_lock($conf) if !$drivehash;
+ if (!$drivehash) {
+ check_lock($conf);
+ 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});
lock_config($vmid, $updatefn);
}
+sub has_feature {
+ my ($feature, $conf, $storecfg, $snapname, $running) = @_;
+
+ my $err;
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+
+ return if drive_is_cdrom($drive);
+ my $volid = $drive->{file};
+ $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname, $running);
+ });
+
+ return $err ? 0 : 1;
+}
+
+sub template_create {
+ my ($vmid, $conf, $disk) = @_;
+
+ my $storecfg = PVE::Storage::config();
+
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+
+ return if drive_is_cdrom($drive);
+ return if $disk && $ds ne $disk;
+
+ my $volid = $drive->{file};
+ return if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
+
+ my $voliddst = PVE::Storage::vdisk_create_base($storecfg, $volid);
+ $drive->{file} = $voliddst;
+ $conf->{$ds} = print_drive($vmid, $drive);
+ update_config_nolock($vmid, $conf, 1);
+ });
+}
+
+sub is_template {
+ my ($conf) = @_;
+
+ return 1 if defined $conf->{template} && $conf->{template} == 1;
+}
+
+sub qemu_img_convert {
+ my ($src_volid, $dst_volid, $size, $snapname) = @_;
+
+ my $storecfg = PVE::Storage::config();
+ my ($src_storeid, $src_volname) = PVE::Storage::parse_volume_id($src_volid, 1);
+ my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1);
+
+ if ($src_storeid && $dst_storeid) {
+ my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid);
+ my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+
+ my $src_format = qemu_img_format($src_scfg, $src_volname);
+ my $dst_format = qemu_img_format($dst_scfg, $dst_volname);
+
+ my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
+ my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+
+ my $cmd = [];
+ push @$cmd, '/usr/bin/qemu-img', 'convert', '-t', 'writeback', '-p', '-C';
+ push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2");
+ push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path, $dst_path;
+
+ my $parser = sub {
+ my $line = shift;
+ if($line =~ m/\((\S+)\/100\%\)/){
+ my $percent = $1;
+ my $transferred = int($size * $percent / 100);
+ my $remaining = $size - $transferred;
+
+ print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n";
+ }
+
+ };
+
+ eval { run_command($cmd, timeout => undef, outfunc => $parser); };
+ my $err = $@;
+ die "copy failed: $err" if $err;
+ }
+}
+
+sub qemu_img_format {
+ my ($scfg, $volname) = @_;
+
+ if ($scfg->{path} && $volname =~ m/\.(raw|qcow2|qed|vmdk)$/) {
+ return $1;
+ } elsif ($scfg->{type} eq 'iscsi') {
+ return "host_device";
+ } else {
+ return "raw";
+ }
+}
+
+sub qemu_drive_mirror {
+ my ($vmid, $drive, $dst_volid, $vmiddst, $maxwait) = @_;
+
+ my $count = 1;
+ my $old_len = 0;
+ my $frozen = undef;
+
+ my $storecfg = PVE::Storage::config();
+ my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1);
+
+ if ($dst_storeid) {
+ my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+
+ my $format;
+ if ($dst_volname =~ m/\.(raw|qcow2)$/){
+ $format = $1;
+ }
+
+ my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+
+ if ($format) {
+ #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",
+ sync => "full", target => $dst_path, format => $format); };
+ } else {
+ eval { vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing",
+ sync => "full", target => $dst_path); };
+ }
+
+ eval {
+ while (1) {
+ 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 $transferred = $stat->{offset};
+ my $total = $stat->{len};
+ my $remaining = $total - $transferred;
+ my $percent = sprintf "%.2f", ($transferred * 100 / $total);
+
+ print "transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent %\n";
+
+ last if ($stat->{len} == $stat->{offset});
+ if ($old_len == $stat->{offset}) {
+ if ($maxwait && $count > $maxwait) {
+ # if writes to disk occurs the disk needs to be freezed
+ # to be able to complete the migration
+ vm_suspend($vmid,1);
+ $count = 0;
+ $frozen = 1;
+ } else {
+ $count++ unless $frozen;
+ }
+ } elsif ($frozen) {
+ vm_resume($vmid,1);
+ $count = 0;
+ }
+ $old_len = $stat->{offset};
+ sleep 1;
+ }
+
+ if ($vmiddst == $vmid) {
+ # switch the disk if source and destination are on the same guest
+ vm_mon_cmd($vmid, "block-job-complete", device => "drive-$drive");
+ }
+ };
+ if (my $err = $@) {
+ eval { vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive"); };
+ die "mirroring error: $err";
+ }
+
+ if ($vmiddst != $vmid) {
+ # if we clone a disk for a new target vm, we don't switch the disk
+ vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive");
+ }
+ }
+}
+
+sub clone_disk {
+ my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
+ $newvmid, $storage, $format, $full, $newvollist) = @_;
+
+ my $newvolid;
+
+ if (!$full) {
+ print "create linked clone of drive $drivename ($drive->{file})\n";
+ $newvolid = PVE::Storage::vdisk_clone($storecfg, $drive->{file}, $newvmid);
+ push @$newvollist, $newvolid;
+ } else {
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
+ $storeid = $storage if $storage;
+
+ my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
+ if (!$format) {
+ $format = $drive->{format} || $defFormat;
+ }
+
+ # test if requested format is supported - else use default
+ my $supported = grep { $_ eq $format } @$validFormats;
+ $format = $defFormat if !$supported;
+
+ my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
+
+ print "create full clone of drive $drivename ($drive->{file})\n";
+ $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $format, undef, ($size/1024));
+ push @$newvollist, $newvolid;
+
+ if (!$running || $snapname) {
+ 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);
+
+ my $disk = $drive;
+ $disk->{format} = undef;
+ $disk->{file} = $newvolid;
+ $disk->{size} = $size;
+
+ 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;