]>
Commit | Line | Data |
---|---|---|
d14a9a1b DM |
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; | |
514b5f82 | 12 | use PVE::Tools; |
d14a9a1b DM |
13 | |
14 | use base qw (PVE::VZDump::Plugin); | |
15 | ||
4ca61ce8 DM |
16 | my $default_mount_point = "/mnt/vzsnap0"; |
17 | ||
d14a9a1b | 18 | my $rsync_vm = sub { |
59a999af | 19 | my ($self, $task, $to, $text) = @_; |
d14a9a1b | 20 | |
59a999af WB |
21 | my $disks = $task->{disks}; |
22 | my $from = $disks->[0]->{dir} . '/'; | |
d14a9a1b DM |
23 | $self->loginfo ("starting $text sync $from to $to"); |
24 | ||
d14a9a1b DM |
25 | my $opts = $self->{vzdump}->{opts}; |
26 | ||
9c9a3b8c WB |
27 | my @xattr = $task->{no_xattrs} ? () : ('-X', '-A'); |
28 | ||
29 | my $rsync = ['rsync', '--stats', @xattr, '--numeric-ids', | |
bd8e1739 WB |
30 | '-aH', '--delete', '--no-whole-file', '--inplace', |
31 | '--one-file-system', '--relative']; | |
32 | push @$rsync, "--bwlimit=$opts->{bwlimit}" if $opts->{bwlimit}; | |
33 | push @$rsync, map { "--exclude=$_" } @{$self->{vzdump}->{findexcl}}; | |
34 | push @$rsync, map { "--exclude=$_" } @{$task->{exclude_dirs}}; | |
d14a9a1b | 35 | |
59a999af | 36 | my $starttime = time(); |
5a8a3087 WB |
37 | # See the rsync(1) manpage for --relative in conjunction with /./ in paths. |
38 | # This is the only way to have exclude-dirs work together with the | |
39 | # --one-file-system option. | |
40 | # This way we can pass multiple source paths and tell rsync which directory | |
41 | # they're supposed to be relative to. | |
42 | # Otherwise with eg. using multiple rsync commands means the --exclude | |
43 | # directives need to be modified for every command as they are meant to be | |
44 | # relative to the rootdir, while rsync treats them as relative to the | |
45 | # source dir. | |
46 | foreach my $disk (@$disks) { | |
bd8e1739 | 47 | push @$rsync, "$from/.$disk->{mp}"; |
5a8a3087 | 48 | } |
bd8e1739 | 49 | $self->cmd([@$rsync, $to]); |
d14a9a1b DM |
50 | my $delay = time () - $starttime; |
51 | ||
52 | $self->loginfo ("$text sync finished ($delay seconds)"); | |
53 | }; | |
54 | ||
55 | sub new { | |
56 | my ($class, $vzdump) = @_; | |
57 | ||
58 | PVE::VZDump::check_bin('lxc-stop'); | |
59 | PVE::VZDump::check_bin('lxc-start'); | |
60 | PVE::VZDump::check_bin('lxc-freeze'); | |
61 | PVE::VZDump::check_bin('lxc-unfreeze'); | |
62 | ||
63 | my $self = bless {}; | |
64 | ||
65 | $self->{vzdump} = $vzdump; | |
66 | $self->{storecfg} = PVE::Storage::config(); | |
67 | ||
68 | $self->{vmlist} = PVE::LXC::config_list(); | |
69 | ||
70 | return $self; | |
71 | } | |
72 | ||
73 | sub type { | |
74 | return 'lxc'; | |
75 | } | |
76 | ||
77 | sub vm_status { | |
78 | my ($self, $vmid) = @_; | |
79 | ||
80 | my $running = PVE::LXC::check_running($vmid) ? 1 : 0; | |
81 | ||
82 | return wantarray ? ($running, $running ? 'running' : 'stopped') : $running; | |
83 | } | |
84 | ||
4ca61ce8 | 85 | my $check_mountpoint_empty = sub { |
b739f640 DM |
86 | my ($mountpoint) = @_; |
87 | ||
4ca61ce8 DM |
88 | die "mountpoint '$mountpoint' is not a directory\n" if ! -d $mountpoint; |
89 | ||
b739f640 DM |
90 | PVE::Tools::dir_glob_foreach($mountpoint, qr/.*/, sub { |
91 | my $entry = shift; | |
92 | return if $entry eq '.' || $entry eq '..'; | |
4ca61ce8 | 93 | die "mountpoint '$mountpoint' not empty\n"; |
b739f640 | 94 | }); |
d14a9a1b DM |
95 | }; |
96 | ||
97 | sub prepare { | |
98 | my ($self, $task, $vmid, $mode) = @_; | |
99 | ||
67afe46e | 100 | my $conf = $self->{vmlist}->{$vmid} = PVE::LXC::Config->load_config($vmid); |
57ed5ed0 | 101 | my $storage_cfg = $self->{storecfg}; |
d14a9a1b DM |
102 | |
103 | my $running = PVE::LXC::check_running($vmid); | |
104 | ||
2e57a9f6 WB |
105 | my $disks = $task->{disks} = []; |
106 | my $exclude_dirs = $task->{exclude_dirs} = []; | |
d14a9a1b | 107 | |
27916659 | 108 | $task->{hostname} = $conf->{'hostname'} || "CT$vmid"; |
d14a9a1b | 109 | |
01dce99b WB |
110 | my ($id_map, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); |
111 | $task->{userns_cmd} = PVE::LXC::userns_command($id_map); | |
112 | ||
5040d81c | 113 | my $volids = $task->{volids} = []; |
2e57a9f6 WB |
114 | PVE::LXC::foreach_mountpoint($conf, sub { |
115 | my ($name, $data) = @_; | |
116 | my $volid = $data->{volume}; | |
117 | my $mount = $data->{mp}; | |
7c921c80 | 118 | my $type = $data->{type}; |
d14a9a1b | 119 | |
ca8aaa5a | 120 | return if !$volid || !$mount; |
2e57a9f6 WB |
121 | |
122 | if ($name ne 'rootfs' && !$data->{backup}) { | |
123 | push @$exclude_dirs, $mount; | |
124 | return; | |
125 | } | |
126 | ||
127 | push @$disks, $data; | |
5040d81c | 128 | push @$volids, $volid |
b5990246 | 129 | if $type eq 'volume'; |
2e57a9f6 | 130 | }); |
d14a9a1b | 131 | |
d14a9a1b | 132 | if ($mode eq 'snapshot') { |
41f044fb | 133 | if (!PVE::LXC::has_feature('snapshot', $conf, $storage_cfg, undef, undef, 1)) { |
c31ad455 | 134 | die "mode failure - some volumes do not support snapshots\n"; |
4ca61ce8 | 135 | } |
d14a9a1b | 136 | |
2d3f23be FG |
137 | unlock_vm($self, $vmid); |
138 | ||
4ca61ce8 DM |
139 | if ($conf->{snapshots} && $conf->{snapshots}->{vzdump}) { |
140 | $self->loginfo("found old vzdump snapshot (force removal)"); | |
050cd7fb | 141 | PVE::LXC::snapshot_delete($vmid, 'vzdump', 1); |
4ca61ce8 | 142 | } |
d14a9a1b | 143 | |
22a91261 WB |
144 | my $rootdir = $default_mount_point; |
145 | mkpath $rootdir; | |
146 | &$check_mountpoint_empty($rootdir); | |
d14a9a1b | 147 | |
c31ad455 | 148 | # set snapshot_count (freezes CT if snapshot_count > 1) |
5040d81c | 149 | $task->{snapshot_count} = scalar(@$volids); |
f5313774 | 150 | } elsif ($mode eq 'stop') { |
22a91261 WB |
151 | my $rootdir = $default_mount_point; |
152 | mkpath $rootdir; | |
153 | &$check_mountpoint_empty($rootdir); | |
5040d81c | 154 | PVE::Storage::activate_volumes($storage_cfg, $volids); |
f5313774 | 155 | } elsif ($mode eq 'suspend') { |
632eca5a | 156 | my $pid = PVE::LXC::find_lxc_pid($vmid); |
2e57a9f6 WB |
157 | foreach my $disk (@$disks) { |
158 | $disk->{dir} = "/proc/$pid/root$disk->{mp}"; | |
159 | } | |
f5313774 DM |
160 | $task->{snapdir} = $task->{tmpdir}; |
161 | } else { | |
2d3f23be | 162 | unlock_vm($self, $vmid); |
f5313774 | 163 | die "unknown mode '$mode'\n"; # should not happen |
d14a9a1b | 164 | } |
b6c491ee WB |
165 | |
166 | if ($mode ne 'suspend') { | |
c31ad455 | 167 | # If we perform mount operations, let's unshare the mount namespace |
b6c491ee WB |
168 | # to not influence the running host. |
169 | PVE::Tools::unshare(PVE::Tools::CLONE_NEWNS); | |
170 | PVE::Tools::run_command(['mount', '--make-rprivate', '/']); | |
171 | } | |
d14a9a1b DM |
172 | } |
173 | ||
174 | sub lock_vm { | |
175 | my ($self, $vmid) = @_; | |
2d3f23be FG |
176 | |
177 | my $lockconfig = sub { | |
178 | my ($self, $vmid) = @_; | |
179 | ||
67afe46e | 180 | my $conf = PVE::LXC::Config->load_config($vmid); |
2d3f23be | 181 | |
67afe46e | 182 | PVE::LXC::Config->check_lock($conf); |
2d3f23be FG |
183 | $conf->{lock} = 'backup'; |
184 | ||
67afe46e | 185 | PVE::LXC::Config->write_config($vmid, $conf); |
2d3f23be FG |
186 | }; |
187 | ||
67afe46e | 188 | PVE::LXC::Config->lock_config($vmid, $lockconfig, ($self, $vmid)); |
d14a9a1b DM |
189 | } |
190 | ||
191 | sub unlock_vm { | |
192 | my ($self, $vmid) = @_; | |
2d3f23be FG |
193 | |
194 | my $unlockconfig = sub { | |
195 | my ($self, $vmid) = @_; | |
196 | ||
67afe46e | 197 | my $conf = PVE::LXC::Config->load_config($vmid); |
2d3f23be FG |
198 | |
199 | if ($conf->{lock} && $conf->{lock} eq 'backup') { | |
200 | delete $conf->{lock}; | |
67afe46e | 201 | PVE::LXC::Config->write_config($vmid, $conf); |
2d3f23be FG |
202 | } |
203 | }; | |
204 | ||
67afe46e | 205 | PVE::LXC::Config->lock_config($vmid, $unlockconfig, ($self, $vmid)); |
d14a9a1b DM |
206 | } |
207 | ||
4ca61ce8 DM |
208 | sub snapshot { |
209 | my ($self, $task, $vmid) = @_; | |
210 | ||
c31ad455 | 211 | $self->loginfo("create storage snapshot 'vzdump'"); |
4ca61ce8 DM |
212 | |
213 | # todo: freeze/unfreeze if we have more than one volid | |
0bfffef4 | 214 | PVE::LXC::snapshot_create($vmid, 'vzdump', 0, "vzdump backup snapshot"); |
4ca61ce8 DM |
215 | $task->{cleanup}->{remove_snapshot} = 1; |
216 | ||
217 | # reload config | |
67afe46e | 218 | my $conf = $self->{vmlist}->{$vmid} = PVE::LXC::Config->load_config($vmid); |
c31ad455 | 219 | die "unable to read vzdump snapshot config - internal error" |
4ca61ce8 DM |
220 | if !($conf->{snapshots} && $conf->{snapshots}->{vzdump}); |
221 | ||
2e57a9f6 | 222 | my $disks = $task->{disks}; |
5040d81c | 223 | my $volids = $task->{volids}; |
4ca61ce8 | 224 | |
22a91261 | 225 | my $rootdir = $default_mount_point; |
2e57a9f6 WB |
226 | my $storage_cfg = $self->{storecfg}; |
227 | ||
5040d81c | 228 | PVE::Storage::activate_volumes($storage_cfg, $volids, 'vzdump'); |
2e57a9f6 WB |
229 | foreach my $disk (@$disks) { |
230 | $disk->{dir} = "${rootdir}$disk->{mp}"; | |
231 | PVE::LXC::mountpoint_mount($disk, $rootdir, $storage_cfg, 'vzdump'); | |
232 | } | |
233 | ||
234 | $task->{snapdir} = $rootdir; | |
4ca61ce8 DM |
235 | } |
236 | ||
d14a9a1b DM |
237 | sub copy_data_phase1 { |
238 | my ($self, $task) = @_; | |
239 | ||
9c9a3b8c WB |
240 | if (my $mntinfo = PVE::VZDump::get_mount_info($task->{snapdir})) { |
241 | if ($mntinfo->{fstype} =~ /^nfs4?/) { | |
242 | warn "temporary directory is on NFS, disabling xattr and acl support" | |
243 | . ", consider configuring a local tmpdir via /etc/vzdump.conf\n"; | |
244 | $task->{no_xattrs} = 1; | |
245 | } | |
246 | } | |
247 | ||
59a999af | 248 | $self->$rsync_vm($task, $task->{snapdir}, "first"); |
d14a9a1b DM |
249 | } |
250 | ||
251 | sub copy_data_phase2 { | |
252 | my ($self, $task) = @_; | |
253 | ||
59a999af | 254 | $self->$rsync_vm($task, $task->{snapdir}, "final"); |
d14a9a1b DM |
255 | } |
256 | ||
257 | sub stop_vm { | |
258 | my ($self, $task, $vmid) = @_; | |
259 | ||
260 | $self->cmd("lxc-stop -n $vmid"); | |
c434e3b4 DM |
261 | |
262 | # make sure container is stopped | |
263 | $self->cmd("lxc-wait -n $vmid -s STOPPED"); | |
d14a9a1b DM |
264 | } |
265 | ||
266 | sub start_vm { | |
267 | my ($self, $task, $vmid) = @_; | |
268 | ||
269 | $self->cmd ("lxc-start -n $vmid"); | |
270 | } | |
271 | ||
272 | sub suspend_vm { | |
273 | my ($self, $task, $vmid) = @_; | |
274 | ||
275 | $self->cmd ("lxc-freeze -n $vmid"); | |
276 | } | |
277 | ||
278 | sub resume_vm { | |
279 | my ($self, $task, $vmid) = @_; | |
280 | ||
281 | $self->cmd ("lxc-unfreeze -n $vmid"); | |
282 | } | |
283 | ||
284 | sub assemble { | |
285 | my ($self, $task, $vmid) = @_; | |
286 | ||
cbd6753d | 287 | my $tmpdir = $task->{tmpdir}; |
d14a9a1b | 288 | |
cbd6753d | 289 | mkpath "$tmpdir/etc/vzdump/"; |
514b5f82 | 290 | |
67afe46e | 291 | my $conf = PVE::LXC::Config->load_config($vmid); |
48314d4e | 292 | delete $conf->{lock}; |
514b5f82 WL |
293 | delete $conf->{snapshots}; |
294 | delete $conf->{'pve.parent'}; | |
295 | ||
cbd6753d | 296 | PVE::Tools::file_set_contents("$tmpdir/etc/vzdump/pct.conf", PVE::LXC::write_pct_config("/lxc/$vmid.conf", $conf)); |
bf040874 WL |
297 | |
298 | my $firewall ="/etc/pve/firewall/$vmid.fw"; | |
299 | if (-e $firewall) { | |
300 | PVE::Tools::file_copy($firewall, "$tmpdir/etc/vzdump/pct.fw"); | |
301 | $task->{fw} = 1; | |
302 | } | |
d14a9a1b DM |
303 | } |
304 | ||
305 | sub archive { | |
306 | my ($self, $task, $vmid, $filename, $comp) = @_; | |
cbd6753d | 307 | |
b7ec90ed | 308 | my $disks = $task->{disks}; |
5a8a3087 | 309 | my @sources; |
b7ec90ed | 310 | |
459fd4d2 | 311 | if ($task->{mode} eq 'stop') { |
22a91261 | 312 | my $rootdir = $default_mount_point; |
2e57a9f6 WB |
313 | my $storage_cfg = $self->{storecfg}; |
314 | foreach my $disk (@$disks) { | |
315 | $disk->{dir} = "${rootdir}$disk->{mp}"; | |
316 | PVE::LXC::mountpoint_mount($disk, $rootdir, $storage_cfg); | |
5a8a3087 WB |
317 | # add every enabled mountpoint (since we use --one-file-system) |
318 | # mp already starts with a / so we only need to add the dot | |
319 | push @sources, ".$disk->{mp}"; | |
2e57a9f6 WB |
320 | } |
321 | $task->{snapdir} = $rootdir; | |
5040d81c FG |
322 | } elsif ($task->{mode} eq 'snapshot') { |
323 | # mounting the vzdump snapshots and setting $snapdir is already done, | |
324 | # but we need to include all mountpoints here! | |
325 | foreach my $disk (@$disks) { | |
326 | push @sources, ".$disk->{mp}"; | |
327 | } | |
5a8a3087 WB |
328 | } else { |
329 | # the data was rsynced to a temporary location, only use '.' to avoid | |
330 | # having mountpoints duplicated | |
331 | push @sources, '.'; | |
459fd4d2 DM |
332 | } |
333 | ||
d14a9a1b | 334 | my $opts = $self->{vzdump}->{opts}; |
d14a9a1b | 335 | my $snapdir = $task->{snapdir}; |
cbd6753d | 336 | my $tmpdir = $task->{tmpdir}; |
d14a9a1b | 337 | |
01dce99b WB |
338 | my $userns_cmd = $task->{userns_cmd}; |
339 | my $tar = [@$userns_cmd, 'tar', 'cpf', '-', '--totals', | |
fc4e132e | 340 | @$PVE::LXC::COMMON_TAR_FLAGS, |
d5cb7040 | 341 | '--one-file-system', '--warning=no-file-ignored']; |
d14a9a1b DM |
342 | |
343 | # note: --remove-files does not work because we do not | |
344 | # backup all files (filters). tar complains: | |
345 | # Cannot rmdir: Directory not empty | |
c31ad455 | 346 | # we disable this optimization for now |
d14a9a1b | 347 | #if ($snapdir eq $task->{tmpdir} && $snapdir =~ m|^$opts->{dumpdir}/|) { |
bd734680 | 348 | # push @$tar, "--remove-files"; # try to save space |
d14a9a1b DM |
349 | #} |
350 | ||
c31ad455 | 351 | # The directory parameter can give an alternative directory as source. |
cbd6753d | 352 | # the second parameter gives the structure in the tar. |
bd734680 | 353 | push @$tar, "--directory=$tmpdir", './etc/vzdump/pct.conf'; |
bf040874 | 354 | push @$tar, "./etc/vzdump/pct.fw" if $task->{fw}; |
bd734680 | 355 | push @$tar, "--directory=$snapdir"; |
e87a47ff | 356 | push @$tar, '--no-anchored', '--exclude=lost+found' if $userns_cmd; |
4953cff8 | 357 | push @$tar, '--anchored'; |
bd734680 | 358 | push @$tar, map { "--exclude=.$_" } @{$self->{vzdump}->{findexcl}}; |
332b1243 | 359 | |
5a8a3087 | 360 | push @$tar, @sources; |
bd734680 WB |
361 | |
362 | my $cmd = [ $tar ]; | |
cbd6753d | 363 | |
d14a9a1b | 364 | my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream |
bd734680 | 365 | push @$cmd, [ 'cstream', '-t', $bwl ] if $opts->{bwlimit}; |
3889966a | 366 | push @$cmd, [ split(/\s+/, $comp) ] if $comp; |
d14a9a1b | 367 | |
d14a9a1b | 368 | if ($opts->{stdout}) { |
bd734680 | 369 | push @{$cmd->[-1]}, \(">&" . fileno($opts->{stdout})); |
d14a9a1b | 370 | } else { |
bd734680 | 371 | push @{$cmd->[-1]}, \(">" . PVE::Tools::shellquote($filename)); |
d14a9a1b | 372 | } |
bd734680 | 373 | $self->cmd($cmd); |
d14a9a1b DM |
374 | } |
375 | ||
376 | sub cleanup { | |
377 | my ($self, $task, $vmid) = @_; | |
378 | ||
67afe46e | 379 | my $conf = PVE::LXC::Config->load_config($vmid); |
d14a9a1b | 380 | |
0c44de7b | 381 | if ($task->{mode} ne 'suspend') { |
96d20be2 DM |
382 | my $rootdir = $default_mount_point; |
383 | my $disks = $task->{disks}; | |
384 | foreach my $disk (reverse @$disks) { | |
385 | PVE::Tools::run_command(['umount', '-l', '-d', $disk->{dir}]) if $disk->{dir}; | |
386 | } | |
2e57a9f6 | 387 | } |
b739f640 | 388 | |
4ca61ce8 DM |
389 | if ($task->{cleanup}->{remove_snapshot}) { |
390 | $self->loginfo("remove vzdump snapshot"); | |
391 | PVE::LXC::snapshot_delete($vmid, 'vzdump', 0); | |
d14a9a1b | 392 | } |
d14a9a1b DM |
393 | } |
394 | ||
395 | 1; |