Amanda-Users

Re: archiving tapes?!

2005-01-29 19:32:30
Subject: Re: archiving tapes?!
From: Mark Costlow <cheeks AT swcp DOT com>
To: amanda-users AT amanda DOT org
Date: Sat, 29 Jan 2005 17:13:58 -0700
> > In the old tape days, I used to force a level-0 dump when I wanted to do
> > that.  This was a pain and never very satisfying.  So I wrote a perl script
> > that will extract the most recent full dump for every disk partition out
> > of the vtapes.
> 
> What a neat idea, and one that works best with vtape as the daily backup.
> 
> Would you consider writing up your experiences and techniques?

Sure, although maybe the script itself says it best? :-)  I'll append it
below.

> I'm guessing that for recovery from the archived dumps you would not
> use amanda's indexing features.

Yes, that's right.  The script builds a table-of-contents file for each
dump and puts that on the disk as well (this is the part which assumes
my dumps are done with gtar).  There might be a smarter way to extract
that info from amanda's existing indices instead.

I also liked someone's idea of copying amanda's indices to the off-site
media and having them avaiable for the restore.  If someone would like
to extend this to have that ability, that would be neat.

> Have you had to do any recoveries from the archived dumps rather
> than the vtapes?

I've done a lot of test restores, and they work fine (using
"dd bs=32k skip=1 ...").  I haven't had a call to use the offsites
to restore anything under pressure yet.  If I never ever get to use
my offsite backups and this is all a waste of time, I'll be a happy
man :-)

Mark
-- 
Mark Costlow    | Southwest Cyberport | Fax:   +1-505-232-7975
cheeks AT swcp DOT com | Web:   www.swcp.com | Voice: +1-505-232-7992

      "Education is never a waste" - Viscount du Valmont

--------------------- amoffsite ------------------------------------------

#!/usr/local/bin/perl

#
# $Id: amoffsite,v 1.3 2005/01/12 06:31:02 cheeks Exp $
#

# Program: amoffsite
# Author: Mark Costlow <cheeks AT swcp DOT com>
# Date: Jan, 2005


#
# This program prepares an offsite dump.  It finds the most recent
# level-0 dump for each disk partition in an Amanda config.  These are in the
# "virtual tapes" of the large RAID that we use for nightly backups.  It
# copies those files to the "offsite" disk.
#
# The idea is that someone will run this script once a month to copy the
# offsite dumps to a disk, then pull that disk out of the disk array and take
# it home with them.  If something happens to our disk array, or if we need to
# restore a file older than what we have on-site, the offsite disk(s) should
# save us.
#

#
# This is the first working version of this program -- there's plenty of room
# for improvement.  If you have suggestions or fixes, please send them to
# cheeks AT swcp DOT com.
#
# As with the rest of amanda, THIS SOFTWARE IS BEING MADE AVAILABLE ``AS-IS''.
# It might work for what you want but it might also delete every backup you
# ever made, cause your computer to melt, and your hair to catch fire.
#



#
# Usage: offsite-dump configname
#
# configname is the name of an amanda config.
#

$| = 1; # No STDOUT buffer
$gzip = "/usr/bin/gzip";
$tar  = "/usr/bin/gtar";

require 'getopts.pl';
Getopts('hzivd:');
$defroot = "/amanda/offsite";
$offroot = $opt_d ? $opt_d : $defroot;
$docomp  = $opt_z;
$doidx   = $opt_i;
$verbose = $opt_v;
$cat     = $docomp ? "/usr/bin/zcat" : "/bin/cat";

$config = shift;


if ($config eq '' || $opt_h) {
  print "Usage: amoffsite [-d dir] configname\n\n";
  print "configname is the name of an amanda config.\n\n";
  print "-h        This help message.\n";
  print "-d dir    Use dir instead of default target directory [$defroot].\n";
  print "-z        Compress the dump files with gzip.\n";
  print "-i        Generate an index for each dump file.\n";
  print "-v        Be verbose about progress.\n";
  exit 1;
}

%disklist = &read_disklist($config);
%tapelist = &find_tapes($config, \%disklist);
$vtape_dir = &get_vtape_dir($config);
%slots = &find_slots($vtape_dir, \%tapelist);
&copy_files($config, $vtape_dir, $offroot, \%tapelist, \%slots);

