]> git.proxmox.com Git - pve-container.git/blame - src/PVE/VZDump/LXC.pm
vzdump: correctly implement snapshot backup
[pve-container.git] / src / PVE / VZDump / LXC.pm
CommitLineData
d14a9a1b
DM
1package PVE::VZDump::LXC;
2
3use strict;
4use warnings;
5use File::Path;
6use File::Basename;
7use PVE::INotify;
8use PVE::Cluster qw(cfs_read_file);
9use PVE::Storage;
10use PVE::VZDump;
11use PVE::LXC;
514b5f82 12use PVE::Tools;
d14a9a1b
DM
13
14use base qw (PVE::VZDump::Plugin);
15
4ca61ce8
DM
16my $default_mount_point = "/mnt/vzsnap0";
17
d14a9a1b
DM
18my $rsync_vm = sub {
19 my ($self, $task, $from, $to, $text) = @_;
20
21 $self->loginfo ("starting $text sync $from to $to");
22
23 my $starttime = time();
24
25 my $opts = $self->{vzdump}->{opts};
26
27 my $rsyncopts = "--stats -x -X --numeric-ids";
28
29 $rsyncopts .= " --bwlimit=$opts->{bwlimit}" if $opts->{bwlimit};
30
31 $self->cmd ("rsync $rsyncopts -aH --delete --no-whole-file --inplace '$from' '$to'");
32
33 my $delay = time () - $starttime;
34
35 $self->loginfo ("$text sync finished ($delay seconds)");
36};
37
38sub new {
39 my ($class, $vzdump) = @_;
40
41 PVE::VZDump::check_bin('lxc-stop');
42 PVE::VZDump::check_bin('lxc-start');
43 PVE::VZDump::check_bin('lxc-freeze');
44 PVE::VZDump::check_bin('lxc-unfreeze');
45
46 my $self = bless {};
47
48 $self->{vzdump} = $vzdump;
49 $self->{storecfg} = PVE::Storage::config();
50
51 $self->{vmlist} = PVE::LXC::config_list();
52
53 return $self;
54}
55
56sub type {
57 return 'lxc';
58}
59
60sub vm_status {
61 my ($self, $vmid) = @_;
62
63 my $running = PVE::LXC::check_running($vmid) ? 1 : 0;
64
65 return wantarray ? ($running, $running ? 'running' : 'stopped') : $running;
66}
67
4ca61ce8 68my $check_mountpoint_empty = sub {
b739f640
DM
69 my ($mountpoint) = @_;
70
4ca61ce8
DM
71 die "mountpoint '$mountpoint' is not a directory\n" if ! -d $mountpoint;
72
b739f640
DM
73 PVE::Tools::dir_glob_foreach($mountpoint, qr/.*/, sub {
74 my $entry = shift;
75 return if $entry eq '.' || $entry eq '..';
4ca61ce8 76 die "mountpoint '$mountpoint' not empty\n";
b739f640 77 });
d14a9a1b
DM
78};
79
80sub prepare {
81 my ($self, $task, $vmid, $mode) = @_;
82
83 my $conf = $self->{vmlist}->{$vmid} = PVE::LXC::load_config($vmid);
84
14731ec6
DM
85 PVE::LXC::foreach_mountpoint($conf, sub {
86 my ($ms, $mountpoint) = @_;
87
88 return if $ms eq 'rootfs';
89 # TODO: implement support for mountpoints
90 die "unable to backup mountpoint '$ms' - feature not implemented\n";
91 });
92
d14a9a1b
DM
93 my $running = PVE::LXC::check_running($vmid);
94
4ca61ce8 95 my $diskinfo = $task->{diskinfo} = {};
d14a9a1b 96
27916659 97 $task->{hostname} = $conf->{'hostname'} || "CT$vmid";
d14a9a1b 98
27916659 99 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
4ca61ce8 100 $diskinfo->{volid} = $rootinfo->{volume};
d14a9a1b 101
4ca61ce8 102 die "missing root volid (no volid)\n" if !$diskinfo->{volid};
c135fbf1 103
27916659 104 # fixme: when do we deactivate ??
4ca61ce8 105 PVE::Storage::activate_volumes($self->{storecfg}, [$diskinfo->{volid}]);
d14a9a1b 106
4ca61ce8 107 $self->loginfo("TEST: prepare");
d14a9a1b
DM
108 if ($mode eq 'snapshot') {
109
4ca61ce8
DM
110 if (!PVE::LXC::has_feature('snapshot', $conf, $self->{storecfg})) {
111 die "mode failure - some volumes does not support snapshots\n";
112 }
d14a9a1b 113
4ca61ce8
DM
114 if ($conf->{snapshots} && $conf->{snapshots}->{vzdump}) {
115 $self->loginfo("found old vzdump snapshot (force removal)");
116 PVE::LXC::snapshot_delete($vmid, 'vzdump', 0);
117 }
d14a9a1b 118
4ca61ce8
DM
119 my $mountpoint = $default_mount_point;
120 mkpath $mountpoint;
121 &$check_mountpoint_empty($mountpoint);
d14a9a1b 122
4ca61ce8
DM
123 # set snapshot_count (freezes CT it snapshot_count > 1)
124 my $volid_list = PVE::LXC::get_vm_volumes($conf);
125 $task->{snapshot_count} = scalar(@$volid_list);
d14a9a1b 126
f5313774 127 } elsif ($mode eq 'stop') {
4ca61ce8
DM
128 my $mountpoint = $default_mount_point;
129 mkpath $mountpoint;
130 &$check_mountpoint_empty($mountpoint);
b739f640 131
4ca61ce8 132 my $volid_list = [$diskinfo->{volid}];
b739f640
DM
133 $task->{cleanup}->{dettach_loops} = $volid_list;
134 my $loopdevs = PVE::LXC::attach_loops($self->{storecfg}, $volid_list);
4ca61ce8 135 my $mp = { volume => $diskinfo->{volid}, mp => "/" };
b739f640 136 PVE::LXC::mountpoint_mount($mp, $mountpoint, $self->{storecfg}, $loopdevs);
f5313774
DM
137 $diskinfo->{dir} = $diskinfo->{mountpoint} = $mountpoint;
138 $task->{snapdir} = $diskinfo->{dir};
139 } elsif ($mode eq 'suspend') {
632eca5a
DM
140 my $pid = PVE::LXC::find_lxc_pid($vmid);
141 $diskinfo->{dir} = "/proc/$pid/root";
f5313774
DM
142 $task->{snapdir} = $task->{tmpdir};
143 } else {
144 die "unknown mode '$mode'\n"; # should not happen
d14a9a1b 145 }
d14a9a1b
DM
146}
147
148sub lock_vm {
149 my ($self, $vmid) = @_;
150
151 PVE::LXC::lock_aquire($vmid);
152}
153
154sub unlock_vm {
155 my ($self, $vmid) = @_;
156
157 PVE::LXC::lock_release($vmid);
158}
159
4ca61ce8
DM
160sub snapshot {
161 my ($self, $task, $vmid) = @_;
162
163 my $diskinfo = $task->{diskinfo};
164
165 $self->loginfo("create storage snapshot snapshot");
166
167 # todo: freeze/unfreeze if we have more than one volid
168 PVE::LXC::snapshot_create($vmid, 'vzdump', "vzdump backup snapshot");
169 $task->{cleanup}->{remove_snapshot} = 1;
170
171 # reload config
172 my $conf = $self->{vmlist}->{$vmid} = PVE::LXC::load_config($vmid);
173 die "unable to read vzdump shanpshot config - internal error"
174 if !($conf->{snapshots} && $conf->{snapshots}->{vzdump});
175
176 # my $snapconf = $conf->{snapshots}->{vzdump};
177 # my $volid_list = PVE::LXC::get_vm_volumes($snapconf);
178 my $volid_list = [$diskinfo->{volid}];
179
180 $task->{cleanup}->{dettach_loops} = $volid_list;
181 my $loopdevs = PVE::LXC::attach_loops($self->{storecfg}, $volid_list, 'vzdump');
182
183 my $mountpoint = $default_mount_point;
184
185 my $mp = { volume => $diskinfo->{volid}, mp => "/" };
186 PVE::LXC::mountpoint_mount($mp, $mountpoint, $self->{storecfg}, $loopdevs, 'vzdump');
187
188 $diskinfo->{dir} = $diskinfo->{mountpoint} = $mountpoint;
189 $task->{snapdir} = $diskinfo->{dir};
190}
191
d14a9a1b
DM
192sub copy_data_phase1 {
193 my ($self, $task) = @_;
194
195 $self->$rsync_vm($task, "$task->{diskinfo}->{dir}/", $task->{snapdir}, "first");
196}
197
198sub copy_data_phase2 {
199 my ($self, $task) = @_;
200
201 $self->$rsync_vm ($task, "$task->{diskinfo}->{dir}/", $task->{snapdir}, "final");
202}
203
204sub stop_vm {
205 my ($self, $task, $vmid) = @_;
206
207 $self->cmd("lxc-stop -n $vmid");
208}
209
210sub start_vm {
211 my ($self, $task, $vmid) = @_;
212
213 $self->cmd ("lxc-start -n $vmid");
214}
215
216sub suspend_vm {
217 my ($self, $task, $vmid) = @_;
218
219 $self->cmd ("lxc-freeze -n $vmid");
220}
221
222sub resume_vm {
223 my ($self, $task, $vmid) = @_;
224
225 $self->cmd ("lxc-unfreeze -n $vmid");
226}
227
228sub assemble {
229 my ($self, $task, $vmid) = @_;
230
cbd6753d 231 my $tmpdir = $task->{tmpdir};
d14a9a1b 232
cbd6753d 233 mkpath "$tmpdir/etc/vzdump/";
514b5f82
WL
234
235 my $conf = PVE::LXC::load_config($vmid);
236 delete $conf->{snapshots};
237 delete $conf->{'pve.parent'};
238
cbd6753d 239 PVE::Tools::file_set_contents("$tmpdir/etc/vzdump/pct.conf", PVE::LXC::write_pct_config("/lxc/$vmid.conf", $conf));
d14a9a1b
DM
240}
241
242sub archive {
243 my ($self, $task, $vmid, $filename, $comp) = @_;
cbd6753d 244
d14a9a1b 245 my $findexcl = $self->{vzdump}->{findexcl};
cbd6753d
DM
246 push @$findexcl, "'('", '-path', "./etc/vzdump", "-prune", "')'", '-o';
247
d14a9a1b
DM
248 my $findargs = join (' ', @$findexcl) . ' -print0';
249 my $opts = $self->{vzdump}->{opts};
250
251 my $srcdir = $task->{diskinfo}->{dir};
252 my $snapdir = $task->{snapdir};
cbd6753d 253 my $tmpdir = $task->{tmpdir};
d14a9a1b
DM
254
255 my $taropts = "--totals --sparse --numeric-owner --no-recursion --xattrs --one-file-system";
256
257 # note: --remove-files does not work because we do not
258 # backup all files (filters). tar complains:
259 # Cannot rmdir: Directory not empty
260 # we we disable this optimization for now
261 #if ($snapdir eq $task->{tmpdir} && $snapdir =~ m|^$opts->{dumpdir}/|) {
262 # $taropts .= " --remove-files"; # try to save space
263 #}
264
265 my $cmd = "(";
266
267 $cmd .= "cd $snapdir;find . $findargs|sed 's/\\\\/\\\\\\\\/g'|";
cbd6753d
DM
268 $cmd .= "tar cpf - $taropts ";
269 # The directory parameter can give a alternative directory as source.
270 # the second parameter gives the structure in the tar.
271 $cmd .= "--directory=$tmpdir ./etc/vzdump/pct.conf ";
272 $cmd .= "--directory=$snapdir --null -T -";
273
d14a9a1b
DM
274 my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream
275 $cmd .= "|cstream -t $bwl" if $opts->{bwlimit};
276 $cmd .= "|$comp" if $comp;
277
278 $cmd .= ")";
279
280 if ($opts->{stdout}) {
148d1cb4 281 $self->cmd ($cmd, output => ">&" . fileno($opts->{stdout}));
d14a9a1b
DM
282 } else {
283 $self->cmd ("$cmd >$filename");
284 }
285}
286
287sub cleanup {
288 my ($self, $task, $vmid) = @_;
289
4ca61ce8 290 my $diskinfo = $task->{diskinfo};
d14a9a1b 291
4ca61ce8 292 if (my $mountpoint = $diskinfo->{mountpoint}) {
b739f640
DM
293 PVE::Tools::run_command(['umount', '-l', '-d', $mountpoint]);
294 };
295
4ca61ce8
DM
296 if (my $volid_list = $task->{cleanup}->{dettach_vzdump_snapshot_loops}) {
297 PVE::LXC::dettach_loops($self->{storecfg}, $volid_list, 'vzdump');
298 }
299
b739f640 300 if (my $volid_list = $task->{cleanup}->{dettach_loops}) {
4ca61ce8
DM
301 if ($task->{cleanup}->{remove_snapshot}) {
302 PVE::LXC::dettach_loops($self->{storecfg}, $volid_list, 'vzdump');
303 } else {
304 PVE::LXC::dettach_loops($self->{storecfg}, $volid_list);
305 }
d14a9a1b
DM
306 }
307
4ca61ce8
DM
308 if ($task->{cleanup}->{remove_snapshot}) {
309 $self->loginfo("remove vzdump snapshot");
310 PVE::LXC::snapshot_delete($vmid, 'vzdump', 0);
d14a9a1b 311 }
d14a9a1b
DM
312}
313
3141;