]>
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 | ||
bd8e1739 WB |
27 | my $rsync = ['rsync', '--stats', '-X', '--numeric-ids', |
28 | '-aH', '--delete', '--no-whole-file', '--inplace', | |
29 | '--one-file-system', '--relative']; | |
30 | push @$rsync, "--bwlimit=$opts->{bwlimit}" if $opts->{bwlimit}; | |
31 | push @$rsync, map { "--exclude=$_" } @{$self->{vzdump}->{findexcl}}; | |
32 | push @$rsync, map { "--exclude=$_" } @{$task->{exclude_dirs}}; | |
d14a9a1b | 33 | |
59a999af | 34 | my $starttime = time(); |
5a8a3087 WB |
35 | # See the rsync(1) manpage for --relative in conjunction with /./ in paths. |
36 | # This is the only way to have exclude-dirs work together with the | |
37 | # --one-file-system option. | |
38 | # This way we can pass multiple source paths and tell rsync which directory | |
39 | # they're supposed to be relative to. | |
40 | # Otherwise with eg. using multiple rsync commands means the --exclude | |
41 | # directives need to be modified for every command as they are meant to be | |
42 | # relative to the rootdir, while rsync treats them as relative to the | |
43 | # source dir. | |
44 | foreach my $disk (@$disks) { | |
bd8e1739 | 45 | push @$rsync, "$from/.$disk->{mp}"; |
5a8a3087 | 46 | } |
bd8e1739 | 47 | $self->cmd([@$rsync, $to]); |
d14a9a1b DM |
48 | my $delay = time () - $starttime; |
49 | ||
50 | $self->loginfo ("$text sync finished ($delay seconds)"); | |
51 | }; | |
52 | ||
53 | sub new { | |
54 | my ($class, $vzdump) = @_; | |
55 | ||
56 | PVE::VZDump::check_bin('lxc-stop'); | |
57 | PVE::VZDump::check_bin('lxc-start'); | |
58 | PVE::VZDump::check_bin('lxc-freeze'); | |
59 | PVE::VZDump::check_bin('lxc-unfreeze'); | |
60 | ||
61 | my $self = bless {}; | |
62 | ||
63 | $self->{vzdump} = $vzdump; | |
64 | $self->{storecfg} = PVE::Storage::config(); | |
65 | ||
66 | $self->{vmlist} = PVE::LXC::config_list(); | |
67 | ||
68 | return $self; | |
69 | } | |
70 | ||
71 | sub type { | |
72 | return 'lxc'; | |
73 | } | |
74 | ||
75 | sub vm_status { | |
76 | my ($self, $vmid) = @_; | |
77 | ||
78 | my $running = PVE::LXC::check_running($vmid) ? 1 : 0; | |
79 | ||
80 | return wantarray ? ($running, $running ? 'running' : 'stopped') : $running; | |
81 | } | |
82 | ||
4ca61ce8 | 83 | my $check_mountpoint_empty = sub { |
b739f640 DM |
84 | my ($mountpoint) = @_; |
85 | ||
4ca61ce8 DM |
86 | die "mountpoint '$mountpoint' is not a directory\n" if ! -d $mountpoint; |
87 | ||
b739f640 DM |
88 | PVE::Tools::dir_glob_foreach($mountpoint, qr/.*/, sub { |
89 | my $entry = shift; | |
90 | return if $entry eq '.' || $entry eq '..'; | |
4ca61ce8 | 91 | die "mountpoint '$mountpoint' not empty\n"; |
b739f640 | 92 | }); |
d14a9a1b DM |
93 | }; |
94 | ||
95 | sub prepare { | |
96 | my ($self, $task, $vmid, $mode) = @_; | |
97 | ||
98 | my $conf = $self->{vmlist}->{$vmid} = PVE::LXC::load_config($vmid); | |
57ed5ed0 | 99 | my $storage_cfg = $self->{storecfg}; |
d14a9a1b DM |
100 | |
101 | my $running = PVE::LXC::check_running($vmid); | |
102 | ||
2e57a9f6 WB |
103 | my $disks = $task->{disks} = []; |
104 | my $exclude_dirs = $task->{exclude_dirs} = []; | |
d14a9a1b | 105 | |
27916659 | 106 | $task->{hostname} = $conf->{'hostname'} || "CT$vmid"; |
d14a9a1b | 107 | |
2e57a9f6 WB |
108 | # fixme: when do we deactivate ?? |
109 | PVE::LXC::foreach_mountpoint($conf, sub { | |
110 | my ($name, $data) = @_; | |
111 | my $volid = $data->{volume}; | |
112 | my $mount = $data->{mp}; | |
d14a9a1b | 113 | |
2e57a9f6 WB |
114 | return if !$volid || !$mount || $volid =~ m|^/|; |
115 | ||
116 | if ($name ne 'rootfs' && !$data->{backup}) { | |
117 | push @$exclude_dirs, $mount; | |
118 | return; | |
119 | } | |
120 | ||
121 | push @$disks, $data; | |
122 | }); | |
123 | my $volid_list = [map { $_->{volume} } @$disks]; | |
124 | PVE::Storage::activate_volumes($storage_cfg, $volid_list); | |
d14a9a1b | 125 | |
d14a9a1b | 126 | if ($mode eq 'snapshot') { |
57ed5ed0 | 127 | if (!PVE::LXC::has_feature('snapshot', $conf, $storage_cfg)) { |
4ca61ce8 DM |
128 | die "mode failure - some volumes does not support snapshots\n"; |
129 | } | |
d14a9a1b | 130 | |
4ca61ce8 DM |
131 | if ($conf->{snapshots} && $conf->{snapshots}->{vzdump}) { |
132 | $self->loginfo("found old vzdump snapshot (force removal)"); | |
133 | PVE::LXC::snapshot_delete($vmid, 'vzdump', 0); | |
134 | } | |
d14a9a1b | 135 | |
22a91261 WB |
136 | my $rootdir = $default_mount_point; |
137 | mkpath $rootdir; | |
138 | &$check_mountpoint_empty($rootdir); | |
d14a9a1b | 139 | |
4ca61ce8 | 140 | # set snapshot_count (freezes CT it snapshot_count > 1) |
4ca61ce8 | 141 | $task->{snapshot_count} = scalar(@$volid_list); |
f5313774 | 142 | } elsif ($mode eq 'stop') { |
22a91261 WB |
143 | my $rootdir = $default_mount_point; |
144 | mkpath $rootdir; | |
145 | &$check_mountpoint_empty($rootdir); | |
f5313774 | 146 | } elsif ($mode eq 'suspend') { |
632eca5a | 147 | my $pid = PVE::LXC::find_lxc_pid($vmid); |
2e57a9f6 WB |
148 | foreach my $disk (@$disks) { |
149 | $disk->{dir} = "/proc/$pid/root$disk->{mp}"; | |
150 | } | |
f5313774 DM |
151 | $task->{snapdir} = $task->{tmpdir}; |
152 | } else { | |
153 | die "unknown mode '$mode'\n"; # should not happen | |
d14a9a1b | 154 | } |
b6c491ee WB |
155 | |
156 | if ($mode ne 'suspend') { | |
157 | # If we preform mount operations, let's unshare the mount namespace | |
158 | # to not influence the running host. | |
159 | PVE::Tools::unshare(PVE::Tools::CLONE_NEWNS); | |
160 | PVE::Tools::run_command(['mount', '--make-rprivate', '/']); | |
161 | } | |
d14a9a1b DM |
162 | } |
163 | ||
164 | sub lock_vm { | |
165 | my ($self, $vmid) = @_; | |
166 | ||
167 | PVE::LXC::lock_aquire($vmid); | |
168 | } | |
169 | ||
170 | sub unlock_vm { | |
171 | my ($self, $vmid) = @_; | |
172 | ||
173 | PVE::LXC::lock_release($vmid); | |
174 | } | |
175 | ||
4ca61ce8 DM |
176 | sub snapshot { |
177 | my ($self, $task, $vmid) = @_; | |
178 | ||
4ca61ce8 DM |
179 | $self->loginfo("create storage snapshot snapshot"); |
180 | ||
181 | # todo: freeze/unfreeze if we have more than one volid | |
182 | PVE::LXC::snapshot_create($vmid, 'vzdump', "vzdump backup snapshot"); | |
183 | $task->{cleanup}->{remove_snapshot} = 1; | |
184 | ||
185 | # reload config | |
186 | my $conf = $self->{vmlist}->{$vmid} = PVE::LXC::load_config($vmid); | |
187 | die "unable to read vzdump shanpshot config - internal error" | |
188 | if !($conf->{snapshots} && $conf->{snapshots}->{vzdump}); | |
189 | ||
2e57a9f6 WB |
190 | my $disks = $task->{disks}; |
191 | my $volid_list = [map { $_->{volume} } @$disks]; | |
4ca61ce8 | 192 | |
22a91261 | 193 | my $rootdir = $default_mount_point; |
2e57a9f6 WB |
194 | my $storage_cfg = $self->{storecfg}; |
195 | ||
196 | foreach my $disk (@$disks) { | |
197 | $disk->{dir} = "${rootdir}$disk->{mp}"; | |
198 | PVE::LXC::mountpoint_mount($disk, $rootdir, $storage_cfg, 'vzdump'); | |
199 | } | |
200 | ||
201 | $task->{snapdir} = $rootdir; | |
4ca61ce8 DM |
202 | } |
203 | ||
d14a9a1b DM |
204 | sub copy_data_phase1 { |
205 | my ($self, $task) = @_; | |
206 | ||
59a999af | 207 | $self->$rsync_vm($task, $task->{snapdir}, "first"); |
d14a9a1b DM |
208 | } |
209 | ||
210 | sub copy_data_phase2 { | |
211 | my ($self, $task) = @_; | |
212 | ||
59a999af | 213 | $self->$rsync_vm($task, $task->{snapdir}, "final"); |
d14a9a1b DM |
214 | } |
215 | ||
216 | sub stop_vm { | |
217 | my ($self, $task, $vmid) = @_; | |
218 | ||
219 | $self->cmd("lxc-stop -n $vmid"); | |
220 | } | |
221 | ||
222 | sub start_vm { | |
223 | my ($self, $task, $vmid) = @_; | |
224 | ||
225 | $self->cmd ("lxc-start -n $vmid"); | |
226 | } | |
227 | ||
228 | sub suspend_vm { | |
229 | my ($self, $task, $vmid) = @_; | |
230 | ||
231 | $self->cmd ("lxc-freeze -n $vmid"); | |
232 | } | |
233 | ||
234 | sub resume_vm { | |
235 | my ($self, $task, $vmid) = @_; | |
236 | ||
237 | $self->cmd ("lxc-unfreeze -n $vmid"); | |
238 | } | |
239 | ||
240 | sub assemble { | |
241 | my ($self, $task, $vmid) = @_; | |
242 | ||
cbd6753d | 243 | my $tmpdir = $task->{tmpdir}; |
d14a9a1b | 244 | |
cbd6753d | 245 | mkpath "$tmpdir/etc/vzdump/"; |
514b5f82 WL |
246 | |
247 | my $conf = PVE::LXC::load_config($vmid); | |
248 | delete $conf->{snapshots}; | |
249 | delete $conf->{'pve.parent'}; | |
250 | ||
cbd6753d | 251 | PVE::Tools::file_set_contents("$tmpdir/etc/vzdump/pct.conf", PVE::LXC::write_pct_config("/lxc/$vmid.conf", $conf)); |
d14a9a1b DM |
252 | } |
253 | ||
254 | sub archive { | |
255 | my ($self, $task, $vmid, $filename, $comp) = @_; | |
cbd6753d | 256 | |
b7ec90ed | 257 | my $disks = $task->{disks}; |
5a8a3087 | 258 | my @sources; |
b7ec90ed | 259 | |
459fd4d2 | 260 | if ($task->{mode} eq 'stop') { |
22a91261 | 261 | my $rootdir = $default_mount_point; |
2e57a9f6 WB |
262 | my $storage_cfg = $self->{storecfg}; |
263 | foreach my $disk (@$disks) { | |
264 | $disk->{dir} = "${rootdir}$disk->{mp}"; | |
265 | PVE::LXC::mountpoint_mount($disk, $rootdir, $storage_cfg); | |
5a8a3087 WB |
266 | # add every enabled mountpoint (since we use --one-file-system) |
267 | # mp already starts with a / so we only need to add the dot | |
268 | push @sources, ".$disk->{mp}"; | |
2e57a9f6 WB |
269 | } |
270 | $task->{snapdir} = $rootdir; | |
5a8a3087 WB |
271 | } else { |
272 | # the data was rsynced to a temporary location, only use '.' to avoid | |
273 | # having mountpoints duplicated | |
274 | push @sources, '.'; | |
459fd4d2 DM |
275 | } |
276 | ||
d14a9a1b | 277 | my $opts = $self->{vzdump}->{opts}; |
d14a9a1b | 278 | my $snapdir = $task->{snapdir}; |
cbd6753d | 279 | my $tmpdir = $task->{tmpdir}; |
d14a9a1b | 280 | |
bd734680 WB |
281 | my $tar = ['tar', 'cpf', '-', |
282 | '--totals', '--sparse', '--numeric-owner', '--xattrs', | |
283 | '--one-file-system']; | |
d14a9a1b DM |
284 | |
285 | # note: --remove-files does not work because we do not | |
286 | # backup all files (filters). tar complains: | |
287 | # Cannot rmdir: Directory not empty | |
288 | # we we disable this optimization for now | |
289 | #if ($snapdir eq $task->{tmpdir} && $snapdir =~ m|^$opts->{dumpdir}/|) { | |
bd734680 | 290 | # push @$tar, "--remove-files"; # try to save space |
d14a9a1b DM |
291 | #} |
292 | ||
cbd6753d DM |
293 | # The directory parameter can give a alternative directory as source. |
294 | # the second parameter gives the structure in the tar. | |
bd734680 WB |
295 | push @$tar, "--directory=$tmpdir", './etc/vzdump/pct.conf'; |
296 | push @$tar, "--directory=$snapdir"; | |
297 | push @$tar, map { "--exclude=.$_" } @{$self->{vzdump}->{findexcl}}; | |
332b1243 | 298 | |
5a8a3087 | 299 | push @$tar, @sources; |
bd734680 WB |
300 | |
301 | my $cmd = [ $tar ]; | |
cbd6753d | 302 | |
d14a9a1b | 303 | my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream |
bd734680 WB |
304 | push @$cmd, [ 'cstream', '-t', $bwl ] if $opts->{bwlimit}; |
305 | push @$cmd, [ $comp ] if $comp; | |
d14a9a1b | 306 | |
d14a9a1b | 307 | if ($opts->{stdout}) { |
bd734680 | 308 | push @{$cmd->[-1]}, \(">&" . fileno($opts->{stdout})); |
d14a9a1b | 309 | } else { |
bd734680 | 310 | push @{$cmd->[-1]}, \(">" . PVE::Tools::shellquote($filename)); |
d14a9a1b | 311 | } |
bd734680 | 312 | $self->cmd($cmd); |
d14a9a1b DM |
313 | } |
314 | ||
315 | sub cleanup { | |
316 | my ($self, $task, $vmid) = @_; | |
317 | ||
2e57a9f6 | 318 | my $conf = PVE::LXC::load_config($vmid); |
d14a9a1b | 319 | |
0c44de7b | 320 | if ($task->{mode} ne 'suspend') { |
96d20be2 DM |
321 | my $rootdir = $default_mount_point; |
322 | my $disks = $task->{disks}; | |
323 | foreach my $disk (reverse @$disks) { | |
324 | PVE::Tools::run_command(['umount', '-l', '-d', $disk->{dir}]) if $disk->{dir}; | |
325 | } | |
2e57a9f6 | 326 | } |
b739f640 | 327 | |
4ca61ce8 DM |
328 | if ($task->{cleanup}->{remove_snapshot}) { |
329 | $self->loginfo("remove vzdump snapshot"); | |
330 | PVE::LXC::snapshot_delete($vmid, 'vzdump', 0); | |
d14a9a1b | 331 | } |
d14a9a1b DM |
332 | } |
333 | ||
334 | 1; |