Amanda on Solaris 8 with Spectra Logic Treefrog(2000).

Hardware: Sun Enterprise 250, Spectra Logic Treefrog with AIT-1 tape(SCSI
interface).
Software: Solaris 8, amanda 2.4.3b2, tar 1.13.19, mtx 1.2.16rel

This document shows how I got Treefrog to work under solaris 8.  Big thank
to Stephen Carville on  Amanda-user mailing list.  He wrote a terrific
changer glue script for Treefrog in perl.  Without his help, it would
probably take another light year to get my own backup working.

Disclaimer:  This document is solely based on my own experience.  I do NOT
guarantee the methods will work in your environment.

Attachment description:
chg-spectra.pl    -   Stephen Carville's perl changer glue script
changer.conf    -    changer's configuration file for chg-2.pl
amanda.conf    -    sample configuration file given to me by Stephen
Carville
barlabler.pl      -    perl script I wrote to label tape according to its
barcode.

Here it goes:
1.  Install your treefrog.  Remeber to set the configuration dial in the
back of the Treefrog to "9" so it works properly under Solaris.  Read your
Treefrog manual on how to change the dial.  You might need to modify
/dev/kernel/sgen.conf to have changer(robotic arm) working properly.  The
tape drive should work without any modification.  Remeber to reboot after
sgen.conf modification.  If install correctly, your changer device
descriptor file would be in /dev/scsi/changer, and device would be in
/dev/rmt (See attachment)

2.  Install MTX.  It's a program that controls SCSI media changer devices.
You can download it from sourceforge.  Confirm that you can use 'mtx' to
control your changer and 'mt' to control your tape device.  Read the man
page on how to use those two commands.

3.  Configure and install amanda.  Follow docs/INSTALL, and you should have
no problem setting it up.  Remember to copy chg-spectra.pl to Amanda's
changer glue script directory, default is in /usr/local/libexec.

4.  Set up your backup configuration files by copying the default
amanda.conf.chg-scsi file from /usr/local/etc/amanda to
/usr/local/etc/amanda/<conf Name>/amanda.conf.  Change chg-scsi script
reference in amanda.conf to attached changer glue script(chg-spectra, see
attached sample amanda.conf) and modify tapedev and changerdev to point to
correct device files.  Make sure you point the log, index, and debug
directories to appropriate place.  Especially debug files, they're very
useful to debug amanda.

5.  Amanda needs to label your tapes before it can write to them.  If you
would like the labels to match your barcode, use the attached perl script
that I wrote(barlabeler).  Make sure 'amlabel' and 'mtx' are in your path,
they're in "/usr/local/sbin" by default.  I like to label them with barcode
because it's easier for me to locate tapes when I need them to restore.  I
wont need a separate label for tapes.  If you do not want to match labels
with the barcodes, you can label the tapes by manually issue amlabel command
to each tape.

6.  Run 'amcheck <conf name>' and correct every errors it reports back.
Amanda doesn't create some directories and files by itself, so you'll have
to do it yourself.  Stuff like log directory, index directory, gnu-tar
directory, or /etc/amandates file.  You also need to make sure permission on
those directories and files are set correctly.  Debug files are very useful
on figuring out where went wrong, so please consult them first when you run
into problem.

7.  When 'amcheck' does not return any more error, you can run some test
dump.  Once you make sure you can run dump and restore correctly, you can
delete log directory's content, index directory's content(if you setup
index), and the file "tapelist", then amanda will run fresh from scratch
again.

#!/usr/local/bin/perl

# Mark Lin.  2/25/2002
# Works with MTX version 1.2.16rel under solaris.  
# This little perl script label every tape same as their barcode.
# It works by using mtx staus to get the barcode and use amlabel
# to label each tape with barcode.

