[PATCH 1/5] Move DeviceFactory classes and related code into a new file.
David Lehman
dlehman at redhat.com
Thu Feb 7 21:45:33 UTC 2013
---
blivet/__init__.py | 740 +---------------------------------------------
blivet/devicefactory.py | 765 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 781 insertions(+), 724 deletions(-)
create mode 100644 blivet/devicefactory.py
diff --git a/blivet/__init__.py b/blivet/__init__.py
index 3b0e23b..6775ee9 100644
--- a/blivet/__init__.py
+++ b/blivet/__init__.py
@@ -1,7 +1,7 @@
# __init__.py
# Entry point for anaconda's storage configuration module.
#
-# Copyright (C) 2009 Red Hat, Inc.
+# Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -54,17 +54,12 @@ from deviceaction import *
from formats import getFormat
from formats import get_device_format_class
from formats import get_default_filesystem_type
+import devicefactory
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.mdraid import raidLevelString
-from devicelibs.lvm import get_pv_space
-from .partitioning import SameSizeSet
-from .partitioning import TotalSizeSet
-from .partitioning import doPartitioning
-from udev import *
+from udev import udev_trigger
import iscsi
import fcoe
import zfcp
@@ -102,48 +97,8 @@ def enable_installer_mode():
flags.installer_mode = True
-DEVICE_TYPE_LVM = 0
-DEVICE_TYPE_MD = 1
-DEVICE_TYPE_PARTITION = 2
-DEVICE_TYPE_BTRFS = 3
-DEVICE_TYPE_DISK = 4
-
-def getDeviceType(device):
- device_types = {"partition": DEVICE_TYPE_PARTITION,
- "lvmlv": DEVICE_TYPE_LVM,
- "btrfs subvolume": DEVICE_TYPE_BTRFS,
- "btrfs volume": DEVICE_TYPE_BTRFS,
- "mdarray": DEVICE_TYPE_MD}
-
- use_dev = device
- if isinstance(device, LUKSDevice):
- use_dev = device.slave
-
- if use_dev.isDisk:
- device_type = DEVICE_TYPE_DISK
- else:
- device_type = device_types.get(use_dev.type)
-
- return device_type
-
-def getRAIDLevel(device):
- # TODO: move this into StorageDevice
- use_dev = device
- if isinstance(device, LUKSDevice):
- use_dev = device.slave
-
- # TODO: lvm and perhaps pulling raid level from md pvs
- raid_level = None
- if hasattr(use_dev, "level"):
- raid_level = raidLevelString(use_dev.level)
- elif hasattr(use_dev, "dataLevel"):
- raid_level = use_dev.dataLevel or "single"
- elif hasattr(use_dev, "volume"):
- raid_level = use_dev.volume.dataLevel or "single"
-
- return raid_level
-
def storageInitialize(storage, ksdata, protected):
+ """ Perform installer-specific storage initialization. """
from pyanaconda.flags import flags as anaconda_flags
flags.update_from_anaconda_flags(anaconda_flags)
@@ -181,6 +136,7 @@ def storageInitialize(storage, ksdata, protected):
log.debug("onlyuse is now: %s" % (",".join(ksdata.ignoredisk.onlyuse)))
def turnOnFilesystems(storage):
+ """ Perform installer-specific activation of storage configuration. """
if not flags.installer_mode:
return
@@ -242,6 +198,7 @@ def writeEscrowPackets(storage):
class StorageDiscoveryConfig(object):
+ """ Class to encapsulate various detection/initialization parameters. """
def __init__(self):
# storage configuration variables
self.ignoreDiskInteractive = False
@@ -269,6 +226,7 @@ class StorageDiscoveryConfig(object):
self.zeroMbr = ksdata.zerombr.zerombr
class Blivet(object):
+ """ Top-level class for managing storage configuration. """
def __init__(self, ksdata=None):
""" Create a Blivet instance.
@@ -1837,240 +1795,7 @@ class Blivet(object):
return fstype
- def setContainerMembers(self, container, factory, members=None,
- device=None):
- """ Set up and return the container's member partitions. """
- log_members = []
- if members:
- log_members = [str(m) for m in members]
- log_method_call(self, container=container, factory=factory,
- members=log_members, device=device)
- if factory.member_list is not None:
- # short-circuit the logic below for partitions
- return factory.member_list
-
- if container and container.exists:
- # don't try to modify an existing container
- return container.parents
-
- if factory.container_size_func is None:
- return []
-
- # set up member devices
- container_size = factory.device_size
- add_disks = []
- remove_disks = []
-
- if members is None:
- members = []
-
- if container:
- members = container.parents[:]
- elif members:
- # mdarray
- container = device
-
- # The basis for whether we are modifying a member set versus creating
- # one must be the member list, as container will be None when modifying
- # the member set of an md array.
-
- # XXX how can we detect/handle failure to use one or more of the disks?
- if members and device:
- # See if we need to add/remove any disks, but only if we are
- # adjusting a device. When adding a new device to a container we do
- # not want to modify the container's disk set.
- _disks = list(set([d for m in members for d in m.disks]))
-
- add_disks = [d for d in factory.disks if d not in _disks]
- remove_disks = [d for d in _disks if d not in factory.disks]
- elif not members:
- # new container, so use the factory's disk set
- add_disks = factory.disks
-
- # drop any new disks that don't have free space
- min_free = min(500, factory.size)
- add_disks = [d for d in add_disks if d.partitioned and
- d.format.free >= min_free]
-
- base_size = max(1, getFormat(factory.member_format).minSize)
-
- # XXX TODO: multiple member devices per disk
-
- # prepare already-defined member partitions for reallocation
- for member in members[:]:
- if any([d in remove_disks for d in member.disks]):
- if isinstance(member, LUKSDevice):
- if container:
- container.removeMember(member)
- self.destroyDevice(member)
- members.remove(member)
- member = member.slave
- else:
- if container:
- container.removeMember(member)
-
- members.remove(member)
-
- self.destroyDevice(member)
- continue
-
- if isinstance(member, LUKSDevice):
- if not factory.encrypted:
- # encryption was toggled for the member devices
- if container:
- container.removeMember(member)
-
- self.destroyDevice(member)
- members.remove(member)
-
- self.formatDevice(member.slave,
- getFormat(factory.member_format))
- members.append(member.slave)
- if container:
- container.addMember(member.slave)
-
- member = member.slave
- elif factory.encrypted:
- # encryption was toggled for the member devices
- if container:
- container.removeMember(member)
-
- members.remove(member)
- self.formatDevice(member, getFormat("luks"))
- luks_member = LUKSDevice("luks-%s" % member.name,
- parents=[member],
- format=getFormat(factory.member_format))
- self.createDevice(luks_member)
- members.append(luks_member)
- if container:
- container.addMember(luks_member)
-
- member.req_base_size = base_size
- member.req_size = member.req_base_size
- member.req_grow = True
-
- # set up new members as needed to accommodate the device
- new_members = []
- for disk in add_disks:
- if factory.encrypted and factory.encrypt_members:
- luks_format = factory.member_format
- member_format = "luks"
- else:
- member_format = factory.member_format
-
- try:
- member = self.newPartition(parents=[disk], grow=True,
- size=base_size,
- fmt_type=member_format)
- except StorageError as e:
- log.error("failed to create new member partition: %s" % e)
- continue
-
- self.createDevice(member)
- if factory.encrypted and factory.encrypt_members:
- fmt = getFormat(luks_format)
- member = LUKSDevice("luks-%s" % member.name,
- parents=[member], format=fmt)
- self.createDevice(member)
-
- members.append(member)
- new_members.append(member)
- if container:
- container.addMember(member)
-
- if container:
- log.debug("using container %s with %d devices" % (container.name,
- len(self.devicetree.getChildren(container))))
- container_size = factory.container_size_func(container, device)
- log.debug("raw container size reported as %d" % container_size)
-
- log.debug("adding a %s with size %d" % (factory.set_class.__name__,
- container_size))
- size_set = factory.set_class(members, container_size)
- self.size_sets.append(size_set)
- for member in members[:]:
- if isinstance(member, LUKSDevice):
- member = member.slave
-
- member.req_max_size = size_set.size
-
- try:
- self.allocatePartitions()
- except PartitioningError as e:
- # try to clean up by destroying all newly added members before re-
- # raising the exception
- self.__cleanUpMemberDevices(new_members, container=container)
- raise
-
- return members
-
- def allocatePartitions(self):
- """ Allocate all requested partitions. """
- try:
- doPartitioning(self)
- except StorageError as e:
- log.error("failed to allocate partitions: %s" % e)
- raise
-
- def getDeviceFactory(self, device_type, size, **kwargs):
- """ Return a suitable DeviceFactory instance for device_type. """
- disks = kwargs.get("disks", [])
- raid_level = kwargs.get("raid_level")
- encrypted = kwargs.get("encrypted", False)
-
- class_table = {DEVICE_TYPE_LVM: LVMFactory,
- DEVICE_TYPE_BTRFS: BTRFSFactory,
- DEVICE_TYPE_PARTITION: PartitionFactory,
- DEVICE_TYPE_MD: MDFactory,
- DEVICE_TYPE_DISK: DiskFactory}
-
- factory_class = class_table[device_type]
- log.debug("instantiating %s: %r, %s, %s, %s" % (factory_class,
- self, size, [d.name for d in disks], raid_level))
- return factory_class(self, size, disks, raid_level, encrypted)
-
- def getContainer(self, factory, device=None, name=None, existing=False):
- # XXX would it be useful to implement this as a series of fallbacks
- # instead of mutually exclusive branches?
- container = None
- if name:
- container = self.devicetree.getDeviceByName(name)
- if container and container not in factory.container_list:
- log.debug("specified container name %s is wrong type (%s)"
- % (name, container.type))
- container = None
- elif device:
- if hasattr(device, "vg"):
- container = device.vg
- elif hasattr(device, "volume"):
- container = device.volume
- else:
- containers = [c for c in factory.container_list if not c.exists]
- if containers:
- container = containers[0]
-
- if container is None and existing:
- containers = [c for c in factory.container_list if c.exists]
- if containers:
- containers.sort(key=lambda c: getattr(c, "freeSpace", c.size),
- reverse=True)
- container = containers[0]
-
- return container
-
- def __cleanUpMemberDevices(self, members, container=None):
- for member in members:
- if container:
- container.removeMember(member)
-
- if isinstance(member, LUKSDevice):
- self.destroyDevice(member)
- member = member.slave
-
- if not member.isDisk:
- self.destroyDevice(member)
-
- def newDevice(self, device_type, size, **kwargs):
+ def factoryDevice(self, device_type, size, **kwargs):
""" Schedule creation of a device based on a top-down specification.
Arguments:
@@ -2094,41 +1819,18 @@ class Blivet(object):
device an already-defined but non-existent device
to adjust instead of creating a new device
-
- Error handling:
-
- If device is None, meaning we're creating a device, the error
- handling aims to remove all evidence of the attempt to create a
- new device by removing unused container devices, reverting the
- size of container devices that contain other devices, &c.
-
- If the device is not None, meaning we're adjusting the size of
- a defined device, the error handling aims to revert the device
- and any container to it previous size.
-
- In either case, we re-raise the exception so the caller knows
- there was a failure. If we failed to clean up as described above
- we raise ErrorRecoveryFailure to alert the caller that things
- will likely be in an inconsistent state.
"""
log_method_call(self, device_type, size, **kwargs)
mountpoint = kwargs.get("mountpoint")
fstype = kwargs.get("fstype")
label = kwargs.get("label")
- disks = kwargs.get("disks")
- encrypted = kwargs.get("encrypted", self.ksdata.autopart.encrypted)
-
name = kwargs.get("name")
container_name = kwargs.get("container_name")
-
device = kwargs.get("device")
- # md, btrfs
- raid_level = kwargs.get("raid_level")
-
# we can't do anything with existing devices
if device and device.exists:
- log.info("newDevice refusing to change device %s" % device)
+ log.info("factoryDevice refusing to change device %s" % device)
return
if not fstype:
@@ -2136,198 +1838,19 @@ class Blivet(object):
if fstype == "swap":
mountpoint = None
- if fstype == "swap" and device_type == DEVICE_TYPE_BTRFS:
- device_type = DEVICE_TYPE_PARTITION
-
- fmt_args = {}
- if label:
- fmt_args["label"] = label
+ if fstype == "swap" and device_type == devicefactory.DEVICE_TYPE_BTRFS:
+ device_type = devicefactory.DEVICE_TYPE_PARTITION
- factory = self.getDeviceFactory(device_type, size, **kwargs)
+ factory = devicefactory.get_device_factory(self, device_type, size,
+ **kwargs)
if not factory.disks:
raise StorageError("no disks specified for new device")
self.size_sets = [] # clear this since there are no growable reqs now
-
- container = self.getContainer(factory, device=device,
- name=container_name)
-
- # TODO: striping, mirroring, &c
- # TODO: non-partition members (pv-on-md)
-
- # setContainerMembers can modify these, so save them now
- old_size = None
- old_disks = []
- if device:
- old_size = device.size
- old_disks = device.disks[:]
-
- members = []
- if device and device.type == "mdarray":
- members = device.parents[:]
-
- try:
- parents = self.setContainerMembers(container, factory,
- members=members, device=device)
- except PartitioningError as e:
- # If this is a new device, just clean up and get out.
- if device:
- # If this is a defined device, try to clean up by reallocating
- # members as before and then get out.
- factory.disks = device.disks
- factory.size = device.size # this should work
-
- if members:
- # If this is an md array we have to reset its member set
- # here.
- # If there is a container device, its member set was reset
- # in the exception handler in setContainerMembers.
- device.parents = members
-
- try:
- self.setContainerMembers(container, factory,
- members=members,
- device=device)
- except StorageError as e:
- log.error("failed to revert device size: %s" % e)
- raise ErrorRecoveryFailure("failed to revert container")
-
- raise
-
- # set up container
- if not container and factory.new_container_attr:
- if not parents:
- raise StorageError("not enough free space on disks")
-
- log.debug("creating new container")
- if container_name:
- kwa = {"name": container_name}
- else:
- kwa = {}
- try:
- container = factory.new_container(parents=parents, **kwa)
- except StorageError as e:
- log.error("failed to create new device: %s" % e)
- # Clean up by destroying the newly created member devices.
- self.__cleanUpMemberDevices(parents)
- raise
-
- self.createDevice(container)
- elif container and not container.exists and \
- hasattr(container, "dataLevel"):
- container.dataLevel = factory.raid_level
-
- if container:
- parents = [container]
- log.debug("%r" % container)
-
- # this will set the device's size if a device is passed in
- size = factory.set_device_size(container, device=device)
- if device:
- # We are adjusting a defined device: size, disk set, encryption,
- # raid level, fstype. The StorageDevice instance exists, but the
- # underlying device does not.
- # TODO: handle toggling of encryption for leaf device
- e = None
- try:
- factory.post_create()
- except StorageError as e:
- log.error("device post-create method failed: %s" % e)
- else:
- if device.size <= device.format.minSize:
- e = StorageError("failed to adjust device -- not enough free space in specified disks?")
-
- if e:
- # Clean up by reverting the device to its previous size.
- factory.size = old_size
- factory.disks = old_disks
- try:
- self.setContainerMembers(container, factory,
- members=members, device=device)
- except StorageError as e:
- # yes, we're replacing e here.
- log.error("failed to revert device size: %s" % e)
- raise ErrorRecoveryFailure("failed to revert device size")
-
- factory.set_device_size(container, device=device)
- try:
- factory.post_create()
- except StorageError as e:
- # yes, we're replacing e here.
- log.error("failed to revert device size: %s" % e)
- raise ErrorRecoveryFailure("failed to revert device size")
-
- raise(e)
- elif factory.new_device_attr:
- log.debug("creating new device")
- if factory.encrypted and factory.encrypt_leaves:
- luks_fmt_type = fstype
- luks_fmt_args = fmt_args
- luks_mountpoint = mountpoint
- fstype = "luks"
- mountpoint = None
- fmt_args = {}
-
- def _container_post_error():
- # Clean up. If there is a container and it has other devices,
- # try to revert it. If there is a container and it has no other
- # devices, remove it. If there is not a container, remove all of
- # the parents.
- if container:
- if container.kids:
- factory.size = 0
- factory.disks = container.disks
- try:
- self.setContainerMembers(container, factory)
- except StorageError as e:
- log.error("failed to revert container: %s" % e)
- raise ErrorRecoveryFailure("failed to revert container")
- else:
- self.destroyDevice(container)
- self.__cleanUpMemberDevices(container.parents)
- else:
- self.__cleanUpMemberDevices(parents)
-
- if name:
- kwa = {"name": name}
- else:
- kwa = {}
-
- try:
- device = factory.new_device(parents=parents,
- size=size,
- fmt_type=fstype,
- mountpoint=mountpoint,
- fmt_args=fmt_args,
- **kwa)
- except (StorageError, ValueError) as e:
- log.error("device instance creation failed: %s" % e)
- _container_post_error()
- raise
-
- self.createDevice(device)
- e = None
- try:
- factory.post_create()
- except StorageError as e:
- log.error("device post-create method failed: %s" % e)
- else:
- if not device.size:
- e = StorageError("failed to create device")
-
- if e:
- self.destroyDevice(device)
- _container_post_error()
- raise StorageError(e)
-
- if factory.encrypted and factory.encrypt_leaves:
- fmt = getFormat(luks_fmt_type,
- mountpoint=luks_mountpoint,
- **luks_fmt_args)
- luks_device = LUKSDevice("luks-" + device.name,
- parents=[device], format=fmt)
- self.createDevice(luks_device)
+ factory.configure_device(device=device, container_name=container_name,
+ name=name, fstype=fstype,
+ mountpoint=mountpoint, label=label)
def copy(self):
new = copy.deepcopy(self)
@@ -3322,234 +2845,3 @@ def parseMounts(devicetree):
mounts[mountpoint] = device
return mounts
-
-class DeviceFactory(object):
- type_desc = None
- member_format = None # format type for member devices
- new_container_attr = None # name of Blivet method to create a container
- new_device_attr = None # name of Blivet method to create a device
- container_list_attr = None # name of Blivet attribute to list containers
- encrypt_members = False
- encrypt_leaves = True
-
- def __init__(self, storage, size, disks, raid_level, encrypted):
- self.storage = storage # the Blivet instance
- self.size = size # the requested size for this device
- self.disks = disks # the set of disks to allocate from
- self.raid_level = raid_level
- self.encrypted = encrypted
-
- # this is a list of member devices, used to short-circuit the logic in
- # setContainerMembers for case of a partition
- self.member_list = None
-
- # choose a size set class for member partition allocation
- if raid_level is not None and raid_level.startswith("raid"):
- self.set_class = SameSizeSet
- else:
- self.set_class = TotalSizeSet
-
- @property
- def container_list(self):
- """ A list of containers of the type used by this device. """
- if not self.container_list_attr:
- return []
-
- return getattr(self.storage, self.container_list_attr)
-
- def new_container(self, *args, **kwargs):
- """ Return the newly created container for this device. """
- return getattr(self.storage, self.new_container_attr)(*args, **kwargs)
-
- def new_device(self, *args, **kwargs):
- """ Return the newly created device. """
- return getattr(self.storage, self.new_device_attr)(*args, **kwargs)
-
- def post_create(self):
- """ Perform actions required after device creation. """
- pass
-
- def container_size_func(self, container, device=None):
- """ Return the total space needed for the specified container. """
- size = container.size
- size += self.device_size
- if device:
- size -= device.size
-
- return size
-
- @property
- def device_size(self):
- """ The total disk space required for this device. """
- return self.size
-
- def set_device_size(self, container, device=None):
- return self.size
-
-class DiskFactory(DeviceFactory):
- type_desc = "disk"
- # this is to protect against changes to these settings in the base class
- encrypt_members = False
- encrypt_leaves = True
-
-class PartitionFactory(DeviceFactory):
- type_desc = "partition"
- new_device_attr = "newPartition"
- default_size = 1
-
- def __init__(self, storage, size, disks, raid_level, encrypted):
- super(PartitionFactory, self).__init__(storage, size, disks, raid_level,
- encrypted)
- self.member_list = self.disks
-
- def new_device(self, *args, **kwargs):
- grow = True
- max_size = kwargs.pop("size")
- kwargs["size"] = 1
-
- device = self.storage.newPartition(*args,
- grow=grow, maxsize=max_size,
- **kwargs)
- return device
-
- def post_create(self):
- self.storage.allocatePartitions()
-
- def set_device_size(self, container, device=None):
- size = self.size
- if device:
- if size != device.size:
- log.info("adjusting device size from %.2f to %.2f"
- % (device.size, size))
-
- base_size = max(PartitionFactory.default_size,
- device.format.minSize)
- size = max(base_size, size)
- device.req_base_size = base_size
- device.req_size = base_size
- device.req_max_size = size
- device.req_grow = size > base_size
-
- # this probably belongs somewhere else but this is our chance to
- # update the disk set
- device.req_disks = self.disks[:]
-
- return size
-
-class BTRFSFactory(DeviceFactory):
- type_desc = "btrfs"
- member_format = "btrfs"
- new_container_attr = "newBTRFS"
- new_device_attr = "newBTRFSSubVolume"
- container_list_attr = "btrfsVolumes"
- encrypt_members = True
- encrypt_leaves = False
-
- def __init__(self, storage, size, disks, raid_level, encrypted):
- super(BTRFSFactory, self).__init__(storage, size, disks, raid_level,
- encrypted)
- self.raid_level = raid_level or "single"
-
- def new_container(self, *args, **kwargs):
- """ Return the newly created container for this device. """
- kwargs["dataLevel"] = self.raid_level
- return getattr(self.storage, self.new_container_attr)(*args, **kwargs)
-
- def container_size_func(self, container, device=None):
- """ Return the total space needed for the specified container. """
- if container.exists:
- container_size = container.size
- else:
- container_size = sum([s.req_size for s in container.subvolumes])
-
- if device:
- size = self.device_size
- else:
- size = container_size + self.device_size
-
- return size
-
- @property
- def device_size(self):
- # until we get/need something better
- if self.raid_level in ("single", "raid0"):
- return self.size
- elif self.raid_level in ("raid1", "raid10"):
- return self.size * len(self.disks)
-
- def new_device(self, *args, **kwargs):
- kwargs["dataLevel"] = self.raid_level
- kwargs["metaDataLevel"] = self.raid_level
- return super(BTRFSFactory, self).new_device(*args, **kwargs)
-
-class LVMFactory(DeviceFactory):
- type_desc = "lvm"
- member_format = "lvmpv"
- new_container_attr = "newVG"
- new_device_attr = "newLV"
- container_list_attr = "vgs"
- encrypt_members = True
- encrypt_leaves = False
-
- @property
- def device_size(self):
- size_func_kwargs = {}
- if self.raid_level in ("raid1", "raid10"):
- size_func_kwargs["mirrored"] = True
- if self.raid_level in ("raid0", "raid10"):
- size_func_kwargs["striped"] = True
- return get_pv_space(self.size, len(self.disks), **size_func_kwargs)
-
- def container_size_func(self, container, device=None):
- size = sum([p.size for p in container.parents])
- size -= container.freeSpace
- size += self.device_size
- if device:
- size -= get_pv_space(device.size, len(container.parents))
-
- return size
-
- def set_device_size(self, container, device=None):
- size = self.size
- free = container.freeSpace
- if device:
- free += device.size
-
- if free < size:
- log.info("adjusting device size from %.2f to %.2f so it fits "
- "in container" % (size, free))
- size = free
-
- if device:
- if size != device.size:
- log.info("adjusting device size from %.2f to %.2f"
- % (device.size, size))
-
- device.size = size
-
- return size
-
-class MDFactory(DeviceFactory):
- type_desc = "md"
- member_format = "mdmember"
- new_container_attr = None
- new_device_attr = "newMDArray"
-
- @property
- def container_list(self):
- return []
-
- @property
- def device_size(self):
- return get_member_space(self.size, len(self.disks),
- level=self.raid_level)
-
- def container_size_func(self, container, device=None):
- return get_member_space(self.size, len(container.parents),
- level=self.raid_level)
-
- def new_device(self, *args, **kwargs):
- kwargs["level"] = self.raid_level
- kwargs["totalDevices"] = len(kwargs.get("parents"))
- kwargs["memberDevices"] = len(kwargs.get("parents"))
- return super(MDFactory, self).new_device(*args, **kwargs)
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
new file mode 100644
index 0000000..e1c5374
--- /dev/null
+++ b/blivet/devicefactory.py
@@ -0,0 +1,765 @@
+# devicefactory.py
+# Creation of devices based on a top-down specification.
+#
+# Copyright (C) 2012, 2013 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): David Lehman <dlehman at redhat.com>
+#
+
+from storage_log import log_method_call
+from errors import *
+from devices import LUKSDevice
+from formats import getFormat
+from devicelibs.mdraid import get_member_space
+from devicelibs.mdraid import raidLevelString
+from devicelibs.lvm import get_pv_space
+from .partitioning import SameSizeSet
+from .partitioning import TotalSizeSet
+from .partitioning import doPartitioning
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+DEVICE_TYPE_LVM = 0
+DEVICE_TYPE_MD = 1
+DEVICE_TYPE_PARTITION = 2
+DEVICE_TYPE_BTRFS = 3
+DEVICE_TYPE_DISK = 4
+
+def get_device_type(device):
+ device_types = {"partition": DEVICE_TYPE_PARTITION,
+ "lvmlv": DEVICE_TYPE_LVM,
+ "btrfs subvolume": DEVICE_TYPE_BTRFS,
+ "btrfs volume": DEVICE_TYPE_BTRFS,
+ "mdarray": DEVICE_TYPE_MD}
+
+ use_dev = device
+ if isinstance(device, LUKSDevice):
+ use_dev = device.slave
+
+ if use_dev.isDisk:
+ device_type = DEVICE_TYPE_DISK
+ else:
+ device_type = device_types.get(use_dev.type)
+
+ return device_type
+
+def get_raid_level(device):
+ # TODO: move this into StorageDevice
+ use_dev = device
+ if isinstance(device, LUKSDevice):
+ use_dev = device.slave
+
+ # TODO: lvm and perhaps pulling raid level from md pvs
+ raid_level = None
+ if hasattr(use_dev, "level"):
+ raid_level = raidLevelString(use_dev.level)
+ elif hasattr(use_dev, "dataLevel"):
+ raid_level = use_dev.dataLevel or "single"
+ elif hasattr(use_dev, "volume"):
+ raid_level = use_dev.volume.dataLevel or "single"
+
+ return raid_level
+
+def get_device_factory(blivet, device_type, size, **kwargs):
+ """ Return a suitable DeviceFactory instance for device_type. """
+ disks = kwargs.get("disks", [])
+ raid_level = kwargs.get("raid_level")
+ encrypted = kwargs.get("encrypted", False)
+
+ class_table = {DEVICE_TYPE_LVM: LVMFactory,
+ DEVICE_TYPE_BTRFS: BTRFSFactory,
+ DEVICE_TYPE_PARTITION: PartitionFactory,
+ DEVICE_TYPE_MD: MDFactory,
+ DEVICE_TYPE_DISK: DiskFactory}
+
+ factory_class = class_table[device_type]
+ log.debug("instantiating %s: %r, %s, %s, %s" % (factory_class,
+ blivet, size, [d.name for d in disks], raid_level))
+ return factory_class(blivet, size, disks, raid_level, encrypted)
+
+class DeviceFactory(object):
+ type_desc = None
+ member_format = None # format type for member devices
+ new_container_attr = None # name of Blivet method to create a container
+ new_device_attr = None # name of Blivet method to create a device
+ container_list_attr = None # name of Blivet attribute to list containers
+ encrypt_members = False
+ encrypt_leaves = True
+
+ def __init__(self, storage, size, disks, raid_level, encrypted):
+ self.storage = storage # the Blivet instance
+ self.size = size # the requested size for this device
+ self.disks = disks # the set of disks to allocate from
+ self.raid_level = raid_level
+ self.encrypted = encrypted
+
+ # this is a list of member devices, used to short-circuit the logic in
+ # set_container_members for case of a partition
+ self.member_list = None
+
+ # choose a size set class for member partition allocation
+ if raid_level is not None and raid_level.startswith("raid"):
+ self.set_class = SameSizeSet
+ else:
+ self.set_class = TotalSizeSet
+
+ @property
+ def container_list(self):
+ """ A list of containers of the type used by this device. """
+ if not self.container_list_attr:
+ return []
+
+ return getattr(self.storage, self.container_list_attr)
+
+ def new_container(self, *args, **kwargs):
+ """ Return the newly created container for this device. """
+ return getattr(self.storage, self.new_container_attr)(*args, **kwargs)
+
+ def new_device(self, *args, **kwargs):
+ """ Return the newly created device. """
+ return getattr(self.storage, self.new_device_attr)(*args, **kwargs)
+
+ def post_create(self):
+ """ Perform actions required after device creation. """
+ pass
+
+ def container_size_func(self, container, device=None):
+ """ Return the total space needed for the specified container. """
+ size = container.size
+ size += self.device_size
+ if device:
+ size -= device.size
+
+ return size
+
+ @property
+ def device_size(self):
+ """ The total disk space required for this device. """
+ return self.size
+
+ def set_device_size(self, container, device=None):
+ return self.size
+
+ def set_container_members(self, container, members=None, device=None):
+ """ Set up and return the container's member partitions. """
+ log_members = []
+ if members:
+ log_members = [str(m) for m in members]
+ log_method_call(self, container=container,
+ members=log_members, device=device)
+ if self.member_list is not None:
+ # short-circuit the logic below for partitions
+ return self.member_list
+
+ if container and container.exists:
+ # don't try to modify an existing container
+ return container.parents
+
+ if self.container_size_func is None:
+ return []
+
+ # set up member devices
+ container_size = self.device_size
+ add_disks = []
+ remove_disks = []
+
+ if members is None:
+ members = []
+
+ if container:
+ members = container.parents[:]
+ elif members:
+ # mdarray
+ container = device
+
+ # XXX how can we detect/handle failure to use one or more of the disks?
+ if members and device:
+ # See if we need to add/remove any disks, but only if we are
+ # adjusting a device. When adding a new device to a container we do
+ # not want to modify the container's disk set.
+ _disks = list(set([d for m in members for d in m.disks]))
+
+ add_disks = [d for d in self.disks if d not in _disks]
+ remove_disks = [d for d in _disks if d not in self.disks]
+ elif not members:
+ # new container, so use the factory's disk set
+ add_disks = self.disks
+
+ # drop any new disks that don't have free space
+ min_free = min(500, self.size)
+ add_disks = [d for d in add_disks if d.partitioned and
+ d.format.free >= min_free]
+
+ base_size = max(1, getFormat(self.member_format).minSize)
+
+ # XXX TODO: multiple member devices per disk
+
+ # prepare already-defined member partitions for reallocation
+ for member in members[:]:
+ if any([d in remove_disks for d in member.disks]):
+ if isinstance(member, LUKSDevice):
+ if container:
+ container.removeMember(member)
+ self.storage.destroyDevice(member)
+ members.remove(member)
+ member = member.slave
+ else:
+ if container:
+ container.removeMember(member)
+
+ members.remove(member)
+
+ self.storage.destroyDevice(member)
+ continue
+
+ if isinstance(member, LUKSDevice):
+ if not self.encrypted:
+ # encryption was toggled for the member devices
+ if container:
+ container.removeMember(member)
+
+ self.storage.destroyDevice(member)
+ members.remove(member)
+
+ self.storage.formatDevice(member.slave,
+ getFormat(self.member_format))
+ members.append(member.slave)
+ if container:
+ container.addMember(member.slave)
+
+ member = member.slave
+ elif self.encrypted:
+ # encryption was toggled for the member devices
+ if container:
+ container.removeMember(member)
+
+ members.remove(member)
+ self.storage.formatDevice(member, getFormat("luks"))
+ luks_member = LUKSDevice("luks-%s" % member.name,
+ parents=[member],
+ format=getFormat(self.member_format))
+ self.storage.createDevice(luks_member)
+ members.append(luks_member)
+ if container:
+ container.addMember(luks_member)
+
+ member.req_base_size = base_size
+ member.req_size = member.req_base_size
+ member.req_grow = True
+
+ # set up new members as needed to accommodate the device
+ new_members = []
+ for disk in add_disks:
+ if self.encrypted and self.encrypt_members:
+ luks_format = self.member_format
+ member_format = "luks"
+ else:
+ member_format = self.member_format
+
+ try:
+ member = self.storage.newPartition(parents=[disk], grow=True,
+ size=base_size,
+ fmt_type=member_format)
+ except StorageError as e:
+ log.error("failed to create new member partition: %s" % e)
+ continue
+
+ self.storage.createDevice(member)
+ if self.encrypted and self.encrypt_members:
+ fmt = getFormat(luks_format)
+ member = LUKSDevice("luks-%s" % member.name,
+ parents=[member], format=fmt)
+ self.storage.createDevice(member)
+
+ members.append(member)
+ new_members.append(member)
+ if container:
+ container.addMember(member)
+
+ if container:
+ log.debug("using container %s with %d devices" % (container.name,
+ len(self.storage.devicetree.getChildren(container))))
+ container_size = self.container_size_func(container, device)
+ log.debug("raw container size reported as %d" % container_size)
+
+ log.debug("adding a %s with size %d" % (self.set_class.__name__,
+ container_size))
+ size_set = self.set_class(members, container_size)
+ self.storage.size_sets.append(size_set)
+ for member in members[:]:
+ if isinstance(member, LUKSDevice):
+ member = member.slave
+
+ member.req_max_size = size_set.size
+
+ try:
+ self.allocate_partitions()
+ except PartitioningError as e:
+ # try to clean up by destroying all newly added members before re-
+ # raising the exception
+ self.storage._clean_up_member_devices(new_members,
+ container=container)
+ raise
+
+ return members
+
+ def allocate_partitions(self):
+ """ Allocate all requested partitions. """
+ try:
+ doPartitioning(self.storage)
+ except StorageError as e:
+ log.error("failed to allocate partitions: %s" % e)
+ raise
+
+ def get_container(self, device=None, name=None, existing=False):
+ # XXX would it be useful to implement this as a series of fallbacks
+ # instead of mutually exclusive branches?
+ container = None
+ if name:
+ container = self.storage.devicetree.getDeviceByName(name)
+ if container and container not in self.container_list:
+ log.debug("specified container name %s is wrong type (%s)"
+ % (name, container.type))
+ container = None
+ elif device:
+ if hasattr(device, "vg"):
+ container = device.vg
+ elif hasattr(device, "volume"):
+ container = device.volume
+ else:
+ containers = [c for c in self.container_list if not c.exists]
+ if containers:
+ container = containers[0]
+
+ if container is None and existing:
+ containers = [c for c in self.container_list if c.exists]
+ if containers:
+ containers.sort(key=lambda c: getattr(c, "freeSpace", c.size),
+ reverse=True)
+ container = containers[0]
+
+ return container
+
+ def _clean_up_member_devices(self, members, container=None):
+ for member in members:
+ if container:
+ container.removeMember(member)
+
+ if isinstance(member, LUKSDevice):
+ self.storage.destroyDevice(member)
+ member = member.slave
+
+ if not member.isDisk:
+ self.storage.destroyDevice(member)
+
+ def configure_device(self, device=None, container_name=None, name=None,
+ fstype=None, mountpoint=None, label=None):
+ """ Schedule creation of a device based on a top-down specification.
+
+ Keyword arguments:
+
+ fstype filesystem type
+ mountpoint filesystem mount point
+ label filesystem label text
+
+ container_name name of requested container
+ name name of requested device
+
+ device an already-defined but non-existent device
+ to adjust instead of creating a new device
+
+
+ Error handling:
+
+ If device is None, meaning we're creating a device, the error
+ handling aims to remove all evidence of the attempt to create a
+ new device by removing unused container devices, reverting the
+ size of container devices that contain other devices, &c.
+
+ If the device is not None, meaning we're adjusting the size of
+ a defined device, the error handling aims to revert the device
+ and any container to it previous size.
+
+ In either case, we re-raise the exception so the caller knows
+ there was a failure. If we failed to clean up as described above
+ we raise ErrorRecoveryFailure to alert the caller that things
+ will likely be in an inconsistent state.
+ """
+ # we can't do anything with existing devices
+ if device and device.exists:
+ log.info("factoryDevice refusing to change device %s" % device)
+ return
+
+ if not fstype:
+ fstype = self.storage.getFSType(mountpoint=mountpoint)
+ if fstype == "swap":
+ mountpoint = None
+
+ fmt_args = {}
+ if label:
+ fmt_args["label"] = label
+
+ container = self.get_container(device=device, name=container_name)
+
+ # TODO: striping, mirroring, &c
+ # TODO: non-partition members (pv-on-md)
+
+ # set_container_members can modify these, so save them now
+ old_size = None
+ old_disks = []
+ if device:
+ old_size = device.size
+ old_disks = device.disks[:]
+
+ members = []
+ if device and device.type == "mdarray":
+ members = device.parents[:]
+
+ try:
+ parents = self.set_container_members(container,
+ members=members, device=device)
+ except PartitioningError as e:
+ # If this is a new device, just clean up and get out.
+ if device:
+ # If this is a defined device, try to clean up by reallocating
+ # members as before and then get out.
+ self.disks = device.disks
+ self.size = device.size # this should work
+
+ if members:
+ # If this is an md array we have to reset its member set
+ # here.
+ # If there is a container device, its member set was reset
+ # in the exception handler in set_container_members.
+ device.parents = members
+
+ try:
+ self.set_container_members(container,
+ members=members,
+ device=device)
+ except StorageError as e:
+ log.error("failed to revert device size: %s" % e)
+ raise ErrorRecoveryFailure("failed to revert container")
+
+ raise
+
+ # set up container
+ if not container and self.new_container_attr:
+ if not parents:
+ raise StorageError("not enough free space on disks")
+
+ log.debug("creating new container")
+ if container_name:
+ kwa = {"name": container_name}
+ else:
+ kwa = {}
+ try:
+ container = self.new_container(parents=parents, **kwa)
+ except StorageError as e:
+ log.error("failed to create new device: %s" % e)
+ # Clean up by destroying the newly created member devices.
+ self._clean_up_member_devices(parents)
+ raise
+
+ self.storage.createDevice(container)
+ elif container and not container.exists and \
+ hasattr(container, "dataLevel"):
+ container.dataLevel = self.raid_level
+
+ if container:
+ parents = [container]
+ log.debug("%r" % container)
+
+ # this will set the device's size if a device is passed in
+ size = self.set_device_size(container, device=device)
+ if device:
+ # We are adjusting a defined device: size, disk set, encryption,
+ # raid level, fstype. The StorageDevice instance exists, but the
+ # underlying device does not.
+ # TODO: handle toggling of encryption for leaf device
+ e = None
+ try:
+ self.post_create()
+ except StorageError as e:
+ log.error("device post-create method failed: %s" % e)
+ else:
+ if device.size <= device.format.minSize:
+ e = StorageError("failed to adjust device -- not enough free space in specified disks?")
+
+ if e:
+ # Clean up by reverting the device to its previous size.
+ self.size = old_size
+ self.disks = old_disks
+ try:
+ self.set_container_members(container,
+ members=members, device=device)
+ except StorageError as e:
+ # yes, we're replacing e here.
+ log.error("failed to revert device size: %s" % e)
+ raise ErrorRecoveryFailure("failed to revert device size")
+
+ self.set_device_size(container, device=device)
+ try:
+ self.post_create()
+ except StorageError as e:
+ # yes, we're replacing e here.
+ log.error("failed to revert device size: %s" % e)
+ raise ErrorRecoveryFailure("failed to revert device size")
+
+ raise(e)
+ elif self.new_device_attr:
+ log.debug("creating new device")
+ if self.encrypted and self.encrypt_leaves:
+ luks_fmt_type = fstype
+ luks_fmt_args = fmt_args
+ luks_mountpoint = mountpoint
+ fstype = "luks"
+ mountpoint = None
+ fmt_args = {}
+
+ def _container_post_error():
+ # Clean up. If there is a container and it has other devices,
+ # try to revert it. If there is a container and it has no other
+ # devices, remove it. If there is not a container, remove all of
+ # the parents.
+ if container:
+ if container.kids:
+ self.size = 0
+ self.disks = container.disks
+ try:
+ self.set_container_members(container, factory)
+ except StorageError as e:
+ log.error("failed to revert container: %s" % e)
+ raise ErrorRecoveryFailure("failed to revert container")
+ else:
+ self.storage.destroyDevice(container)
+ self._clean_up_member_devices(container.parents)
+ else:
+ self._clean_up_member_devices(parents)
+
+ if name:
+ kwa = {"name": name}
+ else:
+ kwa = {}
+
+ try:
+ device = self.new_device(parents=parents,
+ size=size,
+ fmt_type=fstype,
+ mountpoint=mountpoint,
+ fmt_args=fmt_args,
+ **kwa)
+ except (StorageError, ValueError) as e:
+ log.error("device instance creation failed: %s" % e)
+ _container_post_error()
+ raise
+
+ self.storage.createDevice(device)
+ e = None
+ try:
+ self.post_create()
+ except StorageError as e:
+ log.error("device post-create method failed: %s" % e)
+ else:
+ if not device.size:
+ e = StorageError("failed to create device")
+
+ if e:
+ self.storage.destroyDevice(device)
+ _container_post_error()
+ raise StorageError(e)
+
+ if self.encrypted and self.encrypt_leaves:
+ fmt = getFormat(luks_fmt_type,
+ mountpoint=luks_mountpoint,
+ **luks_fmt_args)
+ luks_device = LUKSDevice("luks-" + device.name,
+ parents=[device], format=fmt)
+ self.storage.createDevice(luks_device)
+
+
+class DiskFactory(DeviceFactory):
+ type_desc = "disk"
+ # this is to protect against changes to these settings in the base class
+ encrypt_members = False
+ encrypt_leaves = True
+
+class PartitionFactory(DeviceFactory):
+ type_desc = "partition"
+ new_device_attr = "newPartition"
+ default_size = 1
+
+ def __init__(self, storage, size, disks, raid_level, encrypted):
+ super(PartitionFactory, self).__init__(storage, size, disks, raid_level,
+ encrypted)
+ self.member_list = self.disks
+
+ def new_device(self, *args, **kwargs):
+ grow = True
+ max_size = kwargs.pop("size")
+ kwargs["size"] = 1
+
+ device = self.storage.newPartition(*args,
+ grow=grow, maxsize=max_size,
+ **kwargs)
+ return device
+
+ def post_create(self):
+ self.allocate_partitions()
+
+ def set_device_size(self, container, device=None):
+ size = self.size
+ if device:
+ if size != device.size:
+ log.info("adjusting device size from %.2f to %.2f"
+ % (device.size, size))
+
+ base_size = max(PartitionFactory.default_size,
+ device.format.minSize)
+ size = max(base_size, size)
+ device.req_base_size = base_size
+ device.req_size = base_size
+ device.req_max_size = size
+ device.req_grow = size > base_size
+
+ # this probably belongs somewhere else but this is our chance to
+ # update the disk set
+ device.req_disks = self.disks[:]
+
+ return size
+
+class BTRFSFactory(DeviceFactory):
+ type_desc = "btrfs"
+ member_format = "btrfs"
+ new_container_attr = "newBTRFS"
+ new_device_attr = "newBTRFSSubVolume"
+ container_list_attr = "btrfsVolumes"
+ encrypt_members = True
+ encrypt_leaves = False
+
+ def __init__(self, storage, size, disks, raid_level, encrypted):
+ super(BTRFSFactory, self).__init__(storage, size, disks, raid_level,
+ encrypted)
+ self.raid_level = raid_level or "single"
+
+ def new_container(self, *args, **kwargs):
+ """ Return the newly created container for this device. """
+ kwargs["dataLevel"] = self.raid_level
+ return getattr(self.storage, self.new_container_attr)(*args, **kwargs)
+
+ def container_size_func(self, container, device=None):
+ """ Return the total space needed for the specified container. """
+ if container.exists:
+ container_size = container.size
+ else:
+ container_size = sum([s.req_size for s in container.subvolumes])
+
+ if device:
+ size = self.device_size
+ else:
+ size = container_size + self.device_size
+
+ return size
+
+ @property
+ def device_size(self):
+ # until we get/need something better
+ if self.raid_level in ("single", "raid0"):
+ return self.size
+ elif self.raid_level in ("raid1", "raid10"):
+ return self.size * len(self.disks)
+
+ def new_device(self, *args, **kwargs):
+ kwargs["dataLevel"] = self.raid_level
+ kwargs["metaDataLevel"] = self.raid_level
+ return super(BTRFSFactory, self).new_device(*args, **kwargs)
+
+class LVMFactory(DeviceFactory):
+ type_desc = "lvm"
+ member_format = "lvmpv"
+ new_container_attr = "newVG"
+ new_device_attr = "newLV"
+ container_list_attr = "vgs"
+ encrypt_members = True
+ encrypt_leaves = False
+
+ @property
+ def device_size(self):
+ size_func_kwargs = {}
+ if self.raid_level in ("raid1", "raid10"):
+ size_func_kwargs["mirrored"] = True
+ if self.raid_level in ("raid0", "raid10"):
+ size_func_kwargs["striped"] = True
+ return get_pv_space(self.size, len(self.disks), **size_func_kwargs)
+
+ def container_size_func(self, container, device=None):
+ size = sum([p.size for p in container.parents])
+ size -= container.freeSpace
+ size += self.device_size
+ if device:
+ size -= get_pv_space(device.size, len(container.parents))
+
+ return size
+
+ def set_device_size(self, container, device=None):
+ size = self.size
+ free = container.freeSpace
+ if device:
+ free += device.size
+
+ if free < size:
+ log.info("adjusting device size from %.2f to %.2f so it fits "
+ "in container" % (size, free))
+ size = free
+
+ if device:
+ if size != device.size:
+ log.info("adjusting device size from %.2f to %.2f"
+ % (device.size, size))
+
+ device.size = size
+
+ return size
+
+class MDFactory(DeviceFactory):
+ type_desc = "md"
+ member_format = "mdmember"
+ new_container_attr = None
+ new_device_attr = "newMDArray"
+
+ @property
+ def container_list(self):
+ return []
+
+ @property
+ def device_size(self):
+ return get_member_space(self.size, len(self.disks),
+ level=self.raid_level)
+
+ def container_size_func(self, container, device=None):
+ return get_member_space(self.size, len(container.parents),
+ level=self.raid_level)
+
+ def new_device(self, *args, **kwargs):
+ kwargs["level"] = self.raid_level
+ kwargs["totalDevices"] = len(kwargs.get("parents"))
+ kwargs["memberDevices"] = len(kwargs.get("parents"))
+ return super(MDFactory, self).new_device(*args, **kwargs)
--
1.8.1
More information about the anaconda-patches
mailing list