]>
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 DM |
18 | my $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 | ||
38 | sub 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 | ||
56 | sub type { | |
57 | return 'lxc'; | |
58 | } | |
59 | ||
60 | sub 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 | 68 | my $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 | ||
80 | sub 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 | ||
148 | sub lock_vm { | |
149 | my ($self, $vmid) = @_; | |
150 | ||
151 | PVE::LXC::lock_aquire($vmid); | |
152 | } | |
153 | ||
154 | sub unlock_vm { | |
155 | my ($self, $vmid) = @_; | |
156 | ||
157 | PVE::LXC::lock_release($vmid); | |
158 | } | |
159 | ||
4ca61ce8 DM |
160 | sub 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 |
192 | sub copy_data_phase1 { |
193 | my ($self, $task) = @_; | |
194 | ||
195 | $self->$rsync_vm($task, "$task->{diskinfo}->{dir}/", $task->{snapdir}, "first"); | |
196 | } | |
197 | ||
198 | sub copy_data_phase2 { | |
199 | my ($self, $task) = @_; | |
200 | ||
201 | $self->$rsync_vm ($task, "$task->{diskinfo}->{dir}/", $task->{snapdir}, "final"); | |
202 | } | |
203 | ||
204 | sub stop_vm { | |
205 | my ($self, $task, $vmid) = @_; | |
206 | ||
207 | $self->cmd("lxc-stop -n $vmid"); | |
208 | } | |
209 | ||
210 | sub start_vm { | |
211 | my ($self, $task, $vmid) = @_; | |
212 | ||
213 | $self->cmd ("lxc-start -n $vmid"); | |
214 | } | |
215 | ||
216 | sub suspend_vm { | |
217 | my ($self, $task, $vmid) = @_; | |
218 | ||
219 | $self->cmd ("lxc-freeze -n $vmid"); | |
220 | } | |
221 | ||
222 | sub resume_vm { | |
223 | my ($self, $task, $vmid) = @_; | |
224 | ||
225 | $self->cmd ("lxc-unfreeze -n $vmid"); | |
226 | } | |
227 | ||
228 | sub 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 | ||
242 | sub 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 | ||
287 | sub 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 | ||
314 | 1; |