type => 'integer',
minimum => 0,
},
+ vmstate => {
+ optional => 1,
+ type => 'string', format => 'pve-volume-id',
+ description => "Reference to a volume which stores the VM state. This is used internally for snapshots.",
+ },
};
# what about other qemu settings ?
my $prop = shift;
foreach my $opt (keys %$confdesc) {
- next if $opt eq 'parent' || $opt eq 'snaptime';
+ next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate';
$prop->{$opt} = $confdesc->{$opt};
}
sub qemu_volume_snapshot_delete {
my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
- #need to implement statefile location
- my $statefile="/tmp/$vmid-$snap";
-
- unlink $statefile if -e $statefile;
-
my $running = PVE::QemuServer::check_running($vmid);
return if !PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
return if !$running;
- #need to split delvm monitor command like savevm
-
-}
-
-sub qemu_snapshot_start {
- my ($vmid, $snap) = @_;
-
- #need to implement statefile location
- my $statefile="/tmp/$vmid-$snap";
-
- vm_mon_cmd($vmid, "snapshot-start", statefile => $statefile);
-
-}
-
-sub qemu_snapshot_end {
- my ($vmid) = @_;
-
- vm_mon_cmd($vmid, "snapshot-end");
-
+ vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
}
sub qga_freezefs {
if ($statefile eq 'tcp') {
print "migration listens on port $migrate_port\n";
} else {
- unlink $statefile;
- # fixme: send resume - is that necessary ?
- eval { vm_mon_cmd($vmid, "cont"); };
+ if ($migratedfrom) {
+ unlink $statefile;
+ # fixme: send resume - is that necessary ?
+ eval { vm_mon_cmd($vmid, "cont"); };
+ }
}
}
next if $k eq 'snapshots';
next if $k eq 'snapstate';
next if $k eq 'snaptime';
+ next if $k eq 'vmstate';
next if $k eq 'lock';
next if $k eq 'digest';
next if $k eq 'description';
return $newconf;
};
+sub foreach_writable_storage {
+ my ($conf, $func) = @_;
+
+ my $sidhash = {};
+
+ foreach my $ds (keys %$conf) {
+ next if !valid_drivename($ds);
+
+ my $drive = parse_drive($ds, $conf->{$ds});
+ next if !$drive;
+ next if drive_is_cdrom($drive);
+
+ my $volid = $drive->{file};
+
+ my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+ $sidhash->{$sid} = $sid if $sid;
+ }
+
+ foreach my $sid (sort keys %$sidhash) {
+ &$func($sid);
+ }
+}
+
+my $alloc_vmstate_volid = sub {
+ my ($storecfg, $vmid, $conf, $snapname) = @_;
+
+ # Note: we try to be smart when selecting a $target storage
+
+ my $target;
+
+ # search shared storage first
+ foreach_writable_storage($conf, sub {
+ my ($sid) = @_;
+ my $scfg = PVE::Storage::storage_config($storecfg, $sid);
+ return if !$scfg->{shared};
+
+ $target = $sid if !$target || $scfg->{path}; # prefer file based storage
+ });
+
+ if (!$target) {
+ # now search local storage
+ foreach_writable_storage($conf, sub {
+ my ($sid) = @_;
+ my $scfg = PVE::Storage::storage_config($storecfg, $sid);
+ return if $scfg->{shared};
+
+ $target = $sid if !$target || $scfg->{path}; # prefer file based storage;
+ });
+ }
+
+ $target = 'local' if !$target;
+
+ my $driver_state_size = 32; # assume 32MB is enough to safe all driver state;
+ my $size = $conf->{memory} + $driver_state_size;
+
+ my $name = "vm-$vmid-state-$snapname";
+ my $scfg = PVE::Storage::storage_config($storecfg, $target);
+ $name .= ".raw" if $scfg->{path}; # add filename extension for file base storage
+ my $volid = PVE::Storage::vdisk_alloc($storecfg, $target, $vmid, 'raw', $name, $size*1024);
+
+ return $volid;
+};
+
my $snapshot_prepare = sub {
- my ($vmid, $snapname, $comment) = @_;
+ my ($vmid, $snapname, $save_vmstate, $comment) = @_;
my $snap;
my $storecfg = PVE::Storage::config();
- PVE::QemuServer::foreach_drive($conf, sub {
+ foreach_drive($conf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);
}
});
+
$snap = $conf->{snapshots}->{$snapname} = {};
+ if ($save_vmstate && check_running($vmid)) {
+ $snap->{vmstate} = &$alloc_vmstate_volid($storecfg, $vmid, $conf, $snapname);
+ }
+
&$snapshot_copy_config($conf, $snap);
$snap->{snapstate} = "prepare";
my $prepare = 1;
+ my $storecfg = PVE::Storage::config();
+
my $updatefn = sub {
my $conf = load_config($vmid);
- check_lock($conf) if $prepare;
+ if ($prepare) {
+ check_lock($conf);
+ vm_stop($storecfg, $vmid, undef, undef, 5, undef, undef);
+ }
die "unable to rollback vm $vmid: vm is running\n"
if check_running($vmid);
}
update_config_nolock($vmid, $conf, 1);
+
+ if (!$prepare && $snap->{vmstate}) {
+ my $statefile = PVE::Storage::path($storecfg, $snap->{vmstate});
+ # fixme: this only forws for files currently
+ vm_start($storecfg, $vmid, $statefile);
+ }
+
};
lock_config($vmid, $updatefn);
-
- my $storecfg = PVE::Storage::config();
foreach_drive($snap, sub {
my ($ds, $drive) = @_;
}
sub snapshot_create {
- my ($vmid, $snapname, $vmstate, $freezefs, $comment) = @_;
+ my ($vmid, $snapname, $save_vmstate, $freezefs, $comment) = @_;
- my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
+ my $snap = &$snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
- $freezefs = $vmstate = 0 if !check_running($vmid);
+ $freezefs = $save_vmstate = 0 if !$snap->{vmstate}; # vm is not running
my $drivehash = {};
+ my $running = check_running($vmid);
+
eval {
# create internal snapshots of all drives
-
- qemu_snapshot_start($vmid, $snapname) if $vmstate;
-
- qga_freezefs($vmid) if $freezefs;
my $storecfg = PVE::Storage::config();
+
+ if ($running) {
+ if ($snap->{vmstate}) {
+ my $path = PVE::Storage::path($storecfg, $snap->{vmstate});
+ vm_mon_cmd($vmid, "snapshot-start", statefile => $path);
+ } else {
+ vm_mon_cmd($vmid, "snapshot-start");
+ }
+ };
+
+ qga_freezefs($vmid) if $running && $freezefs;
foreach_drive($snap, sub {
my ($ds, $drive) = @_;
};
my $err = $@;
- eval { gqa_unfreezefs($vmid) if $freezefs; };
+ eval { gqa_unfreezefs($vmid) if $running && $freezefs; };
warn $@ if $@;
- eval { qemu_snapshot_end($vmid) if $vmstate; };
+ eval { vm_mon_cmd($vmid, "snapshot-end") if $running; };
warn $@ if $@;
if ($err) {
}
if ($remove_drive) {
- my $drive = parse_drive($remove_drive, $snap->{$remove_drive});
- my $volid = $drive->{file};
- delete $snap->{$remove_drive};
- add_unused_volume($conf, $volid);
+ if ($remove_drive eq 'vmstate') {
+ delete $snap->{$remove_drive};
+ } else {
+ my $drive = parse_drive($remove_drive, $snap->{$remove_drive});
+ my $volid = $drive->{file};
+ delete $snap->{$remove_drive};
+ add_unused_volume($conf, $volid);
+ }
}
if ($prepare) {
lock_config($vmid, $updatefn);
- # now remove all internal snapshots
+ # now remove vmstate file
my $storecfg = PVE::Storage::config();
- PVE::QemuServer::foreach_drive($snap, sub {
+ if ($snap->{vmstate}) {
+ eval { PVE::Storage::vdisk_free($storecfg, $snap->{vmstate}); };
+ if (my $err = $@) {
+ die $err if !$force;
+ warn $err;
+ }
+ # save changes (remove vmstate from snapshot)
+ lock_config($vmid, $updatefn, 'vmstate') if !$force;
+ };
+
+ # now remove all internal snapshots
+ foreach_drive($snap, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);