mirror of
https://git.proxmox.com/git/qemu-server
synced 2025-06-13 13:04:26 +00:00
add save/load snapshot information, add basic snapshot handling stubs
This commit is contained in:
parent
fc46aff9fa
commit
0d18dcfc27
@ -33,7 +33,7 @@ my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
|
||||
# Note about locking: we use flock on the config file protect
|
||||
# against concurent actions.
|
||||
# Aditionaly, we have a 'lock' setting in the config file. This
|
||||
# can be set to 'migrate' or 'backup'. Most actions are not
|
||||
# can be set to 'migrate', 'backup' or 'snapshot'. Most actions are not
|
||||
# allowed when such lock is set. But you can ignore this kind of
|
||||
# lock with the --skiplock flag.
|
||||
|
||||
@ -171,7 +171,7 @@ my $confdesc = {
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
description => "Lock/unlock the VM.",
|
||||
enum => [qw(migrate backup)],
|
||||
enum => [qw(migrate backup snapshot)],
|
||||
},
|
||||
cpulimit => {
|
||||
optional => 1,
|
||||
@ -1547,6 +1547,7 @@ sub parse_vm_config {
|
||||
|
||||
my $res = {
|
||||
digest => Digest::SHA::sha1_hex($raw),
|
||||
snapshots => {},
|
||||
};
|
||||
|
||||
$filename =~ m|/qemu-server/(\d+)\.conf$|
|
||||
@ -1554,12 +1555,20 @@ sub parse_vm_config {
|
||||
|
||||
my $vmid = $1;
|
||||
|
||||
my $conf = $res;
|
||||
my $descr = '';
|
||||
|
||||
while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
|
||||
my $line = $1;
|
||||
|
||||
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;
|
||||
my $descr = '';
|
||||
$conf = $res->{snapshots}->{$snapname} = {};
|
||||
next;
|
||||
}
|
||||
|
||||
if ($line =~ m/^\#(.*)\s*$/) {
|
||||
$descr .= PVE::Tools::decode_text($1) . "\n";
|
||||
@ -1568,10 +1577,14 @@ sub parse_vm_config {
|
||||
|
||||
if ($line =~ m/^(description):\s*(.*\S)\s*$/) {
|
||||
$descr .= PVE::Tools::decode_text($2);
|
||||
} elsif ($line =~ m/parent:\s*([a-z][a-z0-9_\-]+)\s*$/) {
|
||||
$conf->{parent} = $1;
|
||||
} elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
|
||||
$conf->{snapstate} = $1;
|
||||
} elsif ($line =~ m/^(args):\s*(.*\S)\s*$/) {
|
||||
my $key = $1;
|
||||
my $value = $2;
|
||||
$res->{$key} = $value;
|
||||
$conf->{$key} = $value;
|
||||
} elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
|
||||
my $key = $1;
|
||||
my $value = $2;
|
||||
@ -1592,21 +1605,18 @@ sub parse_vm_config {
|
||||
}
|
||||
|
||||
if ($key eq 'cdrom') {
|
||||
$res->{ide2} = $value;
|
||||
$conf->{ide2} = $value;
|
||||
} else {
|
||||
$res->{$key} = $value;
|
||||
$conf->{$key} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$res->{description} = $descr if $descr;
|
||||
$conf->{description} = $descr if $descr;
|
||||
|
||||
# convert old smp to sockets
|
||||
if ($res->{smp} && !$res->{sockets}) {
|
||||
$res->{sockets} = $res->{smp};
|
||||
}
|
||||
delete $res->{smp};
|
||||
delete $res->{parent}; # just to be sure
|
||||
delete $res->{snapstate}; # just to be sure
|
||||
|
||||
return $res;
|
||||
}
|
||||
@ -1614,6 +1624,9 @@ sub parse_vm_config {
|
||||
sub write_vm_config {
|
||||
my ($filename, $conf) = @_;
|
||||
|
||||
delete $conf->{parent}; # just to be sure
|
||||
delete $conf->{snapstate}; # just to be sure
|
||||
|
||||
if ($conf->{cdrom}) {
|
||||
die "option ide2 conflicts with cdrom\n" if $conf->{ide2};
|
||||
$conf->{ide2} = $conf->{cdrom};
|
||||
@ -1629,9 +1642,11 @@ sub write_vm_config {
|
||||
delete $conf->{smp};
|
||||
}
|
||||
|
||||
# fixme: unused drives and snapshots??!!
|
||||
|
||||
my $new_volids = {};
|
||||
foreach my $key (keys %$conf) {
|
||||
next if $key eq 'digest' || $key eq 'description';
|
||||
next if $key eq 'digest' || $key eq 'description' || $key eq 'snapshots';
|
||||
my $value = $conf->{$key};
|
||||
eval { $value = check_type($key, $value); };
|
||||
die "unable to parse value of '$key' - $@" if $@;
|
||||
@ -1652,18 +1667,28 @@ sub write_vm_config {
|
||||
}
|
||||
}
|
||||
|
||||
# gererate RAW data
|
||||
my $raw = '';
|
||||
my $generate_raw_config = sub {
|
||||
my ($conf) = @_;
|
||||
|
||||
# add description as comment to top of file
|
||||
my $descr = $conf->{description} || '';
|
||||
foreach my $cl (split(/\n/, $descr)) {
|
||||
$raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
|
||||
}
|
||||
my $raw = '';
|
||||
|
||||
foreach my $key (sort keys %$conf) {
|
||||
next if $key eq 'digest' || $key eq 'description';
|
||||
$raw .= "$key: $conf->{$key}\n";
|
||||
# add description as comment to top of file
|
||||
my $descr = $conf->{description} || '';
|
||||
foreach my $cl (split(/\n/, $descr)) {
|
||||
$raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
|
||||
}
|
||||
|
||||
foreach my $key (sort keys %$conf) {
|
||||
next if $key eq 'digest' || $key eq 'description' || $key eq 'snapshots';
|
||||
$raw .= "$key: $conf->{$key}\n";
|
||||
}
|
||||
return $raw;
|
||||
};
|
||||
|
||||
my $raw = &$generate_raw_config($conf);
|
||||
foreach my $snapname (sort keys %{$conf->{snapshots}}) {
|
||||
$raw .= "\n[$snapname]\n";
|
||||
$raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
|
||||
}
|
||||
|
||||
return $raw;
|
||||
@ -3520,4 +3545,162 @@ sub restore_archive {
|
||||
die "unable to commit configuration file '$conffile'\n";
|
||||
};
|
||||
|
||||
|
||||
# Internal snapshots
|
||||
|
||||
# NOTE: Snapshot create/delete involves several non-atomic
|
||||
# action, and can take a long time.
|
||||
# So we try to avoid locking the file and use 'lock' variable
|
||||
# inside the config file instead.
|
||||
|
||||
my $snapshot_prepare = sub {
|
||||
my ($vmid, $snapname, $parent) = @_;
|
||||
|
||||
my $updatefn = sub {
|
||||
|
||||
my $conf = load_config($vmid);
|
||||
|
||||
check_lock($conf);
|
||||
|
||||
die "snapshot name '$snapname' already used\n"
|
||||
if defined($conf->{snapshots}->{$snapname});
|
||||
|
||||
my $snap = $conf->{snapshots}->{$snapname} = {
|
||||
snapstate => "prepare",
|
||||
};
|
||||
|
||||
my $parentconf = $conf;
|
||||
if ($parent) {
|
||||
$parentconf = $conf->{snapshots}->{$parent};
|
||||
die "parent snapshot '$parent' does not exist\n"
|
||||
if !defined($parentconf);
|
||||
}
|
||||
|
||||
foreach my $k (keys %$parentconf) {
|
||||
next if $k eq 'snapshots';
|
||||
next if $k eq 'lock';
|
||||
next if $k eq 'digest';
|
||||
|
||||
$snap->{$k} = $parentconf->{$k};
|
||||
}
|
||||
|
||||
if ($parent) {
|
||||
$snap->{parent} = $parent;
|
||||
} else {
|
||||
delete $snap->{parent};
|
||||
}
|
||||
|
||||
update_config_nolock($vmid, $conf, 1);
|
||||
};
|
||||
|
||||
lock_config($vmid, $updatefn);
|
||||
};
|
||||
|
||||
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 $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};
|
||||
|
||||
# copy snapshot confi to current config
|
||||
my $newconf = {
|
||||
snapshots => $conf->{snapshots},
|
||||
};
|
||||
foreach my $k (keys %$snap) {
|
||||
next if $k eq 'snapshots';
|
||||
next if $k eq 'lock';
|
||||
next if $k eq 'digest';
|
||||
|
||||
$newconf->{$k} = $snap->{$k};
|
||||
}
|
||||
|
||||
update_config_nolock($vmid, $newconf, 1);
|
||||
};
|
||||
|
||||
lock_config($vmid, $updatefn);
|
||||
};
|
||||
|
||||
sub snapshot_create {
|
||||
my ($vmid, $snapname, $parent) = @_;
|
||||
|
||||
&$snapshot_prepare($vmid, $snapname, $parent);
|
||||
|
||||
eval {
|
||||
# create internal snapshots of all drives
|
||||
|
||||
die "implement me\n";
|
||||
};
|
||||
if (my $err = $@) {
|
||||
warn "snapshot create failed: starting cleanup\n";
|
||||
eval { snapshot_delete($vmid, $snapname); };
|
||||
warn $@ if $@;
|
||||
die $err;
|
||||
}
|
||||
|
||||
&$snapshot_commit($vmid, $snapname);
|
||||
}
|
||||
|
||||
sub snapshot_delete {
|
||||
my ($vmid, $snapname, $force) = @_;
|
||||
|
||||
my $prepare = 1;
|
||||
|
||||
my $conf;
|
||||
|
||||
my $updatefn = sub {
|
||||
|
||||
$conf = load_config($vmid);
|
||||
|
||||
check_lock($conf) if !$force;
|
||||
|
||||
my $snap = $conf->{snapshots}->{$snapname};
|
||||
|
||||
die "snapshot '$snapname' does not exist\n" if !defined($snap);
|
||||
|
||||
# remove parent refs
|
||||
foreach my $sn (keys %{$conf->{snapshots}}) {
|
||||
next if $sn eq $snapname;
|
||||
my $snapref = $conf->{snapshots}->{$sn};
|
||||
if ($snapref->{parent} && $snapref->{parent} eq $snapname) {
|
||||
if ($snap->{parent}) {
|
||||
$snapref->{parent} = $snap->{parent};
|
||||
} else {
|
||||
delete $snapref->{parent};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($prepare) {
|
||||
$snap->{snapstate} = 'delete';
|
||||
} else {
|
||||
delete $conf->{snapshots}->{$snapname};
|
||||
}
|
||||
|
||||
update_config_nolock($vmid, $conf, 1);
|
||||
};
|
||||
|
||||
lock_config($vmid, $updatefn);
|
||||
|
||||
# now remove all internal snapshots
|
||||
|
||||
# fixme: implement this
|
||||
|
||||
# now cleanup config
|
||||
$prepare = 0;
|
||||
lock_config($vmid, $updatefn);
|
||||
}
|
||||
|
||||
1;
|
||||
|
Loading…
Reference in New Issue
Block a user