my $vmid = $1;
- my $split_config = sub {
- my ($raw) = @_;
- my $sections = [];
- my $tmp = '';
- while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
- my $line = $1;
- if(!$line) {
- push(@{$sections},$tmp);
- $tmp = '';
- } else {
- $tmp .= "$line\n";
+
+ my $network_counter = 0;
+ my $network_list = [];
+ my $host_ifnames = {};
+ my $snapname;
+ my $network;
+
+ my $find_next_hostif_name = sub {
+ for (my $i = 0; $i < 10; $i++) {
+ my $name = "veth${vmid}.$i";
+ if (!$host_ifnames->{$name}) {
+ $host_ifnames->{$name} = 1;
+ return $name;
}
}
- push(@{$sections},$tmp);
- return $sections;
+ die "unable to find free host_ifname"; # should not happen
};
- my $sec = &$split_config($raw);
-
- foreach my $sec_raw (@{$sec}){
- next if $sec_raw eq '';
- my $snapname = undef;
-
- my $network_counter = 0;
- my $network_list = [];
- my $host_ifnames = {};
-
- my $find_next_hostif_name = sub {
- for (my $i = 0; $i < 10; $i++) {
- my $name = "veth${vmid}.$i";
- if (!$host_ifnames->{$name}) {
- $host_ifnames->{$name} = 1;
- return $name;
- }
- }
-
- die "unable to find free host_ifname"; # should not happen
- };
-
- my $push_network = sub {
- my ($netconf) = @_;
- return if !$netconf;
- push @{$network_list}, $netconf;
- $network_counter++;
- if (my $netname = $netconf->{'veth.pair'}) {
- if ($netname =~ m/^veth(\d+).(\d)$/) {
- die "wrong vmid for network interface pair\n" if $1 != $vmid;
- my $host_ifnames->{$netname} = 1;
- } else {
- die "wrong network interface pair\n";
- }
- }
- };
-
- my $network;
-
- while ($sec_raw && $sec_raw =~ s/^(.*?)(\n|$)//) {
- my $line = $1;
-
- next if $line =~ m/^\#/;
- next if $line =~ m/^\s*$/;
-
- if ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
- my ($subkey, $value) = ($2, $3);
- if ($subkey eq 'type') {
- &$push_network($network);
- $network = { type => $value };
- } elsif ($valid_lxc_network_keys->{$subkey}) {
- $network->{$subkey} = $value;
- } else {
- die "unable to parse config line: $line\n";
- }
- next;
- }
- if ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
- my ($subkey, $value) = ($2, $3);
- if ($valid_pve_network_keys->{$subkey}) {
- $network->{$subkey} = $value;
- } else {
- die "unable to parse config line: $line\n";
- }
- next;
- }
- if ($line =~ m/^(snap\.)?(pve.snapcomment)\s*=\s*(\S.*)\s*$/) {
- my ($name, $value) = ($2, $3);
- if ($snapname) {
- $data->{snapshots}->{$snapname}->{$name} = $value;
- }
- next;
- }
- if ($line =~ m/^(snap\.)?pve\.snapname = (\w*)$/) {
- if (!$snapname) {
- $snapname = $2;
- $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
- } else {
- die "Configuarion broken\n";
- }
- next;
- }
- if ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
- my ($name, $value) = ($2, $3);
-
- if ($lxc_array_configs->{$name}) {
- $data->{$name} = [] if !defined($data->{$name});
- if ($snapname) {
- push @{$data->{snapshots}->{$snapname}->{$name}}, parse_lxc_option($name, $value);
- } else {
- push @{$data->{$name}}, parse_lxc_option($name, $value);
- }
- } else {
- if ($snapname) {
- die "multiple definitions for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
- $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
- } else {
- die "multiple definitions for $name\n" if defined($data->{$name});
- $data->{$name} = parse_lxc_option($name, $value);
- }
- }
-
- next;
+ my $push_network = sub {
+ my ($netconf) = @_;
+ return if !$netconf;
+ push @{$network_list}, $netconf;
+ $network_counter++;
+ if (my $netname = $netconf->{'veth.pair'}) {
+ if ($netname =~ m/^veth(\d+).(\d)$/) {
+ die "wrong vmid for network interface pair\n" if $1 != $vmid;
+ my $host_ifnames->{$netname} = 1;
+ } else {
+ die "wrong network interface pair\n";
}
- die "unable to parse config line: $line\n";
}
- &$push_network($network);
+ };
+ my $finalize_section = sub {
+ &$push_network($network); # flush
+
foreach my $net (@{$network_list}) {
next if $net->{type} eq 'empty'; # skip
$net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
$net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
-
+
if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
if ($snapname) {
$data->{snapshots}->{$snapname}->{"net$1"} = $net;
}
}
}
+
+ # reset helper vars
+ $network_counter = 0;
+ $network_list = [];
+ $host_ifnames = {};
+ $network = undef;
+ };
+
+ while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
+ my $line = $1;
+ next if $line =~ m/^\s*$/; # skip empty lines
+ next if $line =~ m/^#/; # skip comments
+
+ # snap.pve.snapname starts new sections
+ if ($line =~ m/^(snap\.)?pve\.snapname\s*=\s*(\w*)\s*$/) {
+ my $value = $2;
+
+ &$finalize_section();
+
+ $snapname = $value;
+ $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
+
+ } elsif ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
+ my ($subkey, $value) = ($2, $3);
+ if ($subkey eq 'type') {
+ &$push_network($network);
+ $network = { type => $value };
+ } elsif ($valid_lxc_network_keys->{$subkey}) {
+ $network->{$subkey} = $value;
+ } else {
+ die "unable to parse config line: $line\n";
+ }
+ } elsif ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
+ my ($subkey, $value) = ($2, $3);
+ if ($valid_pve_network_keys->{$subkey}) {
+ $network->{$subkey} = $value;
+ } else {
+ die "unable to parse config line: $line\n";
+ }
+ } elsif ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
+ my ($name, $value) = ($2, $3);
+
+ if ($lxc_array_configs->{$name}) {
+ $data->{$name} = [] if !defined($data->{$name});
+ if ($snapname) {
+ push @{$data->{snapshots}->{$snapname}->{$name}}, parse_lxc_option($name, $value);
+ } else {
+ push @{$data->{$name}}, parse_lxc_option($name, $value);
+ }
+ } else {
+ if ($snapname) {
+ die "multiple definitions for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
+ $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
+ } else {
+ die "multiple definitions for $name\n" if defined($data->{$name});
+ $data->{$name} = parse_lxc_option($name, $value);
+ }
+ }
+ } else {
+ die "unable to parse config line: $line\n";
+ }
}
+ &$finalize_section();
+
return $data;
}
&$snapshot_commit($vmid, $snapname);
};
if(my $err = $@) {
- #ToDo implement delete snapshot
+ snapshot_delete($vmid, $snapname, 1);
die "$err\n";
}
}
+sub snapshot_delete {
+ my ($vmid, $snapname, $force) = @_;
+
+ my $snap;
+
+ my $conf;
+
+ my $updatefn = sub {
+
+ $conf = load_config($vmid);
+
+ $snap = $conf->{snapshots}->{$snapname};
+
+ check_lock($conf);
+
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+ $snap->{'pve.snapstate'} = 'delete';
+
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ lock_container($vmid, 10, $updatefn);
+
+ my $storecfg = PVE::Storage::config();
+
+ my $del_snap = sub {
+
+ check_lock($conf);
+
+ if ($conf->{'pve.parent'} eq $snapname) {
+ if ($conf->{snapshots}->{$snapname}->{'pve.snapname'}) {
+ $conf->{'pve.parent'} = $conf->{snapshots}->{$snapname}->{'pve.parent'};
+ } else {
+ delete $conf->{'pve.parent'};
+ }
+ }
+
+ delete $conf->{snapshots}->{$snapname};
+
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ my $volid = $conf->{snapshots}->{$snapname}->{'pve.volid'};
+
+ eval {
+ PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
+ };
+ my $err = $@;
+
+ if(!$err || ($err && $force)) {
+ lock_container($vmid, 10, $del_snap);
+ if ($err) {
+ die "Can't delete snapshot: $vmid $snapname $err\n";
+ }
+ }
+}
+
+sub snapshot_rollback {
+ my ($vmid, $snapname) = @_;
+
+ my $storecfg = PVE::Storage::config();
+
+ my $conf = load_config($vmid);
+
+ my $snap = $conf->{snapshots}->{$snapname};
+
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+ PVE::Storage::volume_rollback_is_possible($storecfg, $snap->{'pve.volid'},
+ $snapname);
+
+ my $updatefn = sub {
+
+ die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" if $snap->{snapstate};
+
+ check_lock($conf);
+
+ system("lxc-stop -n $vmid --kill") if check_running($vmid);
+
+ die "unable to rollback vm $vmid: vm is running\n"
+ if check_running($vmid);
+
+ $conf->{'pve.lock'} = 'rollback';
+
+ my $forcemachine;
+
+ # copy snapshot config to current config
+
+ my $tmp_conf = $conf;
+ &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
+ $conf->{snapshots} = $tmp_conf->{snapshots};
+ delete $conf->{'pve.snaptime'};
+ delete $conf->{'pve.snapname'};
+ $conf->{'pve.parent'} = $snapname;
+
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ my $unlockfn = sub {
+ delete $conf->{'pve.lock'};
+ PVE::LXC::write_config($vmid, $conf);
+ };
+
+ lock_container($vmid, 10, $updatefn);
+
+ PVE::Storage::volume_snapshot_rollback($storecfg, $conf->{'pve.volid'}, $snapname);
+
+ lock_container($vmid, 5, $unlockfn);
+}
+
1;