Fix networks in _PrepareNicModifications()
[ganeti-local] / tools / lvmstrap
index 7e8f85c..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
@@ -45,6 +45,7 @@ import sys
 import optparse
 import time
 import errno
+import re
 
 from ganeti.utils import RunCmd, ReadFile
 from ganeti import constants
@@ -52,8 +53,8 @@ from ganeti import cli
 from ganeti import compat
 
 USAGE = ("\tlvmstrap diskinfo\n"
-         "\tlvmstrap [--vgname=NAME] [--allow-removable]"
-         " { --alldisks | --disks DISKLIST }"
+         "\tlvmstrap [--vg-name=NAME] [--allow-removable]"
+         " { --alldisks | --disks DISKLIST } [--use-sfdisk]"
          " create")
 
 verbose_flag = False
@@ -66,6 +67,26 @@ SUPPORTED_TYPES = [
   "ubd",
   ]
 
+#: Excluded filesystem types
+EXCLUDED_FS = compat.UniqueFrozenset([
+  "nfs",
+  "nfs4",
+  "autofs",
+  "tmpfs",
+  "proc",
+  "sysfs",
+  "usbfs",
+  "devpts",
+  ])
+
+#: A regular expression that matches partitions (must be kept in sync
+# with L{SUPPORTED_TYPES}
+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):
   """Generic exception"""
@@ -149,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" %
@@ -168,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:
@@ -185,7 +208,7 @@ def IsPartitioned(disk):
   Currently only md devices are used as is.
 
   """
-  return not disk.startswith('md')
+  return not (disk.startswith("md") or PART_RE.match(disk))
 
 
 def DeviceName(disk):
@@ -196,12 +219,23 @@ 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
 
 
+def SysfsName(disk):
+  """Returns the sysfs name for a disk or partition.
+
+  """
+  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)
+  return "/sys/block/%s" % disk
+
+
 def ExecCommand(command):
   """Executes a command.
 
@@ -234,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."
@@ -302,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
@@ -415,7 +449,8 @@ def GetDiskList(opts):
     if not compat.any([name.startswith(pfx) for pfx in SUPPORTED_TYPES]):
       continue
 
-    size = ReadSize("/sys/block/%s" % name)
+    disksysfsname = "/sys/block/%s" % name
+    size = ReadSize(disksysfsname)
 
     f = open("/sys/block/%s/removable" % name)
     removable = int(f.read().strip())
@@ -424,18 +459,21 @@ def GetDiskList(opts):
     if removable and not opts.removable_ok:
       continue
 
-    dev = ReadDev("/sys/block/%s" % name)
+    dev = ReadDev(disksysfsname)
     CheckSysDev(name, dev)
     inuse = InUse(name)
     # Enumerate partitions of the block device
     partitions = []
-    for partname in os.listdir("/sys/block/%s" % name):
+    for partname in os.listdir(disksysfsname):
       if not partname.startswith(name):
         continue
-      partdev = ReadDev("/sys/block/%s/%s" % (name, partname))
-      partsize = ReadSize("/sys/block/%s/%s" % (name, partname))
-      CheckSysDev(partname, partdev)
-      partitions.append((partname, partsize, partdev))
+      partsysfsname = "%s/%s" % (disksysfsname, partname)
+      partdev = ReadDev(partsysfsname)
+      partsize = ReadSize(partsysfsname)
+      if partsize >= PART_MINSIZE:
+        CheckSysDev(partname, partdev)
+        partinuse = InUse(partname)
+        partitions.append((partname, partsize, partdev, partinuse))
     partitions.sort()
     dlist.append((name, size, dev, partitions, inuse))
   dlist.sort()
@@ -460,7 +498,7 @@ def GetMountInfo():
   for line in mountlines:
     _, mountpoint, fstype, _ = line.split(None, 3)
     # fs type blacklist
-    if fstype in ["nfs", "nfs4", "autofs", "tmpfs", "proc", "sysfs"]:
+    if fstype in EXCLUDED_FS:
       continue
     try:
       dev = os.stat(mountpoint).st_dev
@@ -477,6 +515,14 @@ def GetMountInfo():
   return mounts
 
 
+def GetSwapInfo():
+  """Reads /proc/swaps and returns the list of swap backing stores.
+
+  """
+  swaplines = ReadFile("/proc/swaps").splitlines()[1:]
+  return [line.split(None, 1)[0] for line in swaplines]
+
+
 def DevInfo(name, dev, mountinfo):
   """Computes miscellaneous information about a block device.
 
@@ -511,30 +557,32 @@ def ShowDiskInfo(opts):
   choice about which disks should be allocated to our volume group.
 
   """
+  def _inuse(inuse):
+    if inuse:
+      return "yes"
+    else:
+      return "no"
+
   mounts = GetMountInfo()
   dlist = GetDiskList(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 = []
   # Flatten the [(disk, [partition,...]), ...] list
   for name, size, dev, parts, inuse in dlist:
-    if inuse:
-      str_inuse = "yes"
-    else:
-      str_inuse = "no"
-    flatlist.append((name, size, dev, str_inuse))
-    for partname, partsize, partdev in parts:
-      flatlist.append((partname, partsize, partdev, ""))
+    flatlist.append((name, size, dev, _inuse(inuse)))
+    for partname, partsize, partdev, partinuse in parts:
+      flatlist.append((partname, partsize, partdev, _inuse(partinuse)))
 
   strlist = []
   for name, size, dev, in_use in flatlist:
@@ -572,7 +620,7 @@ def CheckSysfsHolders(name):
 
   """
   try:
-    contents = os.listdir("/sys/block/%s/holders/" % name)
+    contents = os.listdir("%s/holders/" % SysfsName(name))
   except OSError, err:
     if err.errno == errno.ENOENT:
       contents = []
@@ -603,7 +651,7 @@ def CheckReread(name):
     result = ExecCommand(cmd)
     if not use_blockdev and result.failed:
       break
-    elif not result.failed:
+    elif use_blockdev and not result.failed:
       break
     time.sleep(2)
 
@@ -613,11 +661,32 @@ def CheckReread(name):
     return result.failed
 
 
+def CheckMounted(name):
+  """Check to see if a block device is a mountpoint.
+
+  In recent distros/kernels, this is reported directly via fuser, but
+  on older ones not, so we do an additional check here (manually).
+
+  """
+  minfo = GetMountInfo()
+  dev = ReadDev(SysfsName(name))
+  return dev not in minfo
+
+
+def CheckSwap(name):
+  """Check to see if a block device is being used as swap.
+
+  """
+  name = "/dev/%s" % name
+  return name not in GetSwapInfo()
+
+
 def InUse(name):
   """Returns if a disk is in use or not.
 
   """
-  return not (CheckSysfsHolders(name) and CheckReread(name))
+  return not (CheckSysfsHolders(name) and CheckReread(name) and
+              CheckMounted(name) and CheckSwap(name))
 
 
 def WipeDisk(name):
@@ -664,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,
@@ -673,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):
@@ -739,9 +842,14 @@ def ValidateDiskList(options):
                       " non-removable block devices).")
   sysd_free = []
   sysd_used = []
-  for name, _, _, _, used in sysdisks:
+  for name, _, _, parts, used in sysdisks:
     if used:
       sysd_used.append(name)
+      for partname, _, _, partused in parts:
+        if partused:
+          sysd_used.append(partname)
+        else:
+          sysd_free.append(partname)
     else:
       sysd_free.append(name)
 
@@ -784,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)
@@ -798,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.")