use strict;
  
  my (@result, @test, $line, $line2, $stat);

  if (!@ARGV) {
    print "Usage: barlabler <conf>\n";
    exit 1;
  }
  @result = `mtx status 2>&1`;

  foreach $line (@result) {  
    if ($line =~ m/Data Transfer Element 0:Full \(Storage Element (\d+).*([EC]\d{5})/) 
{
      # preserve the slot number and barcode
      #print "$1, $2\n";
      @test = `amlabel @ARGV[0] $2`;
      if ($_) {
         print "Label #2 Failed. reason below:\n";
         foreach $line2 (@test) {
           print "$line2";
         }
      }
      else { print "label $2 in drive $1 succeed\n"; 
      }
      next;
    }
    # Change your regular expression if your tapes has different barcode.
    if ($line =~ m/\s+Storage Element (\d+).*=([EC]\d{5})/) {
      #print "slot: $1     volume: $2\n";
      @test = `amlabel @ARGV[0] $2 slot $1`;
      if ($_) {
         print "Label #2 Failed. reason below:\n";
         foreach $line2 (@test) {
           print "$line2";
         }
      }
      else {
         print "label $2 in slot $1 succeed\n";
      }
      next;
    }

  }


Attachment: changer.conf
Description: Binary data

Attachment: amanda.conf
Description: Binary data

#!/usr/bin/perl -w
#
# Tape changer glue script for a Spectra 2000 tape changer
# Does not include the 'clean' option
#
# Version 1.0
# Copyright (C) 2001 Stephen Carville
#                    Unix and Network Administrator
#                    Ace Flood USA
#                    [EMAIL PROTECTED]
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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:
# Free Software Foundation, Inc.
# 59 Temple Place, Suite 330,
# Boston, MA  02111-1307  USA

use strict;
# use DB_File;

# paths to amanda directories
my $PREFIX = "/usr/local";
my $SBIN = "$PREFIX/sbin";
my $BIN = "$PREFIX/bin";
my $LIBEXEC = "$PREFIX/libexec";

# debugging files -- set DEBUGDIR to "" to disable debugging output
my $DEBUGDIR = "/tmp/amanda";
my $DBGFILE = "changer.debug";

# set a conservative path.  All commands are full paths so an empty path
# would be OK
my $PATH = "/bin:/sbin";

# commands -- change these to suit your configuration
my $MT =  "/usr/bin/mt";
my $MTX = "/usr/local/sbin/mtx";
my $AMGETCONF = "$SBIN/amgetconf";
my $MAILER = "/usr/bin/mailx";

# ----------- end of user configuration --------------
# exit codes
my ($SUCCESS,$ERROR,$BADERROR) = (0,1,2);

my ($cmd,$parm,$line,$rtn,$status);
my ($tapedev,$changerdev,$changerfile,$mailto);
my ($cleanfile,$accessfile,$slotfile,$labelfile,$dbgfile);

# careful! - global!
my ($firstslot,$lastslot,$cleanslot,$havereader);

if (defined $ARGV[0]) {$cmd = $ARGV[0];} else {$cmd="";};
if (defined $ARGV[1]) {$parm = $ARGV[1];} else {$parm = "";};

# get rid of any leading '-' in the command
$cmd =~ s/-//;

# print "$cmd,$parm\n";

# set the execution path
$ENV{PATH} = $PATH;

# get some values from the configuration file -- careful these are
# global
$tapedev = `$AMGETCONF tapedev`; $tapedev =~ s/\n//;
$changerdev = `$AMGETCONF changerdev`; $changerdev =~ s/\n//;
$changerfile =  `$AMGETCONF changerfile`; $changerfile =~ s/\n//;
$mailto = `$AMGETCONF mailto`; $mailto =~ s/\n//;

# mtx doesn't always need the changer device but it doesn't hurt
$MTX .= " -f $changerdev";
# OTOH, mt usually does need the tape device
$MT .= " -f $tapedev";

# set the debug file
if ( -d $DEBUGDIR) {
  $dbgfile = "$DEBUGDIR/$DBGFILE";
} else {
  $dbgfile = "/dev/null";
}
# this makes it easier to tell what happened
debugmsg ("==========");

# print "$tapedev,$changerdev,$changerfile\n";

# I only use barcodes and conf but I include the rest JIC.
$cleanfile = "$changerfile-clean";
$accessfile = "$changerfile-access";
$slotfile = "$changerfile-slot";
$labelfile = "$changerfile-barcodes";
$changerfile .= ".conf";

# get values from the changerfile
open FILE, $changerfile or die "$BADERROR: Unable to open $changerfile";

foreach $line (<FILE>) {
  $line =~ s/\n//;
  $line =~ s/\s+//g;
  if ($line =~ m/^\#/ || $line eq "") {
    next;
  }
  $line =~ m/(\w+)=(\d+)/;
  # no tricky way to do this?
  if ($1 eq "firstslot") {$firstslot = $2; next; }
  if ($1 eq "lastslot") {$lastslot = $2; next; }
  if ($1 eq "cleanslot") {$cleanslot = $2; next; }
  if ($1 eq "havereader") {$havereader = $2; next; }
}

# print "$firstslot,$lastslot,$cleanslot,$havereader\n";

# branch to the appropiate subroutine
# how are we callled? slot, info, reset, eject, label, search, clean
if ($cmd eq "slot") { 
  ($rtn,$status) = slot ($parm);
  print "$status\n";
  exit $rtn;
}
if ($cmd eq "info") { 
  ($rtn,$status) = info ($parm); 
  print "$status\n";
  exit $rtn;
}
if ($cmd eq "reset") { 
  ($rtn,$status) = rset ($parm); 
  print "$status\n";
  exit $rtn;
}
if ($cmd eq "eject") {
  ($rtn,$status) = eject ($parm); 
  print "$status\n";
  exit $rtn;
}
if ($cmd eq "label") {
  ($rtn,$status) = label ($parm); 
  print "$status\n";
  exit $rtn;
}
if ($cmd eq "search") {
  ($rtn,$status) = search ($parm); 
  print "$status\n";
  exit $rtn;
}
if ($cmd eq "clean") { 
  ($rtn,$status) = clean ($parm);
  print "$status\n";
  exit $rtn;
}

# if get this far cmd is no good
print "Unkown option: $cmd\n";
exit $BADERROR;

# command subroutines

# move a tape from a slot to the tape drive
sub slot {
  my ($parm) = @_;
  my ($usedslot,$barcode,$loadslot);

  ($usedslot,$barcode) = status ();
  $loadslot = -1;

  debugmsg ("SLOT->load tape from slot $parm");

  # what were we asked to do?
  # current -- return current loaded slot or load first slot
  if ($parm eq "current") {
    if ($usedslot < $firstslot) {
      $loadslot = $firstslot;
    } else {
      return $SUCCESS,"$usedslot $tapedev";
    }
    return load ($loadslot);
  }

  # next or advance -- load the next tape or first slot
  if ($parm eq "next" or $parm eq "advance") {
    unload ($usedslot);
    $loadslot = $usedslot+1;
    if ($loadslot > $lastslot) {
      $loadslot = $firstslot;
    }
    return load ($loadslot);
  }

  # prev -- load the previous or last slot
  if ($parm eq "prev") {
    unload ($usedslot);
    $loadslot = $usedslot-1;
    if ($loadslot < $firstslot) {
      $loadslot = $lastslot;
    }
    return load ($loadslot);
  }

  # first -- load the first slot
  if ($parm eq "first") {
    unload ($usedslot);
    return load ($firstslot);
  }

  # last -- load the last slot
  if ($parm eq "last") {
    unload ($usedslot);
    return load ($lastslot);
  }

  # clean -- I do not support cleaning.  I prefer to set it up
  # myself
  if ($parm eq "clean") {
    return clean($parm);
  }

  # check for a legitimate number
  unless ($parm =~ m/\d+/) {
    debugmsg ("SLOT->bad slot ID $parm");
    return $ERROR,"0 Slot $parm is out of range ($firstslot -- $lastslot";
  }
  if ($parm >= $firstslot && $parm <= $lastslot) {
    if ($parm == $usedslot) {
      return $SUCCESS,"$usedslot $tapedev";
    }
    unload ($usedslot);
    return load ($parm);
  } else {
    return $BADERROR, "0 Slot $parm is out of range ($firstslot -- $lastslot)\n";
  }
  return $BADERROR,"0 Unknown error!\n";
}

# return some information about capabilities
sub info {
  my ($parm) = @_;
  my ($usedslot,$barcode,$str);

  ($usedslot,$barcode) = status();

  debugmsg("INFO -> current slot $usedslot, last slot $lastslot");

  if ($usedslot < 0) {
    $usedslot = 0;
  }
  $str = "$usedslot $lastslot 1";

  # someday check the tape library for havereader
  if ($havereader) {
    $str .= " 1";
  }
  debugmsg ("INFO -> $str");
  return $SUCCESS,"$str";
}

# reset the tape library to a known state
sub rset {
  my ($parm) = @_;
  my ($usedslot,$barcode);

  ($usedslot,$barcode) = status();

  debugmsg ("RESET -> loading tape from slot $firstslot to $tapedev");

  # first eject any tape already loaded
  unload ($usedslot);
  return load ($firstslot);
}

# eject the current tape
sub eject {
  my ($usedslot,$barcode);

  ($usedslot,$barcode) = status();

  debugmsg ("EJECT->unloading slot $usedslot");
  return unload ($usedslot);
}

# add a label to the barcodes file
sub label {
  my ($label) = @_;

  my ($usedslot,$barcode);
  my (%barcodes);

  ($usedslot,$barcode) = status();
  %barcodes = load_labelfile();

  debugmsg("Adding Barcode $barcode and amlabel $label for Slot $usedslot into 
$labelfile");

  # if the label exists and is synced
  if (exists $barcodes{$label} && $barcode eq $barcodes{$label} ) {
    debugmsg("Barcode $barcode already synced for $label");
  } else {
    # any other condition, add/overwrite it and save the new file
    $barcodes{$label} = $barcode;
    save_labelfile(%barcodes);
  }
  return $SUCCESS, "0 $usedslot $tapedev";
}

# search for a tape in barcodes and load it into the drive
sub search {
  my ($label) = @_;
  my (%labels,$slot,$err);

  debugmsg("SEARCH->searching for tape $label");

  %labels = load_labelfile();

  # if the label is in the file
  if (exists $labels{$label}) {
    $slot = findbybarcode($labels{$label});
  } else {
    debugmsg("SEARCH->tape $label not found in barcode database");
    errormsg("Error with Barcode reader","Tape $label not found in barcode database");
    return $BADERROR,"tape $label not found in barcode database";
  }
  # if the tape was not found in the changer
  if ($slot < 0) {
    debugmsg("SEARCH->tape $label not found in changer");
    errormsg("Error with Barcode reader","Tape $label not found in changer");
    return $BADERROR,"tape $label not found in changer";
  }
  # tape is already loaded
  if ($slot == 0) {
    return $SUCCESS,"$tapedev";
  }
  # otherwise load the tape
  debugmsg("SEARCH->tape $label found at slot $slot");
  sleep (10);
  load($slot);
  return $SUCCESS,"$tapedev";
}

# cleaning not supported yet
sub clean {
  return $ERROR,"0 Cleaning not supported by driver\n";
}

# unload a tape from the drive and replace it in its slot
sub unload {
  my ($slot) = @_;
  my (@result);

  # we know that -1 is empty
  if ($slot < 0) {
    debugmsg ("UNLOAD->$tapedev already empty");
    return $ERROR, "0 Drive was not loaded";
  }
  @result = `$MTX unload $slot 2>&1`;
  if ($?) {
    debugmsg ("UNLOAD->error unloading slot $slot");
    return $BADERROR,"0 @result";
  }
  debugmsg ("UNLOAD->unloaded tape to slot $slot");
  return $SUCCESS,"$slot $tapedev";
}

# load a tape into the drive
sub load {
  my ($slot) = @_;
  my (@result,$cntr,$off);

  debugmsg ("LOAD->loading tape from slot $slot");

  @result = `$MTX load $slot 2>&1`;
  if ($?) {
    debugmsg ("LOAD->error in loading tape from slot $slot");
    return $BADERROR,"$slot @result";
  }

  # wait for drive to go online but not forever
  $cntr = 0;
  $off = "";
  while ($off eq "") {
    @result = `$MT status 2>&1`;
    $off = grep (/offline/,@result);
    $cntr++;
    # is this the last try?
    if ($cntr > 10) {
      debugmsg ("LOAD->still offline at try # $cntr");
      return $BADERROR,"$slot @result";
    }
    sleep(10);
  }
  # now rewind the tape
  @result = `$MT rewind 2>&1`;
  sleep (1);

  # phew!
  debugmsg ("LOAD->$slot $tapedev");
  return $SUCCESS,"$slot $tapedev";
}

#
# load the label database this is a space separated text file
# but I return a hash to make manipulation easier. This should use
# DB_File but not all systems have it installed so I follow the
# KISS principle. returns %hash{$label}=$barcode
sub load_labelfile {
  my (%labels,$line,$lbl,$bc);

  unless (-f $labelfile) {
    return %labels;
  }
  open LABEL, "$labelfile";
  foreach $line (<LABEL>) {
    $line =~ s/\n//;
    ($lbl,$bc) = split /\s+/, $line;
    if (defined $lbl and defined $bc) {
      $labels{$lbl} = $bc;
    }
  }
  close LABEL;
  return %labels;
}

# save the labelfile -- this completely recreates the file!
sub save_labelfile {
  my (%labels,$lbl) = @_;

  # clobber the existing file
  open LABEL, ">$labelfile";
  foreach $lbl (keys %labels) {
    print LABEL "$lbl $labels{$lbl}\n";
  }
  close LABEL;
}

# return the slot for a particular barcode
sub findbybarcode {
  my ($bc) = @_;
  my (@result,$line,$slot);

  @result = `$MTX status 2>&1`;

  foreach $line (@result) {
    if ($line =~ /[\w\s]+ Element (0):Full \([\w\s\d]+\):VolumeTag = (.+)/) {
      if ($bc eq $2) {
        debugmsg("FINDBYBARCODE->Label $bc is at slot $1");
        return $1;
      }
    }
    if ($line =~ /\w+\s+Element (\d+):Full :VolumeTag=(.+)/) {
      if ($bc eq $2) {
        debugmsg("FINDBYBARCODE->Label $bc is at slot $1");
        return $1;
      }
    }
  }
  debugmsg("FINDBYBARCODE->Label $bc not found!");
  return -1;
}

# return the slot and barcode of the loaded tape
sub status {
  my (@result,$line,$stat,$el,$bc);

  # get the changer status
  @result = `$MTX status 2>&1`;

  # find the line with the tape drive in it
  foreach $line (@result) {
    if ($line =~ m/Data Transfer Element 0:(.+)/) {
      # preserve the slot number and barcode
      $stat = $1;
      last;
    }
  }

  #split the slot number from the barcode
  ($el,$bc) = split /:/,$stat;

  # get the loaded element
  if ($el =~ m/Full \(Storage Element (\d+) Loaded/) {
    $el = $1;
  } else {
    $el = -1;
  }
  if (defined $bc) {
    # get the loaded barcode
    if ($bc =~ m/VolumeTag = (.+)/) {
      $bc = $1;
    } else {
      $bc = -1;
    }
  } else {
    $bc = -1;
  }
#  print "$el,$bc\n";
  return $el,$bc;
}

# add a debugging messsage to the debug file
sub debugmsg {
  my ($message) = @_;

  open DBG, ">>$dbgfile";
  print DBG $message;
  print DBG "\n";
  close DBG;
}

# send an error message to mailto
sub errormsg {
  my ($subject,$message) = @_;

  open PIPE, "| $MAILER -r \"$mailto\" -s \"$subject\" $mailto";
  print PIPE $message;
  close PIPE;
}

Reply via email to