Rename OpDiagnoseOS and LUDiagnoseOS
[ganeti-local] / tools / lvmstrap
index a10296e..654548d 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
 #!/usr/bin/python
 #
 
-# Copyright (C) 2006, 2007 Google Inc.
+# Copyright (C) 2006, 2007, 2011 Google Inc.
 #
 # 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
 #
 # 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
@@ -36,6 +36,7 @@ OSError. The idea behind this is, since we run as root, we should
 usually not get these errors, but if we do it's most probably a system
 error, so they should be handled and the user instructed to report
 them.
 usually not get these errors, but if we do it's most probably a system
 error, so they should be handled and the user instructed to report
 them.
+
 """
 
 
 """
 
 
@@ -43,10 +44,12 @@ import os
 import sys
 import optparse
 import time
 import sys
 import optparse
 import time
+import errno
 
 
-from ganeti.utils import RunCmd
+from ganeti.utils import RunCmd, ReadFile
 from ganeti import constants
 from ganeti import cli
 from ganeti import constants
 from ganeti import cli
+from ganeti import compat
 
 USAGE = ("\tlvmstrap diskinfo\n"
          "\tlvmstrap [--vgname=NAME] [--allow-removable]"
 
 USAGE = ("\tlvmstrap diskinfo\n"
          "\tlvmstrap [--vgname=NAME] [--allow-removable]"
@@ -55,6 +58,14 @@ USAGE = ("\tlvmstrap diskinfo\n"
 
 verbose_flag = False
 
 
 verbose_flag = False
 
+#: Supported disk types (as prefixes)
+SUPPORTED_TYPES = [
+  "hd",
+  "sd",
+  "md",
+  "ubd",
+  ]
+
 
 class Error(Exception):
   """Generic exception"""
 
 class Error(Exception):
   """Generic exception"""
@@ -66,6 +77,7 @@ class ProgrammingError(Error):
 
   This should catch sysfs tree changes, or otherwise incorrect
   assumptions about the contents of the /sys/block/... directories.
 
   This should catch sysfs tree changes, or otherwise incorrect
   assumptions about the contents of the /sys/block/... directories.
+
   """
   pass
 
   """
   pass
 
@@ -79,6 +91,7 @@ class SysconfigError(Error):
 
   This should usually mean that the installation of the Xen node
   failed in some steps.
 
   This should usually mean that the installation of the Xen node
   failed in some steps.
+
   """
   pass
 
   """
   pass
 
@@ -92,6 +105,7 @@ class PrereqError(Error):
 
   This should usually mean that the build steps for the Xen node were
   not followed correctly.
 
   This should usually mean that the build steps for the Xen node were
   not followed correctly.
+
   """
   pass
 
   """
   pass
 
@@ -100,6 +114,7 @@ class OperationalError(Error):
   """Exception denoting actual errors.
 
   Errors during the bootstrapping are signaled using this exception.
   """Exception denoting actual errors.
 
   Errors during the bootstrapping are signaled using this exception.
+
   """
   pass
 
   """
   pass
 
@@ -109,13 +124,15 @@ class ParameterError(Error):
 
   Wrong disks given as parameters will be signaled using this
   exception.
 
   Wrong disks given as parameters will be signaled using this
   exception.
+
   """
   pass
 
 
 def Usage():
   """
   pass
 
 
 def Usage():
-  """Shows program usage information and exits the program."""
+  """Shows program usage information and exits the program.
 
 
+  """
   print >> sys.stderr, "Usage:"
   print >> sys.stderr, USAGE
   sys.exit(2)
   print >> sys.stderr, "Usage:"
   print >> sys.stderr, USAGE
   sys.exit(2)
@@ -127,10 +144,12 @@ def ParseOptions():
   In case of command line errors, it will show the usage and exit the
   program.
 
   In case of command line errors, it will show the usage and exit the
   program.
 
-  Returns:
-    (options, args), as returned by OptionParser.parse_args
+  @rtype: tuple
+  @return: a tuple of (options, args), as returned by
+      OptionParser.parse_args
+
   """
   """
-  global verbose_flag
+  global verbose_flag # pylint: disable-msg=W0603
 
   parser = optparse.OptionParser(usage="\n%s" % USAGE,
                                  version="%%prog (ganeti) %s" %
 
   parser = optparse.OptionParser(usage="\n%s" % USAGE,
                                  version="%%prog (ganeti) %s" %
@@ -142,9 +161,7 @@ def ParseOptions():
   parser.add_option("-d", "--disks", dest="disks",
                     help="Choose disks (e.g. hda,hdg)",
                     metavar="DISKLIST")
   parser.add_option("-d", "--disks", dest="disks",
                     help="Choose disks (e.g. hda,hdg)",
                     metavar="DISKLIST")
-  parser.add_option("-v", "--verbose",
-                    action="store_true", dest="verbose", default=False,
-                    help="print command execution messages to stdout")
+  parser.add_option(cli.VERBOSE_OPT)
   parser.add_option("-r", "--allow-removable",
                     action="store_true", dest="removable_ok", default=False,
                     help="allow and use removable devices too")
   parser.add_option("-r", "--allow-removable",
                     action="store_true", dest="removable_ok", default=False,
                     help="allow and use removable devices too")
@@ -162,6 +179,29 @@ def ParseOptions():
   return options, args
 
 
   return options, args
 
 
+def IsPartitioned(disk):
+  """Returns whether a given disk should be used partitioned or as-is.
+
+  Currently only md devices are used as is.
+
+  """
+  return not disk.startswith('md')
+
+
+def DeviceName(disk):
+  """Returns the appropriate device name for a disk.
+
+  For non-partitioned devices, it returns the name as is, otherwise it
+  returns the first partition.
+
+  """
+  if IsPartitioned(disk):
+    device = '/dev/%s1' % disk
+  else:
+    device = '/dev/%s' % disk
+  return device
+
+
 def ExecCommand(command):
   """Executes a command.
 
 def ExecCommand(command):
   """Executes a command.
 
@@ -169,13 +209,12 @@ def ExecCommand(command):
   difference that if the command line argument -v has been given, it
   will print the command line and the command output on stdout.
 
   difference that if the command line argument -v has been given, it
   will print the command line and the command output on stdout.
 
-  Args:
-    the command line
-  Returns:
-    (status, output) where status is the exit status and output the
-      stdout and stderr of the command together
-  """
+  @param command: the command line to be executed
+  @rtype: tuple
+  @return: a tuple of (status, output) where status is the exit status
+      and output the stdout and stderr of the command together
 
 
+  """
   if verbose_flag:
     print command
   result = RunCmd(command)
   if verbose_flag:
     print command
   result = RunCmd(command)
@@ -189,12 +228,12 @@ def CheckPrereq():
 
   It check that it runs on Linux 2.6, and that /sys is mounted and the
   fact that /sys/block is a directory.
 
   It check that it runs on Linux 2.6, and that /sys is mounted and the
   fact that /sys/block is a directory.
-  """
 
 
+  """
   if os.getuid() != 0:
     raise PrereqError("This tool runs as root only. Really.")
 
   if os.getuid() != 0:
     raise PrereqError("This tool runs as root only. Really.")
 
-  osname, nodename, release, version, arch = os.uname()
+  osname, _, release, _, _ = os.uname()
   if osname != 'Linux':
     raise PrereqError("This tool only runs on Linux"
                       " (detected OS: %s)." % osname)
   if osname != 'Linux':
     raise PrereqError("This tool only runs on Linux"
                       " (detected OS: %s)." % osname)
@@ -222,18 +261,16 @@ def CheckPrereq():
 def CheckVGExists(vgname):
   """Checks to see if a volume group exists.
 
 def CheckVGExists(vgname):
   """Checks to see if a volume group exists.
 
-  Args:
-    vgname: the volume group name
+  @param vgname: the volume group name
 
 
-  Returns:
-    a four-tuple (exists, lv_count, vg_size, vg_free), where:
-      exists: True if the volume exists, otherwise False; if False,
+  @return: a four-tuple (exists, lv_count, vg_size, vg_free), where:
+      - exists: True if the volume exists, otherwise False; if False,
         all other members of the tuple are None
         all other members of the tuple are None
-      lv_count: The number of logical volumes in the volume group
-      vg_size: The total size of the volume group (in gibibytes)
-      vg_free: The available space in the volume group
-  """
+      - lv_count: The number of logical volumes in the volume group
+      - vg_size: The total size of the volume group (in gibibytes)
+      - vg_free: The available space in the volume group
 
 
+  """
   result = ExecCommand("vgs --nohead -o lv_count,vg_size,vg_free"
                        " --nosuffix --units g"
                        " --ignorelockingfailure %s" % vgname)
   result = ExecCommand("vgs --nohead -o lv_count,vg_size,vg_free"
                        " --nosuffix --units g"
                        " --ignorelockingfailure %s" % vgname)
@@ -263,17 +300,13 @@ def CheckSysDev(name, devnum):
   some retries here. Since we only do a stat, we can afford to do many
   short retries.
 
   some retries here. Since we only do a stat, we can afford to do many
   short retries.
 
-  Args:
-   name: the device name, e.g. 'sda'
-   devnum: the device number, e.g. 0x803 (2051 in decimal) for sda3
+  @param name: the device name, e.g. 'sda'
+  @param devnum: the device number, e.g. 0x803 (2051 in decimal) for sda3
+  @raises L{SysconfigError}: in case of failure of the check
 
 
-  Returns:
-    None; failure of the check is signaled by raising a
-      SysconfigError exception
   """
   """
-
   path = "/dev/%s" % name
   path = "/dev/%s" % name
-  for retries in range(40):
+  for _ in range(40):
     if os.path.exists(path):
       break
     time.sleep(0.250)
     if os.path.exists(path):
       break
     time.sleep(0.250)
@@ -295,13 +328,13 @@ def ReadDev(syspath):
   function reads that file and converts the major:minor pair to a dev
   number.
 
   function reads that file and converts the major:minor pair to a dev
   number.
 
-  Args:
-    syspath: the path to a block device dir in sysfs, e.g. /sys/block/sda
+  @type syspath: string
+  @param syspath: the path to a block device dir in sysfs,
+      e.g. C{/sys/block/sda}
 
 
-  Returns:
-    the device number
-  """
+  @return: the device number
 
 
+  """
   if not os.path.exists("%s/dev" % syspath):
     raise ProgrammingError("Invalid path passed to ReadDev: %s" % syspath)
   f = open("%s/dev" % syspath)
   if not os.path.exists("%s/dev" % syspath):
     raise ProgrammingError("Invalid path passed to ReadDev: %s" % syspath)
   f = open("%s/dev" % syspath)
@@ -322,11 +355,13 @@ def ReadSize(syspath):
   function reads that file and converts the number in sectors to the
   size in bytes.
 
   function reads that file and converts the number in sectors to the
   size in bytes.
 
-  Args:
-    syspath: the path to a block device dir in sysfs, e.g. /sys/block/sda
+  @type syspath: string
+  @param syspath: the path to a block device dir in sysfs,
+      e.g. C{/sys/block/sda}
+
+  @rtype: int
+  @return: the device size in bytes
 
 
-  Returns:
-    the device size in bytes
   """
 
   if not os.path.exists("%s/size" % syspath):
   """
 
   if not os.path.exists("%s/size" % syspath):
@@ -343,14 +378,13 @@ def ReadPV(name):
 
   This function tries to see if a block device is a physical volume.
 
 
   This function tries to see if a block device is a physical volume.
 
-  Args:
-    dev: the device name (e.g. sda)
-  Returns:
-    The name of the volume group to which this PV belongs, or
-    "" if this PV is not in use, or
-    None if this is not a PV
-  """
+  @type name: string
+  @param name: the device name (e.g. sda)
+
+  @return: the name of the volume group to which this PV belongs, or
+      "" if this PV is not in use, or None if this is not a PV
 
 
+  """
   result = ExecCommand("pvdisplay -c /dev/%s" % name)
   if result.failed:
     return None
   result = ExecCommand("pvdisplay -c /dev/%s" % name)
   if result.failed:
     return None
@@ -364,25 +398,21 @@ def GetDiskList(opts):
   This function examines the /sys/block tree and using information
   therein, computes the status of the block device.
 
   This function examines the /sys/block tree and using information
   therein, computes the status of the block device.
 
-  Returns:
-    [(name, size, dev, partitions, inuse), ...]
-  where:
-    name is the block device name (e.g. sda)
-    size the size in bytes
-    dev  the device number (e.g. 8704 for hdg)
-    partitions is [(name, size, dev), ...] mirroring the disk list data
-    inuse is a boolean showing the in-use status of the disk, computed as the
-      possibility of re-reading the partition table (the meaning of the
-      operation varies with the kernel version, but is usually accurate;
-      a mounted disk/partition or swap-area or PV with active LVs on it
-      is busy)
-  """
+  @return: a list like [(name, size, dev, partitions, inuse), ...], where:
+      - name is the block device name (e.g. sda)
+      - size the size in bytes
+      - dev is the device number (e.g. 8704 for hdg)
+      - partitions is [(name, size, dev), ...] mirroring the disk list
+        data inuse is a boolean showing the in-use status of the disk,
+        computed as the possibility of re-reading the partition table
+        (the meaning of the operation varies with the kernel version,
+        but is usually accurate; a mounted disk/partition or swap-area
+        or PV with active LVs on it is busy)
 
 
+  """
   dlist = []
   for name in os.listdir("/sys/block"):
   dlist = []
   for name in os.listdir("/sys/block"):
-    if (not name.startswith("hd") and
-        not name.startswith("sd") and
-        not name.startswith("ubd")):
+    if not compat.any([name.startswith(pfx) for pfx in SUPPORTED_TYPES]):
       continue
 
     size = ReadSize("/sys/block/%s" % name)
       continue
 
     size = ReadSize("/sys/block/%s" % name)
@@ -396,7 +426,7 @@ def GetDiskList(opts):
 
     dev = ReadDev("/sys/block/%s" % name)
     CheckSysDev(name, dev)
 
     dev = ReadDev("/sys/block/%s" % name)
     CheckSysDev(name, dev)
-    inuse = not CheckReread(name)
+    inuse = InUse(name)
     # Enumerate partitions of the block device
     partitions = []
     for partname in os.listdir("/sys/block/%s" % name):
     # Enumerate partitions of the block device
     partitions = []
     for partname in os.listdir("/sys/block/%s" % name):
@@ -421,16 +451,14 @@ def GetMountInfo():
   of the results is memorised for later matching against the
   /sys/block devices.
 
   of the results is memorised for later matching against the
   /sys/block devices.
 
-  Returns:
-   a mountpoint: device number dictionary
-  """
+  @rtype: dict
+  @return: a {mountpoint: device number} dictionary
 
 
-  f = open("/proc/mounts", "r")
-  mountlines = f.readlines()
-  f.close()
+  """
+  mountlines = ReadFile("/proc/mounts").splitlines()
   mounts = {}
   for line in mountlines:
   mounts = {}
   for line in mountlines:
-    device, mountpoint, fstype, rest = line.split(None, 3)
+    _, mountpoint, fstype, _ = line.split(None, 3)
     # fs type blacklist
     if fstype in ["nfs", "nfs4", "autofs", "tmpfs", "proc", "sysfs"]:
       continue
     # fs type blacklist
     if fstype in ["nfs", "nfs4", "autofs", "tmpfs", "proc", "sysfs"]:
       continue
@@ -452,16 +480,15 @@ def GetMountInfo():
 def DevInfo(name, dev, mountinfo):
   """Computes miscellaneous information about a block device.
 
 def DevInfo(name, dev, mountinfo):
   """Computes miscellaneous information about a block device.
 
-  Args:
-    name: the device name, e.g. sda
+  @type name: string
+  @param name: the device name, e.g. sda
 
 
-  Returns:
-    (mpath, whatvg, fileinfo), where
-    mpath is the mount path where this device is mounted or None
-    whatvg is the result of the ReadPV function
-    fileinfo is the output of file -bs on the device
-  """
+  @return: a tuple (mpath, whatvg, fileinfo), where:
+      - mpath is the mount path where this device is mounted or None
+      - whatvg is the result of the ReadPV function
+      - fileinfo is the output of file -bs on the device
 
 
+  """
   if dev in mountinfo:
     mpath = mountinfo[dev]
   else:
   if dev in mountinfo:
     mpath = mountinfo[dev]
   else:
@@ -535,24 +562,62 @@ def ShowDiskInfo(opts):
     print line
 
 
     print line
 
 
+def CheckSysfsHolders(name):
+  """Check to see if a device is 'hold' at sysfs level.
+
+  This is usually the case for Physical Volumes under LVM.
+
+  @rtype: boolean
+  @return: true if the device is available according to sysfs
+
+  """
+  try:
+    contents = os.listdir("/sys/block/%s/holders/" % name)
+  except OSError, err:
+    if err.errno == errno.ENOENT:
+      contents = []
+    else:
+      raise
+  return not bool(contents)
+
+
 def CheckReread(name):
   """Check to see if a block device is in use.
 
 def CheckReread(name):
   """Check to see if a block device is in use.
 
-  Uses blockdev to reread the partition table of a block device, and
-  thus compute the in-use status. See the discussion in GetDiskList
-  about the meaning of 'in use'.
+  Uses blockdev to reread the partition table of a block device (or
+  fuser if the device is not partitionable), and thus compute the
+  in-use status.  See the discussion in GetDiskList about the meaning
+  of 'in use'.
+
+  @rtype: boolean
+  @return: the in-use status of the device
 
 
-  Returns:
-    boolean, the in-use status of the device
   """
   """
+  use_blockdev = IsPartitioned(name)
+  if use_blockdev:
+    cmd = "blockdev --rereadpt /dev/%s" % name
+  else:
+    cmd = "fuser -vam /dev/%s" % name
 
 
-  for retries in range(3):
-    result = ExecCommand("blockdev --rereadpt /dev/%s" % name)
-    if not result.failed:
+  for _ in range(3):
+    result = ExecCommand(cmd)
+    if not use_blockdev and result.failed:
+      break
+    elif not result.failed:
       break
     time.sleep(2)
 
       break
     time.sleep(2)
 
-  return not result.failed
+  if use_blockdev:
+    return not result.failed
+  else:
+    return result.failed
+
+
+def InUse(name):
+  """Returns if a disk is in use or not.
+
+  """
+  return not (CheckSysfsHolders(name) and CheckReread(name))
 
 
 def WipeDisk(name):
 
 
 def WipeDisk(name):
@@ -562,11 +627,11 @@ def WipeDisk(name):
   partition table. If not successful, it writes back the old partition
   data, and leaves the cleanup to the user.
 
   partition table. If not successful, it writes back the old partition
   data, and leaves the cleanup to the user.
 
-  Args:
-    the device name (e.g. sda)
+  @param name: the device name (e.g. sda)
+
   """
 
   """
 
-  if not CheckReread(name):
+  if InUse(name):
     raise OperationalError("CRITICAL: disk %s you selected seems to be in"
                            " use. ABORTING!" % name)
 
     raise OperationalError("CRITICAL: disk %s you selected seems to be in"
                            " use. ABORTING!" % name)
 
@@ -586,7 +651,8 @@ def WipeDisk(name):
                            " %d. I don't know how to cleanup. Sorry." %
                            (name, bytes_written))
 
                            " %d. I don't know how to cleanup. Sorry." %
                            (name, bytes_written))
 
-  if not CheckReread(name):
+  if InUse(name):
+    # try to restore the data
     fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
     os.write(fd, olddata)
     os.close(fd)
     fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
     os.write(fd, olddata)
     os.close(fd)
@@ -604,8 +670,8 @@ def PartitionDisk(name):
   This function creates a single partition spanning the entire disk,
   by means of fdisk.
 
   This function creates a single partition spanning the entire disk,
   by means of fdisk.
 
-  Args:
-    the device name, e.g. sda
+  @param name: the device name, e.g. sda
+
   """
   result = ExecCommand(
     'echo ,,8e, | sfdisk /dev/%s' % name)
   """
   result = ExecCommand(
     'echo ,,8e, | sfdisk /dev/%s' % name)
@@ -624,16 +690,16 @@ def CreatePVOnDisk(name):
   This function creates a physical volume on a block device, overriding
   all warnings. So it can wipe existing PVs and PVs which are in a VG.
 
   This function creates a physical volume on a block device, overriding
   all warnings. So it can wipe existing PVs and PVs which are in a VG.
 
-  Args:
-    the device name, e.g. sda
+  @param name: the device name, e.g. sda
 
   """
 
   """
-  result = ExecCommand("pvcreate -yff /dev/%s1 " % name)
+  device = DeviceName(name)
+  result = ExecCommand("pvcreate -yff %s" % device)
   if result.failed:
     raise OperationalError("I cannot create a physical volume on"
   if result.failed:
     raise OperationalError("I cannot create a physical volume on"
-                           " partition /dev/%s1. Error message: %s."
+                           " %s. Error message: %s."
                            " Please clean up yourself." %
                            " Please clean up yourself." %
-                           (name, result.output))
+                           (device, result.output))
 
 
 def CreateVG(vgname, disks):
 
 
 def CreateVG(vgname, disks):
@@ -642,11 +708,10 @@ def CreateVG(vgname, disks):
   This function creates a volume group named `vgname` on the disks
   given as parameters. The physical extent size is set to 64MB.
 
   This function creates a volume group named `vgname` on the disks
   given as parameters. The physical extent size is set to 64MB.
 
-  Args:
-    disks: a list of disk names, e.g. ['sda','sdb']
+  @param disks: a list of disk names, e.g. ['sda','sdb']
 
   """
 
   """
-  pnames = ["'/dev/%s1'" % disk for disk in disks]
+  pnames = [DeviceName(d) for d in disks]
   result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
   if result.failed:
     raise OperationalError("I cannot create the volume group %s from"
   result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
   if result.failed:
     raise OperationalError("I cannot create the volume group %s from"
@@ -663,20 +728,18 @@ def ValidateDiskList(options):
   using the --disks option) such that all given disks are present and
   not in use.
 
   using the --disks option) such that all given disks are present and
   not in use.
 
-  Args:
-    the options returned from OptParser.parse_options
+  @param options: the options returned from OptParser.parse_options
 
 
-  Returns:
-    a list of disk names, e.g. ['sda', 'sdb']
-  """
+  @return: a list of disk names, e.g. ['sda', 'sdb']
 
 
+  """
   sysdisks = GetDiskList(options)
   if not sysdisks:
     raise PrereqError("no disks found (I looked for"
                       " non-removable block devices).")
   sysd_free = []
   sysd_used = []
   sysdisks = GetDiskList(options)
   if not sysdisks:
     raise PrereqError("no disks found (I looked for"
                       " non-removable block devices).")
   sysd_free = []
   sysd_used = []
-  for name, size, dev, part, used in sysdisks:
+  for name, _, _, _, used in sysdisks:
     if used:
       sysd_used.append(name)
     else:
     if used:
       sysd_used.append(name)
     else:
@@ -701,8 +764,9 @@ def ValidateDiskList(options):
 
 
 def BootStrap():
 
 
 def BootStrap():
-  """Actual main routine."""
+  """Actual main routine.
 
 
+  """
   CheckPrereq()
 
   options, args = ParseOptions()
   CheckPrereq()
 
   options, args = ParseOptions()
@@ -725,12 +789,13 @@ def BootStrap():
 
   for disk in disklist:
     WipeDisk(disk)
 
   for disk in disklist:
     WipeDisk(disk)
-    PartitionDisk(disk)
+    if IsPartitioned(disk):
+      PartitionDisk(disk)
   for disk in disklist:
     CreatePVOnDisk(disk)
   CreateVG(vgname, disklist)
 
   for disk in disklist:
     CreatePVOnDisk(disk)
   CreateVG(vgname, disklist)
 
-  status, lv_count, size, free = CheckVGExists(vgname)
+  status, lv_count, size, _ = CheckVGExists(vgname)
   if status:
     print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
                                               ",".join(disklist))
   if status:
     print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
                                               ",".join(disklist))
@@ -740,11 +805,11 @@ def BootStrap():
 
 
 def main():
 
 
 def main():
-  """application entry point.
+  """Application entry point.
 
   This is just a wrapper over BootStrap, to handle our own exceptions.
 
   This is just a wrapper over BootStrap, to handle our own exceptions.
-  """
 
 
+  """
   try:
     BootStrap()
   except PrereqError, err:
   try:
     BootStrap()
   except PrereqError, err: