You are here: start » blog » 2008 » 07 » python_amlabel_bulklabeling

Python + amlabel = Bulklabeling!

A while ago, we upgraded all 50 backup tapes in the tape library at work from LTO-2 to LTO-3. This of course meant unpacking, inserting and labeling those tapes, both physically as well as electronically. While the physical labeling could not be automated1), I finally sat down to at least make the electronic labeling using amanda's amlabel a little bit less unworthy of a sysadmin ;-) The result was a small python script2) capable of bulk labeling a bunch of tapes based on a formatstring pattern of the label format that at least works perfectly fine in our environment – YMMV ;-) I decided to post it here nevertheless as it might be of help for somebody out there besides us3). Some basic usage instructions, the code and a download link follow.

Usage

Usage is quite simple. Given your labels are something like TAPE01 or BACKUP_042, the formatstring would be TAPE%02i or BACKUP_%03i. So if you e.g. want to label five tapes using the above BACKUP format (and amanda's BACKUP config), starting with tape 42 which is located in physical slot 23 of your library, the tapelabel call would be tapelabel -f 23 -c 5 BACKUP BACKUP_%03i 42. For more info, see tapelabel –help:

Usage: tapelabel [options] config formatstring number

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -v, --verbose         verbose output, use multiple times for spam
  -q, --quite           suppress output, use multiple times for silence
  -f FIRSTSLOT, --firstslot=FIRSTSLOT
                        specifies slot of first tape to label [default: '1']
  -l LASTSLOT, --lastslot=LASTSLOT
                        specifies slot of last tape to label
  -c COUNT, --count=COUNT
                        specifies count of tapes to label [default: 'none']
  -F, --force           force writing of label
  --dry-run             simulates run

Code and Download

/root/bin/tapelabel

#!/usr/bin/python
 
import os
import sys
import commands
from optparse import OptionParser
 
SLOT_COMMAND = '/usr/sbin/changer -slot %i'
WRITE_COMMAND = '/usr/sbin/amlabel %s %s'
WRITE_COMMAND_FORCE = '/usr/sbin/amlabel -f %s %s'
READ_COMMAND = '/usr/bin/readlabel /dev/rmt/current'
 
options = config = fs = startValue = None
 
def change_slot(slot):
	dePrint("Changing to slot %i..." % slot)
	if (not options.dryrun):
		(status, output) = commands.getstatusoutput(SLOT_COMMAND % slot)
		if (status != 0):
			print >>sys.stderr, "Error while changing tape: %s" % output
			sys.exit(-1)
	dePrint("\tDone.", 2)
 
def write_label(val):
	global options, config, fs
 
	label = fs % val
	dePrint("Writing label '%s'..." % label)
 
	if (not options.dryrun):
		if (options.force):
			(status, output) = commands.getstatusoutput(WRITE_COMMAND_FORCE % (config, label))
		else:
			(status, output) = commands.getstatusoutput(WRITE_COMMAND % (config, label))
		if (status != 0):
			if (status == 127):
				print >>sys.stderr, "amlabel reported '%s'. You might want to use -f to override this and force the label." % output
				sys.exit(-2)
			else:
				print >>sys.stderr, "Error while labeling: '%s'" % output
				sys.exit(-3)
	dePrint("\tDone.", 2)
 
def verify_label(val):
	global options, config, fs
 
	label = fs % val
	dePrint("Verifying label '%s'..." % label)
	if (not options.dryrun):
		(status, output) = commands.getstatusoutput(READ_COMMAND)
		if (status != 0):
			print >>sys.stderr, "Error while reading label: '%s'" % output
			sys.exit(-4)
 
		current_label = output.split()[-1]
		dePrint("\tRead label '%s'." % current_label, 2)
 
	if (not options.dryrun):
		return (label == current_label)
	else:
		dePrint("\tRead label '%s'." % label, 2)
		return True
 
def main():
	global options, config, fs, startValue	
 
	setup()
 
	try:
		slot = options.firstSlot
		for i in range(options.count):
			change_slot(slot)
			write_label(startValue + i)
			if (verify_label(startValue + i)):
				slot = slot + 1
			else:
				print >>sys.stderr, "Verification of label failed."
				sys.exit(-4)
	except KeyboardInterrupt:
		dePrint("# KeyboardInterrupt caught", level=0)	
 
def setup():
	global options, fs, config,startValue
 
	minVer = [2, 4]
	ver = sys.version_info[0:2]
	if not (ver[0] > minVer[0] or (ver[0] == minVer[0] and ver[1] >= minVer[1])):
		print >>sys.stderr, "This program requires Python >=%s, aborting." % ",".join(map(str, minVer))
		sys.exit(-1)
 
	## argument parsing setup
	#
	parser = OptionParser(version = "%prog 0.1")
	parser.set_usage("%prog [options] config formatstring number")
 
	parser.add_option("-v", "--verbose",
		action = "count", dest = "verbose", default = 0,
		help = "verbose output, use multiple times for spam"
	)
	parser.add_option("-q", "--quite",
		action = "count", dest = "quite", default = 0,
		help = "suppress output, use multiple times for silence"
	)
 
	parser.add_option("-f", "--firstslot",
		type = "int", dest = "firstSlot", default = 1,
		help = "specifies slot of first tape to label "
		     +  "[default: '%default']"
	)
	parser.add_option("-l", "--lastslot",
		type = "int", dest = "lastSlot", default = None,
		help = "specifies slot of last tape to label"
	)
	parser.add_option("-c", "--count",
		type = "int", dest = "count", default = None,
		help = "specifies count of tapes to label "
		     + "[default: '%default']"
	)
	parser.add_option("-F", "--force",
		action = "store_true", dest = "force",
		help = "force writing of label"
	)
 
	parser.add_option("--dry-run",
		action = "store_true", dest = "dryrun",
		help = "simulates run"
	)
 
	(options, fs) = parser.parse_args()
 
	if (len(fs) == 0):
		parser.error("You need to supply a format string.")
	elif (len(fs) != 3):
		parser.error("Need config, formatstring and start value.")
	else:
		(config, fs, startValue) = fs
		startValue = int(startValue)
 
	if (not options.lastSlot and not options.count):
		parser.error("You need to supply either a count of tapes to label or the number of the last slot which to label.")
	elif (not options.lastSlot and options.count):
		options.lastSlot = options.firstSlot + options.count - 1
	elif (options.lastSlot and not options.count):
		options.count = options.lastSlot - options.firstSlot + 1
	elif (options.lastSlot and options.count and not (options.count == options.lastSlot - options.firstSlot + 1)):
		parser.error("Start slot plus tape count does not equal end slot. Please decide on either count or endslot.")
 
	# verbosity
	if options.quite:
		options.verbose = -options.quite
 
 
def dePrint(str, level=1):
	"""prints (debug) messages"""
 
	if (options.verbose - level) >= 0:
		print str
 
 
if __name__ == "__main__":
	main()

Download

1) although we fooled around with the idea of using some Mind Storms construction for this while I waited for all the labels to be printed on the label machine
2) heavily influenced by demod's frmtget
3) and even if it's just for use as an example of how to use the OptionParser module of Python ;-)

Discussion

Enter your comment (wiki syntax is allowed):
SYYPY
blog/2008/07/python_amlabel_bulklabeling.txt · Last modified: 2008/07/13 21:10 by foosel