Merge branch 'stable-2.8' into stable-2.9
[ganeti-local] / tools / lvmstrap
index be8f28a..b79dc11 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2006, 2007, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2011, 2012 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
@@ -54,7 +54,7 @@ from ganeti import compat
 
 USAGE = ("\tlvmstrap diskinfo\n"
          "\tlvmstrap [--vg-name=NAME] [--allow-removable]"
-         " { --alldisks | --disks DISKLIST }"
+         " { --alldisks | --disks DISKLIST } [--use-sfdisk]"
          " create")
 
 verbose_flag = False
@@ -68,7 +68,7 @@ SUPPORTED_TYPES = [
   ]
 
 #: Excluded filesystem types
-EXCLUDED_FS = frozenset([
+EXCLUDED_FS = compat.UniqueFrozenset([
   "nfs",
   "nfs4",
   "autofs",
@@ -85,6 +85,7 @@ PART_RE = re.compile("^((?:h|s|m|ub)d[a-z]{1,2})[0-9]+$")
 
 #: Minimum partition size to be considered (1 GB)
 PART_MINSIZE = 1024 * 1024 * 1024
+MBR_MAX_SIZE = 2 * (10 ** 12)
 
 
 class Error(Exception):
@@ -169,7 +170,7 @@ def ParseOptions():
       OptionParser.parse_args
 
   """
-  global verbose_flag # pylint: disable-msg=W0603
+  global verbose_flag # pylint: disable=W0603
 
   parser = optparse.OptionParser(usage="\n%s" % USAGE,
                                  version="%%prog (ganeti) %s" %
@@ -188,7 +189,9 @@ def ParseOptions():
   parser.add_option("-g", "--vg-name", type="string",
                     dest="vgname", default="xenvg", metavar="NAME",
                     help="the volume group to be created [default: xenvg]")
-
+  parser.add_option("--use-sfdisk", dest="use_sfdisk",
+                    action="store_true", default=False,
+                    help="use sfdisk instead of parted")
 
   options, args = parser.parse_args()
   if len(args) != 1:
@@ -205,7 +208,7 @@ def IsPartitioned(disk):
   Currently only md devices are used as is.
 
   """
-  return not (disk.startswith('md') or PART_RE.match(disk))
+  return not (disk.startswith("md") or PART_RE.match(disk))
 
 
 def DeviceName(disk):
@@ -216,9 +219,9 @@ def DeviceName(disk):
 
   """
   if IsPartitioned(disk):
-    device = '/dev/%s1' % disk
+    device = "/dev/%s1" % disk
   else:
-    device = '/dev/%s' % disk
+    device = "/dev/%s" % disk
   return device
 
 
@@ -229,7 +232,7 @@ def SysfsName(disk):
   match = PART_RE.match(disk)
   if match:
     # this is a partition, which resides in /sys/block under a different name
-    disk = "%s/%s"  % (match.group(1), disk)
+    disk = "%s/%s" % (match.group(1), disk)
   return "/sys/block/%s" % disk
 
 
@@ -265,13 +268,13 @@ def CheckPrereq():
     raise PrereqError("This tool runs as root only. Really.")
 
   osname, _, release, _, _ = os.uname()
-  if osname != 'Linux':
+  if osname != "Linux":
     raise PrereqError("This tool only runs on Linux"
                       " (detected OS: %s)." % osname)
 
-  if not release.startswith("2.6."):
+  if not (release.startswith("2.6.") or release.startswith("3.")):
     raise PrereqError("Wrong major kernel version (detected %s, needs"
-                      " 2.6.*)" % release)
+                      " 2.6.* or 3.*)" % release)
 
   if not os.path.ismount("/sys"):
     raise PrereqError("Can't find a filesystem mounted at /sys."
@@ -333,7 +336,7 @@ def CheckSysDev(name, devnum):
 
   @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
+  @raises SysconfigError: in case of failure of the check
 
   """
   path = "/dev/%s" % name
@@ -565,13 +568,13 @@ def ShowDiskInfo(opts):
 
   print "------- Disk information -------"
   headers = {
-      "name": "Name",
-      "size": "Size[M]",
-      "used": "Used",
-      "mount": "Mount",
-      "lvm": "LVM?",
-      "info": "Info"
-      }
+    "name": "Name",
+    "size": "Size[M]",
+    "used": "Used",
+    "mount": "Mount",
+    "lvm": "LVM?",
+    "info": "Info",
+    }
   fields = ["name", "size", "used", "mount", "lvm", "info"]
 
   flatlist = []
@@ -730,7 +733,7 @@ def WipeDisk(name):
                            name)
 
 
-def PartitionDisk(name):
+def PartitionDisk(name, use_sfdisk):
   """Partitions a disk.
 
   This function creates a single partition spanning the entire disk,
@@ -739,15 +742,49 @@ def PartitionDisk(name):
   @param name: the device name, e.g. sda
 
   """
-  result = ExecCommand(
-    'echo ,,8e, | sfdisk /dev/%s' % name)
+
+  # Check that parted exists
+  result = ExecCommand("parted --help")
   if result.failed:
-    raise OperationalError("CRITICAL: disk %s which I have just partitioned"
-                           " cannot reread its partition table, or there"
-                           " is some other sfdisk error. Likely, it is in"
-                           " use. You have to clean this yourself. Error"
-                           " message from sfdisk: %s" %
-                           (name, result.output))
+    use_sfdisk = True
+    print >> sys.stderr, ("Unable to execute \"parted --help\","
+                          " falling back to sfdisk.")
+
+  # Check disk size - over 2TB means we need to use GPT
+  size = ReadSize("/sys/block/%s" % name)
+  if size > MBR_MAX_SIZE:
+    label_type = "gpt"
+    if use_sfdisk:
+      raise OperationalError("Critical: Disk larger than 2TB detected, but"
+                             " parted is either not installed or --use-sfdisk"
+                             " has been specified")
+  else:
+    label_type = "msdos"
+
+  if use_sfdisk:
+    result = ExecCommand(
+        "echo ,,8e, | sfdisk /dev/%s" % name)
+    if result.failed:
+      raise OperationalError("CRITICAL: disk %s which I have just partitioned"
+                             " cannot reread its partition table, or there"
+                             " is some other sfdisk error. Likely, it is in"
+                             " use. You have to clean this yourself. Error"
+                             " message from sfdisk: %s" %
+                             (name, result.output))
+
+  else:
+    result = ExecCommand("parted -s /dev/%s mklabel %s" % (name, label_type))
+    if result.failed:
+      raise OperationalError("Critical: failed to create %s label on %s" %
+                             (label_type, name))
+    result = ExecCommand("parted -s /dev/%s mkpart pri ext2 1 100%%" % name)
+    if result.failed:
+      raise OperationalError("Critical: failed to create partition on %s" %
+                             name)
+    result = ExecCommand("parted -s /dev/%s set 1 lvm on" % name)
+    if result.failed:
+      raise OperationalError("Critical: failed to set partition on %s to LVM" %
+                             name)
 
 
 def CreatePVOnDisk(name):
@@ -855,13 +892,12 @@ def BootStrap():
                       "  LV count: %s, size: %s, free: %s." %
                       (vgname, lv_count, vg_size, vg_free))
 
-
   disklist = ValidateDiskList(options)
 
   for disk in disklist:
     WipeDisk(disk)
     if IsPartitioned(disk):
-      PartitionDisk(disk)
+      PartitionDisk(disk, options.use_sfdisk)
   for disk in disklist:
     CreatePVOnDisk(disk)
   CreateVG(vgname, disklist)
@@ -869,7 +905,7 @@ def BootStrap():
   status, lv_count, size, _ = CheckVGExists(vgname)
   if status:
     print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
-                                              ",".join(disklist))
+                                                ",".join(disklist))
   else:
     raise OperationalError("Although everything seemed ok, the volume"
                            " group did not get created.")