-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 = 500; # assume 32MB is enough to safe all driver state;
- # we abort live save after $conf->{memory}, so we need at max twice that space
- my $size = $conf->{memory}*2 + $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, $save_vmstate, $comment) = @_;
-
- my $snap;
-
- my $updatefn = sub {
-
- 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});
-
- my $storecfg = PVE::Storage::config();
- die "snapshot feature is not available" if !has_feature('snapshot', $conf, $storecfg);
-
- $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";
- $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);
- };
-
- lock_config($vmid, $updatefn);
-
- return $snap;
-};
-
-my $snapshot_commit = sub {
- my ($vmid, $snapname) = @_;
-
- my $updatefn = sub {
-
- my $conf = load_config($vmid);
-
- die "missing snapshot lock\n"
- if !($conf->{lock} && $conf->{lock} eq 'snapshot');
-
- my $has_machine_config = defined($conf->{machine});
-
- my $snap = $conf->{snapshots}->{$snapname};
-
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
- die "wrong snapshot state\n"
- if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
-
- delete $snap->{snapstate};
- delete $conf->{lock};
-
- my $newconf = &$snapshot_apply_config($conf, $snap);
-
- delete $newconf->{machine} if !$has_machine_config;
-
- $newconf->{parent} = $snapname;
-
- update_config_nolock($vmid, $newconf, 1);
- };
-
- lock_config($vmid, $updatefn);
-};
-
-sub snapshot_rollback {
- my ($vmid, $snapname) = @_;
-
- my $prepare = 1;
-
- my $storecfg = PVE::Storage::config();
-
- my $conf = load_config($vmid);
-
- my $get_snapshot_config = sub {
-
- die "you can't rollback if vm is a template\n" if is_template($conf);
-
- my $res = $conf->{snapshots}->{$snapname};
-
- die "snapshot '$snapname' does not exist\n" if !defined($res);
-
- return $res;
- };
-
- my $snap = &$get_snapshot_config();
-
- foreach_drive($snap, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
-
- my $volid = $drive->{file};
-
- PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
- });
-
- my $updatefn = sub {
-
- $conf = load_config($vmid);
-
- $snap = &$get_snapshot_config();
-
- die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
- if $snap->{snapstate};
-
- 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);
-
- if ($prepare) {
- $conf->{lock} = 'rollback';
- } else {
- die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
- 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, undef, undef, undef, $forcemachine);
- }
- };
-
- lock_config($vmid, $updatefn);
-
- foreach_drive($snap, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
-
- my $volid = $drive->{file};
- my $device = "drive-$ds";
-
- PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
- });
-
- $prepare = 0;
- lock_config($vmid, $updatefn);
-}
-
-my $savevm_wait = sub {
- my ($vmid) = @_;
-
- for(;;) {
- my $stat = vm_mon_cmd_nocheck($vmid, "query-savevm");
- if (!$stat->{status}) {
- die "savevm not active\n";
- } elsif ($stat->{status} eq 'active') {
- sleep(1);
- next;
- } elsif ($stat->{status} eq 'completed') {
- last;
- } else {
- die "query-savevm returned status '$stat->{status}'\n";
- }
- }
-};
-