Automatisches Ein- und Ausschalten einer Tape-Library für Bacula-Backups

Schon seit einiger Zeit läuft hier ein Backup-Setup im Produktivbetrieb, bei dem mein Heimserver diverse Rechner im LAN mittels Bacula auf LTO-Bänder in einer Tape Library sichert. Da aber die Lüfter der Library im Betrieb einen ziemlichen Radau verursachen und die Library auch viel zu viel Strom für einen Dauerbetrieb verbraucht, habe ich nach einem Weg gesucht, die Library „passgenau“ zur Durchführung von Backup-Jobs ein- und danach wieder auszuschalten.

Hardwareseitig war das kein großes Problem: Das Ein- und Ausschalten der 230-V-Spannungsversorgung übernimmt eine „IP-Steckdose“, konkret eine Allnet ALL3075. Zum Glück kommt die Bandbibliothek – eine Overland NEO2000 – problemlos mit dem externen Abschalten der Spannung klar. Es muss lediglich sichergestellt sein, dass sich beim Ausschalten keine Bänder mehr in den Laufwerken befinden, da dies zu Problemen beim nächsten Einschalten führt. Dass die Datenverbindung zum Heimserver durch Fibre Channel realisiert ist und damit elektrisch potentialfrei ist, sorgt für zusätzliche Sicherheit vor Hardwareproblemen.

Die Software-Seite war dagegen etwas aufwendiger: Ein zeitgesteuertes Ein- und Ausschalten z.B. mittels Cronjob schied aus. Zwar starten die Backup-Jobs jeden Abend zur selben Zeit, aber die Laufdauer der Jobs ist stark unterschiedlich: an einem normalen Wochentag, wenn nur inkrementelle Jobs laufen, dauert ein kompletter Lauf weniger als eine halbe Stunde. Ein Lauf von „Full“-Jobs an jedem ersten Sonntag im Monat dauert hingegen über zwölf Stunden. Auch darauf, dass alle Jobs jedes Mal und stets in der gleichen Reihenfolge laufen, wollte ich mich nicht verlassen. Daher habe ich mir einige Shell-Skripte gebastelt, die von den Bacula-Jobs aufgerufen werden. Beim Start eines Jobs trägt das erste Skript „request_tapelibrary.sh“ eine Art Merker in eine Textdatei ein und initalisiert die Tapelibrary, falls sie nicht ohnehin schon läuft:

#!/bin/bash
#
# This script powers up the tapelibrary and adds a line to a semphore file
# 2018-02-12 Sebastian Suchanek
 
SEMAPHORE='/etc/tapelibrary_semaphore'
TURN_ON_CMD='/usr/local/bin/all3075_switch_on'
TURN_OFF_CMD='/usr/local/bin/all3075_switch_off'
GET_SWITCH_STATUS_CMD='/usr/local/bin/get_status_all3075'
RESET_FC_CMD='sudo /usr/local/bin/reset_fc_hba'
LIBRARY_DEVICE='/dev/sg4'
LIBRARY_ID_STRING='mediumx'
 
# Print usage help
print_usage()
{
	echo "Usage: request_tapelibrary <token>"
	echo ""
	echo "<token> is a string which will be used in the semaphore file."
        echo "        It must be unique for ever concurrent backup job."
	echo ""
}
 
# Do a reboot via a power cycle
reboot_library()
{
 	eval "$TURN_OFF_CMD"
	sleep 10s
	eval "$TURN_ON_CMD"
	for i in `seq 1 180`; do
        	echo -n "+"
        	sleep 1s
        done
}
 