exit 0;


sub read_disklist {
  local($config) = @_;
  local(@lines, $line, $host, $disk, $lnum, $dspec, %dlist);

  print "Reading disk list ..." if $verbose;
  $/ = "";  # paragraph input mode
  open(CF, "amadmin $config disklist |") || die "'amadmin $config disklist: 
$!\n";
  while (<CF>) {
    @lines = split(/\n/, $_);
    $host = $disk = '';
    foreach $line (@lines) {
      if ($line =~ /host (\S+):$/) {
        $host = $1;
      } elsif ($line =~ /disk (\S+):$/) {
        $disk = $1;
      } elsif ($line =~ /^line (\d+)/) {
        $lnum = $1;
      }
    }
    if ($host eq '' || $disk eq '') {
      print STDERR "ERROR processing line '$lnum'.  host=$host  disk=$disk\n";
      exit 1;
    }
    $dspec = "${host}:${disk}";
    $dlist{$dspec} = 1;
  }
  close(CF);
  print " done\n" if $verbose;
  $/ = "\n";  # back to line input mode
  return %dlist;
}
         
# $dlist is a hashref
sub find_tapes {
  local($config,$dlist) = @_;
  local(@lines, %tlist, $line, $host, $disk, $dspec, $tspec);
  local($level, $tape, $fnum, $status, $h, $d, $date);
  local(%zdates, @dtmp, $nd);

  $nd = scalar(keys %$dlist);
  print "Finding tapes for $nd disks ..." if $verbose;
  foreach $dspec (sort keys %$dlist) {
    print "." if $verbose;
    ($host,$disk) = split(/:/, $dspec);
    %zdates = ();
    open(AM, "amadmin $config find $host $disk |")
      || die "'amadmin $config find $host $disk: $!\n";
    while (<AM>) {
      ($date, $h, $d, $level, $tape, $fnum, $status) = split;
      next if ($date !~ /^\d\d\d\d-\d\d-\d\d/);
      next if ($status ne 'OK');
      next if ($level ne '0');  
      # If we're still here, then this is a successful level-0 dump
      $tspec = "${tape}:${fnum}";
      $zdates{$date} = $tspec;
    }
    close(AM);

    if (scalar(keys %zdates) == 0) {
      print STDERR "ERROR: no level-0 for $dspec -- Continue? [n] ";
      chomp($ans = <STDIN>);
      if ($ans !~ /y/i) {
        exit 1;
      } else {
        $tlist{$dspec} = 'SKIP';
        next;
      }
    }

    # Sort the list of level-0 dates, and then take the last one, which
    # should be the most recent.
    @dtmp = sort keys %zdates;
    $date = pop(@dtmp);
    $tlist{$dspec} = $zdates{$date};
    $dumpdate{$dspec} = $date;
  }
  print " done\n" if $verbose;

  # Return the modified disklist
  return %tlist;
}

# $tlist is a hashref
sub find_slots {
  local($tdir, $tlist) = @_;
  local(%slots, $slot, $tape, $tspec, $fnum);
  local($files, @files, $nf, $sdir);

  chdir ($tdir) || die "Can't cd to $tdir: $!\n";

  foreach $tspec (sort values %$tlist) {
    next if ($tspec eq 'SKIP');
    ($tape,$fnum) = split(/:/, $tspec);

    next if (defined $slots{$tape});
    

    # There should be a file called slotN/00000.TAPENAME.  This tells us what
    # directory to find the dump files in for this "tape".
    $files = `find . -name 00000.$tape -print`;
    @files = split(/\n/, $files);
    $nf = scalar(@files);
    if ($nf != 1) {
      print STDERR "ERROR: wanted exactly 1 tape with label $tape, but found 
$nf. Abort.\n";
      exit 1;
    }
    $sdir = $files[0];
    $sdir =~ s|^\./||;
    $sdir =~ s|/.*||;
    $slots{$tape} = $sdir;
  }
  return %slots;
}


sub get_vtape_dir {
  local($config) = @_;
  local($str, $file, $dir);

  chomp($str = `amgetconf $config tapedev`);
  ($file, $dir) = split(/:/, $str);
  if ($file ne 'file') {
    print STDERR "ERROR: failed to find a 'file' tape device in $config 
config.\n";
    exit 1;
  }
  return $dir;
}


