KVM: add an instance uid file concept
[ganeti-local] / tools / lvmstrap
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Program which configures LVM on the Ganeti nodes.
23
24 This program wipes disks and creates a volume group on top of them. It
25 can also show disk information to help you decide which disks you want
26 to wipe.
27
28 The error handling is done by raising our own exceptions from most of
29 the functions; these exceptions then handled globally in the main()
30 function. The exceptions that each function can raise are not
31 documented individually, since almost every error path ends in a
32 raise.
33
34 Another two exceptions that are handled globally are IOError and
35 OSError. The idea behind this is, since we run as root, we should
36 usually not get these errors, but if we do it's most probably a system
37 error, so they should be handled and the user instructed to report
38 them.
39
40 """
41
42
43 import os
44 import sys
45 import optparse
46 import time
47
48 from ganeti.utils import RunCmd, ReadFile
49 from ganeti import constants
50 from ganeti import cli
51
52 USAGE = ("\tlvmstrap diskinfo\n"
53          "\tlvmstrap [--vgname=NAME] [--allow-removable]"
54          " { --alldisks | --disks DISKLIST }"
55          " create")
56
57 verbose_flag = False
58
59
60 class Error(Exception):
61   """Generic exception"""
62   pass
63
64
65 class ProgrammingError(Error):
66   """Exception denoting invalid assumptions in programming.
67
68   This should catch sysfs tree changes, or otherwise incorrect
69   assumptions about the contents of the /sys/block/... directories.
70
71   """
72   pass
73
74
75 class SysconfigError(Error):
76   """Exception denoting invalid system configuration.
77
78   If the system configuration is somehow wrong (e.g. /dev files
79   missing, or having mismatched major/minor numbers relative to
80   /sys/block devices), this exception will be raised.
81
82   This should usually mean that the installation of the Xen node
83   failed in some steps.
84
85   """
86   pass
87
88
89 class PrereqError(Error):
90   """Exception denoting invalid prerequisites.
91
92   If the node does not meet the requirements for cluster membership, this
93   exception will be raised. Things like wrong kernel version, or no
94   free disks, etc. belong here.
95
96   This should usually mean that the build steps for the Xen node were
97   not followed correctly.
98
99   """
100   pass
101
102
103 class OperationalError(Error):
104   """Exception denoting actual errors.
105
106   Errors during the bootstrapping are signaled using this exception.
107
108   """
109   pass
110
111
112 class ParameterError(Error):
113   """Exception denoting invalid input from user.
114
115   Wrong disks given as parameters will be signaled using this
116   exception.
117
118   """
119   pass
120
121
122 def Usage():
123   """Shows program usage information and exits the program.
124
125   """
126   print >> sys.stderr, "Usage:"
127   print >> sys.stderr, USAGE
128   sys.exit(2)
129
130
131 def ParseOptions():
132   """Parses the command line options.
133
134   In case of command line errors, it will show the usage and exit the
135   program.
136
137   @rtype: tuple
138   @return: a tuple of (options, args), as returned by
139       OptionParser.parse_args
140
141   """
142   global verbose_flag # pylint: disable-msg=W0603
143
144   parser = optparse.OptionParser(usage="\n%s" % USAGE,
145                                  version="%%prog (ganeti) %s" %
146                                  constants.RELEASE_VERSION)
147
148   parser.add_option("--alldisks", dest="alldisks",
149                     help="erase ALL disks", action="store_true",
150                     default=False)
151   parser.add_option("-d", "--disks", dest="disks",
152                     help="Choose disks (e.g. hda,hdg)",
153                     metavar="DISKLIST")
154   parser.add_option(cli.VERBOSE_OPT)
155   parser.add_option("-r", "--allow-removable",
156                     action="store_true", dest="removable_ok", default=False,
157                     help="allow and use removable devices too")
158   parser.add_option("-g", "--vg-name", type="string",
159                     dest="vgname", default="xenvg", metavar="NAME",
160                     help="the volume group to be created [default: xenvg]")
161
162
163   options, args = parser.parse_args()
164   if len(args) != 1:
165     Usage()
166
167   verbose_flag = options.verbose
168
169   return options, args
170
171
172 def ExecCommand(command):
173   """Executes a command.
174
175   This is just a wrapper around commands.getstatusoutput, with the
176   difference that if the command line argument -v has been given, it
177   will print the command line and the command output on stdout.
178
179   @param command: the command line to be executed
180   @rtype: tuple
181   @return: a tuple of (status, output) where status is the exit status
182       and output the stdout and stderr of the command together
183
184   """
185   if verbose_flag:
186     print command
187   result = RunCmd(command)
188   if verbose_flag:
189     print result.output
190   return result
191
192
193 def CheckPrereq():
194   """Check the prerequisites of this program.
195
196   It check that it runs on Linux 2.6, and that /sys is mounted and the
197   fact that /sys/block is a directory.
198
199   """
200   if os.getuid() != 0:
201     raise PrereqError("This tool runs as root only. Really.")
202
203   osname, _, release, _, _ = os.uname()
204   if osname != 'Linux':
205     raise PrereqError("This tool only runs on Linux"
206                       " (detected OS: %s)." % osname)
207
208   if not release.startswith("2.6."):
209     raise PrereqError("Wrong major kernel version (detected %s, needs"
210                       " 2.6.*)" % release)
211
212   if not os.path.ismount("/sys"):
213     raise PrereqError("Can't find a filesystem mounted at /sys."
214                       " Please mount /sys.")
215
216   if not os.path.isdir("/sys/block"):
217     raise SysconfigError("Can't find /sys/block directory. Has the"
218                          " layout of /sys changed?")
219
220   if not os.path.ismount("/proc"):
221     raise PrereqError("Can't find a filesystem mounted at /proc."
222                       " Please mount /proc.")
223
224   if not os.path.exists("/proc/mounts"):
225     raise SysconfigError("Can't find /proc/mounts")
226
227
228 def CheckVGExists(vgname):
229   """Checks to see if a volume group exists.
230
231   @param vgname: the volume group name
232
233   @return: a four-tuple (exists, lv_count, vg_size, vg_free), where:
234       - exists: True if the volume exists, otherwise False; if False,
235         all other members of the tuple are None
236       - lv_count: The number of logical volumes in the volume group
237       - vg_size: The total size of the volume group (in gibibytes)
238       - vg_free: The available space in the volume group
239
240   """
241   result = ExecCommand("vgs --nohead -o lv_count,vg_size,vg_free"
242                        " --nosuffix --units g"
243                        " --ignorelockingfailure %s" % vgname)
244   if not result.failed:
245     try:
246       lv_count, vg_size, vg_free = result.stdout.strip().split()
247     except ValueError:
248       # This means the output of vgdisplay can't be parsed
249       raise PrereqError("cannot parse output of vgs (%s)" % result.stdout)
250   else:
251     lv_count = vg_size = vg_free = None
252
253   return not result.failed, lv_count, vg_size, vg_free
254
255
256 def CheckSysDev(name, devnum):
257   """Checks consistency between /sys and /dev trees.
258
259   In /sys/block/<name>/dev and /sys/block/<name>/<part>/dev are the
260   kernel-known device numbers. The /dev/<name> block/char devices are
261   created by userspace and thus could differ from the kernel
262   view. This function checks the consistency between the device number
263   read from /sys and the actual device number in /dev.
264
265   Note that since the system could be using udev which removes and
266   recreates the device nodes on partition table rescan, we need to do
267   some retries here. Since we only do a stat, we can afford to do many
268   short retries.
269
270   @param name: the device name, e.g. 'sda'
271   @param devnum: the device number, e.g. 0x803 (2051 in decimal) for sda3
272   @raises L{SysconfigError}: in case of failure of the check
273
274   """
275   path = "/dev/%s" % name
276   for _ in range(40):
277     if os.path.exists(path):
278       break
279     time.sleep(0.250)
280   else:
281     raise SysconfigError("the device file %s does not exist, but the block"
282                          " device exists in the /sys/block tree" % path)
283   rdev = os.stat(path).st_rdev
284   if devnum != rdev:
285     raise SysconfigError("For device %s, the major:minor in /dev is %04x"
286                          " while the major:minor in sysfs is %s" %
287                          (path, rdev, devnum))
288
289
290 def ReadDev(syspath):
291   """Reads the device number from a sysfs path.
292
293   The device number is given in sysfs under a block device directory
294   in a file named 'dev' which contains major:minor (in ASCII). This
295   function reads that file and converts the major:minor pair to a dev
296   number.
297
298   @type syspath: string
299   @param syspath: the path to a block device dir in sysfs,
300       e.g. C{/sys/block/sda}
301
302   @return: the device number
303
304   """
305   if not os.path.exists("%s/dev" % syspath):
306     raise ProgrammingError("Invalid path passed to ReadDev: %s" % syspath)
307   f = open("%s/dev" % syspath)
308   data = f.read().strip()
309   f.close()
310   major, minor = data.split(":", 1)
311   major = int(major)
312   minor = int(minor)
313   dev = os.makedev(major, minor)
314   return dev
315
316
317 def ReadSize(syspath):
318   """Reads the size from a sysfs path.
319
320   The size is given in sysfs under a block device directory in a file
321   named 'size' which contains the number of sectors (in ASCII). This
322   function reads that file and converts the number in sectors to the
323   size in bytes.
324
325   @type syspath: string
326   @param syspath: the path to a block device dir in sysfs,
327       e.g. C{/sys/block/sda}
328
329   @rtype: int
330   @return: the device size in bytes
331
332   """
333
334   if not os.path.exists("%s/size" % syspath):
335     raise ProgrammingError("Invalid path passed to ReadSize: %s" % syspath)
336   f = open("%s/size" % syspath)
337   data = f.read().strip()
338   f.close()
339   size = 512L * int(data)
340   return size
341
342
343 def ReadPV(name):
344   """Reads physical volume information.
345
346   This function tries to see if a block device is a physical volume.
347
348   @type name: string
349   @param name: the device name (e.g. sda)
350
351   @return: the name of the volume group to which this PV belongs, or
352       "" if this PV is not in use, or None if this is not a PV
353
354   """
355   result = ExecCommand("pvdisplay -c /dev/%s" % name)
356   if result.failed:
357     return None
358   vgname = result.stdout.strip().split(":")[1]
359   return vgname
360
361
362 def GetDiskList(opts):
363   """Computes the block device list for this system.
364
365   This function examines the /sys/block tree and using information
366   therein, computes the status of the block device.
367
368   @return: a list like [(name, size, dev, partitions, inuse), ...], where:
369       - name is the block device name (e.g. sda)
370       - size the size in bytes
371       - dev is the device number (e.g. 8704 for hdg)
372       - partitions is [(name, size, dev), ...] mirroring the disk list
373         data inuse is a boolean showing the in-use status of the disk,
374         computed as the possibility of re-reading the partition table
375         (the meaning of the operation varies with the kernel version,
376         but is usually accurate; a mounted disk/partition or swap-area
377         or PV with active LVs on it is busy)
378
379   """
380   dlist = []
381   for name in os.listdir("/sys/block"):
382     if (not name.startswith("hd") and
383         not name.startswith("sd") and
384         not name.startswith("ubd")):
385       continue
386
387     size = ReadSize("/sys/block/%s" % name)
388
389     f = open("/sys/block/%s/removable" % name)
390     removable = int(f.read().strip())
391     f.close()
392
393     if removable and not opts.removable_ok:
394       continue
395
396     dev = ReadDev("/sys/block/%s" % name)
397     CheckSysDev(name, dev)
398     inuse = not CheckReread(name)
399     # Enumerate partitions of the block device
400     partitions = []
401     for partname in os.listdir("/sys/block/%s" % name):
402       if not partname.startswith(name):
403         continue
404       partdev = ReadDev("/sys/block/%s/%s" % (name, partname))
405       partsize = ReadSize("/sys/block/%s/%s" % (name, partname))
406       CheckSysDev(partname, partdev)
407       partitions.append((partname, partsize, partdev))
408     partitions.sort()
409     dlist.append((name, size, dev, partitions, inuse))
410   dlist.sort()
411   return dlist
412
413
414 def GetMountInfo():
415   """Reads /proc/mounts and computes the mountpoint-devnum mapping.
416
417   This function reads /proc/mounts, finds the mounted filesystems
418   (excepting a hard-coded blacklist of network and virtual
419   filesystems) and does a stat on these mountpoints. The st_dev number
420   of the results is memorised for later matching against the
421   /sys/block devices.
422
423   @rtype: dict
424   @return: a {mountpoint: device number} dictionary
425
426   """
427   mountlines = ReadFile("/proc/mounts").splitlines()
428   mounts = {}
429   for line in mountlines:
430     _, mountpoint, fstype, _ = line.split(None, 3)
431     # fs type blacklist
432     if fstype in ["nfs", "nfs4", "autofs", "tmpfs", "proc", "sysfs"]:
433       continue
434     try:
435       dev = os.stat(mountpoint).st_dev
436     except OSError, err:
437       # this should be a fairly rare error, since we are blacklisting
438       # network filesystems; with this in mind, we'll ignore it,
439       # since the rereadpt check catches in-use filesystems,
440       # and this is used for disk information only
441       print >> sys.stderr, ("Can't stat mountpoint '%s': %s" %
442                             (mountpoint, err))
443       print >> sys.stderr, "Ignoring."
444       continue
445     mounts[dev] = mountpoint
446   return mounts
447
448
449 def DevInfo(name, dev, mountinfo):
450   """Computes miscellaneous information about a block device.
451
452   @type name: string
453   @param name: the device name, e.g. sda
454
455   @return: a tuple (mpath, whatvg, fileinfo), where:
456       - mpath is the mount path where this device is mounted or None
457       - whatvg is the result of the ReadPV function
458       - fileinfo is the output of file -bs on the device
459
460   """
461   if dev in mountinfo:
462     mpath = mountinfo[dev]
463   else:
464     mpath = None
465
466   whatvg = ReadPV(name)
467
468   result = ExecCommand("file -bs /dev/%s" % name)
469   if result.failed:
470     fileinfo = "<error: %s>" % result.stderr
471   fileinfo = result.stdout[:45]
472   return mpath, whatvg, fileinfo
473
474
475 def ShowDiskInfo(opts):
476   """Shows a nicely formatted block device list for this system.
477
478   This function shows the user a table with the information gathered
479   by the other functions defined, in order to help the user make a
480   choice about which disks should be allocated to our volume group.
481
482   """
483   mounts = GetMountInfo()
484   dlist = GetDiskList(opts)
485
486   print "------- Disk information -------"
487   headers = {
488       "name": "Name",
489       "size": "Size[M]",
490       "used": "Used",
491       "mount": "Mount",
492       "lvm": "LVM?",
493       "info": "Info"
494       }
495   fields = ["name", "size", "used", "mount", "lvm", "info"]
496
497   flatlist = []
498   # Flatten the [(disk, [partition,...]), ...] list
499   for name, size, dev, parts, inuse in dlist:
500     if inuse:
501       str_inuse = "yes"
502     else:
503       str_inuse = "no"
504     flatlist.append((name, size, dev, str_inuse))
505     for partname, partsize, partdev in parts:
506       flatlist.append((partname, partsize, partdev, ""))
507
508   strlist = []
509   for name, size, dev, in_use in flatlist:
510     mp, vgname, fileinfo = DevInfo(name, dev, mounts)
511     if mp is None:
512       mp = "-"
513     if vgname is None:
514       lvminfo = "-"
515     elif vgname == "":
516       lvminfo = "yes,free"
517     else:
518       lvminfo = "in %s" % vgname
519
520     if len(name) > 3:
521       # Indent partitions
522       name = " %s" % name
523
524     strlist.append([name, "%.2f" % (float(size) / 1024 / 1024),
525                     in_use, mp, lvminfo, fileinfo])
526
527   data = cli.GenerateTable(headers, fields, None,
528                            strlist, numfields=["size"])
529
530   for line in data:
531     print line
532
533
534 def CheckReread(name):
535   """Check to see if a block device is in use.
536
537   Uses blockdev to reread the partition table of a block device, and
538   thus compute the in-use status. See the discussion in GetDiskList
539   about the meaning of 'in use'.
540
541   @rtype: boolean
542   @return: the in-use status of the device
543
544   """
545   for _ in range(3):
546     result = ExecCommand("blockdev --rereadpt /dev/%s" % name)
547     if not result.failed:
548       break
549     time.sleep(2)
550
551   return not result.failed
552
553
554 def WipeDisk(name):
555   """Wipes a block device.
556
557   This function wipes a block device, by clearing and re-reading the
558   partition table. If not successful, it writes back the old partition
559   data, and leaves the cleanup to the user.
560
561   @param name: the device name (e.g. sda)
562
563   """
564
565   if not CheckReread(name):
566     raise OperationalError("CRITICAL: disk %s you selected seems to be in"
567                            " use. ABORTING!" % name)
568
569   fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
570   olddata = os.read(fd, 512)
571   if len(olddata) != 512:
572     raise OperationalError("CRITICAL: Can't read partition table information"
573                            " from /dev/%s (needed 512 bytes, got %d" %
574                            (name, len(olddata)))
575   newdata = "\0" * 512
576   os.lseek(fd, 0, 0)
577   bytes_written = os.write(fd, newdata)
578   os.close(fd)
579   if bytes_written != 512:
580     raise OperationalError("CRITICAL: Can't write partition table information"
581                            " to /dev/%s (tried to write 512 bytes, written"
582                            " %d. I don't know how to cleanup. Sorry." %
583                            (name, bytes_written))
584
585   if not CheckReread(name):
586     fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
587     os.write(fd, olddata)
588     os.close(fd)
589     raise OperationalError("CRITICAL: disk %s which I have just wiped cannot"
590                            " reread partition table. Most likely, it is"
591                            " in use. You have to clean after this yourself."
592                            " I tried to restore the old partition table,"
593                            " but I cannot guarantee nothing has broken." %
594                            name)
595
596
597 def PartitionDisk(name):
598   """Partitions a disk.
599
600   This function creates a single partition spanning the entire disk,
601   by means of fdisk.
602
603   @param name: the device name, e.g. sda
604
605   """
606   result = ExecCommand(
607     'echo ,,8e, | sfdisk /dev/%s' % name)
608   if result.failed:
609     raise OperationalError("CRITICAL: disk %s which I have just partitioned"
610                            " cannot reread its partition table, or there"
611                            " is some other sfdisk error. Likely, it is in"
612                            " use. You have to clean this yourself. Error"
613                            " message from sfdisk: %s" %
614                            (name, result.output))
615
616
617 def CreatePVOnDisk(name):
618   """Creates a physical volume on a block device.
619
620   This function creates a physical volume on a block device, overriding
621   all warnings. So it can wipe existing PVs and PVs which are in a VG.
622
623   @param name: the device name, e.g. sda
624
625   """
626   result = ExecCommand("pvcreate -yff /dev/%s1 " % name)
627   if result.failed:
628     raise OperationalError("I cannot create a physical volume on"
629                            " partition /dev/%s1. Error message: %s."
630                            " Please clean up yourself." %
631                            (name, result.output))
632
633
634 def CreateVG(vgname, disks):
635   """Creates the volume group.
636
637   This function creates a volume group named `vgname` on the disks
638   given as parameters. The physical extent size is set to 64MB.
639
640   @param disks: a list of disk names, e.g. ['sda','sdb']
641
642   """
643   pnames = ["'/dev/%s1'" % disk for disk in disks]
644   result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
645   if result.failed:
646     raise OperationalError("I cannot create the volume group %s from"
647                            " disks %s. Error message: %s. Please clean up"
648                            " yourself." %
649                            (vgname, " ".join(disks), result.output))
650
651
652 def ValidateDiskList(options):
653   """Validates or computes the disk list for create.
654
655   This function either computes the available disk list (if the user
656   gave --alldisks option), or validates the user-given disk list (by
657   using the --disks option) such that all given disks are present and
658   not in use.
659
660   @param options: the options returned from OptParser.parse_options
661
662   @return: a list of disk names, e.g. ['sda', 'sdb']
663
664   """
665   sysdisks = GetDiskList(options)
666   if not sysdisks:
667     raise PrereqError("no disks found (I looked for"
668                       " non-removable block devices).")
669   sysd_free = []
670   sysd_used = []
671   for name, _, _, _, used in sysdisks:
672     if used:
673       sysd_used.append(name)
674     else:
675       sysd_free.append(name)
676
677   if not sysd_free:
678     raise PrereqError("no free disks found! (%d in-use disks)" %
679                       len(sysd_used))
680   if options.alldisks:
681     disklist = sysd_free
682   elif options.disks:
683     disklist = options.disks.split(",")
684     for name in disklist:
685       if name in sysd_used:
686         raise ParameterError("disk %s is in use, cannot wipe!" % name)
687       if name not in sysd_free:
688         raise ParameterError("cannot find disk %s!" % name)
689   else:
690     raise ParameterError("Please use either --alldisks or --disks!")
691
692   return disklist
693
694
695 def BootStrap():
696   """Actual main routine.
697
698   """
699   CheckPrereq()
700
701   options, args = ParseOptions()
702   vgname = options.vgname
703   command = args.pop(0)
704   if command == "diskinfo":
705     ShowDiskInfo(options)
706     return
707   if command != "create":
708     Usage()
709
710   exists, lv_count, vg_size, vg_free = CheckVGExists(vgname)
711   if exists:
712     raise PrereqError("It seems volume group '%s' already exists:\n"
713                       "  LV count: %s, size: %s, free: %s." %
714                       (vgname, lv_count, vg_size, vg_free))
715
716
717   disklist = ValidateDiskList(options)
718
719   for disk in disklist:
720     WipeDisk(disk)
721     PartitionDisk(disk)
722   for disk in disklist:
723     CreatePVOnDisk(disk)
724   CreateVG(vgname, disklist)
725
726   status, lv_count, size, _ = CheckVGExists(vgname)
727   if status:
728     print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
729                                               ",".join(disklist))
730   else:
731     raise OperationalError("Although everything seemed ok, the volume"
732                            " group did not get created.")
733
734
735 def main():
736   """Application entry point.
737
738   This is just a wrapper over BootStrap, to handle our own exceptions.
739
740   """
741   try:
742     BootStrap()
743   except PrereqError, err:
744     print >> sys.stderr, "The prerequisites for running this tool are not met."
745     print >> sys.stderr, ("Please make sure you followed all the steps in"
746                           " the build document.")
747     print >> sys.stderr, "Description: %s" % str(err)
748     sys.exit(1)
749   except SysconfigError, err:
750     print >> sys.stderr, ("This system's configuration seems wrong, at"
751                           " least is not what I expect.")
752     print >> sys.stderr, ("Please check that the installation didn't fail"
753                           " at some step.")
754     print >> sys.stderr, "Description: %s" % str(err)
755     sys.exit(1)
756   except ParameterError, err:
757     print >> sys.stderr, ("Some parameters you gave to the program or the"
758                           " invocation is wrong. ")
759     print >> sys.stderr, "Description: %s" % str(err)
760     Usage()
761   except OperationalError, err:
762     print >> sys.stderr, ("A serious error has happened while modifying"
763                           " the system's configuration.")
764     print >> sys.stderr, ("Please review the error message below and make"
765                           " sure you clean up yourself.")
766     print >> sys.stderr, ("It is most likely that the system configuration"
767                           " has been partially altered.")
768     print >> sys.stderr, str(err)
769     sys.exit(1)
770   except ProgrammingError, err:
771     print >> sys.stderr, ("Internal application error. Please signal this"
772                           " to xencluster-team.")
773     print >> sys.stderr, "Error description: %s" % str(err)
774     sys.exit(1)
775   except Error, err:
776     print >> sys.stderr, "Unhandled application error: %s" % err
777     sys.exit(1)
778   except (IOError, OSError), err:
779     print >> sys.stderr, "I/O error detected, please report."
780     print >> sys.stderr, "Description: %s" % str(err)
781     sys.exit(1)
782
783
784 if __name__ == "__main__":
785   main()