]> git.pld-linux.org Git - packages/amanda.git/blame - amlvm-snapshot.pl
perl 5.38.0 rebuild
[packages/amanda.git] / amlvm-snapshot.pl
CommitLineData
9b104ea3
JR
1#!/usr/bin/perl
2# Copyright (c) 2010, Daniel Duvall. All Rights Reserved.
3#
4# This program is free software; you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 2 as published
6# by the Free Software Foundation.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11# for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16#
17# Author: Daniel Duvall <the.liberal.media@gmail.com>
18
19# PROPERTY:
20#
21# SNAPSHOT-SIZE
22#
23# LVCREATE-PATH
24# LVDISPLAY-PATH
25# LVREMOVE-PATH
26# VGDISPLAY-PATH
27#
3275a137 28# STABLE-MOUNTPOINT
9b104ea3
JR
29# SUDO
30#
31use lib '@@PERL_VENDORARCH@@';
32use strict;
33use Getopt::Long;
34
35package Amanda::Script::Amlvm_snapshot;
36use base qw(Amanda::Script);
37
38use Amanda::Config qw( :getconf :init );
39use Amanda::Debug qw( :logging );
40use Amanda::Util qw( :constants );
41use Amanda::Paths;
42use Amanda::Constants;
43
44use Config;
45use File::Temp qw(tempdir);
3275a137 46use File::Path qw(make_path);
9b104ea3
JR
47use IPC::Open3;
48use Symbol;
3275a137 49use Digest::MD5 qw(md5_hex);
9b104ea3
JR
50
51sub new {
52 my $class = shift;
53 my ($execute_where, $config, $host, $disk, $device, $level, $index,
54 $message, $collection, $record, $snapsize, $lvcreate, $lvdisplay,
3275a137 55 $lvremove, $vgdisplay, $blkid, $stablemount, $sudo) = @_;
9b104ea3
JR
56 my $self = $class->SUPER::new($execute_where, $config);
57
58 $self->{execute_where} = $execute_where;
59 $self->{config} = $config;
60 $self->{host} = $host;
61 $self->{device} = $device;
62 $self->{disk} = $disk;
63 $self->{level} = [ @{$level} ]; # Copy the array
64 $self->{index} = $index;
65 $self->{message} = $message;
66 $self->{collection} = $collection;
67 $self->{record} = $record;
68
69 $self->{snapsize} = $snapsize;
70
71 $self->{lvcreate} = $lvcreate;
72 $self->{lvdisplay} = $lvdisplay;
73 $self->{lvremove} = $lvremove;
74 $self->{vgdisplay} = $vgdisplay;
75 $self->{blkid} = $blkid;
76
3275a137 77 $self->{stablemount} = $stablemount;
9b104ea3
JR
78 $self->{sudo} = $sudo;
79
80 $self->{volume_group} = undef;
81 $self->{fs_type} = undef;
82
83 return $self;
84}
85
86sub calculate_snapsize {
87 my $self = shift;
88
89 my $size;
90
91 # if a snapshot size isn't already set, use all available extents in the
92 # volume group
93 if (!defined $self->{snapsize}) {
94 foreach ($self->execute(1, "$self->{vgdisplay} -c")) {
95 my @parts = split(/:/);
96 my $group = $parts[0];
97 my $total = $parts[13];
98 my $alloc = $parts[14];
99
100 $group =~ s/^\s*//;
101 chomp($group);
102
103 if ($group eq $self->{volume_group}) {
104 $self->{snapsize} = $total - $alloc;
105 last;
106 }
107 }
108 }
109
110 # fallback to just 1 extent (though this might fail anyway)
111 $self->{snapsize} = 1 if (!defined $self->{snapsize});
112}
113
114sub create_snapshot {
115 my $self = shift;
116
117 # calculate default snapshot size
118 $self->calculate_snapsize();
119
120 debug("A snapshot of size `$self->{snapsize}' will be created.");
121
122 my @parts = split('/', $self->{device});
123 my $vg_name = $parts[2];
124 my $lv_name = $parts[3];
125
126 # create a new snapshot with lvcreate
127 $self->execute(1,
128 "$self->{lvcreate}", "--extents", $self->{snapsize},
129 "--snapshot", "--name", "amsnap-$vg_name-$lv_name", $self->{device}
130 );
131 my $snapshot_device = $self->get_snap_device(0);
132
133 debug("Created snapshot of `$self->{device}' at `$snapshot_device'.");
134}
135
136# Executes (safely) the given command and arguments. Optional execution
137# through sudo can be specified, but will only occur if the script was invoked
138# with the '--sudo' argument.
139sub execute {
140 my $self = shift;
141 my $sudo = shift;
142 my $cmd = shift;
143
144 # escape all given arguments
145 my @args = map(quotemeta, @_);
146
147 my ($in, $out, $err, $pid);
148 $err = Symbol::gensym;
149
150 my $full_cmd = ($sudo and $self->{sudo}) ? "sudo $cmd" : $cmd;
151
152 $full_cmd .= " @args";
153
154 $pid = open3($in, $out, $err, $full_cmd);
155
156 close($in);
157
158 my @output = <$out>;
159 my @errors = <$err>;
160
161 close($out);
162 close($err);
163
164 waitpid($pid, 0);
165
166 # NOTE There's an exception for readlink, as it's failure isn't critical.
167 if ($? > 0 and $cmd ne "readlink") {
168 my $err_str = join("", @errors);
169 chomp($err_str);
170
171 $self->print_to_server_and_die(
172 "Failed to execute (status $?) `$full_cmd': $err_str",
173 $Amanda::Script_App::ERROR
174 );
175 }
176
177 return @output;
178}
179
180# Returns the snapshot device path.
181sub get_snap_device {
182 my $self = shift;
183 my $mapper = shift;
184 my @parts = split('/', $self->{device});
185 my $vg_name = $parts[2];
186 my $lv_name = $parts[3];
187
188 if ($mapper) {
189 return "/dev/mapper/$self->{volume_group}-amsnap--$vg_name--$lv_name";
190 } else {
191 return "/dev/$self->{volume_group}/amsnap-$vg_name-$lv_name";
192 }
193}
194
195# Mounts the snapshot device at the configured directory.
196sub mount_snapshot {
197 my $self = shift;
198
199 # mount options
200 my @options = ('ro');
201
202 # special mount options for xfs
203 # XXX should this be left up to the user as an argument?
204 if ($self->{fs_type} eq 'xfs') {
205 push(@options, 'nouuid');
206 }
207
208 # create a temporary mount point and mount the snapshot volume
3275a137
JR
209 if ($self->{stablemount}) {
210 $self->{directory} = File::Spec->tmpdir . "/" . md5_hex($self->{disk});
211 make_path($self->{directory});
212 } else {
213 $self->{directory} = tempdir(CLEANUP => 0);
214 }
9b104ea3
JR
215 my $snapshot_device = $self->get_snap_device(0);
216 $self->execute(1,
217 "mount -o ", join(",", @options),
218 $snapshot_device, $self->{directory}
219 );
220
221 debug("Mounted snapshot `$snapshot_device' at `$self->{directory}'.");
222}
223
224# Readlink wrapper.
225sub readlink {
226 my $self = shift;
227 my $path = shift;
228
229 # NOTE: We don't use perl's readlink here, because it might need to be
230 # executed with elevated privileges (sudo).
231 my $real_path = join("", $self->execute(1, "readlink", $path));
232 chomp($real_path);
233 $real_path =~ s@\.\.@/dev@;
234
235 return ($real_path ne "") ? $real_path : $path;
236}
237
238# Removes the snapshot device.
239sub remove_snapshot {
240 my $self = shift;
241
242 # remove snapshot device with 'lvremove'
243 $self->execute(1, "$self->{lvremove} -f", $self->get_snap_device(0));
244
245 debug("Removed snapshot of `$self->{device}'.");
246}
247
248# Resolves the underlying device on which the configured directory resides.
249sub resolve_device {
250 my $self = shift;
251
252 # Search mtab for the mount point. Get the device path and filesystem type.
253 my $mnt_device = $self->scan_mtab(
254 sub { return $_[0] if ($_[1] eq $self->{disk}); }
255 );
256
257 my $fs_type = $self->scan_mtab(
258 sub { return $_[2] if ($_[1] eq $self->{disk}); }
259 );
260
261 if (!$mnt_device) {
262 if ($self->{disk} eq $self->{device}) {
263 $self->print_to_server_and_die(
264 "Failed to resolve a device from directory `$self->{disk}'. ",
265 $Amanda::Script_App::ERROR
266 );
267 } else {
268 $mnt_device = $self->{device};
269 $fs_type = join("", $self->execute(1, "$self->{blkid} -s TYPE -o value $self->{device}"));
270 chomp($fs_type);
271 }
272 }
273
274 # loop through the LVs to find the one that matches
275 foreach ($self->execute(1, "$self->{lvdisplay} -c")) {
276 my ($device, $group) = split(/:/);
277
278 $device =~ s/^\s*//;
279 chomp($device);
280
281 my $real_device = $self->readlink($device);
282 chomp($real_device);
283
62014f87 284 if (($device eq $mnt_device) || ($real_device eq $mnt_device)) {
9b104ea3
JR
285 $self->{device} = $device;
286 $self->{volume_group} = $group;
287 $self->{fs_type} = $fs_type;
288
289 debug(
290 "Resolved device `$self->{device}' and volume group ".
291 "`$self->{volume_group}' from mount point `$self->{disk}'."
292 );
293
294 last;
295 }
296 }
297}
298
299# Iterates over lines in the system mtab and invokes the given anonymous
300# subroutine with entries from each record:
301# 1. Canonical device path (as resolved from readlink).
302# 2. Mount point directory.
303# 3. Filesystem type.
304sub scan_mtab {
305 my $self = shift;
306 my $sub = shift;
307
308 open(MTAB, "/etc/mtab");
309 my $line;
310 my $result;
311 while ($line = <MTAB>) {
312 chomp($line);
313 my ($device, $directory, $type) = split(/\s+/, $line);
314 $result = $sub->($self->readlink($device), $directory, $type);
315 last if ($result);
316 }
317 close MTAB;
318
319 return $result;
320}
321
322sub setup {
323 my $self = shift;
324
325 # can only be executed in client context
326 if ($self->{execute_where} ne "client") {
327 $self->print_to_server_and_die(
328 "Script must be run on the client",
329 $Amanda::Script_App::ERROR
330 );
331 }
332
333 # resolve paths, if not already provided.
334 if (!defined $self->{lvcreate}) {
335 chomp($self->{lvcreate} = `which lvcreate`);
336 $self->print_to_server_and_die(
337 "lvcreate wasn't found.",
338 $Amanda::Script_App::ERROR
339 ) if $?;
340 }
341
342 if (!defined $self->{lvdisplay}) {
343 chomp($self->{lvdisplay} = `which lvdisplay`);
344 $self->print_to_server_and_die(
345 "lvdisplay wasn't found.",
346 $Amanda::Script_App::ERROR
347 ) if $?;
348 }
349
350 if (!defined $self->{lvremove}) {
351 chomp($self->{lvremove} = `which lvremove`);
352 $self->print_to_server_and_die(
353 "lvremove wasn't found.",
354 $Amanda::Script_App::ERROR
355 ) if $?;
356 }
357
358 if (!defined $self->{vgdisplay}) {
359 chomp($self->{vgdisplay} = `which vgdisplay`);
360 $self->print_to_server_and_die(
361 "vgdisplay wasn't found.",
362 $Amanda::Script_App::ERROR
363 ) if $?;
364 }
365
366 if (!defined $self->{blkid}) {
367 chomp($self->{blkid} = `which blkid`);
368 $self->print_to_server_and_die(
369 "blkid wasn't found.",
370 $Amanda::Script_App::ERROR
371 ) if $?;
372 }
373
374 # resolve actual lvm device
375 $self->resolve_device();
376
377 if (!defined $self->{volume_group}) {
378 $self->print_to_server_and_die(
379 "Failed to resolve device path and volume group.",
380 $Amanda::Script_App::ERROR
381 );
382 }
383}
384
385sub umount_snapshot {
386 my $self = shift;
387 my $device = $self->readlink($self->get_snap_device(0));
388
389 $device =~ s@\.\.@/dev@;
390 debug("umount_snapshot $device");
391
392 my $mnt = $self->scan_mtab(sub { return $_[1] if ($_[0] eq $device); });
393 if (!$mnt) {
394 $device = $self->readlink($self->get_snap_device(1));
395 $mnt = $self->scan_mtab(sub { return $_[1] if ($_[0] eq $device); });
396 }
397
398 if (!$mnt) {
399 $self->print_to_server_and_die(
400 "Failed to get mount point for snapshot device `$device'.",
401 $Amanda::Script_App::ERROR
402 );
403 }
404
405 $self->execute(1, "umount", $mnt);
406
407 debug("Un-mounted snapshot device `$device' from `$mnt'.");
408 rmdir $mnt;
409 debug("Remove snapshot mount point rmdir `$mnt'.");
410}
411
412
413sub command_support {
414 my $self = shift;
415
416 print "CONFIG YES\n";
417 print "HOST YES\n";
418 print "DISK YES\n";
419 print "MESSAGE-LINE YES\n";
420 print "MESSAGE-XML NO\n";
421 print "EXECUTE-WHERE YES\n";
422}
423
424#define a execute_on_* function for every execute_on you want the script to do
425#something
426sub command_pre_dle_backup {
427 my $self = shift;
428
429 $self->setup();
430 $self->create_snapshot();
431 $self->mount_snapshot();
432
433 print "PROPERTY directory $self->{directory}\n";
434}
435
436sub command_post_dle_backup {
437 my $self = shift;
438
439 $self->setup();
440 $self->umount_snapshot();
441 $self->remove_snapshot();
442}
443
444sub command_pre_dle_amcheck {
445 my $self = shift;
446
447 $self->setup();
448 $self->create_snapshot();
449 $self->mount_snapshot();
450
451 print "PROPERTY directory $self->{directory}\n";
452}
453
454sub command_post_dle_amcheck {
455 my $self = shift;
456
457 $self->setup();
458 $self->umount_snapshot();
459 $self->remove_snapshot();
460}
461
462package main;
463
464sub usage {
465 print <<EOF;
3275a137 466Usage: amlvm-snapshot <command> --execute-where=client --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --snapshot-size=<lvm snapshot size> --lvcreate-path=<path> --lvdisplay-path=<path> --lvremove-path=<path> --vgdisplay-path=<path> --blkid-path=<path> --stable-mountpoint=<0|1> --sudo=<0|1>.
9b104ea3
JR
467EOF
468 exit(1);
469}
470
471my $opt_execute_where;
472my $opt_config;
473my $opt_host;
474my $opt_disk;
475my $opt_device;
476my @opt_level;
477my $opt_index;
478my $opt_message;
479my $opt_collection;
480my $opt_record;
481
482my $opt_snapsize;
483my $opt_lvcreate;
484my $opt_lvdisplay;
485my $opt_lvremove;
486my $opt_vgdisplay;
487my $opt_blkid;
3275a137 488my $opt_stablemount;
9b104ea3
JR
489my $opt_sudo;
490
491Getopt::Long::Configure(qw{bundling});
492GetOptions(
493 'execute-where=s' => \$opt_execute_where,
494 'config=s' => \$opt_config,
495 'host=s' => \$opt_host,
496 'disk=s' => \$opt_disk,
497 'device=s' => \$opt_device,
498 'level=s' => \@opt_level,
499 'index=s' => \$opt_index,
500 'message=s' => \$opt_message,
501 'collection=s' => \$opt_collection,
502 'record=s' => \$opt_record,
503 'snapshot-size=s' => \$opt_snapsize,
504 'lvcreate-path=s' => \$opt_lvcreate,
505 'lvdisplay-path=s' => \$opt_lvdisplay,
506 'lvremove-path=s' => \$opt_lvremove,
507 'vgdisplay-path=s' => \$opt_vgdisplay,
508 'blkid=s' => \$opt_blkid,
3275a137 509 'stable-mountpoint=s' => \$opt_stablemount,
9b104ea3
JR
510 'sudo=s' => \$opt_sudo,
511) or usage();
512
513# add SBIN to PATH
514$ENV{'PATH'} = "/sbin:/usr/sbin:$ENV{'PATH'}:/usr/local/sbin";
515
516my $script = Amanda::Script::Amlvm_snapshot->new($opt_execute_where,
517 $opt_config, $opt_host, $opt_disk, $opt_device, \@opt_level, $opt_index,
518 $opt_message, $opt_collection, $opt_record, $opt_snapsize, $opt_lvcreate,
3275a137 519 $opt_lvdisplay, $opt_lvremove, $opt_vgdisplay, $opt_blkid, $opt_stablemount, $opt_sudo);
9b104ea3
JR
520$script->do($ARGV[0]);
521
522# vim: set et sts=4 sw=4 :
This page took 0.1134 seconds and 4 git commands to generate.