#
# The %tlist and %slots hashes are passed by reference to this function
#
sub copy_files {
  local($config, $src_root, $dst_root, $tlist, $slots) = @_;
  local(@disks, $ndisks, $dnum, $ddir, $dfile, $sfile);
  local($now,$datestamp,$target_dir);
  local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);

  # Make a target directory named after the config and today's date
  $now = time();
  ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
  $datestamp = sprintf("%04d%02d%02d", $year+1900, $mon+1, $mday);
  $target_dir = "${dst_root}/${config}-${datestamp}";
  if (-e "$target_dir") {
    print "$target_dir already exists.  Continue? [n] ";
    chomp($ans = <STDIN>);
    if ($ans !~ /y/i) {
      exit 1;
    }
  } else {
    mkdir($target_dir,0700);
    if (! -e "$target_dir") {
      print STDERR "ERROR: Failed to create $target_dir -- abort.\n";
      exit 1;
    }
  }
  &init_dumpdate_file($target_dir);

  @disks = sort keys %$tlist;
  $ndisks = scalar(@disks);
  $dnum = 0;
  foreach $disk (@disks) {
    $dnum++;

    if ($tlist->{$disk} eq 'SKIP') {
      print "Skipping $dnum/$ndisks $disk.\n";
      next;
    }

    ($host, $part) = split(/:/, $disk);
    $part =~ s|/|_|g;
    ($tape, $fnum) = split(/:/, $tlist->{$disk});
    $slot = $slots->{$tape};

    &add_dumpdate($target_dir,$disk,$dumpdate{$disk});

    $sfile = sprintf("%s/%s/%05d.%s.%s.0",
                     $src_root, $slot, $fnum, $host, $part);

    $ddir = "${target_dir}/${host}";
    if (! -d "$ddir") {
      mkdir($ddir,0700);
    }
    $dfile = "${ddir}/${host}.${part}.0";

    # printf("%5s %s\n", &getsize($sfile), $sfile);
    $size_spec = &getsize($sfile);
    print "Copying $size_spec file $dnum/$ndisks -> ${host}.${part}.0  ";
    # system ("cp $sfile $dfile");
    if ($docomp) {
      $dfile = "${dfile}.gz";
      system ("$gzip -v < $sfile > $dfile");
    } else {
      system ("cp $sfile $dfile");
      print "\n";
    }
    if ($doidx) {
      $ifile = "${ddir}/${host}.${part}.0.INDEX.gz";
      print "Creating index file $ifile ...";
      system ("$cat $dfile | dd bs=32k skip=1 | $tar tvf - | $gzip > $ifile");
      print " done.\n";
    }
  }
}


#
sub getsize {
  local($file) = @_;
  local($b, $k, $m, $g, $t);

  local($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
        $atime,$mtime,$ctime,$blksize,$blocks)
    = stat($file);

  $b = $size;
  if ($b > 1024) {
    $k = $b / 1024;
  } else {
    return sprintf("%.2f B", $b);
  }

  if ($k > 1024) {
    $m = $k / 1024;
  } else {
    return sprintf("%.2f KB", $k);
  }

  if ($m > 1024) {
    $g = $m / 1024;
  } else {
    return sprintf("%.2f MB", $m);
  }

  if ($g > 1024) {
    $t = $g / 1024;
  } else {
    return sprintf("%.2f GB", $g);
  }

  # If we get this far, the file is over 1 TB.  Wow.
  return sprintf("%.2f TB", $t);
}

sub getbytes {
  local($file) = @_;

  local($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
        $atime,$mtime,$ctime,$blksize,$blocks)
    = stat($file);

  return $size;
}

sub init_dumpdate_file {
  local($dir) = @_;

  open (DDF, "> $dir/dumpdates.txt") || die "Can't open $dir/dumpdates.txt: 
$!\n";
  close(DDF);
}


sub add_dumpdate {
  local($dir,$disk,$date) = @_;

  open (DDF, ">> $dir/dumpdates.txt") || die "Can't open $dir/dumpdates.txt: 
$!\n";
  print DDF "$date\t$disk\n";
  close(DDF);
}

<Prev in Thread] Current Thread [Next in Thread>