[PATCH 1/20] Move the Blivet class into its own module
vpodzime
installerbot-noreply at redhat.com
Fri Feb 27 15:31:52 UTC 2015
From: Vratislav Podzimek <vpodzime at redhat.com>
The __init__.py file should not contain such a big class with a lot of code. And
importing anything from the 'blivet' package shouldn't require importing all the
things that the 'Blivet' class requires.
---
blivet/__init__.py | 1879 +----------------------------------
blivet/blivet.py | 1858 ++++++++++++++++++++++++++++++++++
tests/pylint/pylint-false-positives | 4 +-
3 files changed, 1890 insertions(+), 1851 deletions(-)
create mode 100644 blivet/blivet.py
diff --git a/blivet/__init__.py b/blivet/__init__.py
index c685eef..5b95ce5 100644
--- a/blivet/__init__.py
+++ b/blivet/__init__.py
@@ -17,6 +17,7 @@
# Red Hat, Inc.
#
# Red Hat Author(s): Dave Lehman <dlehman at redhat.com>
+# Vratislav Podzimek <vpodzime at redhat.com>
#
__version__ = '1.0'
@@ -48,38 +49,11 @@ def cb(self, exn):
## end installer stubs
##
-import os
-import time
-import copy
-import tempfile
-import re
+import sys
+import importlib
-import parted
-
-from pykickstart.constants import AUTOPART_TYPE_LVM, CLEARPART_TYPE_ALL, CLEARPART_TYPE_LINUX, CLEARPART_TYPE_LIST, CLEARPART_TYPE_NONE
-
-from .storage_log import log_exception_info, log_method_call
-from .errors import StorageError
-from .devices import BTRFSDevice, BTRFSSubVolumeDevice, BTRFSVolumeDevice, LVMLogicalVolumeDevice, LVMThinLogicalVolumeDevice, LVMThinPoolDevice, LVMVolumeGroupDevice, MDRaidArrayDevice, PartitionDevice, TmpFSDevice, devicePathToName
-from .devicetree import DeviceTree
-from .deviceaction import ActionCreateDevice, ActionCreateFormat, ActionDestroyDevice, ActionDestroyFormat, ActionResizeDevice, ActionResizeFormat
-from .formats import getFormat
-from .formats import get_default_filesystem_type
-from . import devicefactory
-from .devicelibs.edd import get_edd_dict
-from .devicelibs.dasd import make_dasd_list, write_dasd_conf
-from . import iscsi
-from . import fcoe
-from . import zfcp
from . import util
-from . import arch
from .flags import flags
-from .platform import platform as _platform
-from .size import Size
-from .i18n import _
-
-import shelve
-import contextlib
import logging
log = logging.getLogger("blivet")
@@ -143,1833 +117,40 @@ def setSysroot(storageRoot, sysroot=None):
if sysroot is not None:
_sysroot = sysroot
-def empty_device(device, devicetree):
- empty = True
- if device.partitioned:
- partitions = devicetree.getChildren(device)
- empty = all([p.isMagic for p in partitions])
- else:
- empty = (device.format.type is None)
-
- return empty
-
-
-class StorageDiscoveryConfig(object):
- """ Class to encapsulate various detection/initialization parameters. """
- def __init__(self):
- # storage configuration variables
- self.ignoreDiskInteractive = False
- self.ignoredDisks = []
- self.exclusiveDisks = []
- self.clearPartType = None
- self.clearPartDisks = []
- self.clearPartDevices = []
- self.initializeDisks = False
- self.protectedDevSpecs = []
- self.diskImages = {}
- self.zeroMbr = False
-
- # Whether clearPartitions removes scheduled/non-existent devices and
- # disklabels depends on this flag.
- self.clearNonExistent = False
-
- def update(self, ksdata):
- """ Update configuration from ksdata source.
-
- :param ksdata: kickstart data used as data source
- :type ksdata: :class:`pykickstart.Handler`
- """
- self.ignoredDisks = ksdata.ignoredisk.ignoredisk[:]
- self.exclusiveDisks = ksdata.ignoredisk.onlyuse[:]
- self.clearPartType = ksdata.clearpart.type
- self.clearPartDisks = ksdata.clearpart.drives[:]
- self.clearPartDevices = ksdata.clearpart.devices[:]
- self.initializeDisks = ksdata.clearpart.initAll
- self.zeroMbr = ksdata.zerombr.zerombr
-
-class Blivet(object):
- """ Top-level class for managing storage configuration. """
- def __init__(self, ksdata=None):
- """
- :keyword ksdata: kickstart data store
- :type ksdata: :class:`pykickstart.Handler`
- """
- # XXX: this cannot be imported globally as it would create a dependency
- # cycle in the imports
- from .osinstall import FSSet
-
- self.ksdata = ksdata
- self._bootloader = None
-
- self.config = StorageDiscoveryConfig()
-
- # storage configuration variables
- self.doAutoPart = False
- self.clearPartChoice = None
- self.encryptedAutoPart = False
- self.autoPartType = AUTOPART_TYPE_LVM
- self.encryptionPassphrase = None
- self.encryptionCipher = None
- self.escrowCertificates = {}
- self.autoPartEscrowCert = None
- self.autoPartAddBackupPassphrase = False
- self.autoPartitionRequests = []
- self.eddDict = {}
- self.dasd = []
-
- self.__luksDevs = {}
- self.size_sets = []
- self.setDefaultFSType(get_default_filesystem_type())
- self._defaultBootFSType = None
-
- self.iscsi = iscsi.iscsi()
- self.fcoe = fcoe.fcoe()
- self.zfcp = zfcp.ZFCP()
-
- self._nextID = 0
- self._dumpFile = "%s/storage.state" % tempfile.gettempdir()
-
- # these will both be empty until our reset method gets called
- self.devicetree = DeviceTree(conf=self.config,
- passphrase=self.encryptionPassphrase,
- luksDict=self.__luksDevs,
- iscsi=self.iscsi,
- dasd=self.dasd)
- self.fsset = FSSet(self.devicetree)
- self.roots = []
- self.services = set()
- self._free_space_snapshot = None
-
- def doIt(self, callbacks=None):
- """
- Commit queued changes to disk.
-
- :param callbacks: callbacks to be invoked when actions are executed
- :type callbacks: return value of the :func:`~.callbacks.create_new_callbacks_register`
-
- """
-
- self.devicetree.processActions(callbacks)
- if not flags.installer_mode:
- return
-
- # now set the boot partition's flag
- if self.bootloader and not self.bootloader.skip_bootloader:
- if self.bootloader.stage2_bootable:
- boot = self.bootDevice
- else:
- boot = self.bootLoaderDevice
-
- if boot.type == "mdarray":
- bootDevs = boot.parents
- else:
- bootDevs = [boot]
-
- for dev in bootDevs:
- if not hasattr(dev, "bootable"):
- log.info("Skipping %s, not bootable", dev)
- continue
-
- # Dos labels can only have one partition marked as active
- # and unmarking ie the windows partition is not a good idea
- skip = False
- if dev.disk.format.partedDisk.type == "msdos":
- for p in dev.disk.format.partedDisk.partitions:
- if p.type == parted.PARTITION_NORMAL and \
- p.getFlag(parted.PARTITION_BOOT):
- skip = True
- break
-
- # GPT labeled disks should only have bootable set on the
- # EFI system partition (parted sets the EFI System GUID on
- # GPT partitions with the boot flag)
- if dev.disk.format.labelType == "gpt" and \
- dev.format.type not in ["efi", "macefi"]:
- skip = True
-
- if skip:
- log.info("Skipping %s", dev.name)
- continue
-
- # hfs+ partitions on gpt can't be marked bootable via parted
- if dev.disk.format.partedDisk.type != "gpt" or \
- dev.format.type not in ["hfs+", "macefi"]:
- log.info("setting boot flag on %s", dev.name)
- dev.bootable = True
-
- # Set the boot partition's name on disk labels that support it
- if dev.partedPartition.disk.supportsFeature(parted.DISK_TYPE_PARTITION_NAME):
- ped_partition = dev.partedPartition.getPedPartition()
- ped_partition.set_name(dev.format.name)
- log.info("Setting label on %s to '%s'", dev, dev.format.name)
-
- dev.disk.setup()
- dev.disk.format.commitToDisk()
-
- if flags.installer_mode:
- self.dumpState("final")
-
- @property
- def nextID(self):
- """ Used for creating unique placeholder names. """
- newid = self._nextID
- self._nextID += 1
- return newid
-
- def shutdown(self):
- """ Deactivate all devices (installer_mode only). """
- if not flags.installer_mode:
- return
-
- try:
- self.devicetree.teardownAll()
- except Exception: # pylint: disable=broad-except
- log_exception_info(log.error, "failure tearing down device tree")
-
- def reset(self, cleanupOnly=False):
- """ Reset storage configuration to reflect actual system state.
-
- This will cancel any queued actions and rescan from scratch but not
- clobber user-obtained information like passphrases, iscsi config, &c
-
- :keyword cleanupOnly: prepare the tree only to deactivate devices
- :type cleanupOnly: bool
-
- See :meth:`devicetree.Devicetree.populate` for more information
- about the cleanupOnly keyword argument.
- """
-
- # XXX: this cannot be imported globally as it would create a dependency
- # cycle in the imports
- from .osinstall import findExistingInstallations, FSSet
-
- log.info("resetting Blivet (version %s) instance %s", __version__, self)
- if flags.installer_mode:
- # save passphrases for luks devices so we don't have to reprompt
- self.encryptionPassphrase = None
- for device in self.devices:
- if device.format.type == "luks" and device.format.exists:
- self.__luksDevs[device.format.uuid] = device.format._LUKS__passphrase
-
- if self.ksdata:
- self.config.update(self.ksdata)
-
- if flags.installer_mode and not flags.image_install:
- self.iscsi.startup()
- self.fcoe.startup()
- self.zfcp.startup()
- self.dasd = make_dasd_list(self.dasd, self.devices)
-
- if self.dasd:
- # Reset the internal dasd list (823534)
- self.dasd = []
-
- self.devicetree.reset(conf=self.config,
- passphrase=self.encryptionPassphrase,
- luksDict=self.__luksDevs,
- iscsi=self.iscsi,
- dasd=self.dasd)
- self.devicetree.populate(cleanupOnly=cleanupOnly)
- self.fsset = FSSet(self.devicetree)
- self.eddDict = get_edd_dict(self.partitioned)
- if self.bootloader:
- # clear out bootloader attributes that refer to devices that are
- # no longer in the tree
- self.bootloader.reset()
-
- self.roots = []
- if flags.installer_mode:
- try:
- self.roots = findExistingInstallations(self.devicetree)
- except Exception: # pylint: disable=broad-except
- log_exception_info(log.info, "failure detecting existing installations")
-
- self.dumpState("initial")
-
- if not flags.installer_mode:
- self.devicetree.getActiveMounts()
-
- self.updateBootLoaderDiskList()
-
- @property
- def unusedDevices(self):
- used_devices = []
- for root in self.roots:
- for device in list(root.mounts.values()) + root.swaps:
- if device not in self.devices:
- continue
-
- used_devices.extend(device.ancestors)
-
- for new in [d for d in self.devicetree.leaves if not d.format.exists]:
- if new.format.mountable and not new.format.mountpoint:
- continue
-
- used_devices.extend(new.ancestors)
-
- for device in self.partitions:
- if getattr(device, "isLogical", False):
- extended = device.disk.format.extendedPartition.path
- used_devices.append(self.devicetree.getDeviceByPath(extended))
-
- used = set(used_devices)
- _all = set(self.devices)
- return list(_all.difference(used))
-
- @property
- def devices(self):
- """ A list of all the devices in the device tree. """
- devices = self.devicetree.devices
- devices.sort(key=lambda d: d.name)
- return devices
-
- @property
- def disks(self):
- """ A list of the disks in the device tree.
-
- Ignored disks are excluded, as are disks with no media present.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- disks = []
- for device in self.devicetree.devices:
- if device.isDisk:
- if not device.mediaPresent:
- log.info("Skipping disk: %s: No media present", device.name)
- continue
- disks.append(device)
- disks.sort(key=lambda d: d.name, cmp=self.compareDisks)
- return disks
-
- @property
- def partitioned(self):
- """ A list of the partitioned devices in the device tree.
-
- Ignored devices are not included, nor disks with no media present.
-
- Devices of types for which partitioning is not supported are also
- not included.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- partitioned = []
- for device in self.devicetree.devices:
- if not device.partitioned:
- continue
-
- if not device.mediaPresent:
- log.info("Skipping device: %s: No media present", device.name)
- continue
-
- partitioned.append(device)
-
- partitioned.sort(key=lambda d: d.name)
- return partitioned
-
- @property
- def partitions(self):
- """ A list of the partitions in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- partitions = self.devicetree.getDevicesByInstance(PartitionDevice)
- partitions.sort(key=lambda d: d.name)
- return partitions
-
- @property
- def vgs(self):
- """ A list of the LVM Volume Groups in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- vgs = self.devicetree.getDevicesByType("lvmvg")
- vgs.sort(key=lambda d: d.name)
- return vgs
-
- @property
- def lvs(self):
- """ A list of the LVM Logical Volumes in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- lvs = self.devicetree.getDevicesByType("lvmlv")
- lvs.sort(key=lambda d: d.name)
- return lvs
-
- @property
- def thinlvs(self):
- """ A list of the LVM Thin Logical Volumes in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- thin = self.devicetree.getDevicesByType("lvmthinlv")
- thin.sort(key=lambda d: d.name)
- return thin
-
- @property
- def thinpools(self):
- """ A list of the LVM Thin Pool Logical Volumes in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- pools = self.devicetree.getDevicesByType("lvmthinpool")
- pools.sort(key=lambda d: d.name)
- return pools
-
- @property
- def pvs(self):
- """ A list of the LVM Physical Volumes in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- devices = self.devicetree.devices
- pvs = [d for d in devices if d.format.type == "lvmpv"]
- pvs.sort(key=lambda d: d.name)
- return pvs
-
- @property
- def mdarrays(self):
- """ A list of the MD arrays in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- arrays = self.devicetree.getDevicesByType("mdarray")
- arrays.sort(key=lambda d: d.name)
- return arrays
-
- @property
- def mdcontainers(self):
- """ A list of the MD containers in the device tree. """
- arrays = self.devicetree.getDevicesByType("mdcontainer")
- arrays.sort(key=lambda d: d.name)
- return arrays
-
- @property
- def mdmembers(self):
- """ A list of the MD member devices in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- devices = self.devicetree.devices
- members = [d for d in devices if d.format.type == "mdmember"]
- members.sort(key=lambda d: d.name)
- return members
-
- @property
- def btrfsVolumes(self):
- """ A list of the BTRFS volumes in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- return sorted(self.devicetree.getDevicesByType("btrfs volume"),
- key=lambda d: d.name)
-
- @property
- def swaps(self):
- """ A list of the swap devices in the device tree.
-
- This is based on the current state of the device tree and
- does not necessarily reflect the actual on-disk state of the
- system's disks.
- """
- devices = self.devicetree.devices
- swaps = [d for d in devices if d.format.type == "swap"]
- swaps.sort(key=lambda d: d.name)
- return swaps
-
- def shouldClear(self, device, **kwargs):
- """ Return True if a clearpart settings say a device should be cleared.
-
- :param device: the device (required)
- :type device: :class:`~.devices.StorageDevice`
- :keyword clearPartType: overrides :attr:`self.config.clearPartType`
- :type clearPartType: int
- :keyword clearPartDisks: overrides
- :attr:`self.config.clearPartDisks`
- :type clearPartDisks: list
- :keyword clearPartDevices: overrides
- :attr:`self.config.clearPartDevices`
- :type clearPartDevices: list
- :returns: whether or not clearPartitions should remove this device
- :rtype: bool
- """
- clearPartType = kwargs.get("clearPartType", self.config.clearPartType)
- clearPartDisks = kwargs.get("clearPartDisks",
- self.config.clearPartDisks)
- clearPartDevices = kwargs.get("clearPartDevices",
- self.config.clearPartDevices)
-
- for disk in device.disks:
- # this will not include disks with hidden formats like multipath
- # and firmware raid member disks
- if clearPartDisks and disk.name not in clearPartDisks:
- return False
-
- if not self.config.clearNonExistent:
- if (device.isDisk and not device.format.exists) or \
- (not device.isDisk and not device.exists):
- return False
-
- # the only devices we want to clear when clearPartType is
- # CLEARPART_TYPE_NONE are uninitialized disks, or disks with no
- # partitions, in clearPartDisks, and then only when we have been asked
- # to initialize disks as needed
- if clearPartType in [CLEARPART_TYPE_NONE, None]:
- if not self.config.initializeDisks or not device.isDisk:
- return False
-
- if not empty_device(device, self.devicetree):
- return False
-
- if isinstance(device, PartitionDevice):
- # Never clear the special first partition on a Mac disk label, as
- # that holds the partition table itself.
- # Something similar for the third partition on a Sun disklabel.
- if device.isMagic:
- return False
-
- # We don't want to fool with extended partitions, freespace, &c
- if not device.isPrimary and not device.isLogical:
- return False
-
- if clearPartType == CLEARPART_TYPE_LINUX and \
- not device.format.linuxNative and \
- not device.getFlag(parted.PARTITION_LVM) and \
- not device.getFlag(parted.PARTITION_RAID) and \
- not device.getFlag(parted.PARTITION_SWAP):
- return False
- elif device.isDisk:
- if device.partitioned and clearPartType != CLEARPART_TYPE_ALL:
- # if clearPartType is not CLEARPART_TYPE_ALL but we'll still be
- # removing every partition from the disk, return True since we
- # will want to be able to create a new disklabel on this disk
- if not empty_device(device, self.devicetree):
- return False
-
- # Never clear disks with hidden formats
- if device.format.hidden:
- return False
-
- # When clearPartType is CLEARPART_TYPE_LINUX and a disk has non-
- # linux whole-disk formatting, do not clear it. The exception is
- # the case of an uninitialized disk when we've been asked to
- # initialize disks as needed
- if (clearPartType == CLEARPART_TYPE_LINUX and
- not ((self.config.initializeDisks and
- empty_device(device, self.devicetree)) or
- (not device.partitioned and device.format.linuxNative))):
- return False
-
- # Don't clear devices holding install media.
- descendants = self.devicetree.getDependentDevices(device)
- if device.protected or any(d.protected for d in descendants):
- return False
-
- if clearPartType == CLEARPART_TYPE_LIST and \
- device.name not in clearPartDevices:
- return False
-
- return True
-
- def recursiveRemove(self, device):
- """ Remove a device after removing its dependent devices.
-
- If the device is not a leaf, all of its dependents are removed
- recursively until it is a leaf device. At that point the device is
- removed, unless it is a disk. If the device is a disk, its
- formatting is removed by no attempt is made to actually remove the
- disk device.
- """
- log.debug("removing %s", device.name)
- devices = self.deviceDeps(device)
-
- # this isn't strictly necessary, but it makes the action list easier to
- # read when removing logical partitions because of the automatic
- # renumbering that happens if you remove them in ascending numerical
- # order
- devices.reverse()
-
- while devices:
- log.debug("devices to remove: %s", [d.name for d in devices])
- leaves = [d for d in devices if d.isleaf]
- log.debug("leaves to remove: %s", [d.name for d in leaves])
- for leaf in leaves:
- self.destroyDevice(leaf)
- devices.remove(leaf)
-
- if device.isDisk:
- self.devicetree.registerAction(ActionDestroyFormat(device))
- else:
- self.destroyDevice(device)
-
- def clearPartitions(self):
- """ Clear partitions and dependent devices from disks.
-
- This is also where zerombr is handled.
- """
- # Sort partitions by descending partition number to minimize confusing
- # things like multiple "destroy sda5" actions due to parted renumbering
- # partitions. This can still happen through the UI but it makes sense to
- # avoid it where possible.
- partitions = sorted(self.partitions,
- key=lambda p: p.partedPartition.number,
- reverse=True)
- for part in partitions:
- log.debug("clearpart: looking at %s", part.name)
- if not self.shouldClear(part):
- continue
-
- self.recursiveRemove(part)
- log.debug("partitions: %s", [p.getDeviceNodeName() for p in part.partedPartition.disk.partitions])
-
- # now remove any empty extended partitions
- self.removeEmptyExtendedPartitions()
-
- # ensure all disks have appropriate disklabels
- for disk in self.disks:
- zerombr = (self.config.zeroMbr and disk.format.type is None)
- should_clear = self.shouldClear(disk)
- if should_clear:
- self.recursiveRemove(disk)
-
- if zerombr or should_clear:
- log.debug("clearpart: initializing %s", disk.name)
- self.initializeDisk(disk)
-
- self.updateBootLoaderDiskList()
-
- def initializeDisk(self, disk):
- """ (Re)initialize a disk by creating a disklabel on it.
-
- The disk should not contain any partitions except perhaps for a
- magic partitions on mac and sun disklabels. If the disk does contain
- partitions other than the disklabel-type-specific "magic" partitions
- ValueError will be raised.
-
- :param disk: the disk to initialize
- :type disk: :class:`~.devices.StorageDevice`
- :returns None:
- :raises: ValueError
- """
- # first, remove magic mac/sun partitions from the parted Disk
- if disk.partitioned:
- magic = disk.format.magicPartitionNumber
- expected = 0
- if magic:
- expected = 1
- # remove the magic partition
- for part in self.devicetree.getChildren(disk):
- if part.partedPartition.number == magic:
- log.debug("removing %s", part.name)
- # We can't schedule the magic partition for removal
- # because parted will not allow us to remove it from the
- # disk. Still, we need it out of the devicetree.
- self.devicetree._removeDevice(part, modparent=False)
-
- if len(disk.format.partitions) > expected:
- raise ValueError("cannot initialize a disk that has partitions")
-
- # remove existing formatting from the disk
- destroy_action = ActionDestroyFormat(disk)
- self.devicetree.registerAction(destroy_action)
-
- labelType = _platform.bestDiskLabelType(disk)
-
- # create a new disklabel on the disk
- newLabel = getFormat("disklabel", device=disk.path,
- labelType=labelType)
- create_action = ActionCreateFormat(disk, fmt=newLabel)
- self.devicetree.registerAction(create_action)
-
- def removeEmptyExtendedPartitions(self):
- for disk in self.partitioned:
- log.debug("checking whether disk %s has an empty extended", disk.name)
- extended = disk.format.extendedPartition
- logical_parts = disk.format.logicalPartitions
- log.debug("extended is %s ; logicals is %s", extended, [p.getDeviceNodeName() for p in logical_parts])
- if extended and not logical_parts:
- log.debug("removing empty extended partition from %s", disk.name)
- extended_name = devicePathToName(extended.getDeviceNodeName())
- extended = self.devicetree.getDeviceByName(extended_name)
- self.destroyDevice(extended)
-
- def getFreeSpace(self, disks=None, clearPartType=None):
- """ Return a dict with free space info for each disk.
-
- The dict values are 2-tuples: (disk_free, fs_free). fs_free is
- space available by shrinking filesystems. disk_free is space not
- allocated to any partition.
-
- disks and clearPartType allow specifying a set of disks other than
- self.disks and a clearPartType value other than
- self.config.clearPartType.
-
- :keyword disks: overrides :attr:`disks`
- :type disks: list
- :keyword clearPartType: overrides :attr:`self.config.clearPartType`
- :type clearPartType: int
- :returns: dict with disk name keys and tuple (disk, fs) free values
- :rtype: dict
-
- .. note::
-
- The free space values are :class:`~.size.Size` instances.
-
- """
- if disks is None:
- disks = self.disks
-
- if clearPartType is None:
- clearPartType = self.config.clearPartType
-
- free = {}
- for disk in disks:
- should_clear = self.shouldClear(disk, clearPartType=clearPartType,
- clearPartDisks=[disk.name])
- if should_clear:
- free[disk.name] = (disk.size, Size(0))
- continue
-
- disk_free = Size(0)
- fs_free = Size(0)
- if disk.partitioned:
- disk_free = disk.format.free
- for partition in [p for p in self.partitions if p.disk == disk]:
- # only check actual filesystems since lvm &c require a bunch of
- # operations to translate free filesystem space into free disk
- # space
- should_clear = self.shouldClear(partition,
- clearPartType=clearPartType,
- clearPartDisks=[disk.name])
- if should_clear:
- disk_free += partition.size
- elif hasattr(partition.format, "free"):
- fs_free += partition.format.free
- elif hasattr(disk.format, "free"):
- fs_free = disk.format.free
- elif disk.format.type is None:
- disk_free = disk.size
-
- free[disk.name] = (disk_free, fs_free)
-
- return free
-
- @property
- def names(self):
- """ A list of all of the known in-use device names. """
- return self.devicetree.names
-
- def deviceDeps(self, device):
- """ Return a list of the devices that depend on the specified device.
-
- :param device: the subtree root device
- :type device: :class:`~.devices.StorageDevice`
- :returns: list of dependent devices
- :rtype: list
- """
- return self.devicetree.getDependentDevices(device)
-
- def newPartition(self, *args, **kwargs):
- """ Return a new (unallocated) PartitionDevice instance.
-
- :keyword fmt_type: format type
- :type fmt_type: str
- :keyword fmt_args: arguments for format constructor
- :type fmt_args: dict
- :keyword mountpoint: mountpoint for format (filesystem)
- :type mountpoint: str
-
- All other arguments are passed on to the
- :class:`~.devices.PartitionDevice` constructor.
- """
- if 'fmt_type' in kwargs:
- kwargs["fmt"] = getFormat(kwargs.pop("fmt_type"),
- mountpoint=kwargs.pop("mountpoint",
- None),
- **kwargs.pop("fmt_args", {}))
-
- if 'name' in kwargs:
- name = kwargs.pop("name")
- else:
- name = "req%d" % self.nextID
-
- if "weight" not in kwargs:
- fmt = kwargs.get("fmt")
- if fmt:
- mountpoint = getattr(fmt, "mountpoint", None)
-
- kwargs["weight"] = _platform.weight(mountpoint=mountpoint,
- fstype=fmt.type)
-
-
- return PartitionDevice(name, *args, **kwargs)
-
- def newMDArray(self, *args, **kwargs):
- """ Return a new MDRaidArrayDevice instance.
-
- :keyword fmt_type: format type
- :type fmt_type: str
- :keyword fmt_args: arguments for format constructor
- :type fmt_args: dict
- :keyword mountpoint: mountpoint for format (filesystem)
- :type mountpoint: str
- :returns: the new md array device
- :rtype: :class:`~.devices.MDRaidArrayDevice`
-
- All other arguments are passed on to the
- :class:`~.devices.MDRaidArrayDevice` constructor.
-
- If a name is not specified, one will be generated based on the
- format type, mountpoint, hostname, and/or product name.
- """
- if 'fmt_type' in kwargs:
- kwargs["fmt"] = getFormat(kwargs.pop("fmt_type"),
- mountpoint=kwargs.pop("mountpoint",
- None),
- **kwargs.pop("fmt_args", {}))
-
- name = kwargs.pop("name", None)
- if name:
- safe_name = self.safeDeviceName(name)
- if safe_name != name:
- log.warning("using '%s' instead of specified name '%s'",
- safe_name, name)
- name = safe_name
- else:
- swap = getattr(kwargs.get("fmt"), "type", None) == "swap"
- mountpoint = getattr(kwargs.get("fmt"), "mountpoint", None)
- name = self.suggestDeviceName(prefix=shortProductName,
- swap=swap,
- mountpoint=mountpoint)
-
- return MDRaidArrayDevice(name, *args, **kwargs)
-
- def newVG(self, *args, **kwargs):
- """ Return a new LVMVolumeGroupDevice instance.
-
- :returns: the new volume group device
- :rtype: :class:`~.devices.LVMVolumeGroupDevice`
-
- All arguments are passed on to the
- :class:`~.devices.LVMVolumeGroupDevice` constructor.
-
- If a name is not specified, one will be generated based on the
- hostname, and/or product name.
- """
- pvs = kwargs.pop("parents", [])
- for pv in pvs:
- if pv not in self.devices:
- raise ValueError("pv is not in the device tree")
-
- name = kwargs.pop("name", None)
- if name:
- safe_name = self.safeDeviceName(name)
- if safe_name != name:
- log.warning("using '%s' instead of specified name '%s'",
- safe_name, name)
- name = safe_name
- else:
- hostname = ""
- if self.ksdata and self.ksdata.network.hostname is not None:
- hostname = self.ksdata.network.hostname
-
- name = self.suggestContainerName(hostname=hostname)
-
- if name in self.names:
- raise ValueError("name already in use")
-
- return LVMVolumeGroupDevice(name, pvs, *args, **kwargs)
-
- def newLV(self, *args, **kwargs):
- """ Return a new LVMLogicalVolumeDevice instance.
-
- :keyword fmt_type: format type
- :type fmt_type: str
- :keyword fmt_args: arguments for format constructor
- :type fmt_args: dict
- :keyword mountpoint: mountpoint for format (filesystem)
- :type mountpoint: str
- :keyword thin_pool: whether to create a thin pool
- :type thin_pool: bool
- :keyword thin_volume: whether to create a thin volume
- :type thin_volume: bool
- :returns: the new device
- :rtype: :class:`~.devices.LVMLogicalVolumeDevice`
-
- All other arguments are passed on to the appropriate
- :class:`~.devices.LVMLogicalVolumeDevice` constructor.
-
- If a name is not specified, one will be generated based on the
- format type and/or mountpoint.
-
- .. note::
-
- If you are creating a thin volume, the parents kwarg should
- contain the pool -- not the vg.
- """
- thin_volume = kwargs.pop("thin_volume", False)
- thin_pool = kwargs.pop("thin_pool", False)
- vg = kwargs.get("parents", [None])[0]
- if thin_volume and vg:
- # kwargs["parents"] will contain the pool device, so...
- vg = vg.vg
-
- mountpoint = kwargs.pop("mountpoint", None)
- if 'fmt_type' in kwargs:
- kwargs["fmt"] = getFormat(kwargs.pop("fmt_type"),
- mountpoint=mountpoint,
- **kwargs.pop("fmt_args", {}))
-
- name = kwargs.pop("name", None)
- if name:
- # make sure the specified name is sensible
- safe_vg_name = self.safeDeviceName(vg.name)
- full_name = "%s-%s" % (safe_vg_name, name)
- safe_name = self.safeDeviceName(full_name)
- if safe_name != full_name:
- new_name = safe_name[len(safe_vg_name)+1:]
- log.warning("using '%s' instead of specified name '%s'",
- new_name, name)
- name = new_name
- else:
- if kwargs.get("fmt") and kwargs["fmt"].type == "swap":
- swap = True
- else:
- swap = False
-
- prefix = ""
- if thin_pool:
- prefix = "pool"
-
- name = self.suggestDeviceName(parent=vg,
- swap=swap,
- mountpoint=mountpoint,
- prefix=prefix)
-
- if "%s-%s" % (vg.name, name) in self.names:
- raise ValueError("name already in use")
-
- if thin_pool:
- device_class = LVMThinPoolDevice
- elif thin_volume:
- device_class = LVMThinLogicalVolumeDevice
- else:
- device_class = LVMLogicalVolumeDevice
-
- return device_class(name, *args, **kwargs)
-
- def newBTRFS(self, *args, **kwargs):
- """ Return a new BTRFSVolumeDevice or BRFSSubVolumeDevice.
-
- :keyword fmt_args: arguments for format constructor
- :type fmt_args: dict
- :keyword mountpoint: mountpoint for format (filesystem)
- :type mountpoint: str
- :keyword subvol: whether this is a subvol (as opposed to a volume)
- :type subvol: bool
- :returns: the new device
- :rtype: :class:`~.devices.BTRFSDevice`
-
- All other arguments are passed on to the appropriate
- :class:`~.devices.BTRFSDevice` constructor.
-
- For volumes, the label is the same as the name. If a name/label is
- not specified, one will be generated based on the hostname and/or
- product name.
-
- .. note::
-
- If you are creating a subvolume, the parents kwarg should
- contain the volume you want to contain the subvolume.
-
- """
- log.debug("newBTRFS: args = %s ; kwargs = %s", args, kwargs)
- name = kwargs.pop("name", None)
- if args:
- name = args[0]
-
- mountpoint = kwargs.pop("mountpoint", None)
-
- fmt_args = kwargs.pop("fmt_args", {})
- fmt_args.update({"mountpoint": mountpoint})
-
- if kwargs.pop("subvol", False):
- dev_class = BTRFSSubVolumeDevice
-
- # set up the subvol name, using mountpoint if necessary
- if not name:
- # for btrfs this only needs to ensure the subvol name is not
- # already in use within the parent volume
- name = self.suggestDeviceName(mountpoint=mountpoint)
- fmt_args["mountopts"] = "subvol=%s" % name
- kwargs.pop("metaDataLevel", None)
- kwargs.pop("dataLevel", None)
- else:
- dev_class = BTRFSVolumeDevice
- # set up the volume label, using hostname if necessary
- if not name:
- hostname = ""
- if self.ksdata and self.ksdata.network.hostname is not None:
- hostname = self.ksdata.network.hostname
-
- name = self.suggestContainerName(hostname=hostname)
- if "label" not in fmt_args:
- fmt_args["label"] = name
-
- # discard fmt_type since it's btrfs always
- kwargs.pop("fmt_type", None)
-
- # this is to avoid auto-scheduled format create actions
- device = dev_class(name, **kwargs)
- device.format = getFormat("btrfs", **fmt_args)
- return device
-
- def newBTRFSSubVolume(self, *args, **kwargs):
- """ Return a new BRFSSubVolumeDevice.
-
- :keyword fmt_args: arguments for format constructor
- :type fmt_args: dict
- :keyword mountpoint: mountpoint for format (filesystem)
- :type mountpoint: str
- :returns: the new device
- :rtype: :class:`~.devices.BTRFSSubVolumeDevice`
-
- All other arguments are passed on to the
- :class:`~.devices.BTRFSSubVolumeDevice` constructor.
-
- .. note::
-
- Since you are creating a subvolume, the parents kwarg should
- contain the volume you want to contain the subvolume.
-
- """
- kwargs["subvol"] = True
- return self.newBTRFS(*args, **kwargs)
-
- def newTmpFS(self, *args, **kwargs):
- """ Return a new TmpFSDevice. """
- return TmpFSDevice(*args, **kwargs)
-
- def createDevice(self, device):
- """ Schedule creation of a device.
-
- :param device: the device to schedule creation of
- :type device: :class:`~.devices.StorageDevice`
- :rtype: None
- """
- self.devicetree.registerAction(ActionCreateDevice(device))
- if device.format.type and not device.formatImmutable:
- self.devicetree.registerAction(ActionCreateFormat(device))
-
- def destroyDevice(self, device):
- """ Schedule destruction of a device.
-
- :param device: the device to schedule destruction of
- :type device: :class:`~.devices.StorageDevice`
- :rtype: None
- """
- if device.protected:
- raise ValueError("cannot modify protected device")
-
- if device.format.exists and device.format.type and \
- not device.formatImmutable:
- # schedule destruction of any formatting while we're at it
- self.devicetree.registerAction(ActionDestroyFormat(device))
-
- action = ActionDestroyDevice(device)
- self.devicetree.registerAction(action)
-
- def formatDevice(self, device, fmt):
- """ Schedule formatting of a device.
-
- :param device: the device to create the formatting on
- :type device: :class:`~.devices.StorageDevice`
- :param fmt: the format to create on the device
- :type format: :class:`~.formats.DeviceFormat`
- :rtype: None
-
- A format destroy action will be scheduled first, so it is not
- necessary to create and schedule an
- :class:`~.deviceaction.ActionDestroyFormat` prior to calling this
- method.
- """
- if device.protected:
- raise ValueError("cannot modify protected device")
-
- self.devicetree.registerAction(ActionDestroyFormat(device))
- self.devicetree.registerAction(ActionCreateFormat(device, fmt))
-
- def resetDevice(self, device):
- """ Cancel all scheduled actions and reset formatting.
-
- :param device: the device to reset
- :type device: :class:`~.devices.StorageDevice`
- :rtype: None
- """
- actions = self.devicetree.findActions(device=device)
- for action in reversed(actions):
- self.devicetree.cancelAction(action)
-
- # make sure any random overridden attributes are reset
- device.format = copy.copy(device.originalFormat)
-
- def resizeDevice(self, device, new_size):
- """ Schedule a resize of a device and its formatting, if any.
-
- :param device: the device to resize
- :type device: :class:`~.devices.StorageDevice`
- :param new_size: the new target size for the device
- :type new_size: :class:`~.size.Size`
- :rtype: None
-
- If the device has formatting that is recognized as being resizable
- an action will be scheduled to resize it as well.
- """
- if device.protected:
- raise ValueError("cannot modify protected device")
-
- classes = []
- if device.resizable:
- classes.append(ActionResizeDevice)
-
- if device.format.resizable:
- classes.append(ActionResizeFormat)
-
- if not classes:
- raise ValueError("device cannot be resized")
-
- # if this is a shrink, schedule the format resize first
- if new_size < device.size:
- classes.reverse()
-
- for action_class in classes:
- self.devicetree.registerAction(action_class(device, new_size))
-
- def formatByDefault(self, device):
- """Return whether the device should be reformatted by default."""
- formatlist = ['/boot', '/var', '/tmp', '/usr']
- exceptlist = ['/usr/local', '/var/www']
-
- if not device.format.linuxNative:
- return False
-
- if device.format.mountable:
- if not device.format.mountpoint:
- return False
-
- if device.format.mountpoint == "/" or \
- device.format.mountpoint in formatlist:
- return True
-
- for p in formatlist:
- if device.format.mountpoint.startswith(p):
- for q in exceptlist:
- if device.format.mountpoint.startswith(q):
- return False
- return True
- elif device.format.type == "swap":
- return True
-
- # be safe for anything else and default to off
- return False
-
- def mustFormat(self, device):
- """ Return a string explaining why the device must be reformatted.
-
- Return None if the device need not be reformatted.
- """
- if device.format.mountable and device.format.mountpoint == "/":
- return _("You must create a new filesystem on the root device.")
-
- return None
-
- def safeDeviceName(self, name):
- """ Convert a device name to something safe and return that.
-
- LVM limits lv names to 128 characters. I don't know the limits for
- the other various device types, so I'm going to pick a number so
- that we don't have to have an entire fucking library to determine
- device name limits.
- """
- max_len = 96 # No, you don't need longer names than this. Really.
- tmp = name.strip()
- tmp = tmp.replace("/", "_")
- tmp = re.sub("[^0-9a-zA-Z._-]", "", tmp)
-
- # Remove any '-' or '_' prefixes
- tmp = re.sub("^[-_]*", "", tmp)
-
- # If all that's left is . or .., give up
- if tmp == "." or tmp == "..":
- return ""
-
- if len(tmp) > max_len:
- tmp = tmp[:max_len]
-
- return tmp
-
- def suggestContainerName(self, hostname=None, prefix=""):
- """ Return a reasonable, unused device name.
-
- :keyword hostname: the system's hostname
- :keyword prefix: a prefix for the container name
- :returns: the suggested name
- :rtype: str
- """
- if not prefix:
- prefix = shortProductName
-
- # try to create a device name incorporating the hostname
- if hostname not in (None, "", 'localhost', 'localhost.localdomain'):
- template = "%s_%s" % (prefix, hostname.split('.')[0].lower())
- template = self.safeDeviceName(template)
- else:
- template = prefix
-
- if flags.image_install:
- template = "%s_image" % template
-
- names = self.names
- name = template
- if name in names:
- name = None
- for i in range(100):
- tmpname = "%s%02d" % (template, i,)
- if tmpname not in names:
- name = tmpname
- break
-
- if not name:
- log.error("failed to create device name based on prefix "
- "'%s' and hostname '%s'", prefix, hostname)
- raise RuntimeError("unable to find suitable device name")
-
- return name
-
- def suggestDeviceName(self, parent=None, swap=None,
- mountpoint=None, prefix=""):
- """ Return a suitable, unused name for a new device.
-
- :keyword parent: the parent device
- :type parent: :class:`~.devices.StorageDevice`
- :keyword swap: will this be a swap device
- :type swap: bool
- :keyword mountpoint: the device's mountpoint
- :type mountpoint: str
- :keyword prefix: device name prefix
- :type prefix: str
- :returns: the suggested name
- :rtype: str
- """
- body = ""
- if mountpoint:
- if mountpoint == "/":
- body = "root"
- else:
- body = mountpoint[1:].replace("/", "_")
- elif swap:
- body = "swap"
-
- if prefix and body:
- body = "_" + body
-
- template = self.safeDeviceName(prefix + body)
- names = self.names
- name = template
- def full_name(name, parent):
- full = ""
- if parent:
- full = "%s-" % parent.name
- full += name
- return full
-
- # also include names of any lvs in the parent for the case of the
- # temporary vg in the lvm dialogs, which can contain lvs that are
- # not yet in the devicetree and therefore not in self.names
- if full_name(name, parent) in names or not body:
- for i in range(100):
- name = "%s%02d" % (template, i)
- if full_name(name, parent) not in names:
- break
- else:
- name = ""
-
- if not name:
- log.error("failed to create device name based on parent '%s', "
- "prefix '%s', mountpoint '%s', swap '%s'",
- parent.name, prefix, mountpoint, swap)
- raise RuntimeError("unable to find suitable device name")
-
- return name
-
- def savePassphrase(self, device):
- """ Save a device's LUKS passphrase in case of reset. """
- passphrase = device.format._LUKS__passphrase
- self.__luksDevs[device.format.uuid] = passphrase
- self.devicetree._DeviceTree__luksDevs[device.format.uuid] = passphrase
- self.devicetree._DeviceTree__passphrases.append(passphrase)
-
- def setupDiskImages(self):
- self.devicetree.setDiskImages(self.config.diskImages)
- self.devicetree.setupDiskImages()
-
- @property
- def fileSystemFreeSpace(self):
- """ Combined free space in / and /usr as :class:`~.size.Size`. """
- mountpoints = ["/", "/usr"]
- free = Size(0)
- btrfs_volumes = []
- for mountpoint in mountpoints:
- device = self.mountpoints.get(mountpoint)
- if not device:
- continue
-
- # don't count the size of btrfs volumes repeatedly when multiple
- # subvolumes are present
- if isinstance(device, BTRFSSubVolumeDevice):
- if device.volume in btrfs_volumes:
- continue
- else:
- btrfs_volumes.append(device.volume)
-
- if device.format.exists:
- free += device.format.free
- else:
- free += device.size
-
- return free
-
- def dumpState(self, suffix):
- """ Dump the current device list to the storage shelf. """
- key = "devices.%d.%s" % (time.time(), suffix)
- with contextlib.closing(shelve.open(self._dumpFile)) as shelf:
- try:
- shelf[key] = [d.dict for d in self.devices]
- except AttributeError:
- log_exception_info()
-
- @property
- def packages(self):
- pkgs = set()
- pkgs.update(_platform.packages)
-
- # install support packages for all devices in the system
- for device in self.devices:
- # this takes care of device and filesystem packages
- pkgs.update(device.packages)
-
- return list(pkgs)
-
- def write(self):
- """ Write out all storage-related configuration files. """
- if not os.path.isdir("%s/etc" % _sysroot):
- os.mkdir("%s/etc" % _sysroot)
-
- self.fsset.write()
- self.makeMtab()
- self.iscsi.write(_sysroot, self)
- self.fcoe.write(_sysroot)
- self.zfcp.write(_sysroot)
- write_dasd_conf(self.dasd, _sysroot)
-
- def turnOnSwap(self):
- self.fsset.turnOnSwap(rootPath=_sysroot)
-
- def mountFilesystems(self, readOnly=None, skipRoot=False):
- self.fsset.mountFilesystems(rootPath=_sysroot,
- readOnly=readOnly, skipRoot=skipRoot)
-
- def umountFilesystems(self, swapoff=True):
- self.fsset.umountFilesystems(swapoff=swapoff)
-
- def parseFSTab(self, chroot=None):
- self.fsset.parseFSTab(chroot=chroot)
-
- def mkDevRoot(self):
- self.fsset.mkDevRoot()
-
- def createSwapFile(self, device, size):
- self.fsset.createSwapFile(device, size)
-
- @property
- def bootloader(self):
- if self._bootloader is None and flags.installer_mode:
- self._bootloader = get_bootloader()
-
- return self._bootloader
-
- def updateBootLoaderDiskList(self):
- if not self.bootloader:
- return
-
- boot_disks = [d for d in self.disks if d.partitioned]
- boot_disks.sort(cmp=self.compareDisks, key=lambda d: d.name)
- self.bootloader.set_disk_list(boot_disks)
-
- def setUpBootLoader(self, early=False):
- """ Propagate ksdata into BootLoader.
-
- :keyword bool early: Set to True to skip stage1_device setup
-
- :raises BootloaderError: if stage1 setup fails
-
- If this needs to be run early, eg. to setup stage1_disk but
- not stage1_device 'early' should be set True to prevent
- it from raising BootloaderError
- """
- if not self.bootloader or not self.ksdata:
- log.warning("either ksdata or bootloader data missing")
- return
-
- if self.bootloader.skip_bootloader:
- log.info("user specified that bootloader install be skipped")
- return
-
- # Need to make sure bootDrive has been setup from the latest information
- self.ksdata.bootloader.execute(self, self.ksdata, None)
- self.bootloader.stage1_disk = self.devicetree.resolveDevice(self.ksdata.bootloader.bootDrive)
- self.bootloader.stage2_device = self.bootDevice
- if not early:
- self.bootloader.set_stage1_device(self.devices)
-
- @property
- def bootDisk(self):
- disk = None
- if self.ksdata:
- spec = self.ksdata.bootloader.bootDrive
- disk = self.devicetree.resolveDevice(spec)
- return disk
-
- @property
- def bootDevice(self):
- dev = None
- if self.fsset:
- dev = self.mountpoints.get("/boot", self.rootDevice)
- return dev
-
- @property
- def bootLoaderDevice(self):
- return getattr(self.bootloader, "stage1_device", None)
-
- @property
- def bootFSTypes(self):
- """A list of all valid filesystem types for the boot partition."""
- fstypes = []
- if self.bootloader:
- fstypes = self.bootloader.stage2_format_types
- return fstypes
-
- @property
- def defaultBootFSType(self):
- """The default filesystem type for the boot partition."""
- if self._defaultBootFSType:
- return self._defaultBootFSType
-
- fstype = None
- if self.bootloader:
- fstype = self.bootFSTypes[0]
- return fstype
-
- def _check_valid_fstype(self, newtype):
- """ Check the fstype to see if it is valid
-
- Raise ValueError on invalid input.
- """
- fmt = getFormat(newtype)
- if fmt.type is None:
- raise ValueError("unrecognized value %s for new default fs type" % newtype)
-
- if (not fmt.mountable or not fmt.formattable or not fmt.supported or
- not fmt.linuxNative):
- log.debug("invalid default fstype: %r", fmt)
- raise ValueError("new value %s is not valid as a default fs type" % fmt)
-
- self._defaultFSType = newtype # pylint: disable=attribute-defined-outside-init
-
- def setDefaultBootFSType(self, newtype):
- """ Set the default /boot fstype for this instance.
-
- Raise ValueError on invalid input.
- """
- log.debug("trying to set new default /boot fstype to '%s'", newtype)
- # This will raise ValueError if it isn't valid
- self._check_valid_fstype(newtype)
- self._defaultBootFSType = newtype
-
- @property
- def defaultFSType(self):
- return self._defaultFSType
-
- def setDefaultFSType(self, newtype):
- """ Set the default fstype for this instance.
-
- Raise ValueError on invalid input.
- """
- log.debug("trying to set new default fstype to '%s'", newtype)
- # This will raise ValueError if it isn't valid
- self._check_valid_fstype(newtype)
- self._defaultFSType = newtype # pylint: disable=attribute-defined-outside-init
-
- @property
- def mountpoints(self):
- return self.fsset.mountpoints
-
- @property
- def rootDevice(self):
- return self.fsset.rootDevice
-
- def makeMtab(self):
- path = "/etc/mtab"
- target = "/proc/self/mounts"
- path = os.path.normpath("%s/%s" % (_sysroot, path))
-
- if os.path.islink(path):
- # return early if the mtab symlink is already how we like it
- current_target = os.path.normpath(os.path.dirname(path) +
- "/" + os.readlink(path))
- if current_target == target:
- return
-
- if os.path.exists(path):
- os.unlink(path)
-
- os.symlink(target, path)
-
- def compareDisks(self, first, second):
- if first in self.eddDict and second in self.eddDict:
- one = self.eddDict[first]
- two = self.eddDict[second]
- if (one < two):
- return -1
- elif (one > two):
- return 1
-
- # if one is in the BIOS and the other not prefer the one in the BIOS
- if first in self.eddDict:
- return -1
- if second in self.eddDict:
- return 1
-
- if first.startswith("hd"):
- type1 = 0
- elif first.startswith("sd"):
- type1 = 1
- elif (first.startswith("vd") or first.startswith("xvd")):
- type1 = -1
- else:
- type1 = 2
-
- if second.startswith("hd"):
- type2 = 0
- elif second.startswith("sd"):
- type2 = 1
- elif (second.startswith("vd") or second.startswith("xvd")):
- type2 = -1
- else:
- type2 = 2
-
- if (type1 < type2):
- return -1
- elif (type1 > type2):
- return 1
- else:
- len1 = len(first)
- len2 = len(second)
-
- if (len1 < len2):
- return -1
- elif (len1 > len2):
- return 1
- else:
- if (first < second):
- return -1
- elif (first > second):
- return 1
-
- return 0
-
- def getFSType(self, mountpoint=None):
- """ Return the default filesystem type based on mountpoint. """
- fstype = self.defaultFSType
- if not mountpoint:
- # just return the default
- pass
- elif mountpoint.lower() in ("swap", "biosboot", "prepboot"):
- fstype = mountpoint.lower()
- elif mountpoint == "/boot":
- fstype = self.defaultBootFSType
- elif mountpoint == "/boot/efi":
- if arch.isMactel():
- fstype = "macefi"
- else:
- fstype = "efi"
-
- return fstype
-
- def factoryDevice(self, device_type, size, **kwargs):
- """ Schedule creation of a device based on a top-down specification.
-
- :param device_type: device type constant
- :type device_type: int (:const:`~.devicefactory.DEVICE_TYPE_*`)
- :param size: requested size
- :type size: :class:`~.size.Size`
- :returns: the newly configured device
- :rtype: :class:`~.devices.StorageDevice`
-
- See :class:`~.devicefactory.DeviceFactory` for possible kwargs.
-
- """
- log_method_call(self, device_type, size, **kwargs)
-
- # 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 kwargs.get("fstype"):
- kwargs["fstype"] = self.getFSType(mountpoint=kwargs.get("mountpoint"))
- if kwargs["fstype"] == "swap":
- kwargs["mountpoint"] = None
-
- if kwargs["fstype"] == "swap" and \
- device_type == devicefactory.DEVICE_TYPE_BTRFS:
- device_type = devicefactory.DEVICE_TYPE_PARTITION
-
- 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
- factory.configure()
- return factory.device
-
- def copy(self):
- log.debug("starting Blivet copy")
- new = copy.deepcopy(self)
- # go through and re-get partedPartitions from the disks since they
- # don't get deep-copied
- hidden_partitions = [d for d in new.devicetree._hidden
- if isinstance(d, PartitionDevice)]
- for partition in new.partitions + hidden_partitions:
- if not partition._partedPartition:
- continue
-
- # update the refs in req_disks as well
- req_disks = (new.devicetree.getDeviceByID(disk.id) for disk in partition.req_disks)
- partition.req_disks = [disk for disk in req_disks if disk is not None]
-
- p = partition.disk.format.partedDisk.getPartitionByPath(partition.path)
- partition.partedPartition = p
-
- for root in new.roots:
- root.swaps = [new.devicetree.getDeviceByID(d.id, hidden=True) for d in root.swaps]
- root.swaps = [s for s in root.swaps if s]
-
- for (mountpoint, old_dev) in root.mounts.items():
- if old_dev is None:
- continue
-
- new_dev = new.devicetree.getDeviceByID(old_dev.id, hidden=True)
- if new_dev is None:
- # if the device has been removed don't include this
- # mountpoint at all
- del root.mounts[mountpoint]
- else:
- root.mounts[mountpoint] = new_dev
-
- log.debug("finished Blivet copy")
- return new
-
- def updateKSData(self):
- """ Update ksdata to reflect the settings of this Blivet instance. """
- if not self.ksdata or not self.mountpoints:
- return
-
- # clear out whatever was there before
- self.ksdata.partition.partitions = []
- self.ksdata.logvol.lvList = []
- self.ksdata.raid.raidList = []
- self.ksdata.volgroup.vgList = []
- self.ksdata.btrfs.btrfsList = []
-
- # iscsi?
- # fcoe?
- # zfcp?
- # dmraid?
-
- # bootloader
-
- # ignoredisk
- if self.config.ignoredDisks:
- self.ksdata.ignoredisk.drives = self.config.ignoredDisks[:]
- elif self.config.exclusiveDisks:
- self.ksdata.ignoredisk.onlyuse = self.config.exclusiveDisks[:]
-
- # autopart
- self.ksdata.autopart.autopart = self.doAutoPart
- self.ksdata.autopart.type = self.autoPartType
- self.ksdata.autopart.encrypted = self.encryptedAutoPart
-
- # clearpart
- self.ksdata.clearpart.type = self.config.clearPartType
- self.ksdata.clearpart.drives = self.config.clearPartDisks[:]
- self.ksdata.clearpart.devices = self.config.clearPartDevices[:]
- self.ksdata.clearpart.initAll = self.config.initializeDisks
- if self.ksdata.clearpart.type == CLEARPART_TYPE_NONE:
- # Make a list of initialized disks and of removed partitions. If any
- # partitions were removed from disks that were not completely
- # cleared we'll have to use CLEARPART_TYPE_LIST and provide a list
- # of all removed partitions. If no partitions were removed from a
- # disk that was not cleared/reinitialized we can use
- # CLEARPART_TYPE_ALL.
- self.ksdata.clearpart.devices = []
- self.ksdata.clearpart.drives = []
- fresh_disks = [d.name for d in self.disks if d.partitioned and
- not d.format.exists]
-
- destroy_actions = self.devicetree.findActions(action_type="destroy",
- object_type="device")
-
- cleared_partitions = []
- partial = False
- for action in destroy_actions:
- if action.device.type == "partition":
- if action.device.disk.name not in fresh_disks:
- partial = True
-
- cleared_partitions.append(action.device.name)
-
- if not destroy_actions:
- pass
- elif partial:
- # make a list of removed partitions
- self.ksdata.clearpart.type = CLEARPART_TYPE_LIST
- self.ksdata.clearpart.devices = cleared_partitions
- else:
- # if they didn't partially clear any disks, use the shorthand
- self.ksdata.clearpart.type = CLEARPART_TYPE_ALL
- self.ksdata.clearpart.drives = fresh_disks
-
- if self.doAutoPart:
- return
-
- # custom storage
- ksMap = {PartitionDevice: ("PartData", "partition"),
- TmpFSDevice: ("PartData", "partition"),
- LVMLogicalVolumeDevice: ("LogVolData", "logvol"),
- LVMVolumeGroupDevice: ("VolGroupData", "volgroup"),
- MDRaidArrayDevice: ("RaidData", "raid"),
- BTRFSDevice: ("BTRFSData", "btrfs")}
-
- # make a list of ancestors of all used devices
- devices = list(set(a for d in list(self.mountpoints.values()) + self.swaps
- for a in d.ancestors))
- devices.sort(key=lambda d: len(d.ancestors))
- for device in devices:
- cls = next((c for c in ksMap if isinstance(device, c)), None)
- if cls is None:
- log.info("omitting ksdata: %s", device)
- continue
-
- class_attr, list_attr = ksMap[cls]
-
- cls = getattr(self.ksdata, class_attr)
- data = cls() # all defaults
-
- device.populateKSData(data)
-
- parent = getattr(self.ksdata, list_attr)
- parent.dataList().append(data)
-
- @property
- def freeSpaceSnapshot(self):
- # if no snapshot is available, do it now and return it
- self._free_space_snapshot = self._free_space_snapshot or self.getFreeSpace()
-
- return self._free_space_snapshot
-
- def createFreeSpaceSnapshot(self):
- self._free_space_snapshot = self.getFreeSpace()
-
- return self._free_space_snapshot
-
- def addFstabSwap(self, device):
- """
- Add swap device to the list of swaps that should appear in the fstab.
-
- :param device: swap device that should be added to the list
- :type device: blivet.devices.StorageDevice instance holding a swap format
-
- """
-
- self.fsset.addFstabSwap(device)
-
- def removeFstabSwap(self, device):
- """
- Remove swap device from the list of swaps that should appear in the fstab.
-
- :param device: swap device that should be removed from the list
- :type device: blivet.devices.StorageDevice instance holding a swap format
-
- """
+class _LazyImportObject(object):
+ """
+ A simple class that uses sys.modules and importlib to implement a
+ lazy-imported object. Once it is called (or instantiated) or an attribute of
+ it is requested, the real object is imported and an appropriate method is
+ called on it with all the passed arguments.
- self.fsset.removeFstabSwap(device)
+ """
- def setFstabSwaps(self, devices):
+ def __init__(self, name, real_mod):
"""
- Set swap devices that should appear in the fstab.
+ Create a new instance of a lazy-imported object.
- :param devices: iterable providing devices that should appear in the fstab
- :type devices: iterable providing blivet.devices.StorageDevice instances holding
- a swap format
+ :param str name: name of the real object/class
+ :param str real_mod: the real module the real object lives in
"""
- self.fsset.setFstabSwaps(devices)
+ self._name = name
+ self._real_mod = real_mod
-def mountExistingSystem(fsset, rootDevice, readOnly=None):
- """ Mount filesystems specified in rootDevice's /etc/fstab file. """
- rootPath = _sysroot
+ def __call__(self, *args, **kwargs):
+ mod = importlib.import_module(__package__+"."+self._real_mod)
+ val = getattr(mod, self._name)
+ sys.modules["%s.%s" % (__package__, self._name)] = val
+ return val(*args, **kwargs)
- if readOnly:
- readOnly = "ro"
- else:
- readOnly = ""
-
- if rootDevice.protected and os.path.ismount("/mnt/install/isodir"):
- util.mount("/mnt/install/isodir",
- rootPath,
- fstype=rootDevice.format.type,
- options="bind")
- else:
- rootDevice.setup()
- rootDevice.format.mount(chroot=rootPath,
- mountpoint="/",
- options=readOnly)
+ def __getattr__(self, attr):
+ mod = importlib.import_module(__package__+"."+self._real_mod)
+ val = getattr(mod, self._name)
+ sys.modules["%s.%s" % (__package__, self._name)] = val
+ return getattr(val, attr)
- fsset.parseFSTab()
- fsset.mountFilesystems(rootPath=_sysroot, readOnly=readOnly, skipRoot=True)
+# this way things like 'from blivet import Blivet' work without an overhead of
+# importing of everything the Blivet class needs whenever anything from the
+# 'blivet' package is imported (e.g. the 'arch' module)
+Blivet = _LazyImportObject("Blivet", "blivet")
diff --git a/blivet/blivet.py b/blivet/blivet.py
new file mode 100644
index 0000000..c49a380
--- /dev/null
+++ b/blivet/blivet.py
@@ -0,0 +1,1858 @@
+#
+# Copyright (C) 2009-2015 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 at redhat.com>
+#
+
+import os
+import copy
+import tempfile
+import re
+import shelve
+import contextlib
+import time
+import parted
+
+
+from pykickstart.constants import AUTOPART_TYPE_LVM, CLEARPART_TYPE_ALL, CLEARPART_TYPE_LINUX, CLEARPART_TYPE_LIST, CLEARPART_TYPE_NONE
+
+from .storage_log import log_method_call, log_exception_info
+from .devices import BTRFSDevice, BTRFSSubVolumeDevice, BTRFSVolumeDevice
+from .devices import LVMLogicalVolumeDevice, LVMThinLogicalVolumeDevice, LVMThinPoolDevice, LVMVolumeGroupDevice
+from .devices import MDRaidArrayDevice, PartitionDevice, TmpFSDevice, devicePathToName
+from .deviceaction import ActionCreateDevice, ActionCreateFormat, ActionDestroyDevice
+from .deviceaction import ActionDestroyFormat, ActionResizeDevice, ActionResizeFormat
+from .devicelibs.edd import get_edd_dict
+from .devicelibs.dasd import make_dasd_list, write_dasd_conf
+from .errors import StorageError
+from .size import Size
+from .devicetree import DeviceTree
+from .formats import get_default_filesystem_type
+from .flags import flags
+from .platform import platform as _platform
+from .formats import getFormat
+from .osinstall import FSSet, findExistingInstallations
+from . import arch
+from . import iscsi
+from . import fcoe
+from . import zfcp
+from . import devicefactory
+from . import get_bootloader, getSysroot, shortProductName, __version__
+
+from .i18n import _
+
+import logging
+log = logging.getLogger("blivet")
+
+def empty_device(device, devicetree):
+ empty = True
+ if device.partitioned:
+ partitions = devicetree.getChildren(device)
+ empty = all([p.isMagic for p in partitions])
+ else:
+ empty = (device.format.type is None)
+
+ return empty
+
+class StorageDiscoveryConfig(object):
+ """ Class to encapsulate various detection/initialization parameters. """
+ def __init__(self):
+ # storage configuration variables
+ self.ignoreDiskInteractive = False
+ self.ignoredDisks = []
+ self.exclusiveDisks = []
+ self.clearPartType = None
+ self.clearPartDisks = []
+ self.clearPartDevices = []
+ self.initializeDisks = False
+ self.protectedDevSpecs = []
+ self.diskImages = {}
+ self.zeroMbr = False
+
+ # Whether clearPartitions removes scheduled/non-existent devices and
+ # disklabels depends on this flag.
+ self.clearNonExistent = False
+
+ def update(self, ksdata):
+ """ Update configuration from ksdata source.
+
+ :param ksdata: kickstart data used as data source
+ :type ksdata: :class:`pykickstart.Handler`
+ """
+ self.ignoredDisks = ksdata.ignoredisk.ignoredisk[:]
+ self.exclusiveDisks = ksdata.ignoredisk.onlyuse[:]
+ self.clearPartType = ksdata.clearpart.type
+ self.clearPartDisks = ksdata.clearpart.drives[:]
+ self.clearPartDevices = ksdata.clearpart.devices[:]
+ self.initializeDisks = ksdata.clearpart.initAll
+ self.zeroMbr = ksdata.zerombr.zerombr
+
+class Blivet(object):
+ """ Top-level class for managing storage configuration. """
+ def __init__(self, ksdata=None):
+ """
+ :keyword ksdata: kickstart data store
+ :type ksdata: :class:`pykickstart.Handler`
+ """
+ self.ksdata = ksdata
+ self._bootloader = None
+
+ self.config = StorageDiscoveryConfig()
+
+ # storage configuration variables
+ self.doAutoPart = False
+ self.clearPartChoice = None
+ self.encryptedAutoPart = False
+ self.autoPartType = AUTOPART_TYPE_LVM
+ self.encryptionPassphrase = None
+ self.encryptionCipher = None
+ self.escrowCertificates = {}
+ self.autoPartEscrowCert = None
+ self.autoPartAddBackupPassphrase = False
+ self.autoPartitionRequests = []
+ self.eddDict = {}
+ self.dasd = []
+
+ self.__luksDevs = {}
+ self.size_sets = []
+ self.setDefaultFSType(get_default_filesystem_type())
+ self._defaultBootFSType = None
+
+ self.iscsi = iscsi.iscsi()
+ self.fcoe = fcoe.fcoe()
+ self.zfcp = zfcp.ZFCP()
+
+ self._nextID = 0
+ self._dumpFile = "%s/storage.state" % tempfile.gettempdir()
+
+ # these will both be empty until our reset method gets called
+ self.devicetree = DeviceTree(conf=self.config,
+ passphrase=self.encryptionPassphrase,
+ luksDict=self.__luksDevs,
+ iscsi=self.iscsi,
+ dasd=self.dasd)
+ self.fsset = FSSet(self.devicetree)
+ self.roots = []
+ self.services = set()
+ self._free_space_snapshot = None
+
+ def doIt(self, callbacks=None):
+ """
+ Commit queued changes to disk.
+
+ :param callbacks: callbacks to be invoked when actions are executed
+ :type callbacks: return value of the :func:`~.callbacks.create_new_callbacks_register`
+
+ """
+
+ self.devicetree.processActions(callbacks)
+ if not flags.installer_mode:
+ return
+
+ # now set the boot partition's flag
+ if self.bootloader and not self.bootloader.skip_bootloader:
+ if self.bootloader.stage2_bootable:
+ boot = self.bootDevice
+ else:
+ boot = self.bootLoaderDevice
+
+ if boot.type == "mdarray":
+ bootDevs = boot.parents
+ else:
+ bootDevs = [boot]
+
+ for dev in bootDevs:
+ if not hasattr(dev, "bootable"):
+ log.info("Skipping %s, not bootable", dev)
+ continue
+
+ # Dos labels can only have one partition marked as active
+ # and unmarking ie the windows partition is not a good idea
+ skip = False
+ if dev.disk.format.partedDisk.type == "msdos":
+ for p in dev.disk.format.partedDisk.partitions:
+ if p.type == parted.PARTITION_NORMAL and \
+ p.getFlag(parted.PARTITION_BOOT):
+ skip = True
+ break
+
+ # GPT labeled disks should only have bootable set on the
+ # EFI system partition (parted sets the EFI System GUID on
+ # GPT partitions with the boot flag)
+ if dev.disk.format.labelType == "gpt" and \
+ dev.format.type not in ["efi", "macefi"]:
+ skip = True
+
+ if skip:
+ log.info("Skipping %s", dev.name)
+ continue
+
+ # hfs+ partitions on gpt can't be marked bootable via parted
+ if dev.disk.format.partedDisk.type != "gpt" or \
+ dev.format.type not in ["hfs+", "macefi"]:
+ log.info("setting boot flag on %s", dev.name)
+ dev.bootable = True
+
+ # Set the boot partition's name on disk labels that support it
+ if dev.partedPartition.disk.supportsFeature(parted.DISK_TYPE_PARTITION_NAME):
+ ped_partition = dev.partedPartition.getPedPartition()
+ ped_partition.set_name(dev.format.name)
+ log.info("Setting label on %s to '%s'", dev, dev.format.name)
+
+ dev.disk.setup()
+ dev.disk.format.commitToDisk()
+
+ if flags.installer_mode:
+ self.dumpState("final")
+
+ @property
+ def nextID(self):
+ """ Used for creating unique placeholder names. """
+ newid = self._nextID
+ self._nextID += 1
+ return newid
+
+ def shutdown(self):
+ """ Deactivate all devices (installer_mode only). """
+ if not flags.installer_mode:
+ return
+
+ try:
+ self.devicetree.teardownAll()
+ except Exception: # pylint: disable=broad-except
+ log_exception_info(log.error, "failure tearing down device tree")
+
+ def reset(self, cleanupOnly=False):
+ """ Reset storage configuration to reflect actual system state.
+
+ This will cancel any queued actions and rescan from scratch but not
+ clobber user-obtained information like passphrases, iscsi config, &c
+
+ :keyword cleanupOnly: prepare the tree only to deactivate devices
+ :type cleanupOnly: bool
+
+ See :meth:`devicetree.Devicetree.populate` for more information
+ about the cleanupOnly keyword argument.
+ """
+ log.info("resetting Blivet (version %s) instance %s", __version__, self)
+ if flags.installer_mode:
+ # save passphrases for luks devices so we don't have to reprompt
+ self.encryptionPassphrase = None
+ for device in self.devices:
+ if device.format.type == "luks" and device.format.exists:
+ self.__luksDevs[device.format.uuid] = device.format._LUKS__passphrase
+
+ if self.ksdata:
+ self.config.update(self.ksdata)
+
+ if flags.installer_mode and not flags.image_install:
+ self.iscsi.startup()
+ self.fcoe.startup()
+ self.zfcp.startup()
+ self.dasd = make_dasd_list(self.dasd, self.devices)
+
+ if self.dasd:
+ # Reset the internal dasd list (823534)
+ self.dasd = []
+
+ self.devicetree.reset(conf=self.config,
+ passphrase=self.encryptionPassphrase,
+ luksDict=self.__luksDevs,
+ iscsi=self.iscsi,
+ dasd=self.dasd)
+ self.devicetree.populate(cleanupOnly=cleanupOnly)
+ self.fsset = FSSet(self.devicetree)
+ self.eddDict = get_edd_dict(self.partitioned)
+ if self.bootloader:
+ # clear out bootloader attributes that refer to devices that are
+ # no longer in the tree
+ self.bootloader.reset()
+
+ self.roots = []
+ if flags.installer_mode:
+ try:
+ self.roots = findExistingInstallations(self.devicetree)
+ except Exception: # pylint: disable=broad-except
+ log_exception_info(log.info, "failure detecting existing installations")
+
+ self.dumpState("initial")
+
+ if not flags.installer_mode:
+ self.devicetree.getActiveMounts()
+
+ self.updateBootLoaderDiskList()
+
+ @property
+ def unusedDevices(self):
+ used_devices = []
+ for root in self.roots:
+ for device in list(root.mounts.values()) + root.swaps:
+ if device not in self.devices:
+ continue
+
+ used_devices.extend(device.ancestors)
+
+ for new in [d for d in self.devicetree.leaves if not d.format.exists]:
+ if new.format.mountable and not new.format.mountpoint:
+ continue
+
+ used_devices.extend(new.ancestors)
+
+ for device in self.partitions:
+ if getattr(device, "isLogical", False):
+ extended = device.disk.format.extendedPartition.path
+ used_devices.append(self.devicetree.getDeviceByPath(extended))
+
+ used = set(used_devices)
+ _all = set(self.devices)
+ return list(_all.difference(used))
+
+ @property
+ def devices(self):
+ """ A list of all the devices in the device tree. """
+ devices = self.devicetree.devices
+ devices.sort(key=lambda d: d.name)
+ return devices
+
+ @property
+ def disks(self):
+ """ A list of the disks in the device tree.
+
+ Ignored disks are excluded, as are disks with no media present.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ disks = []
+ for device in self.devicetree.devices:
+ if device.isDisk:
+ if not device.mediaPresent:
+ log.info("Skipping disk: %s: No media present", device.name)
+ continue
+ disks.append(device)
+ disks.sort(key=lambda d: d.name, cmp=self.compareDisks)
+ return disks
+
+ @property
+ def partitioned(self):
+ """ A list of the partitioned devices in the device tree.
+
+ Ignored devices are not included, nor disks with no media present.
+
+ Devices of types for which partitioning is not supported are also
+ not included.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ partitioned = []
+ for device in self.devicetree.devices:
+ if not device.partitioned:
+ continue
+
+ if not device.mediaPresent:
+ log.info("Skipping device: %s: No media present", device.name)
+ continue
+
+ partitioned.append(device)
+
+ partitioned.sort(key=lambda d: d.name)
+ return partitioned
+
+ @property
+ def partitions(self):
+ """ A list of the partitions in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ partitions = self.devicetree.getDevicesByInstance(PartitionDevice)
+ partitions.sort(key=lambda d: d.name)
+ return partitions
+
+ @property
+ def vgs(self):
+ """ A list of the LVM Volume Groups in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ vgs = self.devicetree.getDevicesByType("lvmvg")
+ vgs.sort(key=lambda d: d.name)
+ return vgs
+
+ @property
+ def lvs(self):
+ """ A list of the LVM Logical Volumes in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ lvs = self.devicetree.getDevicesByType("lvmlv")
+ lvs.sort(key=lambda d: d.name)
+ return lvs
+
+ @property
+ def thinlvs(self):
+ """ A list of the LVM Thin Logical Volumes in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ thin = self.devicetree.getDevicesByType("lvmthinlv")
+ thin.sort(key=lambda d: d.name)
+ return thin
+
+ @property
+ def thinpools(self):
+ """ A list of the LVM Thin Pool Logical Volumes in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ pools = self.devicetree.getDevicesByType("lvmthinpool")
+ pools.sort(key=lambda d: d.name)
+ return pools
+
+ @property
+ def pvs(self):
+ """ A list of the LVM Physical Volumes in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ devices = self.devicetree.devices
+ pvs = [d for d in devices if d.format.type == "lvmpv"]
+ pvs.sort(key=lambda d: d.name)
+ return pvs
+
+ @property
+ def mdarrays(self):
+ """ A list of the MD arrays in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ arrays = self.devicetree.getDevicesByType("mdarray")
+ arrays.sort(key=lambda d: d.name)
+ return arrays
+
+ @property
+ def mdcontainers(self):
+ """ A list of the MD containers in the device tree. """
+ arrays = self.devicetree.getDevicesByType("mdcontainer")
+ arrays.sort(key=lambda d: d.name)
+ return arrays
+
+ @property
+ def mdmembers(self):
+ """ A list of the MD member devices in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ devices = self.devicetree.devices
+ members = [d for d in devices if d.format.type == "mdmember"]
+ members.sort(key=lambda d: d.name)
+ return members
+
+ @property
+ def btrfsVolumes(self):
+ """ A list of the BTRFS volumes in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ return sorted(self.devicetree.getDevicesByType("btrfs volume"),
+ key=lambda d: d.name)
+
+ @property
+ def swaps(self):
+ """ A list of the swap devices in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ devices = self.devicetree.devices
+ swaps = [d for d in devices if d.format.type == "swap"]
+ swaps.sort(key=lambda d: d.name)
+ return swaps
+
+ def shouldClear(self, device, **kwargs):
+ """ Return True if a clearpart settings say a device should be cleared.
+
+ :param device: the device (required)
+ :type device: :class:`~.devices.StorageDevice`
+ :keyword clearPartType: overrides :attr:`self.config.clearPartType`
+ :type clearPartType: int
+ :keyword clearPartDisks: overrides
+ :attr:`self.config.clearPartDisks`
+ :type clearPartDisks: list
+ :keyword clearPartDevices: overrides
+ :attr:`self.config.clearPartDevices`
+ :type clearPartDevices: list
+ :returns: whether or not clearPartitions should remove this device
+ :rtype: bool
+ """
+ clearPartType = kwargs.get("clearPartType", self.config.clearPartType)
+ clearPartDisks = kwargs.get("clearPartDisks",
+ self.config.clearPartDisks)
+ clearPartDevices = kwargs.get("clearPartDevices",
+ self.config.clearPartDevices)
+
+ for disk in device.disks:
+ # this will not include disks with hidden formats like multipath
+ # and firmware raid member disks
+ if clearPartDisks and disk.name not in clearPartDisks:
+ return False
+
+ if not self.config.clearNonExistent:
+ if (device.isDisk and not device.format.exists) or \
+ (not device.isDisk and not device.exists):
+ return False
+
+ # the only devices we want to clear when clearPartType is
+ # CLEARPART_TYPE_NONE are uninitialized disks, or disks with no
+ # partitions, in clearPartDisks, and then only when we have been asked
+ # to initialize disks as needed
+ if clearPartType in [CLEARPART_TYPE_NONE, None]:
+ if not self.config.initializeDisks or not device.isDisk:
+ return False
+
+ if not empty_device(device, self.devicetree):
+ return False
+
+ if isinstance(device, PartitionDevice):
+ # Never clear the special first partition on a Mac disk label, as
+ # that holds the partition table itself.
+ # Something similar for the third partition on a Sun disklabel.
+ if device.isMagic:
+ return False
+
+ # We don't want to fool with extended partitions, freespace, &c
+ if not device.isPrimary and not device.isLogical:
+ return False
+
+ if clearPartType == CLEARPART_TYPE_LINUX and \
+ not device.format.linuxNative and \
+ not device.getFlag(parted.PARTITION_LVM) and \
+ not device.getFlag(parted.PARTITION_RAID) and \
+ not device.getFlag(parted.PARTITION_SWAP):
+ return False
+ elif device.isDisk:
+ if device.partitioned and clearPartType != CLEARPART_TYPE_ALL:
+ # if clearPartType is not CLEARPART_TYPE_ALL but we'll still be
+ # removing every partition from the disk, return True since we
+ # will want to be able to create a new disklabel on this disk
+ if not empty_device(device, self.devicetree):
+ return False
+
+ # Never clear disks with hidden formats
+ if device.format.hidden:
+ return False
+
+ # When clearPartType is CLEARPART_TYPE_LINUX and a disk has non-
+ # linux whole-disk formatting, do not clear it. The exception is
+ # the case of an uninitialized disk when we've been asked to
+ # initialize disks as needed
+ if (clearPartType == CLEARPART_TYPE_LINUX and
+ not ((self.config.initializeDisks and
+ empty_device(device, self.devicetree)) or
+ (not device.partitioned and device.format.linuxNative))):
+ return False
+
+ # Don't clear devices holding install media.
+ descendants = self.devicetree.getDependentDevices(device)
+ if device.protected or any(d.protected for d in descendants):
+ return False
+
+ if clearPartType == CLEARPART_TYPE_LIST and \
+ device.name not in clearPartDevices:
+ return False
+
+ return True
+
+ def recursiveRemove(self, device):
+ """ Remove a device after removing its dependent devices.
+
+ If the device is not a leaf, all of its dependents are removed
+ recursively until it is a leaf device. At that point the device is
+ removed, unless it is a disk. If the device is a disk, its
+ formatting is removed by no attempt is made to actually remove the
+ disk device.
+ """
+ log.debug("removing %s", device.name)
+ devices = self.deviceDeps(device)
+
+ # this isn't strictly necessary, but it makes the action list easier to
+ # read when removing logical partitions because of the automatic
+ # renumbering that happens if you remove them in ascending numerical
+ # order
+ devices.reverse()
+
+ while devices:
+ log.debug("devices to remove: %s", [d.name for d in devices])
+ leaves = [d for d in devices if d.isleaf]
+ log.debug("leaves to remove: %s", [d.name for d in leaves])
+ for leaf in leaves:
+ self.destroyDevice(leaf)
+ devices.remove(leaf)
+
+ if device.isDisk:
+ self.devicetree.registerAction(ActionDestroyFormat(device))
+ else:
+ self.destroyDevice(device)
+
+ def clearPartitions(self):
+ """ Clear partitions and dependent devices from disks.
+
+ This is also where zerombr is handled.
+ """
+ # Sort partitions by descending partition number to minimize confusing
+ # things like multiple "destroy sda5" actions due to parted renumbering
+ # partitions. This can still happen through the UI but it makes sense to
+ # avoid it where possible.
+ partitions = sorted(self.partitions,
+ key=lambda p: p.partedPartition.number,
+ reverse=True)
+ for part in partitions:
+ log.debug("clearpart: looking at %s", part.name)
+ if not self.shouldClear(part):
+ continue
+
+ self.recursiveRemove(part)
+ log.debug("partitions: %s", [p.getDeviceNodeName() for p in part.partedPartition.disk.partitions])
+
+ # now remove any empty extended partitions
+ self.removeEmptyExtendedPartitions()
+
+ # ensure all disks have appropriate disklabels
+ for disk in self.disks:
+ zerombr = (self.config.zeroMbr and disk.format.type is None)
+ should_clear = self.shouldClear(disk)
+ if should_clear:
+ self.recursiveRemove(disk)
+
+ if zerombr or should_clear:
+ log.debug("clearpart: initializing %s", disk.name)
+ self.initializeDisk(disk)
+
+ self.updateBootLoaderDiskList()
+
+ def initializeDisk(self, disk):
+ """ (Re)initialize a disk by creating a disklabel on it.
+
+ The disk should not contain any partitions except perhaps for a
+ magic partitions on mac and sun disklabels. If the disk does contain
+ partitions other than the disklabel-type-specific "magic" partitions
+ ValueError will be raised.
+
+ :param disk: the disk to initialize
+ :type disk: :class:`~.devices.StorageDevice`
+ :returns None:
+ :raises: ValueError
+ """
+ # first, remove magic mac/sun partitions from the parted Disk
+ if disk.partitioned:
+ magic = disk.format.magicPartitionNumber
+ expected = 0
+ if magic:
+ expected = 1
+ # remove the magic partition
+ for part in self.devicetree.getChildren(disk):
+ if part.partedPartition.number == magic:
+ log.debug("removing %s", part.name)
+ # We can't schedule the magic partition for removal
+ # because parted will not allow us to remove it from the
+ # disk. Still, we need it out of the devicetree.
+ self.devicetree._removeDevice(part, modparent=False)
+
+ if len(disk.format.partitions) > expected:
+ raise ValueError("cannot initialize a disk that has partitions")
+
+ # remove existing formatting from the disk
+ destroy_action = ActionDestroyFormat(disk)
+ self.devicetree.registerAction(destroy_action)
+
+ labelType = _platform.bestDiskLabelType(disk)
+
+ # create a new disklabel on the disk
+ newLabel = getFormat("disklabel", device=disk.path,
+ labelType=labelType)
+ create_action = ActionCreateFormat(disk, fmt=newLabel)
+ self.devicetree.registerAction(create_action)
+
+ def removeEmptyExtendedPartitions(self):
+ for disk in self.partitioned:
+ log.debug("checking whether disk %s has an empty extended", disk.name)
+ extended = disk.format.extendedPartition
+ logical_parts = disk.format.logicalPartitions
+ log.debug("extended is %s ; logicals is %s", extended, [p.getDeviceNodeName() for p in logical_parts])
+ if extended and not logical_parts:
+ log.debug("removing empty extended partition from %s", disk.name)
+ extended_name = devicePathToName(extended.getDeviceNodeName())
+ extended = self.devicetree.getDeviceByName(extended_name)
+ self.destroyDevice(extended)
+
+ def getFreeSpace(self, disks=None, clearPartType=None):
+ """ Return a dict with free space info for each disk.
+
+ The dict values are 2-tuples: (disk_free, fs_free). fs_free is
+ space available by shrinking filesystems. disk_free is space not
+ allocated to any partition.
+
+ disks and clearPartType allow specifying a set of disks other than
+ self.disks and a clearPartType value other than
+ self.config.clearPartType.
+
+ :keyword disks: overrides :attr:`disks`
+ :type disks: list
+ :keyword clearPartType: overrides :attr:`self.config.clearPartType`
+ :type clearPartType: int
+ :returns: dict with disk name keys and tuple (disk, fs) free values
+ :rtype: dict
+
+ .. note::
+
+ The free space values are :class:`~.size.Size` instances.
+
+ """
+ if disks is None:
+ disks = self.disks
+
+ if clearPartType is None:
+ clearPartType = self.config.clearPartType
+
+ free = {}
+ for disk in disks:
+ should_clear = self.shouldClear(disk, clearPartType=clearPartType,
+ clearPartDisks=[disk.name])
+ if should_clear:
+ free[disk.name] = (disk.size, Size(0))
+ continue
+
+ disk_free = Size(0)
+ fs_free = Size(0)
+ if disk.partitioned:
+ disk_free = disk.format.free
+ for partition in [p for p in self.partitions if p.disk == disk]:
+ # only check actual filesystems since lvm &c require a bunch of
+ # operations to translate free filesystem space into free disk
+ # space
+ should_clear = self.shouldClear(partition,
+ clearPartType=clearPartType,
+ clearPartDisks=[disk.name])
+ if should_clear:
+ disk_free += partition.size
+ elif hasattr(partition.format, "free"):
+ fs_free += partition.format.free
+ elif hasattr(disk.format, "free"):
+ fs_free = disk.format.free
+ elif disk.format.type is None:
+ disk_free = disk.size
+
+ free[disk.name] = (disk_free, fs_free)
+
+ return free
+
+ @property
+ def names(self):
+ """ A list of all of the known in-use device names. """
+ return self.devicetree.names
+
+ def deviceDeps(self, device):
+ """ Return a list of the devices that depend on the specified device.
+
+ :param device: the subtree root device
+ :type device: :class:`~.devices.StorageDevice`
+ :returns: list of dependent devices
+ :rtype: list
+ """
+ return self.devicetree.getDependentDevices(device)
+
+ def newPartition(self, *args, **kwargs):
+ """ Return a new (unallocated) PartitionDevice instance.
+
+ :keyword fmt_type: format type
+ :type fmt_type: str
+ :keyword fmt_args: arguments for format constructor
+ :type fmt_args: dict
+ :keyword mountpoint: mountpoint for format (filesystem)
+ :type mountpoint: str
+
+ All other arguments are passed on to the
+ :class:`~.devices.PartitionDevice` constructor.
+ """
+ if 'fmt_type' in kwargs:
+ kwargs["fmt"] = getFormat(kwargs.pop("fmt_type"),
+ mountpoint=kwargs.pop("mountpoint",
+ None),
+ **kwargs.pop("fmt_args", {}))
+
+ if 'name' in kwargs:
+ name = kwargs.pop("name")
+ else:
+ name = "req%d" % self.nextID
+
+ if "weight" not in kwargs:
+ fmt = kwargs.get("fmt")
+ if fmt:
+ mountpoint = getattr(fmt, "mountpoint", None)
+
+ kwargs["weight"] = _platform.weight(mountpoint=mountpoint,
+ fstype=fmt.type)
+
+
+ return PartitionDevice(name, *args, **kwargs)
+
+ def newMDArray(self, *args, **kwargs):
+ """ Return a new MDRaidArrayDevice instance.
+
+ :keyword fmt_type: format type
+ :type fmt_type: str
+ :keyword fmt_args: arguments for format constructor
+ :type fmt_args: dict
+ :keyword mountpoint: mountpoint for format (filesystem)
+ :type mountpoint: str
+ :returns: the new md array device
+ :rtype: :class:`~.devices.MDRaidArrayDevice`
+
+ All other arguments are passed on to the
+ :class:`~.devices.MDRaidArrayDevice` constructor.
+
+ If a name is not specified, one will be generated based on the
+ format type, mountpoint, hostname, and/or product name.
+ """
+ if 'fmt_type' in kwargs:
+ kwargs["fmt"] = getFormat(kwargs.pop("fmt_type"),
+ mountpoint=kwargs.pop("mountpoint",
+ None),
+ **kwargs.pop("fmt_args", {}))
+
+ name = kwargs.pop("name", None)
+ if name:
+ safe_name = self.safeDeviceName(name)
+ if safe_name != name:
+ log.warning("using '%s' instead of specified name '%s'",
+ safe_name, name)
+ name = safe_name
+ else:
+ swap = getattr(kwargs.get("fmt"), "type", None) == "swap"
+ mountpoint = getattr(kwargs.get("fmt"), "mountpoint", None)
+ name = self.suggestDeviceName(prefix=shortProductName,
+ swap=swap,
+ mountpoint=mountpoint)
+
+ return MDRaidArrayDevice(name, *args, **kwargs)
+
+ def newVG(self, *args, **kwargs):
+ """ Return a new LVMVolumeGroupDevice instance.
+
+ :returns: the new volume group device
+ :rtype: :class:`~.devices.LVMVolumeGroupDevice`
+
+ All arguments are passed on to the
+ :class:`~.devices.LVMVolumeGroupDevice` constructor.
+
+ If a name is not specified, one will be generated based on the
+ hostname, and/or product name.
+ """
+ pvs = kwargs.pop("parents", [])
+ for pv in pvs:
+ if pv not in self.devices:
+ raise ValueError("pv is not in the device tree")
+
+ name = kwargs.pop("name", None)
+ if name:
+ safe_name = self.safeDeviceName(name)
+ if safe_name != name:
+ log.warning("using '%s' instead of specified name '%s'",
+ safe_name, name)
+ name = safe_name
+ else:
+ hostname = ""
+ if self.ksdata and self.ksdata.network.hostname is not None:
+ hostname = self.ksdata.network.hostname
+
+ name = self.suggestContainerName(hostname=hostname)
+
+ if name in self.names:
+ raise ValueError("name already in use")
+
+ return LVMVolumeGroupDevice(name, pvs, *args, **kwargs)
+
+ def newLV(self, *args, **kwargs):
+ """ Return a new LVMLogicalVolumeDevice instance.
+
+ :keyword fmt_type: format type
+ :type fmt_type: str
+ :keyword fmt_args: arguments for format constructor
+ :type fmt_args: dict
+ :keyword mountpoint: mountpoint for format (filesystem)
+ :type mountpoint: str
+ :keyword thin_pool: whether to create a thin pool
+ :type thin_pool: bool
+ :keyword thin_volume: whether to create a thin volume
+ :type thin_volume: bool
+ :returns: the new device
+ :rtype: :class:`~.devices.LVMLogicalVolumeDevice`
+
+ All other arguments are passed on to the appropriate
+ :class:`~.devices.LVMLogicalVolumeDevice` constructor.
+
+ If a name is not specified, one will be generated based on the
+ format type and/or mountpoint.
+
+ .. note::
+
+ If you are creating a thin volume, the parents kwarg should
+ contain the pool -- not the vg.
+ """
+ thin_volume = kwargs.pop("thin_volume", False)
+ thin_pool = kwargs.pop("thin_pool", False)
+ vg = kwargs.get("parents", [None])[0]
+ if thin_volume and vg:
+ # kwargs["parents"] will contain the pool device, so...
+ vg = vg.vg
+
+ mountpoint = kwargs.pop("mountpoint", None)
+ if 'fmt_type' in kwargs:
+ kwargs["fmt"] = getFormat(kwargs.pop("fmt_type"),
+ mountpoint=mountpoint,
+ **kwargs.pop("fmt_args", {}))
+
+ name = kwargs.pop("name", None)
+ if name:
+ # make sure the specified name is sensible
+ safe_vg_name = self.safeDeviceName(vg.name)
+ full_name = "%s-%s" % (safe_vg_name, name)
+ safe_name = self.safeDeviceName(full_name)
+ if safe_name != full_name:
+ new_name = safe_name[len(safe_vg_name)+1:]
+ log.warning("using '%s' instead of specified name '%s'",
+ new_name, name)
+ name = new_name
+ else:
+ if kwargs.get("fmt") and kwargs["fmt"].type == "swap":
+ swap = True
+ else:
+ swap = False
+
+ prefix = ""
+ if thin_pool:
+ prefix = "pool"
+
+ name = self.suggestDeviceName(parent=vg,
+ swap=swap,
+ mountpoint=mountpoint,
+ prefix=prefix)
+
+ if "%s-%s" % (vg.name, name) in self.names:
+ raise ValueError("name already in use")
+
+ if thin_pool:
+ device_class = LVMThinPoolDevice
+ elif thin_volume:
+ device_class = LVMThinLogicalVolumeDevice
+ else:
+ device_class = LVMLogicalVolumeDevice
+
+ return device_class(name, *args, **kwargs)
+
+ def newBTRFS(self, *args, **kwargs):
+ """ Return a new BTRFSVolumeDevice or BRFSSubVolumeDevice.
+
+ :keyword fmt_args: arguments for format constructor
+ :type fmt_args: dict
+ :keyword mountpoint: mountpoint for format (filesystem)
+ :type mountpoint: str
+ :keyword subvol: whether this is a subvol (as opposed to a volume)
+ :type subvol: bool
+ :returns: the new device
+ :rtype: :class:`~.devices.BTRFSDevice`
+
+ All other arguments are passed on to the appropriate
+ :class:`~.devices.BTRFSDevice` constructor.
+
+ For volumes, the label is the same as the name. If a name/label is
+ not specified, one will be generated based on the hostname and/or
+ product name.
+
+ .. note::
+
+ If you are creating a subvolume, the parents kwarg should
+ contain the volume you want to contain the subvolume.
+
+ """
+ log.debug("newBTRFS: args = %s ; kwargs = %s", args, kwargs)
+ name = kwargs.pop("name", None)
+ if args:
+ name = args[0]
+
+ mountpoint = kwargs.pop("mountpoint", None)
+
+ fmt_args = kwargs.pop("fmt_args", {})
+ fmt_args.update({"mountpoint": mountpoint})
+
+ if kwargs.pop("subvol", False):
+ dev_class = BTRFSSubVolumeDevice
+
+ # set up the subvol name, using mountpoint if necessary
+ if not name:
+ # for btrfs this only needs to ensure the subvol name is not
+ # already in use within the parent volume
+ name = self.suggestDeviceName(mountpoint=mountpoint)
+ fmt_args["mountopts"] = "subvol=%s" % name
+ kwargs.pop("metaDataLevel", None)
+ kwargs.pop("dataLevel", None)
+ else:
+ dev_class = BTRFSVolumeDevice
+ # set up the volume label, using hostname if necessary
+ if not name:
+ hostname = ""
+ if self.ksdata and self.ksdata.network.hostname is not None:
+ hostname = self.ksdata.network.hostname
+
+ name = self.suggestContainerName(hostname=hostname)
+ if "label" not in fmt_args:
+ fmt_args["label"] = name
+
+ # discard fmt_type since it's btrfs always
+ kwargs.pop("fmt_type", None)
+
+ # this is to avoid auto-scheduled format create actions
+ device = dev_class(name, **kwargs)
+ device.format = getFormat("btrfs", **fmt_args)
+ return device
+
+ def newBTRFSSubVolume(self, *args, **kwargs):
+ """ Return a new BRFSSubVolumeDevice.
+
+ :keyword fmt_args: arguments for format constructor
+ :type fmt_args: dict
+ :keyword mountpoint: mountpoint for format (filesystem)
+ :type mountpoint: str
+ :returns: the new device
+ :rtype: :class:`~.devices.BTRFSSubVolumeDevice`
+
+ All other arguments are passed on to the
+ :class:`~.devices.BTRFSSubVolumeDevice` constructor.
+
+ .. note::
+
+ Since you are creating a subvolume, the parents kwarg should
+ contain the volume you want to contain the subvolume.
+
+ """
+ kwargs["subvol"] = True
+ return self.newBTRFS(*args, **kwargs)
+
+ def newTmpFS(self, *args, **kwargs):
+ """ Return a new TmpFSDevice. """
+ return TmpFSDevice(*args, **kwargs)
+
+ def createDevice(self, device):
+ """ Schedule creation of a device.
+
+ :param device: the device to schedule creation of
+ :type device: :class:`~.devices.StorageDevice`
+ :rtype: None
+ """
+ self.devicetree.registerAction(ActionCreateDevice(device))
+ if device.format.type and not device.formatImmutable:
+ self.devicetree.registerAction(ActionCreateFormat(device))
+
+ def destroyDevice(self, device):
+ """ Schedule destruction of a device.
+
+ :param device: the device to schedule destruction of
+ :type device: :class:`~.devices.StorageDevice`
+ :rtype: None
+ """
+ if device.protected:
+ raise ValueError("cannot modify protected device")
+
+ if device.format.exists and device.format.type and \
+ not device.formatImmutable:
+ # schedule destruction of any formatting while we're at it
+ self.devicetree.registerAction(ActionDestroyFormat(device))
+
+ action = ActionDestroyDevice(device)
+ self.devicetree.registerAction(action)
+
+ def formatDevice(self, device, fmt):
+ """ Schedule formatting of a device.
+
+ :param device: the device to create the formatting on
+ :type device: :class:`~.devices.StorageDevice`
+ :param fmt: the format to create on the device
+ :type format: :class:`~.formats.DeviceFormat`
+ :rtype: None
+
+ A format destroy action will be scheduled first, so it is not
+ necessary to create and schedule an
+ :class:`~.deviceaction.ActionDestroyFormat` prior to calling this
+ method.
+ """
+ if device.protected:
+ raise ValueError("cannot modify protected device")
+
+ self.devicetree.registerAction(ActionDestroyFormat(device))
+ self.devicetree.registerAction(ActionCreateFormat(device, fmt))
+
+ def resetDevice(self, device):
+ """ Cancel all scheduled actions and reset formatting.
+
+ :param device: the device to reset
+ :type device: :class:`~.devices.StorageDevice`
+ :rtype: None
+ """
+ actions = self.devicetree.findActions(device=device)
+ for action in reversed(actions):
+ self.devicetree.cancelAction(action)
+
+ # make sure any random overridden attributes are reset
+ device.format = copy.copy(device.originalFormat)
+
+ def resizeDevice(self, device, new_size):
+ """ Schedule a resize of a device and its formatting, if any.
+
+ :param device: the device to resize
+ :type device: :class:`~.devices.StorageDevice`
+ :param new_size: the new target size for the device
+ :type new_size: :class:`~.size.Size`
+ :rtype: None
+
+ If the device has formatting that is recognized as being resizable
+ an action will be scheduled to resize it as well.
+ """
+ if device.protected:
+ raise ValueError("cannot modify protected device")
+
+ classes = []
+ if device.resizable:
+ classes.append(ActionResizeDevice)
+
+ if device.format.resizable:
+ classes.append(ActionResizeFormat)
+
+ if not classes:
+ raise ValueError("device cannot be resized")
+
+ # if this is a shrink, schedule the format resize first
+ if new_size < device.size:
+ classes.reverse()
+
+ for action_class in classes:
+ self.devicetree.registerAction(action_class(device, new_size))
+
+ def formatByDefault(self, device):
+ """Return whether the device should be reformatted by default."""
+ formatlist = ['/boot', '/var', '/tmp', '/usr']
+ exceptlist = ['/home', '/usr/local', '/opt', '/var/www']
+
+ if not device.format.linuxNative:
+ return False
+
+ if device.format.mountable:
+ if not device.format.mountpoint:
+ return False
+
+ if device.format.mountpoint == "/" or \
+ device.format.mountpoint in formatlist:
+ return True
+
+ for p in formatlist:
+ if device.format.mountpoint.startswith(p):
+ for q in exceptlist:
+ if device.format.mountpoint.startswith(q):
+ return False
+ return True
+ elif device.format.type == "swap":
+ return True
+
+ # be safe for anything else and default to off
+ return False
+
+ def mustFormat(self, device):
+ """ Return a string explaining why the device must be reformatted.
+
+ Return None if the device need not be reformatted.
+ """
+ if device.format.mountable and device.format.mountpoint == "/":
+ return _("You must create a new filesystem on the root device.")
+
+ return None
+
+ def safeDeviceName(self, name):
+ """ Convert a device name to something safe and return that.
+
+ LVM limits lv names to 128 characters. I don't know the limits for
+ the other various device types, so I'm going to pick a number so
+ that we don't have to have an entire fucking library to determine
+ device name limits.
+ """
+ max_len = 96 # No, you don't need longer names than this. Really.
+ tmp = name.strip()
+ tmp = tmp.replace("/", "_")
+ tmp = re.sub("[^0-9a-zA-Z._-]", "", tmp)
+
+ # Remove any '-' or '_' prefixes
+ tmp = re.sub("^[-_]*", "", tmp)
+
+ # If all that's left is . or .., give up
+ if tmp == "." or tmp == "..":
+ return ""
+
+ if len(tmp) > max_len:
+ tmp = tmp[:max_len]
+
+ return tmp
+
+ def suggestContainerName(self, hostname=None, prefix=""):
+ """ Return a reasonable, unused device name.
+
+ :keyword hostname: the system's hostname
+ :keyword prefix: a prefix for the container name
+ :returns: the suggested name
+ :rtype: str
+ """
+ if not prefix:
+ prefix = shortProductName
+
+ # try to create a device name incorporating the hostname
+ if hostname not in (None, "", 'localhost', 'localhost.localdomain'):
+ template = "%s_%s" % (prefix, hostname.split('.')[0].lower())
+ template = self.safeDeviceName(template)
+ else:
+ template = prefix
+
+ if flags.image_install:
+ template = "%s_image" % template
+
+ names = self.names
+ name = template
+ if name in names:
+ name = None
+ for i in range(100):
+ tmpname = "%s%02d" % (template, i,)
+ if tmpname not in names:
+ name = tmpname
+ break
+
+ if not name:
+ log.error("failed to create device name based on prefix "
+ "'%s' and hostname '%s'", prefix, hostname)
+ raise RuntimeError("unable to find suitable device name")
+
+ return name
+
+ def suggestDeviceName(self, parent=None, swap=None,
+ mountpoint=None, prefix=""):
+ """ Return a suitable, unused name for a new device.
+
+ :keyword parent: the parent device
+ :type parent: :class:`~.devices.StorageDevice`
+ :keyword swap: will this be a swap device
+ :type swap: bool
+ :keyword mountpoint: the device's mountpoint
+ :type mountpoint: str
+ :keyword prefix: device name prefix
+ :type prefix: str
+ :returns: the suggested name
+ :rtype: str
+ """
+ body = ""
+ if mountpoint:
+ if mountpoint == "/":
+ body = "root"
+ else:
+ body = mountpoint[1:].replace("/", "_")
+ elif swap:
+ body = "swap"
+
+ if prefix and body:
+ body = "_" + body
+
+ template = self.safeDeviceName(prefix + body)
+ names = self.names
+ name = template
+ def full_name(name, parent):
+ full = ""
+ if parent:
+ full = "%s-" % parent.name
+ full += name
+ return full
+
+ # also include names of any lvs in the parent for the case of the
+ # temporary vg in the lvm dialogs, which can contain lvs that are
+ # not yet in the devicetree and therefore not in self.names
+ if full_name(name, parent) in names or not body:
+ for i in range(100):
+ name = "%s%02d" % (template, i)
+ if full_name(name, parent) not in names:
+ break
+ else:
+ name = ""
+
+ if not name:
+ log.error("failed to create device name based on parent '%s', "
+ "prefix '%s', mountpoint '%s', swap '%s'",
+ parent.name, prefix, mountpoint, swap)
+ raise RuntimeError("unable to find suitable device name")
+
+ return name
+
+ def savePassphrase(self, device):
+ """ Save a device's LUKS passphrase in case of reset. """
+ passphrase = device.format._LUKS__passphrase
+ self.__luksDevs[device.format.uuid] = passphrase
+ self.devicetree._DeviceTree__luksDevs[device.format.uuid] = passphrase
+ self.devicetree._DeviceTree__passphrases.append(passphrase)
+
+ def setupDiskImages(self):
+ self.devicetree.setDiskImages(self.config.diskImages)
+ self.devicetree.setupDiskImages()
+
+ @property
+ def fileSystemFreeSpace(self):
+ """ Combined free space in / and /usr as :class:`~.size.Size`. """
+ mountpoints = ["/", "/usr"]
+ free = Size(0)
+ btrfs_volumes = []
+ for mountpoint in mountpoints:
+ device = self.mountpoints.get(mountpoint)
+ if not device:
+ continue
+
+ # don't count the size of btrfs volumes repeatedly when multiple
+ # subvolumes are present
+ if isinstance(device, BTRFSSubVolumeDevice):
+ if device.volume in btrfs_volumes:
+ continue
+ else:
+ btrfs_volumes.append(device.volume)
+
+ if device.format.exists:
+ free += device.format.free
+ else:
+ free += device.size
+
+ return free
+
+ def dumpState(self, suffix):
+ """ Dump the current device list to the storage shelf. """
+ key = "devices.%d.%s" % (time.time(), suffix)
+ with contextlib.closing(shelve.open(self._dumpFile)) as shelf:
+ try:
+ shelf[key] = [d.dict for d in self.devices]
+ except AttributeError:
+ log_exception_info()
+
+ @property
+ def packages(self):
+ pkgs = set()
+ pkgs.update(_platform.packages)
+
+ # install support packages for all devices in the system
+ for device in self.devices:
+ # this takes care of device and filesystem packages
+ pkgs.update(device.packages)
+
+ return list(pkgs)
+
+ def write(self):
+ """ Write out all storage-related configuration files. """
+ if not os.path.isdir("%s/etc" % getSysroot()):
+ os.mkdir("%s/etc" % getSysroot())
+
+ self.fsset.write()
+ self.makeMtab()
+ self.iscsi.write(getSysroot(), self)
+ self.fcoe.write(getSysroot())
+ self.zfcp.write(getSysroot())
+ write_dasd_conf(self.dasd, getSysroot())
+
+ def turnOnSwap(self):
+ self.fsset.turnOnSwap(rootPath=getSysroot())
+
+ def mountFilesystems(self, readOnly=None, skipRoot=False):
+ self.fsset.mountFilesystems(rootPath=getSysroot(),
+ readOnly=readOnly, skipRoot=skipRoot)
+
+ def umountFilesystems(self, swapoff=True):
+ self.fsset.umountFilesystems(swapoff=swapoff)
+
+ def parseFSTab(self, chroot=None):
+ self.fsset.parseFSTab(chroot=chroot)
+
+ def mkDevRoot(self):
+ self.fsset.mkDevRoot()
+
+ def createSwapFile(self, device, size):
+ self.fsset.createSwapFile(device, size)
+
+ @property
+ def bootloader(self):
+ if self._bootloader is None and flags.installer_mode:
+ self._bootloader = get_bootloader()
+
+ return self._bootloader
+
+ def updateBootLoaderDiskList(self):
+ if not self.bootloader:
+ return
+
+ boot_disks = [d for d in self.disks if d.partitioned]
+ boot_disks.sort(cmp=self.compareDisks, key=lambda d: d.name)
+ self.bootloader.set_disk_list(boot_disks)
+
+ def setUpBootLoader(self, early=False):
+ """ Propagate ksdata into BootLoader.
+
+ :keyword bool early: Set to True to skip stage1_device setup
+
+ :raises BootloaderError: if stage1 setup fails
+
+ If this needs to be run early, eg. to setup stage1_disk but
+ not stage1_device 'early' should be set True to prevent
+ it from raising BootloaderError
+ """
+ if not self.bootloader or not self.ksdata:
+ log.warning("either ksdata or bootloader data missing")
+ return
+
+ if self.bootloader.skip_bootloader:
+ log.info("user specified that bootloader install be skipped")
+ return
+
+ # Need to make sure bootDrive has been setup from the latest information
+ self.ksdata.bootloader.execute(self, self.ksdata, None)
+ self.bootloader.stage1_disk = self.devicetree.resolveDevice(self.ksdata.bootloader.bootDrive)
+ self.bootloader.stage2_device = self.bootDevice
+ if not early:
+ self.bootloader.set_stage1_device(self.devices)
+
+ @property
+ def bootDisk(self):
+ disk = None
+ if self.ksdata:
+ spec = self.ksdata.bootloader.bootDrive
+ disk = self.devicetree.resolveDevice(spec)
+ return disk
+
+ @property
+ def bootDevice(self):
+ dev = None
+ if self.fsset:
+ dev = self.mountpoints.get("/boot", self.rootDevice)
+ return dev
+
+ @property
+ def bootLoaderDevice(self):
+ return getattr(self.bootloader, "stage1_device", None)
+
+ @property
+ def bootFSTypes(self):
+ """A list of all valid filesystem types for the boot partition."""
+ fstypes = []
+ if self.bootloader:
+ fstypes = self.bootloader.stage2_format_types
+ return fstypes
+
+ @property
+ def defaultBootFSType(self):
+ """The default filesystem type for the boot partition."""
+ if self._defaultBootFSType:
+ return self._defaultBootFSType
+
+ fstype = None
+ if self.bootloader:
+ fstype = self.bootFSTypes[0]
+ return fstype
+
+ def _check_valid_fstype(self, newtype):
+ """ Check the fstype to see if it is valid
+
+ Raise ValueError on invalid input.
+ """
+ fmt = getFormat(newtype)
+ if fmt.type is None:
+ raise ValueError("unrecognized value %s for new default fs type" % newtype)
+
+ if (not fmt.mountable or not fmt.formattable or not fmt.supported or
+ not fmt.linuxNative):
+ log.debug("invalid default fstype: %r", fmt)
+ raise ValueError("new value %s is not valid as a default fs type" % fmt)
+
+ self._defaultFSType = newtype # pylint: disable=attribute-defined-outside-init
+
+ def setDefaultBootFSType(self, newtype):
+ """ Set the default /boot fstype for this instance.
+
+ Raise ValueError on invalid input.
+ """
+ log.debug("trying to set new default /boot fstype to '%s'", newtype)
+ # This will raise ValueError if it isn't valid
+ self._check_valid_fstype(newtype)
+ self._defaultBootFSType = newtype
+
+ @property
+ def defaultFSType(self):
+ return self._defaultFSType
+
+ def setDefaultFSType(self, newtype):
+ """ Set the default fstype for this instance.
+
+ Raise ValueError on invalid input.
+ """
+ log.debug("trying to set new default fstype to '%s'", newtype)
+ # This will raise ValueError if it isn't valid
+ self._check_valid_fstype(newtype)
+ self._defaultFSType = newtype # pylint: disable=attribute-defined-outside-init
+
+ @property
+ def mountpoints(self):
+ return self.fsset.mountpoints
+
+ @property
+ def rootDevice(self):
+ return self.fsset.rootDevice
+
+ def makeMtab(self):
+ path = "/etc/mtab"
+ target = "/proc/self/mounts"
+ path = os.path.normpath("%s/%s" % (getSysroot(), path))
+
+ if os.path.islink(path):
+ # return early if the mtab symlink is already how we like it
+ current_target = os.path.normpath(os.path.dirname(path) +
+ "/" + os.readlink(path))
+ if current_target == target:
+ return
+
+ if os.path.exists(path):
+ os.unlink(path)
+
+ os.symlink(target, path)
+
+ def compareDisks(self, first, second):
+ if first in self.eddDict and second in self.eddDict:
+ one = self.eddDict[first]
+ two = self.eddDict[second]
+ if (one < two):
+ return -1
+ elif (one > two):
+ return 1
+
+ # if one is in the BIOS and the other not prefer the one in the BIOS
+ if first in self.eddDict:
+ return -1
+ if second in self.eddDict:
+ return 1
+
+ if first.startswith("hd"):
+ type1 = 0
+ elif first.startswith("sd"):
+ type1 = 1
+ elif (first.startswith("vd") or first.startswith("xvd")):
+ type1 = -1
+ else:
+ type1 = 2
+
+ if second.startswith("hd"):
+ type2 = 0
+ elif second.startswith("sd"):
+ type2 = 1
+ elif (second.startswith("vd") or second.startswith("xvd")):
+ type2 = -1
+ else:
+ type2 = 2
+
+ if (type1 < type2):
+ return -1
+ elif (type1 > type2):
+ return 1
+ else:
+ len1 = len(first)
+ len2 = len(second)
+
+ if (len1 < len2):
+ return -1
+ elif (len1 > len2):
+ return 1
+ else:
+ if (first < second):
+ return -1
+ elif (first > second):
+ return 1
+
+ return 0
+
+ def getFSType(self, mountpoint=None):
+ """ Return the default filesystem type based on mountpoint. """
+ fstype = self.defaultFSType
+ if not mountpoint:
+ # just return the default
+ pass
+ elif mountpoint.lower() in ("swap", "biosboot", "prepboot"):
+ fstype = mountpoint.lower()
+ elif mountpoint == "/boot":
+ fstype = self.defaultBootFSType
+ elif mountpoint == "/boot/efi":
+ if arch.isMactel():
+ fstype = "macefi"
+ else:
+ fstype = "efi"
+
+ return fstype
+
+ def factoryDevice(self, device_type, size, **kwargs):
+ """ Schedule creation of a device based on a top-down specification.
+
+ :param device_type: device type constant
+ :type device_type: int (:const:`~.devicefactory.DEVICE_TYPE_*`)
+ :param size: requested size
+ :type size: :class:`~.size.Size`
+ :returns: the newly configured device
+ :rtype: :class:`~.devices.StorageDevice`
+
+ See :class:`~.devicefactory.DeviceFactory` for possible kwargs.
+
+ """
+ log_method_call(self, device_type, size, **kwargs)
+
+ # 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 kwargs.get("fstype"):
+ kwargs["fstype"] = self.getFSType(mountpoint=kwargs.get("mountpoint"))
+ if kwargs["fstype"] == "swap":
+ kwargs["mountpoint"] = None
+
+ if kwargs["fstype"] == "swap" and \
+ device_type == devicefactory.DEVICE_TYPE_BTRFS:
+ device_type = devicefactory.DEVICE_TYPE_PARTITION
+
+ 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
+ factory.configure()
+ return factory.device
+
+ def copy(self):
+ log.debug("starting Blivet copy")
+ new = copy.deepcopy(self)
+ # go through and re-get partedPartitions from the disks since they
+ # don't get deep-copied
+ hidden_partitions = [d for d in new.devicetree._hidden
+ if isinstance(d, PartitionDevice)]
+ for partition in new.partitions + hidden_partitions:
+ if not partition._partedPartition:
+ continue
+
+ # update the refs in req_disks as well
+ req_disks = (new.devicetree.getDeviceByID(disk.id) for disk in partition.req_disks)
+ partition.req_disks = [disk for disk in req_disks if disk is not None]
+
+ p = partition.disk.format.partedDisk.getPartitionByPath(partition.path)
+ partition.partedPartition = p
+
+ for root in new.roots:
+ root.swaps = [new.devicetree.getDeviceByID(d.id, hidden=True) for d in root.swaps]
+ root.swaps = [s for s in root.swaps if s]
+
+ for (mountpoint, old_dev) in root.mounts.items():
+ if old_dev is None:
+ continue
+
+ new_dev = new.devicetree.getDeviceByID(old_dev.id, hidden=True)
+ if new_dev is None:
+ # if the device has been removed don't include this
+ # mountpoint at all
+ del root.mounts[mountpoint]
+ else:
+ root.mounts[mountpoint] = new_dev
+
+ log.debug("finished Blivet copy")
+ return new
+
+ def updateKSData(self):
+ """ Update ksdata to reflect the settings of this Blivet instance. """
+ if not self.ksdata or not self.mountpoints:
+ return
+
+ # clear out whatever was there before
+ self.ksdata.partition.partitions = []
+ self.ksdata.logvol.lvList = []
+ self.ksdata.raid.raidList = []
+ self.ksdata.volgroup.vgList = []
+ self.ksdata.btrfs.btrfsList = []
+
+ # iscsi?
+ # fcoe?
+ # zfcp?
+ # dmraid?
+
+ # bootloader
+
+ # ignoredisk
+ if self.config.ignoredDisks:
+ self.ksdata.ignoredisk.drives = self.config.ignoredDisks[:]
+ elif self.config.exclusiveDisks:
+ self.ksdata.ignoredisk.onlyuse = self.config.exclusiveDisks[:]
+
+ # autopart
+ self.ksdata.autopart.autopart = self.doAutoPart
+ self.ksdata.autopart.type = self.autoPartType
+ self.ksdata.autopart.encrypted = self.encryptedAutoPart
+
+ # clearpart
+ self.ksdata.clearpart.type = self.config.clearPartType
+ self.ksdata.clearpart.drives = self.config.clearPartDisks[:]
+ self.ksdata.clearpart.devices = self.config.clearPartDevices[:]
+ self.ksdata.clearpart.initAll = self.config.initializeDisks
+ if self.ksdata.clearpart.type == CLEARPART_TYPE_NONE:
+ # Make a list of initialized disks and of removed partitions. If any
+ # partitions were removed from disks that were not completely
+ # cleared we'll have to use CLEARPART_TYPE_LIST and provide a list
+ # of all removed partitions. If no partitions were removed from a
+ # disk that was not cleared/reinitialized we can use
+ # CLEARPART_TYPE_ALL.
+ self.ksdata.clearpart.devices = []
+ self.ksdata.clearpart.drives = []
+ fresh_disks = [d.name for d in self.disks if d.partitioned and
+ not d.format.exists]
+
+ destroy_actions = self.devicetree.findActions(action_type="destroy",
+ object_type="device")
+
+ cleared_partitions = []
+ partial = False
+ for action in destroy_actions:
+ if action.device.type == "partition":
+ if action.device.disk.name not in fresh_disks:
+ partial = True
+
+ cleared_partitions.append(action.device.name)
+
+ if not destroy_actions:
+ pass
+ elif partial:
+ # make a list of removed partitions
+ self.ksdata.clearpart.type = CLEARPART_TYPE_LIST
+ self.ksdata.clearpart.devices = cleared_partitions
+ else:
+ # if they didn't partially clear any disks, use the shorthand
+ self.ksdata.clearpart.type = CLEARPART_TYPE_ALL
+ self.ksdata.clearpart.drives = fresh_disks
+
+ if self.doAutoPart:
+ return
+
+ # custom storage
+ ksMap = {PartitionDevice: ("PartData", "partition"),
+ TmpFSDevice: ("PartData", "partition"),
+ LVMLogicalVolumeDevice: ("LogVolData", "logvol"),
+ LVMVolumeGroupDevice: ("VolGroupData", "volgroup"),
+ MDRaidArrayDevice: ("RaidData", "raid"),
+ BTRFSDevice: ("BTRFSData", "btrfs")}
+
+ # make a list of ancestors of all used devices
+ devices = list(set(a for d in list(self.mountpoints.values()) + self.swaps
+ for a in d.ancestors))
+ devices.sort(key=lambda d: len(d.ancestors))
+ for device in devices:
+ cls = next((c for c in ksMap if isinstance(device, c)), None)
+ if cls is None:
+ log.info("omitting ksdata: %s", device)
+ continue
+
+ class_attr, list_attr = ksMap[cls]
+
+ cls = getattr(self.ksdata, class_attr)
+ data = cls() # all defaults
+
+ device.populateKSData(data)
+
+ parent = getattr(self.ksdata, list_attr)
+ parent.dataList().append(data)
+
+ @property
+ def freeSpaceSnapshot(self):
+ # if no snapshot is available, do it now and return it
+ self._free_space_snapshot = self._free_space_snapshot or self.getFreeSpace()
+
+ return self._free_space_snapshot
+
+ def createFreeSpaceSnapshot(self):
+ self._free_space_snapshot = self.getFreeSpace()
+
+ return self._free_space_snapshot
+
+ def addFstabSwap(self, device):
+ """
+ Add swap device to the list of swaps that should appear in the fstab.
+
+ :param device: swap device that should be added to the list
+ :type device: blivet.devices.StorageDevice instance holding a swap format
+
+ """
+
+ self.fsset.addFstabSwap(device)
+
+ def removeFstabSwap(self, device):
+ """
+ Remove swap device from the list of swaps that should appear in the fstab.
+
+ :param device: swap device that should be removed from the list
+ :type device: blivet.devices.StorageDevice instance holding a swap format
+
+ """
+
+ self.fsset.removeFstabSwap(device)
+
+ def setFstabSwaps(self, devices):
+ """
+ Set swap devices that should appear in the fstab.
+
+ :param devices: iterable providing devices that should appear in the fstab
+ :type devices: iterable providing blivet.devices.StorageDevice instances holding
+ a swap format
+
+ """
+
+ self.fsset.setFstabSwaps(devices)
+
diff --git a/tests/pylint/pylint-false-positives b/tests/pylint/pylint-false-positives
index ee4247a..cfa3b88 100644
--- a/tests/pylint/pylint-false-positives
+++ b/tests/pylint/pylint-false-positives
@@ -1,7 +1,7 @@
^blivet/arch.py:[[:digit:]]+: \[F0401\(import\-error\), numBits\] Unable to import 'platform'$
^blivet/formats/__init__.py:[[:digit:]]+: \[W0201\(attribute\-defined\-outside\-init\), DeviceFormat._setDevice\] Attribute '_device' defined outside __init__$
-^blivet/__init__.py:[[:digit:]]+: \[E1101\(no\-member\), Blivet.savePassphrase\] Instance of 'DeviceTree' has no '_DeviceTree__luksDevs' member$
-^blivet/__init__.py:[[:digit:]]+: \[E1101\(no\-member\), Blivet.savePassphrase\] Instance of 'DeviceTree' has no '_DeviceTree__passphrases' member$
+^blivet/blivet.py:[[:digit:]]+: \[E1101\(no\-member\), Blivet.savePassphrase\] Instance of 'DeviceTree' has no '_DeviceTree__luksDevs' member$
+^blivet/blivet.py:[[:digit:]]+: \[E1101\(no\-member\), Blivet.savePassphrase\] Instance of 'DeviceTree' has no '_DeviceTree__passphrases' member$
^blivet/__init__.py:[[:digit:]]+: \[W0602\(global\-variable\-not\-assigned\), enable_installer_mode\] Using global for 'BootLoaderError' but no assignment is done$
^blivet/__init__.py:[[:digit:]]+: \[W0602\(global\-variable\-not\-assigned\), enable_installer_mode\] Using global for 'errorHandler' but no assignment is done$
^blivet/__init__.py:[[:digit:]]+: \[W0602\(global\-variable\-not\-assigned\), enable_installer_mode\] Using global for 'ERROR_RAISE' but no assignment is done$
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/65f95ac603853d6362b2c3da498b68ed8b54cd27
More information about the anaconda-patches
mailing list