X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/6714256cfaac44e6db3b5c3744e65cbab4bfb62d..43c16a8a1adfd543751fcaf60ad4c8e04cf83688:/tools/lvmstrap diff --git a/tools/lvmstrap b/tools/lvmstrap index 1cc746d..6512477 100755 --- a/tools/lvmstrap +++ b/tools/lvmstrap @@ -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 @@ -78,6 +79,14 @@ EXCLUDED_FS = frozenset([ "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""" @@ -161,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" % @@ -180,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: @@ -197,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): @@ -208,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. @@ -246,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." @@ -427,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()) @@ -436,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() @@ -489,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. @@ -523,6 +557,12 @@ 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) @@ -540,13 +580,9 @@ def ShowDiskInfo(opts): 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: @@ -584,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 = [] @@ -615,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) @@ -625,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): @@ -676,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, @@ -685,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): @@ -751,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) @@ -796,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)