summaryrefslogtreecommitdiffstats
path: root/pyanaconda/storage/partitioning.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyanaconda/storage/partitioning.py')
-rw-r--r--pyanaconda/storage/partitioning.py1947
1 files changed, 0 insertions, 1947 deletions
diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py
deleted file mode 100644
index 243c62b80..000000000
--- a/pyanaconda/storage/partitioning.py
+++ /dev/null
@@ -1,1947 +0,0 @@
-# partitioning.py
-# Disk partitioning functions.
-#
-# Copyright (C) 2009 Red Hat, Inc.
-#
-# This copyrighted material is made available to anyone wishing to use,
-# modify, copy, or redistribute it subject to the terms and conditions of
-# the GNU General Public License v.2, or (at your option) any later version.
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY expressed or implied, including the implied warranties of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
-# Public License for more details. You should have received a copy of the
-# GNU General Public License along with this program; if not, write to the
-# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
-# source code or documentation are not subject to the GNU General Public
-# License and may only be used or replicated with the express permission of
-# Red Hat, Inc.
-#
-# Red Hat Author(s): Dave Lehman <dlehman@redhat.com>
-#
-
-import sys
-import os
-from operator import add, sub, gt, lt
-
-import parted
-from pykickstart.constants import *
-
-from errors import *
-from deviceaction import *
-from devices import PartitionDevice, LUKSDevice, devicePathToName
-from formats import getFormat
-
-import gettext
-_ = lambda x: gettext.ldgettext("anaconda", x)
-
-import logging
-log = logging.getLogger("storage")
-
-def _getCandidateDisks(storage):
- """ Return a list of disks with space for a default-sized partition. """
- disks = []
- for disk in storage.partitioned:
- if storage.config.clearPartDisks and \
- (disk.name not in storage.config.clearPartDisks):
- continue
-
- part = disk.format.firstPartition
- while part:
- if not part.type & parted.PARTITION_FREESPACE:
- part = part.nextPartition()
- continue
-
- if part.getSize(unit="MB") > PartitionDevice.defaultSize:
- disks.append(disk)
- break
-
- part = part.nextPartition()
-
- return disks
-
-def _scheduleImplicitPartitions(storage, disks):
- """ Schedule creation of a lvm/btrfs partition on each disk in disks. """
- # create a separate pv or btrfs partition for each disk with free space
- devs = []
-
- # only schedule the partitions if either lvm or btrfs autopart was chosen
- if storage.autoPartType not in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_BTRFS):
- return devs
-
- for disk in disks:
- if storage.encryptedAutoPart:
- fmt_type = "luks"
- fmt_args = {"passphrase": storage.encryptionPassphrase,
- "cipher": storage.encryptionCipher,
- "escrow_cert": storage.autoPartEscrowCert,
- "add_backup_passphrase": storage.autoPartAddBackupPassphrase}
- else:
- if storage.autoPartType == AUTOPART_TYPE_LVM:
- fmt_type = "lvmpv"
- else:
- fmt_type = "btrfs"
- fmt_args = {}
- part = storage.newPartition(fmt_type=fmt_type,
- fmt_args=fmt_args,
- grow=True,
- parents=[disk])
- storage.createDevice(part)
- devs.append(part)
-
- return devs
-
-def _schedulePartitions(storage, disks):
- """ Schedule creation of autopart partitions. """
- # basis for requests with requiredSpace is the sum of the sizes of the
- # two largest free regions
- all_free = getFreeRegions(disks)
- all_free.sort(key=lambda f: f.length, reverse=True)
- if not all_free:
- # this should never happen since we've already filtered the disks
- # to those with at least 500MB free
- log.error("no free space on disks %s" % ([d.name for d in disks],))
- return
-
- free = all_free[0].getSize()
- if len(all_free) > 1:
- free += all_free[1].getSize()
-
- # The boot disk must be set at this point. See if any platform-specific
- # stage1 device we might allocate already exists on the boot disk.
- stage1_device = None
- for device in storage.devices:
- if storage.bootloader.stage1_disk not in device.disks:
- continue
-
- if storage.bootloader.is_valid_stage1_device(device):
- stage1_device = device
- break
-
- #
- # First pass is for partitions only. We'll do LVs later.
- #
- for request in storage.autoPartitionRequests:
- if (request.lv and storage.autoPartType == AUTOPART_TYPE_LVM) or \
- (request.btr and storage.autoPartType == AUTOPART_TYPE_BTRFS):
- continue
-
- if request.requiredSpace and request.requiredSpace > free:
- continue
-
- elif request.fstype in ("prepboot", "efi", "hfs+") and \
- (storage.bootloader.skip_bootloader or stage1_device):
- # there should never be a need for more than one of these
- # partitions, so skip them.
- log.info("skipping unneeded stage1 %s request" % request.fstype)
- log.debug(request)
-
- if request.fstype == "efi":
- # Set the mountpoint for the existing EFI boot partition
- stage1_device.format.mountpoint = "/boot/efi"
-
- log.debug(stage1_device)
- continue
- elif request.fstype == "biosboot":
- is_gpt = (stage1_device and
- getattr(stage1_device.format, "labelType", None) == "gpt")
- has_bios_boot = (stage1_device and
- any([p.format.type == "biosboot"
- for p in storage.partitions
- if p.disk == stage1_device]))
- if (storage.bootloader.skip_bootloader or
- not (stage1_device and stage1_device.isDisk and
- is_gpt and not has_bios_boot)):
- # there should never be a need for more than one of these
- # partitions, so skip them.
- log.info("skipping unneeded stage1 %s request" % request.fstype)
- log.debug(request)
- log.debug(stage1_device)
- continue
-
- if request.encrypted and storage.encryptedAutoPart:
- fmt_type = "luks"
- fmt_args = {"passphrase": storage.encryptionPassphrase,
- "cipher": storage.encryptionCipher,
- "escrow_cert": storage.autoPartEscrowCert,
- "add_backup_passphrase": storage.autoPartAddBackupPassphrase}
- else:
- fmt_type = request.fstype
- fmt_args = {}
-
- dev = storage.newPartition(fmt_type=fmt_type,
- fmt_args=fmt_args,
- size=request.size,
- grow=request.grow,
- maxsize=request.maxSize,
- mountpoint=request.mountpoint,
- parents=disks,
- weight=request.weight)
-
- # schedule the device for creation
- storage.createDevice(dev)
-
- if request.encrypted and storage.encryptedAutoPart:
- luks_fmt = getFormat(request.fstype,
- device=dev.path,
- mountpoint=request.mountpoint)
- luks_dev = LUKSDevice("luks-%s" % dev.name,
- format=luks_fmt,
- size=dev.size,
- parents=dev)
- storage.createDevice(luks_dev)
-
- # make sure preexisting broken lvm/raid configs get out of the way
- return
-
-def _scheduleVolumes(storage, devs):
- """ Schedule creation of autopart lvm/btrfs volumes. """
- if not devs:
- return
-
- if storage.autoPartType == AUTOPART_TYPE_LVM:
- new_container = storage.newVG
- new_volume = storage.newLV
- format_name = "lvmpv"
- else:
- new_container = storage.newBTRFS
- new_volume = storage.newBTRFS
- format_name = "btrfs"
-
- if storage.encryptedAutoPart:
- pvs = []
- for dev in devs:
- pv = LUKSDevice("luks-%s" % dev.name,
- format=getFormat(format_name, device=dev.path),
- size=dev.size,
- parents=dev)
- pvs.append(pv)
- storage.createDevice(pv)
- else:
- pvs = devs
-
- # create a vg containing all of the autopart pvs
- container = new_container(parents=pvs)
- storage.createDevice(container)
-
- #
- # Convert storage.autoPartitionRequests into Device instances and
- # schedule them for creation.
- #
- # Second pass, for LVs only.
- for request in storage.autoPartitionRequests:
- btr = storage.autoPartType == AUTOPART_TYPE_BTRFS and request.btr
- lv = storage.autoPartType == AUTOPART_TYPE_LVM and request.lv
-
- if not btr and not lv:
- continue
-
- # required space isn't relevant on btrfs
- if lv and \
- request.requiredSpace and request.requiredSpace > container.size:
- continue
-
- if request.fstype is None:
- if btr:
- # btrfs volumes can only contain btrfs filesystems
- request.fstype = "btrfs"
- else:
- request.fstype = storage.defaultFSType
-
- kwargs = {"mountpoint": request.mountpoint,
- "fmt_type": request.fstype}
- if lv:
- kwargs.update({"parents": [container],
- "grow": request.grow,
- "maxsize": request.maxSize,
- "size": request.size,
- "singlePV": request.singlePV})
- else:
- kwargs.update({"parents": [container],
- "size": request.size,
- "subvol": True})
-
- dev = new_volume(**kwargs)
-
- # schedule the device for creation
- storage.createDevice(dev)
-
-def doAutoPartition(storage, data):
- log.debug("doAutoPart: %s" % storage.doAutoPart)
- log.debug("encryptedAutoPart: %s" % storage.encryptedAutoPart)
- log.debug("autoPartType: %s" % storage.autoPartType)
- log.debug("clearPartType: %s" % storage.config.clearPartType)
- log.debug("clearPartDisks: %s" % storage.config.clearPartDisks)
- log.debug("autoPartitionRequests:\n%s" % "".join([str(p) for p in storage.autoPartitionRequests]))
- log.debug("storage.disks: %s" % [d.name for d in storage.disks])
- log.debug("storage.partitioned: %s" % [d.name for d in storage.partitioned])
- log.debug("all names: %s" % [d.name for d in storage.devices])
- log.debug("boot disk: %s" % getattr(storage.bootDisk, "name", None))
-
- disks = []
- devs = []
-
- if not storage.doAutoPart:
- return
-
- if not storage.partitioned:
- raise NoDisksError(_("No usable disks selected"))
-
- disks = _getCandidateDisks(storage)
- devs = _scheduleImplicitPartitions(storage, disks)
- log.debug("candidate disks: %s" % disks)
- log.debug("devs: %s" % devs)
-
- if disks == []:
- raise NotEnoughFreeSpaceError(_("Not enough free space on disks for "
- "automatic partitioning"))
-
- _schedulePartitions(storage, disks)
-
- # run the autopart function to allocate and grow partitions
- doPartitioning(storage)
- _scheduleVolumes(storage, devs)
-
- # grow LVs
- growLVM(storage)
-
- storage.setUpBootLoader()
-
- # now do a full check of the requests
- (errors, warnings) = storage.sanityCheck()
- for error in errors:
- log.error(error)
- for warning in warnings:
- log.warning(warning)
- if errors:
- raise PartitioningError("\n".join(errors))
-
-def partitionCompare(part1, part2):
- """ More specifically defined partitions come first.
-
- < 1 => x < y
- 0 => x == y
- > 1 => x > y
- """
- ret = 0
-
- if part1.req_base_weight:
- ret -= part1.req_base_weight
-
- if part2.req_base_weight:
- ret += part2.req_base_weight
-
- # more specific disk specs to the front of the list
- # req_disks being empty is equivalent to it being an infinitely long list
- if part1.req_disks and not part2.req_disks:
- ret -= 500
- elif not part1.req_disks and part2.req_disks:
- ret += 500
- else:
- ret += cmp(len(part1.req_disks), len(part2.req_disks)) * 500
-
- # primary-only to the front of the list
- ret -= cmp(part1.req_primary, part2.req_primary) * 200
-
- # fixed size requests to the front
- ret += cmp(part1.req_grow, part2.req_grow) * 100
-
- # larger requests go to the front of the list
- ret -= cmp(part1.req_base_size, part2.req_base_size) * 50
-
- # potentially larger growable requests go to the front
- if part1.req_grow and part2.req_grow:
- if not part1.req_max_size and part2.req_max_size:
- ret -= 25
- elif part1.req_max_size and not part2.req_max_size:
- ret += 25
- else:
- ret -= cmp(part1.req_max_size, part2.req_max_size) * 25
-
- # give a little bump based on mountpoint
- if hasattr(part1.format, "mountpoint") and \
- hasattr(part2.format, "mountpoint"):
- ret += cmp(part1.format.mountpoint, part2.format.mountpoint) * 10
-
- if ret > 0:
- ret = 1
- elif ret < 0:
- ret = -1
-
- return ret
-
-def getNextPartitionType(disk, no_primary=None):
- """ Find the type of partition to create next on a disk.
-
- Return a parted partition type value representing the type of the
- next partition we will create on this disk.
-
- If there is only one free primary partition and we can create an
- extended partition, we do that.
-
- If there are free primary slots and an extended partition we will
- recommend creating a primary partition. This can be overridden
- with the keyword argument no_primary.
-
- Arguments:
-
- disk -- a parted.Disk instance representing the disk
-
- Keyword arguments:
-
- no_primary -- given a choice between primary and logical
- partitions, prefer logical
-
- """
- part_type = None
- extended = disk.getExtendedPartition()
- supports_extended = disk.supportsFeature(parted.DISK_TYPE_EXTENDED)
- logical_count = len(disk.getLogicalPartitions())
- max_logicals = disk.getMaxLogicalPartitions()
- primary_count = disk.primaryPartitionCount
-
- if primary_count < disk.maxPrimaryPartitionCount:
- if primary_count == disk.maxPrimaryPartitionCount - 1:
- # can we make an extended partition? now's our chance.
- if not extended and supports_extended:
- part_type = parted.PARTITION_EXTENDED
- elif not extended:
- # extended partitions not supported. primary or nothing.
- if not no_primary:
- part_type = parted.PARTITION_NORMAL
- else:
- # there is an extended and a free primary
- if not no_primary:
- part_type = parted.PARTITION_NORMAL
- elif logical_count < max_logicals:
- # we have an extended with logical slots, so use one.
- part_type = parted.PARTITION_LOGICAL
- else:
- # there are two or more primary slots left. use one unless we're
- # not supposed to make primaries.
- if not no_primary:
- part_type = parted.PARTITION_NORMAL
- elif extended and logical_count < max_logicals:
- part_type = parted.PARTITION_LOGICAL
- elif extended and logical_count < max_logicals:
- part_type = parted.PARTITION_LOGICAL
-
- return part_type
-
-def getBestFreeSpaceRegion(disk, part_type, req_size,
- boot=None, best_free=None, grow=None):
- """ Return the "best" free region on the specified disk.
-
- For non-boot partitions, we return the largest free region on the
- disk. For boot partitions, we return the first region that is
- large enough to hold the partition.
-
- Partition type (parted's PARTITION_NORMAL, PARTITION_LOGICAL) is
- taken into account when locating a suitable free region.
-
- For locating the best region from among several disks, the keyword
- argument best_free allows the specification of a current "best"
- free region with which to compare the best from this disk. The
- overall best region is returned.
-
- Arguments:
-
- disk -- the disk (a parted.Disk instance)
- part_type -- the type of partition we want to allocate
- (one of parted's partition type constants)
- req_size -- the requested size of the partition (in MB)
-
- Keyword arguments:
-
- boot -- indicates whether this will be a bootable partition
- (boolean)
- best_free -- current best free region for this partition
- grow -- indicates whether this is a growable request
-
- """
- log.debug("getBestFreeSpaceRegion: disk=%s part_type=%d req_size=%dMB "
- "boot=%s best=%s grow=%s" %
- (disk.device.path, part_type, req_size, boot, best_free, grow))
- extended = disk.getExtendedPartition()
-
- for _range in disk.getFreeSpaceRegions():
- if extended:
- # find out if there is any overlap between this region and the
- # extended partition
- log.debug("looking for intersection between extended (%d-%d) and free (%d-%d)" %
- (extended.geometry.start, extended.geometry.end, _range.start, _range.end))
-
- # parted.Geometry.overlapsWith can handle this
- try:
- free_geom = extended.geometry.intersect(_range)
- except ArithmeticError:
- # this freespace region does not lie within the extended
- # partition's geometry
- free_geom = None
-
- if (free_geom and part_type == parted.PARTITION_NORMAL) or \
- (not free_geom and part_type == parted.PARTITION_LOGICAL):
- log.debug("free region not suitable for request")
- continue
-
- if part_type == parted.PARTITION_NORMAL:
- # we're allocating a primary and the region is not within
- # the extended, so we use the original region
- free_geom = _range
- else:
- free_geom = _range
-
- if free_geom.start > disk.maxPartitionStartSector:
- log.debug("free range start sector beyond max for new partitions")
- continue
-
- if boot:
- free_start_mb = sectorsToSize(free_geom.start,
- disk.device.sectorSize)
- req_end_mb = free_start_mb + req_size
- if req_end_mb > 2*1024*1024:
- log.debug("free range position would place boot req above 2TB")
- continue
-
- log.debug("current free range is %d-%d (%dMB)" % (free_geom.start,
- free_geom.end,
- free_geom.getSize()))
- free_size = free_geom.getSize()
-
- # For boot partitions, we want the first suitable region we find.
- # For growable or extended partitions, we want the largest possible
- # free region.
- # For all others, we want the smallest suitable free region.
- if grow or part_type == parted.PARTITION_EXTENDED:
- op = gt
- else:
- op = lt
- if req_size <= free_size:
- if not best_free or op(free_geom.length, best_free.length):
- best_free = free_geom
-
- if boot:
- # if this is a bootable partition we want to
- # use the first freespace region large enough
- # to satisfy the request
- break
-
- return best_free
-
-def sectorsToSize(sectors, sectorSize):
- """ Convert length in sectors to size in MB.
-
- Arguments:
-
- sectors - sector count
- sectorSize - sector size for the device, in bytes
- """
- return (sectors * sectorSize) / (1024.0 * 1024.0)
-
-def sizeToSectors(size, sectorSize):
- """ Convert size in MB to length in sectors.
-
- Arguments:
-
- size - size in MB
- sectorSize - sector size for the device, in bytes
- """
- return (size * 1024.0 * 1024.0) / sectorSize
-
-def removeNewPartitions(disks, partitions):
- """ Remove newly added input partitions from input disks.
-
- Arguments:
-
- disks -- list of StorageDevice instances with DiskLabel format
- partitions -- list of PartitionDevice instances
-
- """
- log.debug("removing all non-preexisting partitions %s from disk(s) %s"
- % (["%s(id %d)" % (p.name, p.id) for p in partitions
- if not p.exists],
- [d.name for d in disks]))
- for part in partitions:
- if part.partedPartition and part.disk in disks:
- if part.exists:
- # we're only removing partitions that don't physically exist
- continue
-
- if part.isExtended:
- # these get removed last
- continue
-
- part.disk.format.partedDisk.removePartition(part.partedPartition)
- part.partedPartition = None
- part.disk = None
-
- for disk in disks:
- # remove empty extended so it doesn't interfere
- extended = disk.format.extendedPartition
- if extended and not disk.format.logicalPartitions:
- log.debug("removing empty extended partition from %s" % disk.name)
- disk.format.partedDisk.removePartition(extended)
-
-def addPartition(disklabel, free, part_type, size):
- """ Return new partition after adding it to the specified disk.
-
- Arguments:
-
- disklabel -- disklabel instance to add partition to
- free -- where to add the partition (parted.Geometry instance)
- part_type -- partition type (parted.PARTITION_* constant)
- size -- size (in MB) of the new partition
-
- The new partition will be aligned.
-
- Return value is a parted.Partition instance.
-
- """
- start = free.start
- if not disklabel.alignment.isAligned(free, start):
- start = disklabel.alignment.alignNearest(free, start)
-
- if disklabel.labelType == "sun" and start == 0:
- start = disklabel.alignment.alignUp(free, start)
-
- if part_type == parted.PARTITION_LOGICAL:
- # make room for logical partition's metadata
- start += disklabel.alignment.grainSize
-
- if start != free.start:
- log.debug("adjusted start sector from %d to %d" % (free.start, start))
-
- if part_type == parted.PARTITION_EXTENDED:
- end = free.end
- length = end - start + 1
- else:
- # size is in MB
- length = sizeToSectors(size, disklabel.partedDevice.sectorSize)
- end = start + length - 1
-
- if not disklabel.endAlignment.isAligned(free, end):
- end = disklabel.endAlignment.alignNearest(free, end)
- log.debug("adjusted length from %d to %d" % (length, end - start + 1))
- if start > end:
- raise PartitioningError(_("unable to allocate aligned partition"))
-
- new_geom = parted.Geometry(device=disklabel.partedDevice,
- start=start,
- end=end)
-
- max_length = disklabel.partedDisk.maxPartitionLength
- if max_length and new_geom.length > max_length:
- raise PartitioningError(_("requested size exceeds maximum allowed"))
-
- # create the partition and add it to the disk
- partition = parted.Partition(disk=disklabel.partedDisk,
- type=part_type,
- geometry=new_geom)
- constraint = parted.Constraint(exactGeom=new_geom)
- disklabel.partedDisk.addPartition(partition=partition,
- constraint=constraint)
- return partition
-
-def getFreeRegions(disks):
- """ Return a list of free regions on the specified disks.
-
- Arguments:
-
- disks -- list of parted.Disk instances
-
- Return value is a list of unaligned parted.Geometry instances.
-
- """
- free = []
- for disk in disks:
- for f in disk.format.partedDisk.getFreeSpaceRegions():
- if f.length > 0:
- free.append(f)
-
- return free
-
-def updateExtendedPartitions(storage, disks):
- # XXX hack -- if we created any extended partitions we need to add
- # them to the tree now
- for disk in disks:
- extended = disk.format.extendedPartition
- if not extended:
- # remove any obsolete extended partitions
- for part in storage.partitions:
- if part.disk == disk and part.isExtended:
- if part.exists:
- storage.destroyDevice(part)
- else:
- storage.devicetree._removeDevice(part, moddisk=False)
- continue
-
- extendedName = devicePathToName(extended.getDeviceNodeName())
- # remove any obsolete extended partitions
- for part in storage.partitions:
- if part.disk == disk and part.isExtended and \
- part.partedPartition not in disk.format.partitions:
- if part.exists:
- storage.destroyDevice(part)
- else:
- storage.devicetree._removeDevice(part, moddisk=False)
-
- device = storage.devicetree.getDeviceByName(extendedName)
- if device:
- if not device.exists:
- # created by us, update partedPartition
- device.partedPartition = extended
- continue
-
- # This is a little odd because normally instantiating a partition
- # that does not exist means leaving self.parents empty and instead
- # populating self.req_disks. In this case, we need to skip past
- # that since this partition is already defined.
- device = PartitionDevice(extendedName, parents=disk)
- device.parents = [disk]
- device.partedPartition = extended
- # just add the device for now -- we'll handle actions at the last
- # moment to simplify things
- storage.devicetree._addDevice(device)
-
-def doPartitioning(storage):
- """ Allocate and grow partitions.
-
- When this function returns without error, all PartitionDevice
- instances must have their parents set to the disk they are
- allocated on, and their partedPartition attribute set to the
- appropriate parted.Partition instance from their containing
- disk. All req_xxxx attributes must be unchanged.
-
- Arguments:
-
- storage - Main anaconda Storage instance
-
- Keyword/Optional Arguments:
-
- None
-
- """
- disks = storage.partitioned
- if storage.config.exclusiveDisks:
- disks = [d for d in disks if d.name in storage.config.exclusiveDisks]
-
- for disk in disks:
- try:
- disk.setup()
- except DeviceError as (msg, name):
- log.error("failed to set up disk %s: %s" % (name, msg))
- raise PartitioningError(_("disk %s inaccessible") % disk.name)
-
- partitions = storage.partitions[:]
- for part in storage.partitions:
- part.req_bootable = False
-
- if part.exists:
- # if the partition is preexisting or part of a complex device
- # then we shouldn't modify it
- partitions.remove(part)
- continue
-
- if not part.exists:
- # start over with flexible-size requests
- part.req_size = part.req_base_size
-
- try:
- storage.bootDevice.req_bootable = True
- except AttributeError:
- # there's no stage2 device. hopefully it's temporary.
- pass
-
- removeNewPartitions(disks, partitions)
- free = getFreeRegions(disks)
- try:
- allocatePartitions(storage, disks, partitions, free)
- growPartitions(disks, partitions, free, size_sets=storage.size_sets)
- except Exception:
- raise
- else:
- # Mark all growable requests as no longer growable.
- for partition in storage.partitions:
- log.debug("fixing size of %s at %.2f" % (partition, partition.size))
- partition.req_grow = False
- partition.req_base_size = partition.size
- partition.req_size = partition.size
- finally:
- # these are only valid for one allocation run
- storage.size_sets = []
-
- # The number and thus the name of partitions may have changed now,
- # allocatePartitions() takes care of this for new partitions, but not
- # for pre-existing ones, so we update the name of all partitions here
- for part in storage.partitions:
- # leave extended partitions as-is -- we'll handle them separately
- if part.isExtended:
- continue
- part.updateName()
-
- updateExtendedPartitions(storage, disks)
-
- for part in [p for p in storage.partitions if not p.exists]:
- problem = part.checkSize()
- if problem < 0:
- raise PartitioningError(_("partition is too small for %(format)s formatting "
- "(allowable size is %(minSize)d MB to %(maxSize)d MB)")
- % {"format": part.format.name, "minSize": part.format.minSize,
- "maxSize": part.format.maxSize})
- elif problem > 0:
- raise PartitioningError(_("partition is too large for %(format)s formatting "
- "(allowable size is %(minSize)d MB to %(maxSize)d MB)")
- % {"format": part.format.name, "minSize": part.format.minSize,
- "maxSize": part.format.maxSize})
-
-def allocatePartitions(storage, disks, partitions, freespace):
- """ Allocate partitions based on requested features.
-
- Non-existing partitions are sorted according to their requested
- attributes, and then allocated.
-
- The basic approach to sorting is that the more specifically-
- defined a request is, the earlier it will be allocated. See
- the function partitionCompare for details on the sorting
- criteria.
-
- The PartitionDevice instances will have their name and parents
- attributes set once they have been allocated.
- """
- log.debug("allocatePartitions: disks=%s ; partitions=%s" %
- ([d.name for d in disks],
- ["%s(id %d)" % (p.name, p.id) for p in partitions]))
-
- new_partitions = [p for p in partitions if not p.exists]
- new_partitions.sort(cmp=partitionCompare)
-
- # the following dicts all use device path strings as keys
- disklabels = {} # DiskLabel instances for each disk
- all_disks = {} # StorageDevice for each disk
- for disk in disks:
- if disk.path not in disklabels.keys():
- disklabels[disk.path] = disk.format
- all_disks[disk.path] = disk
-
- removeNewPartitions(disks, new_partitions)
-
- for _part in new_partitions:
- if _part.partedPartition and _part.isExtended:
- # ignore new extendeds as they are implicit requests
- continue
-
- # obtain the set of candidate disks
- req_disks = []
- if _part.req_disks:
- # use the requested disk set
- req_disks = _part.req_disks
- else:
- # no disks specified means any disk will do
- req_disks = disks
-
- # sort the disks, making sure the boot disk is first
- req_disks.sort(key=lambda d: d.name, cmp=storage.compareDisks)
- for disk in req_disks:
- if storage.bootDisk and disk == storage.bootDisk:
- boot_index = req_disks.index(disk)
- req_disks.insert(0, req_disks.pop(boot_index))
-
- boot = _part.req_base_weight > 1000
-
- log.debug("allocating partition: %s ; id: %d ; disks: %s ;\n"
- "boot: %s ; primary: %s ; size: %dMB ; grow: %s ; "
- "max_size: %s" % (_part.name, _part.id,
- [d.name for d in req_disks],
- boot, _part.req_primary,
- _part.req_size, _part.req_grow,
- _part.req_max_size))
- free = None
- use_disk = None
- part_type = None
- growth = 0
- # loop through disks
- for _disk in req_disks:
- disklabel = disklabels[_disk.path]
- sectorSize = disklabel.partedDevice.sectorSize
- best = None
- current_free = free
-
- # for growable requests, we don't want to pass the current free
- # geometry to getBestFreeRegion -- this allows us to try the
- # best region from each disk and choose one based on the total
- # growth it allows
- if _part.req_grow:
- current_free = None
-
- log.debug("checking freespace on %s" % _disk.name)
-
- new_part_type = getNextPartitionType(disklabel.partedDisk)
- if new_part_type is None:
- # can't allocate any more partitions on this disk
- log.debug("no free partition slots on %s" % _disk.name)
- continue
-
- if _part.req_primary and new_part_type != parted.PARTITION_NORMAL:
- if (disklabel.partedDisk.primaryPartitionCount <
- disklabel.partedDisk.maxPrimaryPartitionCount):
- # don't fail to create a primary if there are only three
- # primary partitions on the disk (#505269)
- new_part_type = parted.PARTITION_NORMAL
- else:
- # we need a primary slot and none are free on this disk
- log.debug("no primary slots available on %s" % _disk.name)
- continue
-
- best = getBestFreeSpaceRegion(disklabel.partedDisk,
- new_part_type,
- _part.req_size,
- best_free=current_free,
- boot=boot,
- grow=_part.req_grow)
-
- if best == free and not _part.req_primary and \
- new_part_type == parted.PARTITION_NORMAL:
- # see if we can do better with a logical partition
- log.debug("not enough free space for primary -- trying logical")
- new_part_type = getNextPartitionType(disklabel.partedDisk,
- no_primary=True)
- if new_part_type:
- best = getBestFreeSpaceRegion(disklabel.partedDisk,
- new_part_type,
- _part.req_size,
- best_free=current_free,
- boot=boot,
- grow=_part.req_grow)
-
- if best and free != best:
- update = True
- allocated = new_partitions[:new_partitions.index(_part)+1]
- if any([p.req_grow for p in allocated]):
- log.debug("evaluating growth potential for new layout")
- new_growth = 0
- for disk_path in disklabels.keys():
- log.debug("calculating growth for disk %s" % disk_path)
- # Now we check, for growable requests, which of the two
- # free regions will allow for more growth.
-
- # set up chunks representing the disks' layouts
- temp_parts = []
- for _p in new_partitions[:new_partitions.index(_part)]:
- if _p.disk.path == disk_path:
- temp_parts.append(_p)
-
- # add the current request to the temp disk to set up
- # its partedPartition attribute with a base geometry
- if disk_path == _disk.path:
- _part_type = new_part_type
- _free = best
- if new_part_type == parted.PARTITION_EXTENDED:
- addPartition(disklabel, best, new_part_type,
- None)
-
- _part_type = parted.PARTITION_LOGICAL
-
- _free = getBestFreeSpaceRegion(disklabel.partedDisk,
- _part_type,
- _part.req_size,
- boot=boot,
- grow=_part.req_grow)
- if not _free:
- log.info("not enough space after adding "
- "extended partition for growth test")
- if new_part_type == parted.PARTITION_EXTENDED:
- e = disklabel.extendedPartition
- disklabel.partedDisk.removePartition(e)
-
- continue
-
- temp_part = addPartition(disklabel,
- _free,
- _part_type,
- _part.req_size)
- _part.partedPartition = temp_part
- _part.disk = _disk
- temp_parts.append(_part)
-
- chunks = getDiskChunks(all_disks[disk_path],
- temp_parts, freespace)
-
- # grow all growable requests
- disk_growth = 0
- disk_sector_size = disklabels[disk_path].partedDevice.sectorSize
- for chunk in chunks:
- chunk.growRequests()
- # record the growth for this layout
- new_growth += chunk.growth
- disk_growth += chunk.growth
- for req in chunk.requests:
- log.debug("request %d (%s) growth: %d (%dMB) "
- "size: %dMB" %
- (req.device.id,
- req.device.name,
- req.growth,
- sectorsToSize(req.growth,
- disk_sector_size),
- sectorsToSize(req.growth + req.base,
- disk_sector_size)))
- log.debug("disk %s growth: %d (%dMB)" %
- (disk_path, disk_growth,
- sectorsToSize(disk_growth,
- disk_sector_size)))
-
- disklabel.partedDisk.removePartition(temp_part)
- _part.partedPartition = None
- _part.disk = None
-
- if new_part_type == parted.PARTITION_EXTENDED:
- e = disklabel.extendedPartition
- disklabel.partedDisk.removePartition(e)
-
- log.debug("total growth: %d sectors" % new_growth)
-
- # update the chosen free region unless the previous
- # choice yielded greater total growth
- if free is not None and new_growth <= growth:
- log.debug("keeping old free: %d <= %d" % (new_growth,
- growth))
- update = False
- else:
- growth = new_growth
-
- if update:
- # now we know we are choosing a new free space,
- # so update the disk and part type
- log.debug("updating use_disk to %s, type: %s"
- % (_disk.name, new_part_type))
- part_type = new_part_type
- use_disk = _disk
- log.debug("new free: %d-%d / %dMB" % (best.start,
- best.end,
- best.getSize()))
- log.debug("new free allows for %d sectors of growth" %
- growth)
- free = best
-
- if free and boot:
- # if this is a bootable partition we want to
- # use the first freespace region large enough
- # to satisfy the request
- log.debug("found free space for bootable request")
- break
-
- if free is None:
- raise PartitioningError(_("not enough free space on disks"))
-
- _disk = use_disk
- disklabel = _disk.format
-
- # create the extended partition if needed
- if part_type == parted.PARTITION_EXTENDED:
- log.debug("creating extended partition")
- addPartition(disklabel, free, part_type, None)
-
- # now the extended partition exists, so set type to logical
- part_type = parted.PARTITION_LOGICAL
-
- # recalculate freespace
- log.debug("recalculating free space")
- free = getBestFreeSpaceRegion(disklabel.partedDisk,
- part_type,
- _part.req_size,
- boot=boot,
- grow=_part.req_grow)
- if not free:
- raise PartitioningError(_("not enough free space after "
- "creating extended partition"))
-
- partition = addPartition(disklabel, free, part_type, _part.req_size)
- log.debug("created partition %s of %dMB and added it to %s" %
- (partition.getDeviceNodeName(), partition.getSize(),
- disklabel.device))
-
- # this one sets the name
- _part.partedPartition = partition
- _part.disk = _disk
-
- # parted modifies the partition in the process of adding it to
- # the disk, so we need to grab the latest version...
- _part.partedPartition = disklabel.partedDisk.getPartitionByPath(_part.path)
-
-
-class Request(object):
- """ A partition request.
-
- Request instances are used for calculating how much to grow
- partitions.
- """
- def __init__(self, device):
- """ Create a Request instance.
-
- Arguments:
-
- """
- self.device = device
- self.growth = 0 # growth in sectors
- self.max_growth = 0 # max growth in sectors
- self.done = not getattr(device, "req_grow", True) # can we grow this
- # request more?
- self.base = 0 # base sectors
-
- @property
- def growable(self):
- """ True if this request is growable. """
- return getattr(self.device, "req_grow", True)
-
- @property
- def id(self):
- """ The id of the Device instance this request corresponds to. """
- return self.device.id
-
- def __repr__(self):
- s = ("%(type)s instance --\n"
- "id = %(id)s name = %(name)s growable = %(growable)s\n"
- "base = %(base)d growth = %(growth)d max_grow = %(max_grow)d\n"
- "done = %(done)s" %
- {"type": self.__class__.__name__, "id": self.id,
- "name": self.device.name, "growable": self.growable,
- "base": self.base, "growth": self.growth,
- "max_grow": self.max_growth, "done": self.done})
- return s
-
-
-class PartitionRequest(Request):
- def __init__(self, partition):
- """ Create a PartitionRequest instance.
-
- Arguments:
-
- partition -- a PartitionDevice instance
-
- """
- super(PartitionRequest, self).__init__(partition)
- self.base = partition.partedPartition.geometry.length # base sectors
-
- sector_size = partition.partedPartition.disk.device.sectorSize
-
- if partition.req_grow:
- limits = filter(lambda l: l > 0,
- [sizeToSectors(partition.req_max_size, sector_size),
- sizeToSectors(partition.format.maxSize, sector_size),
- partition.partedPartition.disk.maxPartitionLength])
-
- if limits:
- max_sectors = min(limits)
- self.max_growth = max_sectors - self.base
- if self.max_growth <= 0:
- # max size is less than or equal to base, so we're done
- self.done = True
-
-
-class LVRequest(Request):
- def __init__(self, lv):
- """ Create a LVRequest instance.
-
- Arguments:
-
- lv -- an LVMLogicalVolumeDevice instance
-
- """
- super(LVRequest, self).__init__(lv)
-
- # Round up to nearest pe. For growable requests this will mean that
- # first growth is to fill the remainder of any unused extent.
- self.base = lv.vg.align(lv.req_size, roundup=True) / lv.vg.peSize # pe
-
- if lv.req_grow:
- limits = [l / lv.vg.peSize for l in
- [lv.vg.align(lv.req_max_size),
- lv.vg.align(lv.format.maxSize)] if l > 0]
-
- if limits:
- max_units = min(limits)
- self.max_growth = max_units - self.base
- if self.max_growth <= 0:
- # max size is less than or equal to base, so we're done
- self.done = True
-
-
-class Chunk(object):
- """ A free region from which devices will be allocated """
- def __init__(self, length, requests=None):
- """ Create a Chunk instance.
-
- Arguments:
-
- length -- the length of the chunk in allocation units
-
-
- Keyword Arguments:
-
- requests -- list of Request instances allocated from this chunk
-
- """
- if not hasattr(self, "path"):
- self.path = None
- self.length = length
- self.pool = length # free unit count
- self.base = 0 # sum of growable requests' base
- # sizes
- self.requests = [] # list of Request instances
- if isinstance(requests, list):
- for req in requests:
- self.addRequest(req)
-
- self.skip_list = []
-
- def __repr__(self):
- s = ("%(type)s instance --\n"
- "device = %(device)s length = %(length)d size = %(size)d\n"
- "remaining = %(rem)d pool = %(pool)d" %
- {"type": self.__class__.__name__, "device": self.path,
- "length": self.length, "size": self.lengthToSize(self.length),
- "pool": self.pool, "rem": self.remaining})
-
- return s
-
- def __str__(self):
- s = "%d on %s" % (self.length, self.path)
- return s
-
- def addRequest(self, req):
- """ Add a Request to this chunk. """
- log.debug("adding request %d to chunk %s" % (req.device.id, self))
-
- self.requests.append(req)
- self.pool -= req.base
-
- if not req.done:
- self.base += req.base
-
- def reclaim(self, request, amount):
- """ Reclaim units from a request and return them to the pool. """
- log.debug("reclaim: %s %d (%d MB)" % (request, amount, self.lengthToSize(amount)))
- if request.growth < amount:
- log.error("tried to reclaim %d from request with %d of growth"
- % (amount, request.growth))
- raise ValueError(_("cannot reclaim more than request has grown"))
-
- request.growth -= amount
- self.pool += amount
-
- # put this request in the skip list so we don't try to grow it the
- # next time we call growRequests to allocate the newly re-acquired pool
- if request not in self.skip_list:
- self.skip_list.append(request)
-
- @property
- def growth(self):
- """ Sum of growth for all requests in this chunk. """
- return sum(r.growth for r in self.requests)
-
- @property
- def hasGrowable(self):
- """ True if this chunk contains at least one growable request. """
- for req in self.requests:
- if req.growable:
- return True
- return False
-
- @property
- def remaining(self):
- """ Number of requests still being grown in this chunk. """
- return len([d for d in self.requests if not d.done])
-
- @property
- def done(self):
- """ True if we are finished growing all requests in this chunk. """
- return self.remaining == 0
-
- def maxGrowth(self, req):
- return req.max_growth
-
- def lengthToSize(self, length):
- return length
-
- def sizeToLength(self, size):
- return size
-
- def trimOverGrownRequest(self, req, base=None):
- """ Enforce max growth and return extra units to the pool. """
- max_growth = self.maxGrowth(req)
- if max_growth and req.growth >= max_growth:
- if req.growth > max_growth:
- # we've grown beyond the maximum. put some back.
- extra = req.growth - max_growth
- log.debug("taking back %d (%dMB) from %d (%s)" %
- (extra, self.lengthToSize(extra),
- req.device.id, req.device.name))
- self.pool += extra
- req.growth = max_growth
-
- # We're done growing this request, so it no longer
- # factors into the growable base used to determine
- # what fraction of the pool each request gets.
- if base is not None:
- base -= req.base
- req.done = True
-
- return base
-
- def sortRequests(self):
- pass
-
- def growRequests(self, uniform=False):
- """ Calculate growth amounts for requests in this chunk. """
- log.debug("Chunk.growRequests: %r" % self)
-
- self.sortRequests()
- for req in self.requests:
- log.debug("req: %r" % req)
-
- # we use this to hold the base for the next loop through the
- # chunk's requests since we want the base to be the same for
- # all requests in any given growth iteration
- new_base = self.base
- last_pool = 0 # used to track changes to the pool across iterations
- while not self.done and self.pool and last_pool != self.pool:
- last_pool = self.pool # to keep from getting stuck
- self.base = new_base
- if uniform:
- growth = last_pool / self.remaining
-
- log.debug("%d requests and %d (%dMB) left in chunk" %
- (self.remaining, self.pool, self.lengthToSize(self.pool)))
- for p in self.requests:
- if p.done or p in self.skip_list:
- continue
-
- if not uniform:
- # Each request is allocated free units from the pool
- # based on the relative _base_ sizes of the remaining
- # growable requests.
- share = p.base / float(self.base)
- growth = int(share * last_pool) # truncate, don't round
-
- p.growth += growth
- self.pool -= growth
- log.debug("adding %d (%dMB) to %d (%s)" %
- (growth, self.lengthToSize(growth),
- p.device.id, p.device.name))
-
- new_base = self.trimOverGrownRequest(p, base=new_base)
- log.debug("new grow amount for request %d (%s) is %d "
- "units, or %dMB" %
- (p.device.id, p.device.name, p.growth,
- self.lengthToSize(p.growth)))
-
- if self.pool:
- # allocate any leftovers in pool to the first partition
- # that can still grow
- for p in self.requests:
- if p.done:
- continue
-
- growth = self.pool
- p.growth += growth
- self.pool = 0
- log.debug("adding %d (%dMB) to %d (%s)" %
- (growth, self.lengthToSize(growth),
- p.device.id, p.device.name))
-
- self.trimOverGrownRequest(p)
- log.debug("new grow amount for request %d (%s) is %d "
- "units, or %dMB" %
- (p.device.id, p.device.name, p.growth,
- self.lengthToSize(p.growth)))
-
- if self.pool == 0:
- break
-
- # requests that were skipped over this time through are back on the
- # table next time
- self.skip_list = []
-
-
-class DiskChunk(Chunk):
- """ A free region on disk from which partitions will be allocated """
- def __init__(self, geometry, requests=None):
- """ Create a Chunk instance.
-
- Arguments:
-
- geometry -- parted.Geometry instance describing the free space
-
-
- Keyword Arguments:
-
- requests -- list of Request instances allocated from this chunk
-
-
- Note: We will limit partition growth based on disklabel
- limitations for partition end sector, so a 10TB disk with an
- msdos disklabel will be treated like a 2TB disk.
-
- """
- self.geometry = geometry # parted.Geometry
- self.sectorSize = self.geometry.device.sectorSize
- self.path = self.geometry.device.path
- super(DiskChunk, self).__init__(self.geometry.length, requests=requests)
-
- def __repr__(self):
- s = super(DiskChunk, self).__str__()
- s += (" start = %(start)d end = %(end)d\n"
- "sectorSize = %(sectorSize)d\n" %
- {"start": self.geometry.start, "end": self.geometry.end,
- "sectorSize": self.sectorSize})
- return s
-
- def __str__(self):
- s = "%d (%d-%d) on %s" % (self.length, self.geometry.start,
- self.geometry.end, self.path)
- return s
-
- def addRequest(self, req):
- """ Add a Request to this chunk. """
- if not isinstance(req, PartitionRequest):
- raise ValueError(_("DiskChunk requests must be of type "
- "PartitionRequest"))
-
- if not self.requests:
- # when adding the first request to the chunk, adjust the pool
- # size to reflect any disklabel-specific limits on end sector
- max_sector = req.device.partedPartition.disk.maxPartitionStartSector
- chunk_end = min(max_sector, self.geometry.end)
- if chunk_end <= self.geometry.start:
- # this should clearly never be possible, but if the chunk's
- # start sector is beyond the maximum allowed end sector, we
- # cannot continue
- log.error("chunk start sector is beyond disklabel maximum")
- raise PartitioningError(_("partitions allocated outside "
- "disklabel limits"))
-
- new_pool = chunk_end - self.geometry.start + 1
- if new_pool != self.pool:
- log.debug("adjusting pool to %d based on disklabel limits"
- % new_pool)
- self.pool = new_pool
-
- super(DiskChunk, self).addRequest(req)
-
- def maxGrowth(self, req):
- req_end = req.device.partedPartition.geometry.end
- req_start = req.device.partedPartition.geometry.start
-
- # Establish the current total number of sectors of growth for requests
- # that lie before this one within this chunk. We add the total count
- # to this request's end sector to obtain the end sector for this
- # request, including growth of earlier requests but not including
- # growth of this request. Maximum growth values are obtained using
- # this end sector and various values for maximum end sector.
- growth = 0
- for request in self.requests:
- if request.device.partedPartition.geometry.start < req_start:
- growth += request.growth
- req_end += growth
-
- # obtain the set of possible maximum sectors-of-growth values for this
- # request and use the smallest
- limits = []
-
- # disklabel-specific maximum sector
- max_sector = req.device.partedPartition.disk.maxPartitionStartSector
- limits.append(max_sector - req_end)
-
- # 2TB limit on bootable partitions, regardless of disklabel
- if req.device.req_bootable:
- limits.append(sizeToSectors(2*1024*1024, self.sectorSize) - req_end)
-
- # request-specific maximum (see Request.__init__, above, for details)
- if req.max_growth:
- limits.append(req.max_growth)
-
- max_growth = min(limits)
- return max_growth
-
- def lengthToSize(self, length):
- return sectorsToSize(length, self.sectorSize)
-
- def sizeToLength(self, size):
- return sizeToSectors(size, self.sectorSize)
-
- def sortRequests(self):
- # sort the partitions by start sector
- self.requests.sort(key=lambda r: r.device.partedPartition.geometry.start)
-
-
-class VGChunk(Chunk):
- """ A free region in an LVM VG from which LVs will be allocated """
- def __init__(self, vg, requests=None):
- """ Create a VGChunk instance.
-
- Arguments:
-
- vg -- an LVMVolumeGroupDevice within which this chunk resides
-
-
- Keyword Arguments:
-
- requests -- list of Request instances allocated from this chunk
-
- """
- self.vg = vg
- self.path = vg.path
- usable_extents = vg.extents - (vg.reservedSpace / vg.peSize)
- super(VGChunk, self).__init__(usable_extents, requests=requests)
-
- def addRequest(self, req):
- """ Add a Request to this chunk. """
- if not isinstance(req, LVRequest):
- raise ValueError(_("VGChunk requests must be of type "
- "LVRequest"))
-
- super(VGChunk, self).addRequest(req)
-
- def lengthToSize(self, length):
- return length * self.vg.peSize
-
- def sizeToLength(self, size):
- return size / self.vg.peSize
-
- def sortRequests(self):
- # sort the partitions by start sector
- self.requests.sort(key=lambda r: r.device, cmp=lvCompare)
-
- def growRequests(self):
- self.sortRequests()
-
- # grow the percentage-based requests
- last_pool = self.pool
- for req in self.requests:
- if req.done or not req.device.req_percent:
- continue
-
- growth = int(req.device.req_percent * 0.01 * self.length)# truncate
- req.growth += growth
- self.pool -= growth
- log.debug("adding %d (%dMB) to %d (%s)" %
- (growth, self.lengthToSize(growth),
- req.device.id, req.device.name))
-
- new_base = self.trimOverGrownRequest(req)
- log.debug("new grow amount for request %d (%s) is %d "
- "units, or %dMB" %
- (req.device.id, req.device.name, req.growth,
- self.lengthToSize(req.growth)))
-
- # we're done with this request, so remove its base from the
- # chunk's base
- if not req.done:
- self.base -= req.base
- req.done = True
-
- super(VGChunk, self).growRequests()
-
-
-def getDiskChunks(disk, partitions, free):
- """ Return a list of Chunk instances representing a disk.
-
- Arguments:
-
- disk -- a StorageDevice with a DiskLabel format
- partitions -- list of PartitionDevice instances
- free -- list of parted.Geometry instances representing free space
-
- Partitions and free regions not on the specified disk are ignored.
-
- """
- # list of all new partitions on this disk
- disk_parts = [p for p in partitions if p.disk == disk and not p.exists]
- disk_free = [f for f in free if f.device.path == disk.path]
-
-
- chunks = [DiskChunk(f) for f in disk_free]
-
- for p in disk_parts:
- if p.isExtended:
- # handle extended partitions specially since they are
- # indeed very special
- continue
-
- for i, f in enumerate(disk_free):
- if f.contains(p.partedPartition.geometry):
- chunks[i].addRequest(PartitionRequest(p))
- break
-
- return chunks
-
-class TotalSizeSet(object):
- """ Set of device requests with a target combined size.
-
- This will be handled by growing the requests until the desired combined
- size has been achieved.
- """
- def __init__(self, devices, size):
- self.devices = []
- for device in devices:
- if isinstance(device, LUKSDevice):
- partition = device.slave
- else:
- partition = device
-
- self.devices.append(partition)
-
- self.size = size
-
- self.requests = []
-
- self.allocated = sum([d.req_base_size for d in self.devices])
- log.debug("set.allocated = %d" % self.allocated)
-
- def allocate(self, amount):
- log.debug("allocating %d to TotalSizeSet with %d/%d (%d needed)"
- % (amount, self.allocated, self.size, self.needed))
- self.allocated += amount
-
- @property
- def needed(self):
- return self.size - self.allocated
-
- def deallocate(self, amount):
- log.debug("deallocating %d from TotalSizeSet with %d/%d (%d needed)"
- % (amount, self.allocated, self.size, self.needed))
- self.allocated -= amount
-
-class SameSizeSet(object):
- """ Set of device requests with a common target size. """
- def __init__(self, devices, size, grow=False, max_size=None):
- self.devices = []
- for device in devices:
- if isinstance(device, LUKSDevice):
- partition = device.slave
- else:
- partition = device
-
- self.devices.append(partition)
-
- self.size = int(size / len(devices))
- self.grow = grow
- self.max_size = max_size
-
- self.requests = []
-
-def manageSizeSets(size_sets, chunks):
- growth_by_request = {}
- requests_by_device = {}
- chunks_by_request = {}
- for chunk in chunks:
- for request in chunk.requests:
- requests_by_device[request.device] = request
- chunks_by_request[request] = chunk
- growth_by_request[request] = 0
-
- for i in range(2):
- reclaimed = dict([(chunk, 0) for chunk in chunks])
- for ss in size_sets:
- if isinstance(ss, TotalSizeSet):
- # TotalSizeSet members are trimmed to achieve the requested
- # total size
- log.debug("set: %s %d/%d" % ([d.name for d in ss.devices],
- ss.allocated, ss.size))
-
- for device in ss.devices:
- request = requests_by_device[device]
- chunk = chunks_by_request[request]
- new_growth = request.growth - growth_by_request[request]
- ss.allocate(chunk.lengthToSize(new_growth))
-
- # decide how much to take back from each request
- # We may assume that all requests have the same base size.
- # We're shooting for a roughly equal distribution by trimming
- # growth from the requests that have grown the most first.
- requests = sorted([requests_by_device[d] for d in ss.devices],
- key=lambda r: r.growth, reverse=True)
- needed = ss.needed
- for request in requests:
- chunk = chunks_by_request[request]
- log.debug("%s" % request)
- log.debug("needed: %d" % ss.needed)
-
- if ss.needed < 0:
- # it would be good to take back some from each device
- # instead of taking all from the last one(s)
- extra = -chunk.sizeToLength(needed) / len(ss.devices)
- if extra > request.growth and i == 0:
- log.debug("not reclaiming from this request")
- continue
- else:
- extra = min(extra, request.growth)
-
- reclaimed[chunk] += extra
- chunk.reclaim(request, extra)
- ss.deallocate(chunk.lengthToSize(extra))
-
- if ss.needed <= 0:
- request.done = True
-
- elif isinstance(ss, SameSizeSet):
- # SameSizeSet members all have the same size as the smallest
- # member
- requests = [requests_by_device[d] for d in ss.devices]
- _min_growth = min([r.growth for r in requests])
- log.debug("set: %s %d" % ([d.name for d in ss.devices], ss.size))
- log.debug("min growth is %d" % _min_growth)
- for request in requests:
- chunk = chunks_by_request[request]
- _max_growth = chunk.sizeToLength(ss.size) - request.base
- log.debug("max growth for %s is %d" % (request, _max_growth))
- min_growth = max(min(_min_growth, _max_growth), 0)
- if request.growth > min_growth:
- extra = request.growth - min_growth
- reclaimed[chunk] += extra
- chunk.reclaim(request, extra)
- request.done = True
- elif request.growth == min_growth:
- request.done = True
-
- # store previous growth amounts so we know how much was allocated in
- # the latest growRequests call
- for request in growth_by_request.keys():
- growth_by_request[request] = request.growth
-
- for chunk in chunks:
- if reclaimed[chunk] and not chunk.done:
- chunk.growRequests()
-
-def growPartitions(disks, partitions, free, size_sets=None):
- """ Grow all growable partition requests.
-
- Partitions have already been allocated from chunks of free space on
- the disks. This function does not modify the ordering of partitions
- or the free chunks from which they are allocated.
-
- Free space within a given chunk is allocated to each growable
- partition allocated from that chunk in an amount corresponding to
- the ratio of that partition's base size to the sum of the base sizes
- of all growable partitions allocated from the chunk.
-
- Arguments:
-
- disks -- a list of all usable disks (DiskDevice instances)
- partitions -- a list of all partitions (PartitionDevice instances)
- free -- a list of all free regions (parted.Geometry instances)
- """
- log.debug("growPartitions: disks=%s, partitions=%s" %
- ([d.name for d in disks],
- ["%s(id %d)" % (p.name, p.id) for p in partitions]))
- all_growable = [p for p in partitions if p.req_grow]
- if not all_growable:
- log.debug("no growable partitions")
- return
-
- if size_sets is None:
- size_sets = []
-
- log.debug("growable partitions are %s" % [p.name for p in all_growable])
-
- #
- # collect info about each disk and the requests it contains
- #
- chunks = []
- for disk in disks:
- sector_size = disk.format.partedDevice.sectorSize
-
- # list of free space regions on this disk prior to partition allocation
- disk_free = [f for f in free if f.device.path == disk.path]
- if not disk_free:
- log.debug("no free space on %s" % disk.name)
- continue
-
- disk_chunks = getDiskChunks(disk, partitions, disk_free)
- log.debug("disk %s has %d chunks" % (disk.name, len(disk_chunks)))
- chunks.extend(disk_chunks)
-
- #
- # grow the partitions in each chunk as a group
- #
- for chunk in chunks:
- if not chunk.hasGrowable:
- # no growable partitions in this chunk
- continue
-
- chunk.growRequests()
-
- # adjust set members' growth amounts as needed
- manageSizeSets(size_sets, chunks)
-
- for disk in disks:
- log.debug("growing partitions on %s" % disk.name)
- for chunk in chunks:
- if chunk.path != disk.path:
- continue
-
- if not chunk.hasGrowable:
- # no growable partitions in this chunk
- continue
-
- # recalculate partition geometries
- disklabel = disk.format
- start = chunk.geometry.start
-
- # find any extended partition on this disk
- extended_geometry = getattr(disklabel.extendedPartition,
- "geometry",
- None) # parted.Geometry
-
- # align start sector as needed
- if not disklabel.alignment.isAligned(chunk.geometry, start):
- start = disklabel.alignment.alignUp(chunk.geometry, start)
- new_partitions = []
- for p in chunk.requests:
- ptype = p.device.partedPartition.type
- log.debug("partition %s (%d): %s" % (p.device.name,
- p.device.id, ptype))
- if ptype == parted.PARTITION_EXTENDED:
- continue
-
- # XXX since we need one metadata sector before each
- # logical partition we burn one logical block to
- # safely align the start of each logical partition
- if ptype == parted.PARTITION_LOGICAL:
- start += disklabel.alignment.grainSize
-
- old_geometry = p.device.partedPartition.geometry
- new_length = p.base + p.growth
- end = start + new_length - 1
- # align end sector as needed
- if not disklabel.endAlignment.isAligned(chunk.geometry, end):
- end = disklabel.endAlignment.alignDown(chunk.geometry, end)
- new_geometry = parted.Geometry(device=disklabel.partedDevice,
- start=start,
- end=end)
- log.debug("new geometry for %s: %s" % (p.device.name,
- new_geometry))
- start = end + 1
- new_partition = parted.Partition(disk=disklabel.partedDisk,
- type=ptype,
- geometry=new_geometry)
- new_partitions.append((new_partition, p.device))
-
- # remove all new partitions from this chunk
- removeNewPartitions([disk], [r.device for r in chunk.requests])
- log.debug("back from removeNewPartitions")
-
- # adjust the extended partition as needed
- # we will ony resize an extended partition that we created
- log.debug("extended: %s" % extended_geometry)
- if extended_geometry and \
- chunk.geometry.contains(extended_geometry):
- log.debug("setting up new geometry for extended on %s" % disk.name)
- ext_start = 0
- for (partition, device) in new_partitions:
- if partition.type != parted.PARTITION_LOGICAL:
- continue
-
- if not ext_start or partition.geometry.start < ext_start:
- # account for the logical block difference in start
- # sector for the extended -v- first logical
- # (partition.geometry.start is already aligned)
- ext_start = partition.geometry.start - disklabel.alignment.grainSize
-
- new_geometry = parted.Geometry(device=disklabel.partedDevice,
- start=ext_start,
- end=chunk.geometry.end)
- log.debug("new geometry for extended: %s" % new_geometry)
- new_extended = parted.Partition(disk=disklabel.partedDisk,
- type=parted.PARTITION_EXTENDED,
- geometry=new_geometry)
- ptypes = [p.type for (p, d) in new_partitions]
- for pt_idx, ptype in enumerate(ptypes):
- if ptype == parted.PARTITION_LOGICAL:
- new_partitions.insert(pt_idx, (new_extended, None))
- break
-
- # add the partitions with their new geometries to the disk
- for (partition, device) in new_partitions:
- if device:
- name = device.name
- else:
- # If there was no extended partition on this disk when
- # doPartitioning was called we won't have a
- # PartitionDevice instance for it.
- name = partition.getDeviceNodeName()
-
- log.debug("setting %s new geometry: %s" % (name,
- partition.geometry))
- constraint = parted.Constraint(exactGeom=partition.geometry)
- disklabel.partedDisk.addPartition(partition=partition,
- constraint=constraint)
- path = partition.path
- if device:
- # set the device's name
- device.partedPartition = partition
- # without this, the path attr will be a basename. eek.
- device.disk = disk
-
- # make sure we store the disk's version of the partition
- newpart = disklabel.partedDisk.getPartitionByPath(path)
- device.partedPartition = newpart
-
-
-def lvCompare(lv1, lv2):
- """ More specifically defined lvs come first.
-
- < 1 => x < y
- 0 => x == y
- > 1 => x > y
- """
- ret = 0
-
- # larger requests go to the front of the list
- ret -= cmp(lv1.size, lv2.size) * 100
-
- # fixed size requests to the front
- ret += cmp(lv1.req_grow, lv2.req_grow) * 50
-
- # potentially larger growable requests go to the front
- if lv1.req_grow and lv2.req_grow:
- if not lv1.req_max_size and lv2.req_max_size:
- ret -= 25
- elif lv1.req_max_size and not lv2.req_max_size:
- ret += 25
- else:
- ret -= cmp(lv1.req_max_size, lv2.req_max_size) * 25
-
- if ret > 0:
- ret = 1
- elif ret < 0:
- ret = -1
-
- return ret
-
-def growLVM(storage):
- """ Grow LVs according to the sizes of the PVs. """
- for vg in storage.vgs:
- total_free = vg.freeSpace
- if total_free < 0:
- # by now we have allocated the PVs so if there isn't enough
- # space in the VG we have a real problem
- raise PartitioningError(_("not enough space for LVM requests"))
- elif not total_free:
- log.debug("vg %s has no free space" % vg.name)
- continue
-
- log.debug("vg %s: %dMB free ; lvs: %s" % (vg.name, total_free,
- [l.lvname for l in vg.lvs]))
-
- chunk = VGChunk(vg, requests=[LVRequest(l) for l in vg.lvs])
- chunk.growRequests()
-
- # now grow the lvs by the amounts we've calculated above
- for req in chunk.requests:
- if not req.device.req_grow:
- continue
-
- # Base is in pe, which means potentially rounded up by as much as
- # pesize-1. As a result, you can't just add the growth to the
- # initial size.
- req.device.size = chunk.lengthToSize(req.base + req.growth)