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