packages: amanda/amlvm-snapshot.README (NEW), amanda/amlvm-snapshot.conf (N...
baggins
baggins at pld-linux.org
Sat May 12 12:23:26 CEST 2012
Author: baggins Date: Sat May 12 10:23:26 2012 GMT
Module: packages Tag: HEAD
---- Log message:
- scipt to backup lvm snapshots
---- Files affected:
packages/amanda:
amlvm-snapshot.README (NONE -> 1.1) (NEW), amlvm-snapshot.conf (NONE -> 1.1) (NEW), amlvm-snapshot.pl (NONE -> 1.1) (NEW)
---- Diffs:
================================================================
Index: packages/amanda/amlvm-snapshot.README
diff -u /dev/null packages/amanda/amlvm-snapshot.README:1.1
--- /dev/null Sat May 12 12:23:26 2012
+++ packages/amanda/amlvm-snapshot.README Sat May 12 12:23:21 2012
@@ -0,0 +1,98 @@
+Amanda LVM-snapshot Plugin
+==========================
+
+This plugin provides support for LVM snapshots in Amanda dumps. It interfaces
+with Amanda through the [Script API][1].
+
+Install
+-------
+
+Sorry, there's no Makefile yet. Simply copy the amlvm-snapshot.pl script into
+Amanda's application directory.
+
+For example:
+
+ cp amlvm-snapshot.pl /usr/libexec/amanda/application/amlvm-snapshot
+
+You may need to edit the location of Amanda's Perl libraries in the script
+itself, the following line.
+
+ use lib '/usr/local/share/perl/5.8.4';
+
+Configure Amanda
+----------------
+
+Somewhere in your Amanda config you must define a `script-tool` that loads the
+plugin. You can simply include the provided `lvm-snapshot.conf` file if you like.
+
+ cp lvm-snapshot.conf /etc/amanda/DailySet1/lvm-snapshot.conf
+ echo 'includefile "lvm-snapshot.conf"' >> /etc/amanda/DailySet1/amanda.conf
+
+Once you have the `lvm-snapshot` `script-tool` defined, you can include it in
+a `dumptype` definition. Note, however, that your dumptype _must use an
+application-tool program_: Only application-tool programs can handle the
+alternate mount point—of the snapshot device—that the script defines.
+
+ define dumptype lvm-comp-amgtar {
+ comment "LVM snapshot dumped with amgtar"
+ global
+ program "APPLICATION"
+ application "app_amgtar"
+ script "lvm-snapshot"
+ compress client fast
+ index
+ }
+
+To allow amanda to dump more than one lvm-snapshot in parallel,
+lvm-snapshot.conf has to have something like:
+
+ property "SNAPSHOT-SIZE" "250"
+
+(the unit is PEs, for LVM PE Size 4,00 MiB it results in 1G snapshot-size)
+
+otherwise the first snapshot uses up all extents and the second snapshot fails.
+
+Configure Permissions
+---------------------
+
+This plugin requires elevated permissions in order to create and remove LVM
+devices. There are two ways to provide access: setting setuid on the plugin
+script itself, or by configuring `sudo` to allow execution of the LVM
+programs.
+
+### setuid
+
+NOTE: I'm currently having trouble getting this to work right, as Amanda's
+Perl libraries don't seem to play nice with setuid scripts.
+
+For setuid, simply configure the ownership and mode on `amlvm-snapshot`. In
+this example, `disk` is the group that Amanda runs under.
+
+ chown root:disk /usr/libexec/amanda/application/amlvm-snapshot
+ chmod 4750 /usr/libexec/amanda/application/amlvm-snapshot
+
+This will require that you have a version of Perl installed that was compiled
+with `ENABLE_SUIDPERL`.
+
+### sudo
+
+For sudo, add the following to the `/etc/sudoers` file where "amandabackup" is
+the name of your Amanda user.
+
+ amandabackup ALL=(ALL) NOPASSWD: /sbin/lvcreate, /sbin/lvdisplay, /sbin/lvremove, /sbin/vgdisplay, /bin/readlink, /bin/mount, /bin/umount, /sbin/blkid
+
+The commands listed are those used by `amlvm-snapshot` to interact with the
+LVM volumes.
+
+Remember to enable the `SUDO` property. This is already included in the
+example `lvm-snapshot.conf` file.
+
+ define script-tool lvm-snapshot {
+ # ...
+ property "SUDO" "1"
+ }
+
+Enjoy,
+Daniel
+
+[1]: http://wiki.zmanda.com/index.php/Script_API
================================================================
Index: packages/amanda/amlvm-snapshot.conf
diff -u /dev/null packages/amanda/amlvm-snapshot.conf:1.1
--- /dev/null Sat May 12 12:23:26 2012
+++ packages/amanda/amlvm-snapshot.conf Sat May 12 12:23:21 2012
@@ -0,0 +1,7 @@
+define script-tool lvm-snapshot {
+ comment "LVM snapshot"
+ plugin "amlvm-snapshot"
+ execute-where client
+ execute-on pre-dle-backup, post-dle-backup, pre-dle-amcheck, post-dle-amcheck
+ property "SUDO" "1"
+}
================================================================
Index: packages/amanda/amlvm-snapshot.pl
diff -u /dev/null packages/amanda/amlvm-snapshot.pl:1.1
--- /dev/null Sat May 12 12:23:26 2012
+++ packages/amanda/amlvm-snapshot.pl Sat May 12 12:23:21 2012
@@ -0,0 +1,511 @@
+#!/usr/bin/perl
+# Copyright (c) 2010, Daniel Duvall. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Author: Daniel Duvall <the.liberal.media at gmail.com>
+
+# PROPERTY:
+#
+# SNAPSHOT-SIZE
+#
+# LVCREATE-PATH
+# LVDISPLAY-PATH
+# LVREMOVE-PATH
+# VGDISPLAY-PATH
+#
+# SUDO
+#
+use lib '@@PERL_VENDORARCH@@';
+use strict;
+use Getopt::Long;
+
+package Amanda::Script::Amlvm_snapshot;
+use base qw(Amanda::Script);
+
+use Amanda::Config qw( :getconf :init );
+use Amanda::Debug qw( :logging );
+use Amanda::Util qw( :constants );
+use Amanda::Paths;
+use Amanda::Constants;
+
+use Config;
+use File::Temp qw(tempdir);
+use IPC::Open3;
+use Symbol;
+
+sub new {
+ my $class = shift;
+ my ($execute_where, $config, $host, $disk, $device, $level, $index,
+ $message, $collection, $record, $snapsize, $lvcreate, $lvdisplay,
+ $lvremove, $vgdisplay, $blkid, $sudo) = @_;
+ my $self = $class->SUPER::new($execute_where, $config);
+
+ $self->{execute_where} = $execute_where;
+ $self->{config} = $config;
+ $self->{host} = $host;
+ $self->{device} = $device;
+ $self->{disk} = $disk;
+ $self->{level} = [ @{$level} ]; # Copy the array
+ $self->{index} = $index;
+ $self->{message} = $message;
+ $self->{collection} = $collection;
+ $self->{record} = $record;
+
+ $self->{snapsize} = $snapsize;
+
+ $self->{lvcreate} = $lvcreate;
+ $self->{lvdisplay} = $lvdisplay;
+ $self->{lvremove} = $lvremove;
+ $self->{vgdisplay} = $vgdisplay;
+ $self->{blkid} = $blkid;
+
+ $self->{sudo} = $sudo;
+
+ $self->{volume_group} = undef;
+ $self->{fs_type} = undef;
+
+ return $self;
+}
+
+sub calculate_snapsize {
+ my $self = shift;
+
+ my $size;
+
+ # if a snapshot size isn't already set, use all available extents in the
+ # volume group
+ if (!defined $self->{snapsize}) {
+ foreach ($self->execute(1, "$self->{vgdisplay} -c")) {
+ my @parts = split(/:/);
+ my $group = $parts[0];
+ my $total = $parts[13];
+ my $alloc = $parts[14];
+
+ $group =~ s/^\s*//;
+ chomp($group);
+
+ if ($group eq $self->{volume_group}) {
+ $self->{snapsize} = $total - $alloc;
+ last;
+ }
+ }
+ }
+
+ # fallback to just 1 extent (though this might fail anyway)
+ $self->{snapsize} = 1 if (!defined $self->{snapsize});
+}
+
+sub create_snapshot {
+ my $self = shift;
+
+ # calculate default snapshot size
+ $self->calculate_snapsize();
+
+ debug("A snapshot of size `$self->{snapsize}' will be created.");
+
+ my @parts = split('/', $self->{device});
+ my $vg_name = $parts[2];
+ my $lv_name = $parts[3];
+
+ # create a new snapshot with lvcreate
+ $self->execute(1,
+ "$self->{lvcreate}", "--extents", $self->{snapsize},
+ "--snapshot", "--name", "amsnap-$vg_name-$lv_name", $self->{device}
+ );
+ my $snapshot_device = $self->get_snap_device(0);
+
+ debug("Created snapshot of `$self->{device}' at `$snapshot_device'.");
+}
+
+# Executes (safely) the given command and arguments. Optional execution
+# through sudo can be specified, but will only occur if the script was invoked
+# with the '--sudo' argument.
+sub execute {
+ my $self = shift;
+ my $sudo = shift;
+ my $cmd = shift;
+
+ # escape all given arguments
+ my @args = map(quotemeta, @_);
+
+ my ($in, $out, $err, $pid);
+ $err = Symbol::gensym;
+
+ my $full_cmd = ($sudo and $self->{sudo}) ? "sudo $cmd" : $cmd;
+
+ $full_cmd .= " @args";
+
+ $pid = open3($in, $out, $err, $full_cmd);
+
+ close($in);
+
+ my @output = <$out>;
+ my @errors = <$err>;
+
+ close($out);
+ close($err);
+
+ waitpid($pid, 0);
+
+ # NOTE There's an exception for readlink, as it's failure isn't critical.
+ if ($? > 0 and $cmd ne "readlink") {
+ my $err_str = join("", @errors);
+ chomp($err_str);
+
+ $self->print_to_server_and_die(
+ "Failed to execute (status $?) `$full_cmd': $err_str",
+ $Amanda::Script_App::ERROR
+ );
+ }
+
+ return @output;
+}
+
+# Returns the snapshot device path.
+sub get_snap_device {
+ my $self = shift;
+ my $mapper = shift;
+ my @parts = split('/', $self->{device});
+ my $vg_name = $parts[2];
+ my $lv_name = $parts[3];
+
+ if ($mapper) {
+ return "/dev/mapper/$self->{volume_group}-amsnap--$vg_name--$lv_name";
+ } else {
+ return "/dev/$self->{volume_group}/amsnap-$vg_name-$lv_name";
+ }
+}
+
+# Mounts the snapshot device at the configured directory.
+sub mount_snapshot {
+ my $self = shift;
+
+ # mount options
+ my @options = ('ro');
+
+ # special mount options for xfs
+ # XXX should this be left up to the user as an argument?
+ if ($self->{fs_type} eq 'xfs') {
+ push(@options, 'nouuid');
+ }
+
+ # create a temporary mount point and mount the snapshot volume
+ $self->{directory} = tempdir(CLEANUP => 0);
+ my $snapshot_device = $self->get_snap_device(0);
+ $self->execute(1,
+ "mount -o ", join(",", @options),
+ $snapshot_device, $self->{directory}
+ );
+
+ debug("Mounted snapshot `$snapshot_device' at `$self->{directory}'.");
+}
+
+# Readlink wrapper.
+sub readlink {
+ my $self = shift;
+ my $path = shift;
+
+ # NOTE: We don't use perl's readlink here, because it might need to be
+ # executed with elevated privileges (sudo).
+ my $real_path = join("", $self->execute(1, "readlink", $path));
+ chomp($real_path);
+ $real_path =~ s@\.\.@/dev@;
+
+ return ($real_path ne "") ? $real_path : $path;
+}
+
+# Removes the snapshot device.
+sub remove_snapshot {
+ my $self = shift;
+
+ # remove snapshot device with 'lvremove'
+ $self->execute(1, "$self->{lvremove} -f", $self->get_snap_device(0));
+
+ debug("Removed snapshot of `$self->{device}'.");
+}
+
+# Resolves the underlying device on which the configured directory resides.
+sub resolve_device {
+ my $self = shift;
+
+ # Search mtab for the mount point. Get the device path and filesystem type.
+ my $mnt_device = $self->scan_mtab(
+ sub { return $_[0] if ($_[1] eq $self->{disk}); }
+ );
+
+ my $fs_type = $self->scan_mtab(
+ sub { return $_[2] if ($_[1] eq $self->{disk}); }
+ );
+
+ if (!$mnt_device) {
+ if ($self->{disk} eq $self->{device}) {
+ $self->print_to_server_and_die(
+ "Failed to resolve a device from directory `$self->{disk}'. ",
+ $Amanda::Script_App::ERROR
+ );
+ } else {
+ $mnt_device = $self->{device};
+ $fs_type = join("", $self->execute(1, "$self->{blkid} -s TYPE -o value $self->{device}"));
+ chomp($fs_type);
+ }
+ }
+
+ # loop through the LVs to find the one that matches
+ foreach ($self->execute(1, "$self->{lvdisplay} -c")) {
+ my ($device, $group) = split(/:/);
+
+ $device =~ s/^\s*//;
+ chomp($device);
+
+ my $real_device = $self->readlink($device);
+ chomp($real_device);
+
+ if ($real_device eq $mnt_device) {
+ $self->{device} = $device;
+ $self->{volume_group} = $group;
+ $self->{fs_type} = $fs_type;
+
+ debug(
+ "Resolved device `$self->{device}' and volume group ".
+ "`$self->{volume_group}' from mount point `$self->{disk}'."
+ );
+
+ last;
+ }
+ }
+}
+
+# Iterates over lines in the system mtab and invokes the given anonymous
+# subroutine with entries from each record:
+# 1. Canonical device path (as resolved from readlink).
+# 2. Mount point directory.
+# 3. Filesystem type.
+sub scan_mtab {
+ my $self = shift;
+ my $sub = shift;
+
+ open(MTAB, "/etc/mtab");
+ my $line;
+ my $result;
+ while ($line = <MTAB>) {
+ chomp($line);
+ my ($device, $directory, $type) = split(/\s+/, $line);
+ $result = $sub->($self->readlink($device), $directory, $type);
+ last if ($result);
+ }
+ close MTAB;
+
+ return $result;
+}
+
+sub setup {
+ my $self = shift;
+
+ # can only be executed in client context
+ if ($self->{execute_where} ne "client") {
+ $self->print_to_server_and_die(
+ "Script must be run on the client",
+ $Amanda::Script_App::ERROR
+ );
+ }
+
+ # resolve paths, if not already provided.
+ if (!defined $self->{lvcreate}) {
+ chomp($self->{lvcreate} = `which lvcreate`);
+ $self->print_to_server_and_die(
+ "lvcreate wasn't found.",
+ $Amanda::Script_App::ERROR
+ ) if $?;
+ }
+
+ if (!defined $self->{lvdisplay}) {
+ chomp($self->{lvdisplay} = `which lvdisplay`);
+ $self->print_to_server_and_die(
+ "lvdisplay wasn't found.",
+ $Amanda::Script_App::ERROR
+ ) if $?;
+ }
+
+ if (!defined $self->{lvremove}) {
+ chomp($self->{lvremove} = `which lvremove`);
+ $self->print_to_server_and_die(
+ "lvremove wasn't found.",
+ $Amanda::Script_App::ERROR
+ ) if $?;
+ }
+
+ if (!defined $self->{vgdisplay}) {
+ chomp($self->{vgdisplay} = `which vgdisplay`);
+ $self->print_to_server_and_die(
+ "vgdisplay wasn't found.",
+ $Amanda::Script_App::ERROR
+ ) if $?;
+ }
+
+ if (!defined $self->{blkid}) {
+ chomp($self->{blkid} = `which blkid`);
+ $self->print_to_server_and_die(
+ "blkid wasn't found.",
+ $Amanda::Script_App::ERROR
+ ) if $?;
+ }
+
+ # resolve actual lvm device
+ $self->resolve_device();
+
+ if (!defined $self->{volume_group}) {
+ $self->print_to_server_and_die(
+ "Failed to resolve device path and volume group.",
+ $Amanda::Script_App::ERROR
+ );
+ }
+}
+
+sub umount_snapshot {
+ my $self = shift;
+ my $device = $self->readlink($self->get_snap_device(0));
+
+ $device =~ s@\.\.@/dev@;
+ debug("umount_snapshot $device");
+
+ my $mnt = $self->scan_mtab(sub { return $_[1] if ($_[0] eq $device); });
+ if (!$mnt) {
+ $device = $self->readlink($self->get_snap_device(1));
+ $mnt = $self->scan_mtab(sub { return $_[1] if ($_[0] eq $device); });
+ }
+
+ if (!$mnt) {
+ $self->print_to_server_and_die(
+ "Failed to get mount point for snapshot device `$device'.",
+ $Amanda::Script_App::ERROR
+ );
+ }
+
+ $self->execute(1, "umount", $mnt);
+
+ debug("Un-mounted snapshot device `$device' from `$mnt'.");
+ rmdir $mnt;
+ debug("Remove snapshot mount point rmdir `$mnt'.");
+}
+
+
+sub command_support {
+ my $self = shift;
+
+ print "CONFIG YES\n";
+ print "HOST YES\n";
+ print "DISK YES\n";
+ print "MESSAGE-LINE YES\n";
+ print "MESSAGE-XML NO\n";
+ print "EXECUTE-WHERE YES\n";
+}
+
+#define a execute_on_* function for every execute_on you want the script to do
+#something
+sub command_pre_dle_backup {
+ my $self = shift;
+
+ $self->setup();
+ $self->create_snapshot();
+ $self->mount_snapshot();
+
+ print "PROPERTY directory $self->{directory}\n";
+}
+
+sub command_post_dle_backup {
+ my $self = shift;
+
+ $self->setup();
+ $self->umount_snapshot();
+ $self->remove_snapshot();
+}
+
+sub command_pre_dle_amcheck {
+ my $self = shift;
+
+ $self->setup();
+ $self->create_snapshot();
+ $self->mount_snapshot();
+
+ print "PROPERTY directory $self->{directory}\n";
+}
+
+sub command_post_dle_amcheck {
+ my $self = shift;
+
+ $self->setup();
+ $self->umount_snapshot();
+ $self->remove_snapshot();
+}
+
+package main;
+
+sub usage {
+ print <<EOF;
+Usage: 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> --sudo=<0|1>.
+EOF
+ exit(1);
+}
+
+my $opt_execute_where;
+my $opt_config;
+my $opt_host;
+my $opt_disk;
+my $opt_device;
+my @opt_level;
+my $opt_index;
+my $opt_message;
+my $opt_collection;
+my $opt_record;
+
+my $opt_snapsize;
+my $opt_lvcreate;
<<Diff was trimmed, longer than 597 lines>>
More information about the pld-cvs-commit
mailing list