# Check if token was passed
if [ $# -eq 0 ]; then
	print_usage
	exit 1
fi
 
# Check if semaphore file exists and create otherwise
if ! [ -f $SEMAPHORE ]; then
	echo "Semaphore file not found, creating $SEMAPHORE"
	touch $SEMAPHORE
	chmod 664 $SEMAPHORE
else
	echo "Semaphore file $SEMAPHORE found."
fi
 
# Get status of power switch and turn on library if is not running already
SWITCH_STATUS=`$GET_SWITCH_STATUS_CMD`
if [ "$SWITCH_STATUS" -eq 0 ]; then
	echo "Tapelibrary is powered off, turning on."
	eval "$TURN_ON_CMD"
else
	echo "Tapelibrary is already powered on."
	# Add line to semaphore file
	CUR_TIME=`date +"%F %T"`
	echo "$CUR_TIME - $1" >> $SEMAPHORE
	exit 0
fi
 
# Add line to semaphore file
CUR_TIME=`date +"%F %T"`
echo "$CUR_TIME - $1" >> $SEMAPHORE
 
# Wait grace period to allow initialisation of library
echo -n "Waiting grace period to allow initialisation "
for i in `seq 1 180`; do
	echo -n "."
	sleep 1s
done
echo ""
 
# Check, if library is properly initialised and initialise otherwise
echo "Checking availability of tapelibrary."
LIB_STATUS=`lsscsi | grep -c $LIBRARY_ID_STRING`
if [ "$LIB_STATUS" -eq 0 ]; then
	echo "Tapelibrary is not initialised yet."
    i=1
	echo -n "Reseting Fibre Channel link "
	while [ `lsscsi | grep -c $LIBRARY_ID_STRING` -eq 0 ]; do
		if [ $((i % 6)) -eq 0 ]; then
			echo "Tapelibrary seems to have a problem. Doing a cold reset."
			reboot_library
                else
			eval "$RESET_FC_CMD"
			sleep 5s
			echo -n "."
		fi
                let "i++"
	done
	echo ""
else
	echo "Tapelibrary is accessible."
fi
 
# Library was just powered on, so check readiness and eject cartridges
if [ "$SWITCH_STATUS" -eq 0 ]; then
 
	mtx -f $LIBRARY_DEVICE status > /dev/null 2>&1
        STATUS=$?
	if [ $STATUS != 0 ]; then
		echo -n "Tapelibrary seems to be still initialising. Waiting "
		until [ $STATUS -eq 0 ]
		do
			sleep 5
			mtx -f $LIBRARY_DEVICE status > /dev/null 2>&1
			STATUS=$?
			echo -n "."
		done
		echo " "
	fi
 
	DRIVESTATE=`mtx -f $LIBRARY_DEVICE status | grep "Data Transfer Element 0" | grep -c Full`
	if [ $DRIVESTATE -eq 1 ]; then
		echo "A Cartridge is still in LTO-1 drive. Ejecting..."
 
bconsole <<END_OF_COMMANDS
release storage=LTO1-Drive
 
 
quit
END_OF_COMMANDS
 
        fi
 
        DRIVESTATE=`mtx -f $LIBRARY_DEVICE status | grep "Data Transfer Element 1" | grep -c Full`
        if [ $DRIVESTATE -eq 1 ]; then
                echo "A Cartridge is still in LTO-4 drive. Ejecting..."
 
bconsole <<END_OF_COMMANDS
release storage=LTO4-Drive
 
 
quit
END_OF_COMMANDS
 
        fi
fi
 
echo "Tapelibrary is now ready for operation."

Die drei darin unter anderem aufgerufenen Kommandos „all3075_switch_on“, „all3075_switch_off“ und „get_status_all3075“ sind jeweils kleine ebenfalls selbstgeschrieben Shell-Skripte, die im Prinzip curl-Aufrufe zum Ein- und Ausschalten sowie zum Auslesen des Schalt-Status’ der Allnet ALL3075 dienen. Das Skript „reset_fc_hba“, das, wie man unschwer am Namen erkennen kann, den Fibre-Channel-Controller resettet und ihn damit veranlasst, die Verbindungen zur Tape Library neu zu initalisieren, sieht so aus:

#!/bin/sh
echo 1 > /sys/class/fc_host/host0/issue_lip
echo 1 > /sys/class/fc_host/host7/issue_lip
echo "- - -" > /sys/class/scsi_host/host0/scan
echo "- - -" > /sys/class/scsi_host/host7/scan

Da das Initalisieren der Tape Library nach dem Einschalten eine Weile dauert, ist es notwendig, dass man bei einem Bacula-Job durch eine entsprechend niedrigen Prioritätswert dafür sorgt, dass er sicher als erstes läuft und während dessen Laufzeit auch keine weiteren Jobs laufen. Am Ende jeden Jobs wird dann von Bacula das Skript „release_tapelibrary.sh“ aufgerufen, das „seinen“ Merker wieder aus der zentralen Textdatei löscht:

#!/bin/sh
#!/bin/bash
#
# This script checks if all concurrent jobs on the tapelibrary are finished
# and powers down the library if yes.
# 2018-02-12 Sebastian Suchanek
 
SEMAPHORE='/etc/tapelibrary_semaphore'
TURN_OFF_CMD='/usr/local/bin/all3075_switch_off'
GET_SWITCH_STATUS_CMD='/usr/local/bin/get_status_all3075'
 
# Print usage help
print_usage()
{
	echo "Usage: release_tapelibrary <token>"
	echo ""
	echo "<token> is a string which will be used in the semaphore file."
        echo "        It must be unique for ever concurrent backup job."
	echo ""
}
 
 
# Check if token was passed
if [ $# -eq 0 ]; then
	print_usage
	exit 1
fi
 
# Check if semaphore file exists and exit otherwise
if ! [ -f $SEMAPHORE ]; then
	echo "Semaphore file not found, quitting"
	exit 1
else
	echo "Semaphore file $SEMAPHORE found."
fi
 
# Check if token is included in semaphore file
COUNT_TOKEN=`grep -c "$1" $SEMAPHORE`
if [ "$COUNT_TOKEN" -gt 1 ]; then
	echo "Token found more then once in semaphore file. Something's wrong, quitting."
	exit 1
fi
if [ "$COUNT_TOKEN" -eq 0 ]; then
        echo "Token not found in semaphore file. Something's wrong, quitting."
        exit 1
fi
 
# Token is valid. Remove from semaphore file
TFILE=`mktemp -t libsemtemp.XXXXX`
sed "/$1/d" $SEMAPHORE > "$TFILE"
cat "$TFILE" > $SEMAPHORE
rm $TFILE
 
# Check if semaphore file is empty, i.e. no jobs are left
if ! [ -s $SEMAPHORE ]; then
        echo "No jobs left to process. Tapelibrary will be powered down soon."
else
	COUNT_JOBS=`wc -l < $SEMAPHORE`
	echo "Still $COUNT_JOBS job(s) left to process. Leaving tapelibrary powered on."
fi

Zusammen sieht das in einer Bacula-Job-Definition dann zum Beispiel so aus:

Job {
  Name = "B_TigersclawServer"
  Client = tigersclaw-fd
  Type = Backup
  Storage = LTO4-Drive
  FileSet = "Tigersclaw Server"
  Schedule = "WeeklyCycle"
  Pool = File
  Full Backup Pool = LTO41
  Differential Backup Pool = LTO40
  Incremental Backup Pool = LTO40
  RunBeforeJob = "/usr/local/bin/request_tapelibrary.sh TigersclawServer"
  RunScript {
    Command  = "/usr/local/bin/release_tapelibrary.sh TigersclawServer"
    RunsWhen = After
    RunsOnFailure = yes
    RunsOnClient = no
    RunsOnSuccess = yes
  }
  Messages = Standard
  Priority = 10
  Write Bootstrap = "/var/lib/bacula/%c_%n.bsr"
  Maximum Concurrent Jobs = 2
}

Schlussendlich kommt noch ein drittes Skript, „poweroff_tapelibrary.sh“, ins Spiel. Dieses wird von einem Cronjob alle fünf Minuten aufgerufen und prüft, ob aktuell Backup-Jobs laufen. Ist der letzte Job abgeschlossen (die „Merker-Datei“ ist gerade leer geworden), wartet das Skript sicherheitshalber insgesamt drei Aufrufe (entspricht 15 Minuten) ab, veranlasst dann das Auswerfen aller LTO-Bänder aus den Laufwerken der Library und schaltet die Tape Library danach ab.

#!/bin/bash
#
# This script checks if the semaphore file is empty and powers down
# the tapelibrary after a given amount of repeats.
# 2018-03-12
 
MAXCOUNT=3
 
SEMAPHORE='/etc/tapelibrary_semaphore'
COUNTF='/etc/tapelibrary_count'
TURN_OFF_CMD='/usr/local/bin/all3075_switch_off'
GET_SWITCH_STATUS_CMD='/usr/local/bin/get_status_all3075'
LIBRARY_DEVICE='/dev/sg4'
 
# Get status of tapelibrary
SWITCH_STATUS=`$GET_SWITCH_STATUS_CMD`
if [ "$SWITCH_STATUS" -eq 0 ]; then
#        echo "Tapelibrary is currently powered down."
        exit 0
fi
 
# Check if semaphore file exists and is not empty
if [ -f $SEMAPHORE ] && [ -s $SEMAPHORE ]; then
#	echo "Semaphore file exists and is not empty. Quitting."
	echo "1" > $COUNTF # As a precautionary measure, reset counting file
	exit 0
#else
#	echo "Semaphore file either doesn't exist or is empty."
fi
 
# Check if counting file exists. If so, read counting value and if not, create it.
if [ ! -f $COUNTF ]; then
	echo "1" > $COUNTF
        CURCOUNT=1
else
	CURCOUNT=`cat $COUNTF`
fi
 
#echo "$CURCOUNT. read of status."
 
# Check if tapes are left in drives and eject them if necessary
if [ "$CURCOUNT" -gt "$MAXCOUNT" ]; then
	echo "Maximum re-checks of $MAXCOUNT reached."
 
        DRIVESTATE=`mtx -f $LIBRARY_DEVICE status | grep "Data Transfer Element 0" | grep -c Full`
        if [ "$DRIVESTATE" -eq 1 ]; then
                echo "A Cartridge is still in LTO-1 drive. Ejecting..."
 
bconsole <<END_OF_COMMANDS
release storage=LTO1-Drive
 
 
quit
END_OF_COMMANDS
 
        fi
 
        DRIVESTATE=`mtx -f $LIBRARY_DEVICE status | grep "Data Transfer Element 1" | grep -c Full`
        if [ "$DRIVESTATE" -eq 1 ]; then
                echo "A Cartridge is still in LTO-4 drive. Ejecting..."
 
bconsole <<END_OF_COMMANDS
release storage=LTO4-Drive
 
 
quit
END_OF_COMMANDS
 
        fi
 
	echo -n "Waiting grace period to allow tapelibrary to settle down "
	for i in `seq 1 10`; do
        	echo -n "."
        	sleep 1s
	done
	echo ""
 
	echo "Powering down tapelibrary now."
        echo "1" > $COUNTF
	eval "$TURN_OFF_CMD"
else
	CURCOUNT=$((CURCOUNT+1))
#	echo "Increasing count to $CURCOUNT"
        echo $CURCOUNT > $COUNTF
fi

Dieses Setup läuft nun schon seit über einem Jahr stabil.

0 comments on “Automatisches Ein- und Ausschalten einer Tape-Library für Bacula-Backups

1 Pings/Trackbacks für "Automatisches Ein- und Ausschalten einer Tape-Library für Bacula-Backups"

Schreibe einen Kommentar