From 83807e8d2fe38ea4cc1df176cd1bc91cafe49fa3 Mon Sep 17 00:00:00 2001 From: David Lehman Date: Thu, 26 Jul 2012 16:43:49 -0500 Subject: [PATCH 5/6] Add support for creating device based on a top-down specification. General Approach: Instead of an all-or-nothing approach, the goal is to come as close as possible to satisfying the new device specification. Once the container has been allocated, we simply adjust the new device's size as needed to fit in the container. This uses growable partition requests, but it then fixes their size as soon as they have been allocated. It also changes doPartitioning to reallocate container member devices each time instead of fixing them on disk once the container is defined. Since the members are not growable anymore once the container is defined, this is less disruptive than it would otherwise be. It allows for maximum flexibility to allocate the set of requests while still preserving already-defined containers' sizes. Notes: Creation of md devices is completely untested. Creation any device (md, lvm, btrfs) with striping, mirroring, or any other RAID-like features is completely untested. There is no support for container members of any type other than normal partitions. --- pyanaconda/storage/__init__.py | 230 +++++++++++++++++++++++++++++++++++- pyanaconda/storage/partitioning.py | 3 +- 2 files changed, 228 insertions(+), 5 deletions(-) diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index 30cae9e..f6168c5 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -49,6 +49,11 @@ from devicelibs.dm import name_from_dm_node from devicelibs.crypto import generateBackupPassphrase from devicelibs.mpath import MultipathConfigWriter from devicelibs.edd import get_edd_dict +from devicelibs.mdraid import get_member_space +from devicelibs.lvm import get_pv_space +from .partitioning import SameSizeSet +from .partitioning import TotalSizeSet +from .partitioning import doPartitioning from udev import * import iscsi import fcoe @@ -1358,9 +1363,6 @@ class Storage(object): # also include names of any lvs in the parent for the case of the # temporary vg in the lvm dialogs, which can contain lvs that are # not yet in the devicetree and therefore not in self.names - if hasattr(parent, "lvs"): - names.extend([full_name(d.lvname, parent) for d in parent.lvs]) - if full_name(name, parent) in names or not body: for i in range(100): name = "%s%02d" % (template, i) @@ -1848,6 +1850,228 @@ class Storage(object): return 0 + def getFSType(self, mountpoint=None): + fstype = self.defaultFSType + if not mountpoint: + # just return the default + pass + elif mountpoint.lower() == "swap": + fstype = "swap" + elif mountpoint == "/boot": + fstype = self.defaultBootFSType + elif mountpoint == "/boot/efi": + if iutil.isMactel(): + fstype = "hfs+" + else: + fstype = "efi" + + return fstype + + def newUIDevice(self, device_type, size, **kwargs): + mountpoint = kwargs.get("mountpoint") + fstype = kwargs.get("fstype") + device = kwargs.get("device") + disks = kwargs.get("disks") + encrypted = kwargs.get("encrypted", self.data.autopart.encrypted) + + # md, btrfs + raid_level = kwargs.get("level") + + # lvm + striped = kwargs.get("striped") + mirrored = kwargs.get("mirrored") + + if not fstype: + fstype = self.getFSType(mountpoint=mountpoint) + if fstype == "swap": + mountpoint = None + + if fstype == "swap" and device_type == AUTOPART_TYPE_BTRFS: + device_type = AUTOPART_TYPE_PLAIN + elif device_type != AUTOPART_TYPE_PLAIN and \ + mountpoint and mountpoint.startswith("/boot"): + device_type = AUTOPART_TYPE_PLAIN + + # TODO: striping, mirroring, &c + # TODO: non-partition members (pv-on-md) + if device_type == AUTOPART_TYPE_PLAIN: + # XXX FIXME: for a size of 500MB this is growing unbounded + device = self.newPartition(grow=True, maxsize=size, + fmt_type=fstype, mountpoint=mountpoint) + self.createDevice(device) + try: + doPartitioning(self) + except StorageError as e: + log.error("failed to allocate partitions: %s" % e) + actions = self.devicetree.findActions(device=device) + for a in reversed(actions): + self.devicetree.cancelAction(a) + else: + # fix the sizes of all allocated partitions + for partition in self.partitions: + partition.req_grow = False + partition.req_base_size = partition.size + partition.req_size = partition.size + + return + elif device_type == AUTOPART_TYPE_BTRFS: + def btrfs_size(size, disks, level): + if level in ("single", "raid0"): + return size + elif level in ("raid1", "raid10"): + return size * len(disks) + + type_desc = "btrfs" + member_format = "btrfs" + size_func = btrfs_size + size_func_kwargs = {"level": raid_level or "single"} + new_container = self.newBTRFS + new_device = self.newBTRFSSubVolume + container_list = self.btrfsVolumes + container_size_func = lambda c: c.size + if raid_level in ("raid1", "raid10"): + set_class = SameSizeSet + else: + set_class = TotalSizeSet + elif device_type == AUTOPART_TYPE_LVM: + type_desc = "lvm" + member_format = "lvmpv" + size_func = get_pv_space + size_func_kwargs = {"striped": striped, + "mirrored": mirrored} + new_container = self.newVG + new_device = self.newLV + container_list = self.vgs + container_size_func = lambda c: c.size - c.freeSpace + if mirrored: + set_class = SameSizeSet + else: + set_class = TotalSizeSet + #elif device_type == AUTOPART_TYPE_MD: + else: + type_desc = "md" + member_format = "mdmember" + size_func = get_member_space + size_func_kwargs = {"level": raid_level} + new_container = None + new_device = self.newMD + container_list = [] + container_size_func = lambda c: c.size + if raid_level != "linear": + set_class = SameSizeSet + else: + set_class = TotalSizeSet + + container = None + containers = [c for c in container_list if not c.exists] + if containers: + container = containers[0] + + # If we have a container and also a disk list, how do we reconcile the + # two to arrive at a member device count? + # For now, existing container wins since we will only modify one layer + # at a time. + + # set up member devices + disks = disks + container_size = 0 + if container: + log.debug("using container %s with %d devices" % (container.name, + len(self.devicetree.getChildren(container)))) + container_size = container_size_func(container) + log.debug("raw container size reported as %d" % container_size) + disks = list(set([d for m in container.parents for d in m.disks])) + disks.sort(key=lambda d: d.name, cmp=self.compareDisks) + + device_space = size_func(size, len(disks), **size_func_kwargs) + log.debug("device requires %d" % device_space) + container_size += device_space + + # XXX TODO: multiple member devices per disk + + # XXX Never try to reuse member devices. They can be converted by the + # user into free space. + if container: + members = container.parents[:] + for member in members[:]: + if isinstance(member, LUKSDevice): + member = member.slave + + member.req_base_size = PartitionDevice.defaultSize + member.req_size = member.req_base_size + member.req_grow = True + + for ss in self.size_sets[:]: + if member in ss.devices: + self.size_sets.remove(ss) + else: + # set up members as needed to accommodate the device + members = [] + for disk in disks: + if encrypted: + luks_format_type = member_type + member_type = "luks" + + member = self.newPartition(parents=[disk], grow=True, + fmt_type=member_format) + self.createDevice(member) + if encrypted: + fmt = getFormat(luks_format_type) + member = LUKSDevice("luks-%s" % member.name, + parents=[member], format=fmt) + self.createDevice(member) + + members.append(member) + + log.debug("adding a %s with size %d" % (set_class.__name__, + container_size)) + size_set = set_class(members, container_size) + self.size_sets.append(size_set) + + try: + doPartitioning(self) + except StorageError as e: + log.error("UI: failed to allocate partitions: %s" % e) + for device in members: + actions = self.devicetree.findActions(device=device) + for a in reversed(actions): + self.devicetree.cancelAction(a) + # FIXME: revert container to its original state + return + else: + # fix the sizes of all allocated partitions + for partition in self.partitions: + partition.req_grow = False + partition.req_base_size = partition.size + partition.req_size = partition.size + + parents = members + + # set up container + if not container and new_container: + log.debug("creating new container") + container = new_container(parents=parents) + self.createDevice(container) + + if container: + parents = [container] + log.debug("%r" % container) + + # add the new device to the container + if new_device: + free = getattr(container, "freeSpace", size) + if free < size: + log.info("adjusting device size from %.2f to %.2f so it fits " + "in container" % (size, free)) + size = free + + log.debug("creating new device") + device = new_device(parents=parents, + size=size, + fmt_type=fstype, + mountpoint=mountpoint) + self.createDevice(device) + def mountExistingSystem(fsset, rootEnt, allowDirty=None, dirtyCB=None, readOnly=None): diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py index 4c25928..3b55f99 100644 --- a/pyanaconda/storage/partitioning.py +++ b/pyanaconda/storage/partitioning.py @@ -768,8 +768,7 @@ def doPartitioning(storage): for part in storage.partitions: part.req_bootable = False - if part.exists or \ - (storage.deviceImmutable(part) and part.partedPartition): + if part.exists: # if the partition is preexisting or part of a complex device # then we shouldn't modify it partitions.remove(part) -- 1.7.7.6