diff options
Diffstat (limited to 'storage/devices.py')
-rw-r--r-- | storage/devices.py | 3576 |
1 files changed, 3576 insertions, 0 deletions
diff --git a/storage/devices.py b/storage/devices.py new file mode 100644 index 0000000..f310cef --- /dev/null +++ b/storage/devices.py @@ -0,0 +1,3576 @@ +# devices.py +# Device classes for anaconda's storage configuration module. +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + + +""" + Device classes for use by anaconda. + + This is the hierarchy of device objects that anaconda will use for + managing storage devices in the system. These classes will + individually make use of external support modules as needed to + perform operations specific to the type of device they represent. + + TODO: + - see how to do network devices (NetworkManager may help) + - perhaps just a wrapper here + - document return values of all methods/functions + - find out what other kinds of wild and crazy devices we need to + represent here (iseries? xen? more mainframe? mac? ps?) + - PReP + - this is a prime candidate for a PseudoDevice + - DASD + - ZFCP + - XEN + + What specifications do we allow? new existing + partitions + usage + + + filesystem, partition type are implicit + mountpoint + + + size + exact + - + range + - + resize - + + format - + + encryption + + + + disk + exact + - + set + - + how will we specify this? + partition w/ multiple parents cannot otherwise occur + primary + - + + mdraid sets + filesystem (*) + + + mountpoint + + + size? + format - + + encryption + + + + level + ? + device minor + ? + member devices + ? + spares + ? + name? + bitmap? (boolean) + - + + volume groups + name + - + member pvs + + + pesize + ? + + logical volumes + filesystem + + + mountpoint + + + size + exact + ? + format - + + encryption + + + + name + ? + vgname + ? + + +""" + +import os +import math +import copy +import time + +# device backend modules +from devicelibs import mdraid +from devicelibs import lvm +from devicelibs import dm +import parted +import _ped +import block + +from errors import * +from iutil import notify_kernel, numeric_type +from .storage_log import log_method_call +from udev import * +from formats import get_device_format_class, getFormat, DeviceFormat + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) + +import logging +log = logging.getLogger("storage") + +def get_device_majors(): + majors = {} + for line in open("/proc/devices").readlines(): + try: + (major, device) = line.split() + except ValueError: + continue + try: + majors[int(major)] = device + except ValueError: + continue + return majors +device_majors = get_device_majors() + + +def devicePathToName(devicePath): + if devicePath.startswith("/dev/"): + name = devicePath[5:] + else: + name = devicePath + + if name.startswith("mapper/"): + name = name[7:] + + return name + + +def deviceNameToDiskByPath(deviceName=None): + bypath = '/dev/disk/by-path' + + if not deviceName: + return "" + + if not os.path.isdir(bypath): + return "" + + deviceName = os.path.basename(deviceName) + + for path in os.listdir(bypath): + entry = bypath + '/' + path + + if os.path.islink(entry): + target = os.path.basename(os.readlink(entry)) + else: + target = os.path.basename(entry) + + if target == deviceName: + return entry + + return "" + + +class Device(object): + """ A generic device. + + Device instances know which devices they depend upon (parents + attribute). They do not know which devices depend upon them, but + they do know whether or not they have any dependent devices + (isleaf attribute). + + A Device's setup method should set up all parent devices as well + as the device itself. It should not run the resident format's + setup method. + + Which Device types rely on their parents' formats being active? + DMCryptDevice + + A Device's teardown method should accept the keyword argument + recursive, which takes a boolean value and indicates whether or + not to recursively close parent devices. + + A Device's create method should create all parent devices as well + as the device itself. It should also run the Device's setup method + after creating the device. The create method should not create a + device's resident format. + + Which device type rely on their parents' formats to be created + before they can be created/assembled? + VolumeGroup + DMCryptDevice + + A Device's destroy method should destroy any resident format + before destroying the device itself. + + """ + + # This is a counter for generating unique ids for Devices. + _id = 0 + + _type = "generic device" + _packages = [] + + def __init__(self, name, parents=None): + """ Create a Device instance. + + Arguments: + + name -- the device name (generally a device node's basename) + + Keyword Arguments: + + parents -- a list of required Device instances + + """ + self._name = name + if parents is None: + parents = [] + elif not isinstance(parents, list): + raise ValueError("parents must be a list of Device instances") + self.parents = parents + self.kids = 0 + + # Set this instance's id and increment the counter. + self.id = Device._id + Device._id += 1 + + for parent in self.parents: + parent.addChild() + + def __deepcopy__(self, memo): + """ Create a deep copy of a Device instance. + + We can't do copy.deepcopy on parted objects, which is okay. + For these parted objects, we just do a shallow copy. + """ + new = self.__class__.__new__(self.__class__) + memo[id(self)] = new + dont_copy_attrs = ('_raidSet',) + shallow_copy_attrs = ('_partedDevice', '_partedPartition') + for (attr, value) in self.__dict__.items(): + if attr in dont_copy_attrs: + setattr(new, attr, value) + elif attr in shallow_copy_attrs: + setattr(new, attr, copy.copy(value)) + else: + setattr(new, attr, copy.deepcopy(value, memo)) + + return new + + def __str__(self): + s = ("%(type)s instance (%(id)s) --\n" + " name = %(name)s status = %(status)s" + " parents = %(parents)s\n" + " kids = %(kids)s\n" + " id = %(dev_id)s\n" % + {"type": self.__class__.__name__, "id": "%#x" % id(self), + "name": self.name, "parents": self.parents, "kids": self.kids, + "status": self.status, "dev_id": self.id}) + return s + + @property + def dict(self): + d = {"type": self.type, "name": self.name, + "parents": [p.name for p in self.parents]} + return d + + def writeKS(self, f, preexisting=False, noformat=False, s=None): + return + + def removeChild(self): + log_method_call(self, name=self.name, kids=self.kids) + self.kids -= 1 + + def addChild(self): + log_method_call(self, name=self.name, kids=self.kids) + self.kids += 1 + + def setup(self, intf=None): + """ Open, or set up, a device. """ + raise NotImplementedError("setup method not defined for Device") + + def teardown(self, recursive=None): + """ Close, or tear down, a device. """ + raise NotImplementedError("teardown method not defined for Device") + + def create(self, intf=None): + """ Create the device. """ + raise NotImplementedError("create method not defined for Device") + + def destroy(self): + """ Destroy the device. """ + raise NotImplementedError("destroy method not defined for Device") + + def setupParents(self, orig=False): + """ Run setup method of all parent devices. """ + log_method_call(self, name=self.name, orig=orig, kids=self.kids) + for parent in self.parents: + parent.setup(orig=orig) + + def teardownParents(self, recursive=None): + """ Run teardown method of all parent devices. """ + for parent in self.parents: + parent.teardown(recursive=recursive) + + def createParents(self): + """ Run create method of all parent devices. """ + log.info("NOTE: recursive device creation disabled") + for parent in self.parents: + if not parent.exists: + raise DeviceError("parent device does not exist", self.name) + #parent.create() + + def dependsOn(self, dep): + """ Return True if this device depends on dep. """ + # XXX does a device depend on itself? + if dep in self.parents: + return True + + for parent in self.parents: + if parent.dependsOn(dep): + return True + + return False + + def dracutSetupString(self): + return "" + + @property + def status(self): + """ This device's status. + + For now, this should return a boolean: + True the device is open and ready for use + False the device is not open + """ + return False + + @property + def name(self): + """ This device's name. """ + return self._name + + @property + def isleaf(self): + """ True if this device has no children. """ + return self.kids == 0 + + @property + def typeDescription(self): + """ String describing the device type. """ + return self._type + + @property + def type(self): + """ Device type. """ + return self._type + + @property + def packages(self): + """ List of packages required to manage devices of this type. + + This list includes the packages required by its parent devices. + """ + packages = self._packages + for parent in self.parents: + for package in parent.packages: + if package not in packages: + packages.append(package) + + return packages + + @property + def mediaPresent(self): + return True + + +class NetworkStorageDevice(object): + """ Virtual base class for network backed storage devices """ + + def __init__(self, host_address=None, nic=None): + """ Create a NetworkStorage Device instance. Note this class is only + to be used as a baseclass and then only with multiple inheritance. + The only correct use is: + class MyStorageDevice(StorageDevice, NetworkStorageDevice): + + The sole purpose of this class is to: + 1) Be able to check if a StorageDevice is network backed + (using isinstance). + 2) To be able to get the host address of the host (server) backing + the storage *or* the NIC through which the storage is connected + + Arguments: + + host_address -- host address of the backing server + nic -- nic to which the storage is bound + """ + self.host_address = host_address + self.nic = nic + + +class StorageDevice(Device): + """ A generic storage device. + + A fully qualified path to the device node can be obtained via the + path attribute, although it is not guaranteed to be useful, or + even present, unless the StorageDevice's setup method has been + run. + + StorageDevice instances can optionally contain a filesystem, + represented by an FS instance. A StorageDevice's create method + should create a filesystem if one has been specified. + """ + _type = "storage device" + _devDir = "/dev" + sysfsBlockDir = "class/block" + _resizable = False + _partitionable = False + _isDisk = False + + def __init__(self, device, format=None, + size=None, major=None, minor=None, + sysfsPath='', parents=None, exists=None, serial=None, + vendor="", model="", bus=""): + """ Create a StorageDevice instance. + + Arguments: + + device -- the device name (generally a device node's basename) + + Keyword Arguments: + + size -- the device's size (units/format TBD) + major -- the device major + minor -- the device minor + sysfsPath -- sysfs device path + format -- a DeviceFormat instance + parents -- a list of required Device instances + serial -- the ID_SERIAL_SHORT for this device + vendor -- the manufacturer of this Device + model -- manufacturer's device model string + bus -- the interconnect this device uses + + """ + # allow specification of individual parents + if isinstance(parents, Device): + parents = [parents] + + self.exists = exists + Device.__init__(self, device, parents=parents) + + self.uuid = None + self._format = None + self._size = numeric_type(size) + self.major = numeric_type(major) + self.minor = numeric_type(minor) + self.sysfsPath = sysfsPath + self._serial = serial + self._vendor = vendor + self._model = model + self.bus = bus + + self.protected = False + + self.format = format + self.originalFormat = self.format + self.fstabComment = "" + self._targetSize = self._size + + self._partedDevice = None + + @property + def packages(self): + """ List of packages required to manage devices of this type. + + This list includes the packages required by this device's + format type as well those required by all of its parent + devices. + """ + packages = super(StorageDevice, self).packages + packages.extend(self.format.packages) + for parent in self.parents: + for package in parent.format.packages: + if package not in packages: + packages.append(package) + + return packages + + @property + def partedDevice(self): + if self.exists and self.status and not self._partedDevice: + log.debug("looking up parted Device: %s" % self.path) + + # We aren't guaranteed to be able to get a device. In + # particular, built-in USB flash readers show up as devices but + # do not always have any media present, so parted won't be able + # to find a device. + try: + self._partedDevice = parted.Device(path=self.path) + except (_ped.IOException, _ped.DeviceException): + pass + + return self._partedDevice + + def _getTargetSize(self): + return self._targetSize + + def _setTargetSize(self, newsize): + self._targetSize = newsize + + targetSize = property(lambda s: s._getTargetSize(), + lambda s, v: s._setTargetSize(v), + doc="Target size of this device") + + def __str__(self): + s = Device.__str__(self) + s += (" uuid = %(uuid)s format = %(format)r size = %(size)s\n" + " major = %(major)s minor = %(minor)r exists = %(exists)s\n" + " sysfs path = %(sysfs)s partedDevice = %(partedDevice)r\n" + " target size = %(targetSize)s path = %(path)s\n" + " format args = %(formatArgs)s originalFormat = %(origFmt)s" % + {"uuid": self.uuid, "format": self.format, "size": self.size, + "major": self.major, "minor": self.minor, "exists": self.exists, + "sysfs": self.sysfsPath, "partedDevice": self.partedDevice, + "targetSize": self.targetSize, "path": self.path, + "formatArgs": self.formatArgs, "origFmt": self.originalFormat}) + return s + + @property + def dict(self): + d = super(StorageDevice, self).dict + d.update({"uuid": self.uuid, "size": self.size, + "format": self.format.dict, "removable": self.removable, + "major": self.major, "minor": self.minor, + "exists": self.exists, "sysfs": self.sysfsPath, + "targetSize": self.targetSize, "path": self.path}) + return d + + @property + def path(self): + """ Device node representing this device. """ + return "%s/%s" % (self._devDir, self.name) + + def updateSysfsPath(self): + """ Update this device's sysfs path. """ + log_method_call(self, self.name, status=self.status) + sysfsName = self.name.replace("/", "!") + path = os.path.join("/sys", self.sysfsBlockDir, sysfsName) + self.sysfsPath = os.path.realpath(path)[4:] + log.debug("%s sysfsPath set to %s" % (self.name, self.sysfsPath)) + + @property + def formatArgs(self): + """ Device-specific arguments to format creation program. """ + return [] + + @property + def resizable(self): + """ Can this type of device be resized? """ + return self._resizable and self.exists and \ + ((self.format and self.format.resizable) or not self.format) + + def notifyKernel(self): + """ Send a 'change' uevent to the kernel for this device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + log.debug("not sending change uevent for non-existent device") + return + + if not self.status: + log.debug("not sending change uevent for inactive device") + return + + path = os.path.normpath("/sys/%s" % self.sysfsPath) + try: + notify_kernel(path, action="change") + except Exception, e: + log.warning("failed to notify kernel of change: %s" % e) + + @property + def fstabSpec(self): + spec = self.path + if self.format and self.format.uuid: + spec = "UUID=%s" % self.format.uuid + return spec + + def resize(self, intf=None): + """ Resize the device. + + New size should already be set. + """ + raise NotImplementedError("resize method not defined for StorageDevice") + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + self.setupParents(orig=orig) + for parent in self.parents: + if orig: + parent.originalFormat.setup() + else: + parent.format.setup() + + def teardown(self, recursive=None): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists and not recursive: + raise DeviceError("device has not been created", self.name) + + if self.status: + if self.originalFormat.exists: + self.originalFormat.teardown() + if self.format.exists: + self.format.teardown() + udev_settle() + + if recursive: + self.teardownParents(recursive=recursive) + + def _getSize(self): + """ Get the device's size in MB, accounting for pending changes. """ + if self.exists and not self.mediaPresent: + return 0 + + if self.exists and self.partedDevice: + self._size = self.currentSize + + size = self._size + if self.exists and self.resizable and self.targetSize != size: + size = self.targetSize + + return size + + def _setSize(self, newsize): + """ Set the device's size to a new value. """ + if newsize > self.maxSize: + raise DeviceError("device cannot be larger than %s MB" % + (self.maxSize(),), self.name) + self._size = newsize + + size = property(lambda x: x._getSize(), + lambda x, y: x._setSize(y), + doc="The device's size in MB, accounting for pending changes") + + @property + def currentSize(self): + """ The device's actual size. """ + size = 0 + if self.exists and self.partedDevice: + size = self.partedDevice.getSize() + elif self.exists: + size = self._size + return size + + @property + def minSize(self): + """ The minimum size this device can be. """ + if self.format.minSize: + return self.format.minSize + else: + return self.size + + @property + def maxSize(self): + """ The maximum size this device can be. """ + if self.format.maxSize > self.currentSize: + return self.currentSize + else: + return self.format.maxSize + + @property + def status(self): + """ This device's status. + + For now, this should return a boolean: + True the device is open and ready for use + False the device is not open + """ + if not self.exists: + return False + return os.access(self.path, os.W_OK) + + def _setFormat(self, format): + """ Set the Device's format. """ + if not format: + format = getFormat(None, device=self.path, exists=self.exists) + log_method_call(self, self.name, type=format.type, + current=getattr(self._format, "type", None)) + if self._format and self._format.status: + # FIXME: self.format.status doesn't mean much + raise DeviceError("cannot replace active format", self.name) + + self._format = format + + def _getFormat(self): + return self._format + + format = property(lambda d: d._getFormat(), + lambda d,f: d._setFormat(f), + doc="The device's formatting.") + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + if self.exists: + raise DeviceError("device has already been created", self.name) + + self.createParents() + self.setupParents() + self.exists = True + self.setup() + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if not self.isleaf: + raise DeviceError("Cannot destroy non-leaf device", self.name) + + self.exists = False + # we already did this in DeviceTree._removeDevice + #for parent in self.parents: + # parent.removeChild() + + @property + def removable(self): + devpath = os.path.normpath("/sys/%s" % self.sysfsPath) + remfile = os.path.normpath("%s/removable" % devpath) + rem_f = None + try: + rem_f = open(remfile, "r") + except IOError as err: + if err.errno != 2: + raise + return False + try: + return (self.sysfsPath and os.path.exists(devpath) and + os.access(remfile, os.R_OK) and + rem_f.readline().strip() == "1") + finally: + rem_f.close() + + @property + def isDisk(self): + return self._isDisk + + @property + def partitionable(self): + return self._partitionable + + @property + def partitioned(self): + return self.format.type == "disklabel" and self.partitionable + + @property + def serial(self): + return self._serial + + @property + def model(self): + if not self._model: + self._model = getattr(self.partedDevice, "model", "") + return self._model + + @property + def vendor(self): + return self._vendor + +class DiskDevice(StorageDevice): + """ A disk """ + _type = "disk" + _partitionable = True + _isDisk = True + + def __init__(self, device, format=None, + size=None, major=None, minor=None, sysfsPath='', + parents=None, serial=None, vendor="", model="", bus="", + exists=True): + """ Create a DiskDevice instance. + + Arguments: + + device -- the device name (generally a device node's basename) + + Keyword Arguments: + + size -- the device's size (units/format TBD) + major -- the device major + minor -- the device minor + sysfsPath -- sysfs device path + format -- a DeviceFormat instance + parents -- a list of required Device instances + removable -- whether or not this is a removable device + serial -- the ID_SERIAL_SHORT for this device + vendor -- the manufacturer of this Device + model -- manufacturer's device model string + bus -- the interconnect this device uses + + + DiskDevices always exist. + """ + StorageDevice.__init__(self, device, format=format, size=size, + major=major, minor=minor, exists=exists, + sysfsPath=sysfsPath, parents=parents, + serial=serial, model=model, + vendor=vendor, bus=bus) + + def __str__(self): + s = StorageDevice.__str__(self) + s += (" removable = %(removable)s partedDevice = %(partedDevice)r" % + {"removable": self.removable, "partedDevice": self.partedDevice}) + return s + + @property + def mediaPresent(self): + if not self.partedDevice: + return False + + # Some drivers (cpqarray <blegh>) make block device nodes for + # controllers with no disks attached and then report a 0 size, + # treat this as no media present + return self.partedDevice.getSize() != 0 + + @property + def description(self): + return self.model + + @property + def size(self): + """ The disk's size in MB """ + return super(DiskDevice, self).size + #size = property(StorageDevice._getSize) + + def probe(self): + """ Probe for any missing information about this device. + + pyparted should be able to tell us anything we want to know. + size, disklabel type, maybe even partition layout + """ + log_method_call(self, self.name, size=self.size, partedDevice=self.partedDevice) + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + if not self.mediaPresent: + raise DeviceError("cannot destroy disk with no media", self.name) + + self.teardown() + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + if not os.path.exists(self.path): + raise DeviceError("device does not exist", self.name) + + +class PartitionDevice(StorageDevice): + """ A disk partition. + + On types and flags... + + We don't need to deal with numerical partition types at all. The + only type we are concerned with is primary/logical/extended. Usage + specification is accomplished through the use of flags, which we + will set according to the partition's format. + """ + _type = "partition" + _resizable = True + defaultSize = 500 + + def __init__(self, name, format=None, + size=None, grow=False, maxsize=None, + major=None, minor=None, bootable=None, + sysfsPath='', parents=None, exists=None, + partType=None, primary=False, weight=0): + """ Create a PartitionDevice instance. + + Arguments: + + name -- the device name (generally a device node's basename) + + Keyword Arguments: + + exists -- indicates whether this is an existing device + format -- the device's format (DeviceFormat instance) + + For existing partitions: + + parents -- the disk that contains this partition + major -- the device major + minor -- the device minor + sysfsPath -- sysfs device path + + For new partitions: + + partType -- primary,extended,&c (as parted constant) + grow -- whether or not to grow the partition + maxsize -- max size for growable partitions (in MB) + size -- the device's size (in MB) + bootable -- whether the partition is bootable + parents -- a list of potential containing disks + weight -- an initial sorting weight to assign + """ + self.req_disks = [] + self.req_partType = None + self.req_primary = None + self.req_grow = None + self.req_bootable = None + self.req_size = 0 + self.req_base_size = 0 + self.req_max_size = 0 + self.req_base_weight = 0 + + self._bootable = False + + StorageDevice.__init__(self, name, format=format, size=size, + major=major, minor=minor, exists=exists, + sysfsPath=sysfsPath, parents=parents) + + if not exists: + # this is a request, not a partition -- it has no parents + self.req_disks = self.parents[:] + for dev in self.parents: + dev.removeChild() + self.parents = [] + + # FIXME: Validate partType, but only if this is a new partition + # Otherwise, overwrite it with the partition's type. + self._partType = None + self.partedFlags = {} + self._partedPartition = None + self._origPath = None + self._currentSize = 0 + + # FIXME: Validate size, but only if this is a new partition. + # For existing partitions we will get the size from + # parted. + + if self.exists: + log.debug("looking up parted Partition: %s" % self.path) + self._partedPartition = self.disk.format.partedDisk.getPartitionByPath(self.path) + if not self._partedPartition: + raise DeviceError("cannot find parted partition instance", self.name) + + self._origPath = self.path + # collect information about the partition from parted + self.probe() + if self.getFlag(parted.PARTITION_PREP): + # the only way to identify a PPC PReP Boot partition is to + # check the partition type/flags, so do it here. + self.format = getFormat("prepboot", device=self.path, exists=True) + else: + # XXX It might be worthwhile to create a shit-simple + # PartitionRequest class and pass one to this constructor + # for new partitions. + if not self._size: + # default size for new partition requests + self._size = self.defaultSize + self.req_name = name + self.req_partType = partType + self.req_primary = primary + self.req_max_size = numeric_type(maxsize) + self.req_grow = grow + self.req_bootable = bootable + + # req_size may be manipulated in the course of partitioning + self.req_size = self._size + + # req_base_size will always remain constant + self.req_base_size = self._size + + self.req_base_weight = weight + + def __str__(self): + s = StorageDevice.__str__(self) + s += (" grow = %(grow)s max size = %(maxsize)s bootable = %(bootable)s\n" + " part type = %(partType)s primary = %(primary)s\n" + " partedPartition = %(partedPart)r disk = %(disk)r\n" % + {"grow": self.req_grow, "maxsize": self.req_max_size, + "bootable": self.bootable, "partType": self.partType, + "primary": self.req_primary, + "partedPart": self.partedPartition, "disk": self.disk}) + + if self.partedPartition: + s += (" start = %(start)s end = %(end)s length = %(length)s\n" + " flags = %(flags)s" % + {"length": self.partedPartition.geometry.length, + "start": self.partedPartition.geometry.start, + "end": self.partedPartition.geometry.end, + "flags": self.partedPartition.getFlagsAsString()}) + + return s + + @property + def dict(self): + d = super(PartitionDevice, self).dict + d.update({"type": self.partType}) + if not self.exists: + d.update({"grow": self.req_grow, "maxsize": self.req_max_size, + "bootable": self.bootable, + "primary": self.req_primary}) + + if self.partedPartition: + d.update({"length": self.partedPartition.geometry.length, + "start": self.partedPartition.geometry.start, + "end": self.partedPartition.geometry.end, + "flags": self.partedPartition.getFlagsAsString()}) + return d + + def writeKS(self, f, preexisting=False, noformat=False, s=None): + args = [] + + if self.isExtended: + return + + if self.req_grow: + args.append("--grow") + if self.req_max_size: + args.append("--maxsize=%s" % self.req_max_size) + if self.req_primary: + args.append("--asprimary") + if self.req_size: + args.append("--size=%s" % (self.req_size or self.defaultSize)) + if preexisting: + if len(self.req_disks) == 1: + args.append("--ondisk=%s" % self.req_disks[0].name) + else: + args.append("--onpart=%s" % self.name) + if noformat: + args.append("--noformat") + + f.write("#part ") + self.format.writeKS(f) + f.write(" %s" % " ".join(args)) + if s: + f.write(" %s" % s) + + def _setTargetSize(self, newsize): + if newsize != self.currentSize: + # change this partition's geometry in-memory so that other + # partitioning operations can complete (e.g., autopart) + self._targetSize = newsize + disk = self.disk.format.partedDisk + + # resize the partition's geometry in memory + (constraint, geometry) = self._computeResize(self.partedPartition) + disk.setPartitionGeometry(partition=self.partedPartition, + constraint=constraint, + start=geometry.start, end=geometry.end) + + @property + def path(self): + """ Device node representing this device. """ + if not self.parents: + # Bogus, but code in various places compares devices by path + # So we must return something unique + return self.name + + return "%s/%s" % (self.parents[0]._devDir, self.name) + + @property + def partType(self): + """ Get the partition's type (as parted constant). """ + try: + ptype = self.partedPartition.type + except AttributeError: + ptype = self._partType + + if not self.exists and ptype is None: + ptype = self.req_partType + + return ptype + + @property + def isExtended(self): + return (self.partType is not None and + self.partType & parted.PARTITION_EXTENDED) + + @property + def isLogical(self): + return (self.partType is not None and + self.partType & parted.PARTITION_LOGICAL) + + @property + def isPrimary(self): + return (self.partType is not None and + self.partType == parted.PARTITION_NORMAL) + + @property + def isProtected(self): + return (self.partType is not None and + self.partType & parted.PARTITION_PROTECTED) + + @property + def fstabSpec(self): + spec = self.path + if self.disk and self.disk.type == 'dasd': + spec = deviceNameToDiskByPath(self.path) + elif self.format and self.format.uuid: + spec = "UUID=%s" % self.format.uuid + return spec + + def _getPartedPartition(self): + return self._partedPartition + + def _setPartedPartition(self, partition): + """ Set this PartitionDevice's parted Partition instance. """ + log_method_call(self, self.name) + if partition is None: + path = None + elif isinstance(partition, parted.Partition): + path = partition.path + else: + raise ValueError("partition must be a parted.Partition instance") + + log.debug("device %s new partedPartition %s has path %s" % (self.name, + partition, + path)) + self._partedPartition = partition + self.updateName() + + partedPartition = property(lambda d: d._getPartedPartition(), + lambda d,p: d._setPartedPartition(p)) + + def resetPartedPartition(self): + """ Re-get self.partedPartition from the original disklabel. """ + log_method_call(self, self.name) + if not self.exists: + return + + # find the correct partition on the original parted.Disk since the + # name/number we're now using may no longer match + _disklabel = self.disk.originalFormat + + if self.isExtended: + # getPartitionBySector doesn't work on extended partitions + _partition = _disklabel.extendedPartition + log.debug("extended lookup found partition %s" + % devicePathToName(getattr(_partition, "path", None))) + else: + # lookup the partition by sector to avoid the renumbering + # nonsense entirely + _sector = self.partedPartition.geometry.start + _partition = _disklabel.partedDisk.getPartitionBySector(_sector) + log.debug("sector-based lookup found partition %s" + % devicePathToName(getattr(_partition, "path", None))) + + self.partedPartition = _partition + + def _getWeight(self): + return self.req_base_weight + + def _setWeight(self, weight): + self.req_base_weight = weight + + weight = property(lambda d: d._getWeight(), + lambda d,w: d._setWeight(w)) + + def updateSysfsPath(self): + """ Update this device's sysfs path. """ + log_method_call(self, self.name, status=self.status) + if not self.parents: + self.sysfsPath = '' + + elif self.parents[0]._devDir == "/dev/mapper": + dm_node = dm.dm_node_from_name(self.name) + path = os.path.join("/sys", self.sysfsBlockDir, dm_node) + self.sysfsPath = os.path.realpath(path)[4:] + + else: + StorageDevice.updateSysfsPath(self) + + def updateName(self): + if self.partedPartition is None: + self._name = self.req_name + else: + self._name = \ + devicePathToName(self.partedPartition.getDeviceNodeName()) + + def dependsOn(self, dep): + """ Return True if this device depends on dep. """ + if isinstance(dep, PartitionDevice) and dep.isExtended and \ + self.isLogical and self.disk == dep.disk: + return True + + return Device.dependsOn(self, dep) + + def _setFormat(self, format): + """ Set the Device's format. """ + log_method_call(self, self.name) + StorageDevice._setFormat(self, format) + + def _setBootable(self, bootable): + """ Set the bootable flag for this partition. """ + if self.partedPartition: + if iutil.isS390(): + return + if self.flagAvailable(parted.PARTITION_BOOT): + if bootable: + self.setFlag(parted.PARTITION_BOOT) + else: + self.unsetFlag(parted.PARTITION_BOOT) + else: + raise DeviceError("boot flag not available for this partition", self.name) + + self._bootable = bootable + else: + self.req_bootable = bootable + + def _getBootable(self): + return self._bootable or self.req_bootable + + bootable = property(_getBootable, _setBootable) + + def flagAvailable(self, flag): + log_method_call(self, path=self.path, flag=flag) + if not self.partedPartition: + return + + return self.partedPartition.isFlagAvailable(flag) + + def getFlag(self, flag): + log_method_call(self, path=self.path, flag=flag) + if not self.partedPartition or not self.flagAvailable(flag): + return + + return self.partedPartition.getFlag(flag) + + def setFlag(self, flag): + log_method_call(self, path=self.path, flag=flag) + if not self.partedPartition or not self.flagAvailable(flag): + return + + self.partedPartition.setFlag(flag) + + def unsetFlag(self, flag): + log_method_call(self, path=self.path, flag=flag) + if not self.partedPartition or not self.flagAvailable(flag): + return + + self.partedPartition.unsetFlag(flag) + + def probe(self): + """ Probe for any missing information about this device. + + size, partition type, flags + """ + log_method_call(self, self.name, exists=self.exists) + if not self.exists: + return + + # this is in MB + self._size = self.partedPartition.getSize() + self._currentSize = self._size + self.targetSize = self._size + + self._partType = self.partedPartition.type + + self._bootable = self.getFlag(parted.PARTITION_BOOT) + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + if self.exists: + raise DeviceError("device already exists", self.name) + + w = None + if intf: + w = intf.waitWindow(_("Creating"), + _("Creating device %s") % (self.path,)) + + try: + self.createParents() + self.setupParents() + + self.disk.format.addPartition(self.partedPartition) + + try: + self.disk.format.commit() + except DiskLabelCommitError: + part = self.disk.format.partedDisk.getPartitionByPath(self.path) + self.disk.format.removePartition(part) + raise + + # Ensure old metadata which lived in freespace so did not get + # explictly destroyed by a destroyformat action gets wiped + DeviceFormat(device=self.path, exists=True).destroy() + except Exception: + raise + else: + self.partedPartition = self.disk.format.partedDisk.getPartitionByPath(self.path) + + self.exists = True + self._currentSize = self.partedPartition.getSize() + self.setup() + finally: + if w: + w.pop() + + def _computeResize(self, partition): + log_method_call(self, self.name, status=self.status) + + # compute new size for partition + currentGeom = partition.geometry + currentDev = currentGeom.device + newLen = long(self.targetSize * 1024 * 1024) / currentDev.sectorSize + newGeometry = parted.Geometry(device=currentDev, + start=currentGeom.start, + length=newLen) + # and align the end sector + newGeometry.end = self.disk.format.endAlignment.alignDown(newGeometry, + newGeometry.end) + constraint = parted.Constraint(exactGeom=newGeometry) + + return (constraint, newGeometry) + + def resize(self, intf=None): + """ Resize the device. + + self.targetSize must be set to the new size. + """ + log_method_call(self, self.name, status=self.status) + + if self.targetSize != self.currentSize: + # partedDisk has been restored to _origPartedDisk, so + # recalculate resize geometry because we may have new + # partitions on the disk, which could change constraints + partedDisk = self.disk.format.partedDisk + partition = partedDisk.getPartitionByPath(self.path) + (constraint, geometry) = self._computeResize(partition) + + partedDisk.setPartitionGeometry(partition=partition, + constraint=constraint, + start=geometry.start, + end=geometry.end) + + self.disk.format.commit() + self._currentSize = partition.getSize() + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if not self.sysfsPath: + return + + if not self.isleaf: + raise DeviceError("Cannot destroy non-leaf device", self.name) + + self.setupParents(orig=True) + # we should have already set self.partedPartition to point to the + # partition on the original disklabel + self.disk.originalFormat.removePartition(self.partedPartition) + try: + self.disk.originalFormat.commit() + except DiskLabelCommitError: + self.disk.originalFormat.addPartition(self.partedPartition) + self.partedPartition = self.disk.originalFormat.partedDisk.getPartitionByPath(self.path) + raise + + self.exists = False + + def teardown(self, recursive=None): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists and not recursive: + raise DeviceError("device has not been created", self.name) + + if self.status: + if self.originalFormat.exists: + self.originalFormat.teardown() + if self.format.exists: + self.format.teardown() + if self.parents[0].type == 'dm-multipath': + devmap = block.getMap(major=self.major, minor=self.minor) + if devmap: + try: + block.removeDeviceMap(devmap) + except Exception as e: + raise DeviceTeardownError("failed to tear down device-mapper partition %s: %s" % (self.name, e)) + udev_settle() + + StorageDevice.teardown(self, recursive=recursive) + + def _getSize(self): + """ Get the device's size. """ + size = self._size + if self.partedPartition: + # this defaults to MB + size = self.partedPartition.getSize() + return size + + def _setSize(self, newsize): + """ Set the device's size (for resize, not creation). + + Arguments: + + newsize -- the new size (in MB) + + """ + log_method_call(self, self.name, + status=self.status, size=self._size, newsize=newsize) + if not self.exists: + raise DeviceError("device does not exist", self.name) + + if newsize > self.disk.size: + raise ValueError("partition size would exceed disk size") + + # this defaults to MB + maxAvailableSize = self.partedPartition.getMaxAvailableSize() + + if newsize > maxAvailableSize: + raise ValueError("new size is greater than available space") + + # now convert the size to sectors and update the geometry + geometry = self.partedPartition.geometry + physicalSectorSize = geometry.device.physicalSectorSize + + new_length = (newsize * (1024 * 1024)) / physicalSectorSize + geometry.length = new_length + + def _getDisk(self): + """ The disk that contains this partition.""" + try: + disk = self.parents[0] + except IndexError: + disk = None + return disk + + def _setDisk(self, disk): + """Change the parent. + + Setting up a disk is not trivial. It has the potential to change + the underlying object. If necessary we must also change this object. + """ + log_method_call(self, self.name, old=getattr(self.disk, "name", None), + new=getattr(disk, "name", None)) + if self.disk: + self.disk.removeChild() + + if disk: + self.parents = [disk] + disk.addChild() + else: + self.parents = [] + + disk = property(lambda p: p._getDisk(), lambda p,d: p._setDisk(d)) + + @property + def maxSize(self): + """ The maximum size this partition can be. """ + # XXX: this is MB by default + maxPartSize = self.partedPartition.getMaxAvailableSize() + + if self.format.maxSize > maxPartSize: + return maxPartSize + else: + return self.format.maxSize + + @property + def currentSize(self): + """ The device's actual size. """ + if self.exists: + return self._currentSize + else: + return 0 + + +class DMDevice(StorageDevice): + """ A device-mapper device """ + _type = "dm" + _devDir = "/dev/mapper" + + def __init__(self, name, format=None, size=None, dmUuid=None, + target=None, exists=None, parents=None, sysfsPath=''): + """ Create a DMDevice instance. + + Arguments: + + name -- the device name (generally a device node's basename) + + Keyword Arguments: + + target -- the device-mapper target type (string) + size -- the device's size (units/format TBD) + dmUuid -- the device's device-mapper UUID + sysfsPath -- sysfs device path + format -- a DeviceFormat instance + parents -- a list of required Device instances + exists -- indicates whether this is an existing device + """ + StorageDevice.__init__(self, name, format=format, size=size, + exists=exists, + parents=parents, sysfsPath=sysfsPath) + self.target = target + self.dmUuid = dmUuid + + def __str__(self): + s = StorageDevice.__str__(self) + s += (" target = %(target)s dmUuid = %(dmUuid)s" % + {"target": self.target, "dmUuid": self.dmUuid}) + return s + + @property + def dict(self): + d = super(DMDevice, self).dict + d.update({"target": self.target, "dmUuid": self.dmUuid}) + return d + + @property + def fstabSpec(self): + """ Return the device specifier for use in /etc/fstab. """ + return self.path + + @property + def mapName(self): + """ This device's device-mapper map name """ + return self.name + + @property + def status(self): + _status = False + for map in block.dm.maps(): + if map.name == self.mapName: + _status = map.live_table and not map.suspended + break + + return _status + + def updateSysfsPath(self): + """ Update this device's sysfs path. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if self.status: + dm_node = self.getDMNode() + path = os.path.join("/sys", self.sysfsBlockDir, dm_node) + self.sysfsPath = os.path.realpath(path)[4:] + else: + self.sysfsPath = '' + + #def getTargetType(self): + # return dm.getDmTarget(name=self.name) + + def getDMNode(self): + """ Return the dm-X (eg: dm-0) device node for this device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + return dm.dm_node_from_name(self.name) + + def _setName(self, name): + """ Set the device's map name. """ + log_method_call(self, self.name, status=self.status) + if self.status: + raise DeviceError("cannot rename active device", self.name) + + self._name = name + #self.sysfsPath = "/dev/disk/by-id/dm-name-%s" % self.name + + name = property(lambda d: d._name, + lambda d,n: d._setName(n)) + + +class DMCryptDevice(DMDevice): + """ A dm-crypt device """ + _type = "dm-crypt" + + def __init__(self, name, format=None, size=None, uuid=None, + exists=None, sysfsPath='', parents=None): + """ Create a DMCryptDevice instance. + + Arguments: + + name -- the device name (generally a device node's basename) + + Keyword Arguments: + + size -- the device's size (units/format TBD) + sysfsPath -- sysfs device path + format -- a DeviceFormat instance + parents -- a list of required Device instances + exists -- indicates whether this is an existing device + """ + DMDevice.__init__(self, name, format=format, size=size, + parents=parents, sysfsPath=sysfsPath, + exists=exists, target="crypt") + +class LUKSDevice(DMCryptDevice): + """ A mapped LUKS device. """ + _type = "luks/dm-crypt" + + def __init__(self, name, format=None, size=None, uuid=None, + exists=None, sysfsPath='', parents=None): + """ Create a LUKSDevice instance. + + Arguments: + + name -- the device name + + Keyword Arguments: + + size -- the device's size in MB + uuid -- the device's UUID + sysfsPath -- sysfs device path + format -- a DeviceFormat instance + parents -- a list of required Device instances + exists -- indicates whether this is an existing device + """ + DMCryptDevice.__init__(self, name, format=format, size=size, + parents=parents, sysfsPath=sysfsPath, + uuid=None, exists=exists) + + def writeKS(self, f, preexisting=False, noformat=False, s=None): + self.slave.writeKS(f, preexisting=preexisting, noformat=noformat, s=s) + self.format.writeKS(f) + if s: + f.write(" %s" % s) + + @property + def size(self): + if not self.exists or not self.partedDevice: + # the LUKS header takes up 4040 512-byte sectors w/ a 512-bit key + size = float(self.slave.size) - ((4040 * 2.0) / 1024) + else: + size = self.partedDevice.getSize() + return size + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + if self.exists: + raise DeviceError("device already exists", self.name) + + self.createParents() + self.setupParents() + + #if not self.slave.format.exists: + # self.slave.format.create() + self._name = self.slave.format.mapName + self.exists = True + self.setup() + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + self.slave.setup(orig=orig) + if orig: + self.slave.originalFormat.setup() + else: + self.slave.format.setup() + udev_settle() + + # we always probe since the device may not be set up when we want + # information about it + self._size = self.currentSize + + def teardown(self, recursive=False): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists and not recursive: + raise DeviceError("device has not been created", self.name) + + if self.status: + if self.originalFormat.exists: + self.originalFormat.teardown() + if self.format.exists: + self.format.teardown() + udev_settle() + + if self.slave.originalFormat.exists: + self.slave.originalFormat.teardown() + udev_settle() + + if self.slave.format.exists: + self.slave.format.teardown() + udev_settle() + + if recursive: + self.teardownParents(recursive=recursive) + + def destroy(self): + log_method_call(self, self.name, status=self.status) + self.format.teardown() + udev_settle() + self.teardown() + + @property + def slave(self): + """ This device's backing device. """ + return self.parents[0] + + def dracutSetupString(self): + return "rd_LUKS_UUID=luks-%s" % self.slave.format.uuid + + +class LVMVolumeGroupDevice(DMDevice): + """ An LVM Volume Group + + XXX Maybe this should inherit from StorageDevice instead of + DMDevice since there's no actual device. + """ + _type = "lvmvg" + + def __init__(self, name, parents, size=None, free=None, + peSize=None, peCount=None, peFree=None, pvCount=None, + lvNames=[], uuid=None, exists=None, sysfsPath=''): + """ Create a LVMVolumeGroupDevice instance. + + Arguments: + + name -- the device name (generally a device node's basename) + parents -- a list of physical volumes (StorageDevice) + + Keyword Arguments: + + peSize -- physical extent size (in MB) + exists -- indicates whether this is an existing device + sysfsPath -- sysfs device path + + For existing VG's only: + + size -- the VG's size (in MB) + free -- amount of free space in the VG + peFree -- number of free extents + peCount -- total number of extents + pvCount -- number of PVs in this VG + lvNames -- the names of this VG's LVs + uuid -- the VG's UUID + + """ + self.pvClass = get_device_format_class("lvmpv") + if not self.pvClass: + raise StorageError("cannot find 'lvmpv' class") + + if isinstance(parents, list): + for dev in parents: + if not isinstance(dev.format, self.pvClass): + raise ValueError("constructor requires a list of PVs") + elif not isinstance(parents.format, self.pvClass): + raise ValueError("constructor requires a list of PVs") + + DMDevice.__init__(self, name, parents=parents, + exists=exists, sysfsPath=sysfsPath) + + self.uuid = uuid + self.free = numeric_type(free) + self.peSize = numeric_type(peSize) + self.peCount = numeric_type(peCount) + self.peFree = numeric_type(peFree) + self.pvCount = numeric_type(pvCount) + self.lvNames = lvNames + + # circular references, here I come + self._lvs = [] + + # TODO: validate peSize if given + if not self.peSize: + self.peSize = 32.0 # MB + + #self.probe() + + def __str__(self): + s = DMDevice.__str__(self) + s += (" free = %(free)s PE Size = %(peSize)s PE Count = %(peCount)s\n" + " PE Free = %(peFree)s PV Count = %(pvCount)s\n" + " LV Names = %(lvNames)s modified = %(modified)s\n" + " extents = %(extents)s free space = %(freeSpace)s\n" + " free extents = %(freeExtents)s\n" + " PVs = %(pvs)s\n" + " LVs = %(lvs)s" % + {"free": self.free, "peSize": self.peSize, "peCount": self.peCount, + "peFree": self.peFree, "pvCount": self.pvCount, + "lvNames": self.lvNames, "modified": self.isModified, + "extents": self.extents, "freeSpace": self.freeSpace, + "freeExtents": self.freeExtents, "pvs": self.pvs, "lvs": self.lvs}) + return s + + @property + def dict(self): + d = super(LVMVolumeGroupDevice, self).dict + d.update({"free": self.free, "peSize": self.peSize, + "peCount": self.peCount, "peFree": self.peFree, + "pvCount": self.pvCount, "extents": self.extents, + "freeSpace": self.freeSpace, + "freeExtents": self.freeExtents, + "lvNames": [lv.name for lv in self.lvs]}) + return d + + def writeKS(self, f, preexisting=False, noformat=False, s=None): + args = ["--pesize=%s" % int(self.peSize * 1024)] + pvs = [] + + for pv in self.pvs: + pvs.append("pv.%s" % pv.format.uuid) + + if preexisting: + args.append("--useexisting") + if noformat: + args.append("--noformat") + + f.write("#volgroup %s %s %s" % (self.name, " ".join(args), " ".join(pvs))) + if s: + f.write(" %s" % s) + + def probe(self): + """ Probe for any information about this device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + @property + def mapName(self): + """ This device's device-mapper map name """ + # Thank you lvm for this lovely hack. + return self.name.replace("-","--") + + @property + def path(self): + """ Device node representing this device. """ + return "%s/%s" % (self._devDir, self.mapName) + + def updateSysfsPath(self): + """ Update this device's sysfs path. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + self.sysfsPath = '' + + @property + def status(self): + """ The device's status (True means active). """ + if not self.exists: + return False + + # certainly if any of this VG's LVs are active then so are we + for lv in self.lvs: + if lv.status: + return True + + # if any of our PVs are not active then we cannot be + for pv in self.pvs: + if not pv.status: + return False + + # if we are missing some of our PVs we cannot be active + if len(self.pvs) != self.pvCount: + return False + + return True + + def _addDevice(self, device): + """ Add a new physical volume device to the volume group. + + XXX This is for use by device probing routines and is not + intended for modification of the VG. + """ + log_method_call(self, + self.name, + device=device.name, + status=self.status) + if not self.exists: + raise DeviceError("device does not exist", self.name) + + if not isinstance(device.format, self.pvClass): + raise ValueError("addDevice requires a PV arg") + + if self.uuid and device.format.vgUuid != self.uuid: + raise ValueError("UUID mismatch") + + if device in self.pvs: + raise ValueError("device is already a member of this VG") + + self.parents.append(device) + device.addChild() + + # now see if the VG can be activated + if len(self.parents) == self.pvCount: + self.setup() + + def _removeDevice(self, device): + """ Remove a physical volume from the volume group. + + This is for cases like clearing of preexisting partitions. + """ + log_method_call(self, + self.name, + device=device.name, + status=self.status) + try: + self.parents.remove(device) + except ValueError, e: + raise ValueError("cannot remove non-member PV device from VG") + + device.removeChild() + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. + + XXX we don't do anything like "vgchange -ay" because we don't + want all of the LVs activated, just the VG itself. + """ + log_method_call(self, self.name, orig=orig, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if self.status: + return + + if len(self.parents) < self.pvCount: + raise DeviceError("cannot activate VG with missing PV(s)", self.name) + + self.setupParents(orig=orig) + + def teardown(self, recursive=None): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists and not recursive: + raise DeviceError("device has not been created", self.name) + + if self.status: + lvm.vgdeactivate(self.name) + + if recursive: + self.teardownParents(recursive=recursive) + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + if self.exists: + raise DeviceError("device already exists", self.name) + + w = None + if intf: + w = intf.progressWindow(_("Creating"), + _("Creating device %s") + % (self.path,), + 100, pulse = True) + try: + self.createParents() + self.setupParents() + + pv_list = [pv.path for pv in self.parents] + lvm.vgcreate(self.name, pv_list, self.peSize, progress=w) + except Exception: + raise + else: + # FIXME set / update self.uuid here + self.exists = True + self.setup() + finally: + if w: + w.pop() + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + # set up the pvs since lvm needs access to them to do the vgremove + self.setupParents(orig=True) + + # this sometimes fails for some reason. + try: + lvm.vgreduce(self.name, [], rm=True) + lvm.vgremove(self.name) + except lvm.LVMError: + raise DeviceError("Could not completely remove VG", self.name) + finally: + self.exists = False + + def reduce(self, pv_list): + """ Remove the listed PVs from the VG. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + lvm.vgreduce(self.name, pv_list) + # XXX do we need to notify the kernel? + + def _addLogVol(self, lv): + """ Add an LV to this VG. """ + if lv in self._lvs: + raise ValueError("lv is already part of this vg") + + # verify we have the space, then add it + # do not verify for growing vg (because of ks) + if not lv.exists and \ + not [pv for pv in self.pvs if getattr(pv, "req_grow", None)] and \ + lv.size > self.freeSpace: + raise DeviceError("new lv is too large to fit in free space", self.name) + + self._lvs.append(lv) + + def _removeLogVol(self, lv): + """ Remove an LV from this VG. """ + if lv not in self.lvs: + raise ValueError("specified lv is not part of this vg") + + self._lvs.remove(lv) + + def _addPV(self, pv): + """ Add a PV to this VG. """ + if pv in self.pvs: + raise ValueError("pv is already part of this vg") + + # for the time being we will not allow vgextend + if self.exists: + raise DeviceError("cannot add pv to existing vg", self.name) + + self.parents.append(pv) + pv.addChild() + + def _removePV(self, pv): + """ Remove an PV from this VG. """ + if not pv in self.pvs: + raise ValueError("specified pv is not part of this vg") + + # for the time being we will not allow vgreduce + if self.exists: + raise DeviceError("cannot remove pv from existing vg", self.name) + + self.parents.remove(pv) + pv.removeChild() + + # We can't rely on lvm to tell us about our size, free space, &c + # since we could have modifications queued, unless the VG and all of + # its PVs already exist. + # + # -- liblvm may contain support for in-memory devices + + @property + def isModified(self): + """ Return True if the VG has changes queued that LVM is unaware of. """ + modified = True + if self.exists and not filter(lambda d: not d.exists, self.pvs): + modified = False + + return modified + + @property + def size(self): + """ The size of this VG """ + # TODO: just ask lvm if isModified returns False + + # sum up the sizes of the PVs and align to pesize + size = 0 + for pv in self.pvs: + size += max(0, self.align(pv.size - pv.format.peStart)) + + return size + + @property + def extents(self): + """ Number of extents in this VG """ + # TODO: just ask lvm if isModified returns False + + return self.size / self.peSize + + @property + def freeSpace(self): + """ The amount of free space in this VG (in MB). """ + # TODO: just ask lvm if isModified returns False + + # total the sizes of any LVs + used = 0 + size = self.size + log.debug("%s size is %dMB" % (self.name, size)) + for lv in self.lvs: + log.debug("lv %s uses %dMB" % (lv.name, lv.vgSpaceUsed)) + used += self.align(lv.vgSpaceUsed, roundup=True) + + free = self.size - used + log.debug("vg %s has %dMB free" % (self.name, free)) + return free + + @property + def freeExtents(self): + """ The number of free extents in this VG. """ + # TODO: just ask lvm if isModified returns False + return self.freeSpace / self.peSize + + def align(self, size, roundup=None): + """ Align a size to a multiple of physical extent size. """ + size = numeric_type(size) + + if roundup: + round = math.ceil + else: + round = math.floor + + # we want Kbytes as a float for our math + size *= 1024.0 + pesize = self.peSize * 1024.0 + return long((round(size / pesize) * pesize) / 1024) + + @property + def pvs(self): + """ A list of this VG's PVs """ + return self.parents[:] # we don't want folks changing our list + + @property + def lvs(self): + """ A list of this VG's LVs """ + return self._lvs[:] # we don't want folks changing our list + + @property + def complete(self): + """Check if the vg has all its pvs in the system + Return True if complete. + """ + return len(self.pvs) == self.pvCount or not self.exists + + +class LVMLogicalVolumeDevice(DMDevice): + """ An LVM Logical Volume """ + _type = "lvmlv" + _resizable = True + + def __init__(self, name, vgdev, size=None, uuid=None, + stripes=1, logSize=0, snapshotSpace=0, + format=None, exists=None, sysfsPath='', + grow=None, maxsize=None, percent=None): + """ Create a LVMLogicalVolumeDevice instance. + + Arguments: + + name -- the device name (generally a device node's basename) + vgdev -- volume group (LVMVolumeGroupDevice instance) + + Keyword Arguments: + + size -- the device's size (in MB) + uuid -- the device's UUID + stripes -- number of copies in the vg (>1 for mirrored lvs) + logSize -- size of log volume (for mirrored lvs) + snapshotSpace -- sum of sizes of snapshots of this lv + sysfsPath -- sysfs device path + format -- a DeviceFormat instance + exists -- indicates whether this is an existing device + + For new (non-existent) LVs only: + + grow -- whether to grow this LV + maxsize -- maximum size for growable LV (in MB) + percent -- percent of VG space to take + + """ + if isinstance(vgdev, list): + if len(vgdev) != 1: + raise ValueError("constructor requires a single LVMVolumeGroupDevice instance") + elif not isinstance(vgdev[0], LVMVolumeGroupDevice): + raise ValueError("constructor requires a LVMVolumeGroupDevice instance") + elif not isinstance(vgdev, LVMVolumeGroupDevice): + raise ValueError("constructor requires a LVMVolumeGroupDevice instance") + DMDevice.__init__(self, name, size=size, format=format, + sysfsPath=sysfsPath, parents=vgdev, + exists=exists) + + self.uuid = uuid + self.snapshotSpace = snapshotSpace + self.stripes = stripes + self.logSize = logSize + + self.req_grow = None + self.req_max_size = 0 + self.req_size = 0 + self.req_percent = 0 + + if not self.exists: + self.req_grow = grow + self.req_max_size = numeric_type(maxsize) + # XXX should we enforce that req_size be pe-aligned? + self.req_size = self._size + self.req_percent = numeric_type(percent) + + # here we go with the circular references + self.vg._addLogVol(self) + + def __str__(self): + s = DMDevice.__str__(self) + s += (" VG device = %(vgdev)r percent = %(percent)s\n" + " mirrored = %(mirrored)s stripes = %(stripes)d" + " snapshot total = %(snapshots)dMB\n" + " VG space used = %(vgspace)dMB" % + {"vgdev": self.vg, "percent": self.req_percent, + "mirrored": self.mirrored, "stripes": self.stripes, + "snapshots": self.snapshotSpace, "vgspace": self.vgSpaceUsed }) + return s + + @property + def dict(self): + d = super(LVMLogicalVolumeDevice, self).dict + if self.exists: + d.update({"mirrored": self.mirrored, "stripes": self.stripes, + "snapshots": self.snapshotSpace, + "vgspace": self.vgSpaceUsed}) + else: + d.update({"percent": self.req_percent}) + + return d + + def writeKS(self, f, preexisting=False, noformat=False, s=None): + args = ["--name=%s" % self.lvname, + "--vgname=%s" % self.vg.name] + + if self.req_grow: + args.extend(["--grow", "--size=%s" % (self.req_size or 1)]) + + if self.req_max_size > 0: + args.append("--maxsize=%s" % self.req_max_size) + else: + if self.req_percent > 0: + args.append("--percent=%s" % self.req_percent) + elif self.req_size > 0: + args.append("--size=%s" % self.req_size) + + if preexisting: + args.append("--useexisting") + if noformat: + args.append("--noformat") + + f.write("#logvol ") + self.format.writeKS(f) + f.write(" %s" % " ".join(args)) + if s: + f.write(" %s" % s) + + @property + def mirrored(self): + return self.stripes > 1 + + def _setSize(self, size): + size = self.vg.align(numeric_type(size)) + log.debug("trying to set lv %s size to %dMB" % (self.name, size)) + if size <= (self.vg.freeSpace + self._size): + self._size = size + self.targetSize = size + else: + log.debug("failed to set size: %dMB short" % (size - (self.vg.freeSpace + self._size),)) + raise ValueError("not enough free space in volume group") + + size = property(StorageDevice._getSize, _setSize) + + @property + def vgSpaceUsed(self): + return self.size * self.stripes + self.logSize + self.snapshotSpace + + @property + def vg(self): + """ This Logical Volume's Volume Group. """ + return self.parents[0] + + @property + def mapName(self): + """ This device's device-mapper map name """ + # Thank you lvm for this lovely hack. + return "%s-%s" % (self.vg.mapName, self._name.replace("-","--")) + + @property + def path(self): + """ Device node representing this device. """ + return "%s/%s" % (self._devDir, self.mapName) + + def getDMNode(self): + """ Return the dm-X (eg: dm-0) device node for this device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + return dm.dm_node_from_name(self.mapName) + + @property + def name(self): + """ This device's name. """ + return "%s-%s" % (self.vg.name, self._name) + + @property + def lvname(self): + """ The LV's name (not including VG name). """ + return self._name + + @property + def complete(self): + """ Test if vg exits and if it has all pvs. """ + return self.vg.complete + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if self.status: + return + + self.vg.setup(orig=orig) + lvm.lvactivate(self.vg.name, self._name) + + # we always probe since the device may not be set up when we want + # information about it + self._size = self.currentSize + + def teardown(self, recursive=None): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists and not recursive: + raise DeviceError("device has not been created", self.name) + + if self.status: + if self.originalFormat.exists: + self.originalFormat.teardown() + if self.format.exists: + self.format.teardown() + udev_settle() + + if self.status: + lvm.lvdeactivate(self.vg.name, self._name) + + if recursive: + # It's likely that teardown of a VG will fail due to other + # LVs being active (filesystems mounted, &c), so don't let + # it bring everything down. + try: + self.vg.teardown(recursive=recursive) + except Exception as e: + log.debug("vg %s teardown failed; continuing" % self.vg.name) + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + if self.exists: + raise DeviceError("device already exists", self.name) + + w = None + if intf: + w = intf.progressWindow(_("Creating"), + _("Creating device %s") + % (self.path,), + 100, pulse = True) + try: + self.createParents() + self.setupParents() + + # should we use --zero for safety's sake? + lvm.lvcreate(self.vg.name, self._name, self.size, progress=w) + except Exception: + raise + else: + # FIXME set / update self.uuid here + self.exists = True + self.setup() + finally: + if w: + w.pop() + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + self.teardown() + # set up the vg's pvs so lvm can remove the lv + self.vg.setupParents(orig=True) + lvm.lvremove(self.vg.name, self._name) + self.exists = False + + def resize(self, intf=None): + # XXX resize format probably, right? + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + # Setup VG parents (in case they are dmraid partitions for example) + self.vg.setupParents(orig=True) + + if self.originalFormat.exists: + self.originalFormat.teardown() + if self.format.exists: + self.format.teardown() + + udev_settle() + lvm.lvresize(self.vg.name, self._name, self.size) + + def dracutSetupString(self): + # Note no mapName usage here, this is a lvm cmdline name, which + # is different (ofcourse) + return "rd_LVM_LV=%s/%s" % (self.vg.name, self._name) + + +class MDRaidArrayDevice(StorageDevice): + """ An mdraid (Linux RAID) device. """ + _type = "mdarray" + + def __init__(self, name, level=None, major=None, minor=None, size=None, + memberDevices=None, totalDevices=None, bitmap=False, + uuid=None, format=None, exists=None, + parents=None, sysfsPath=''): + """ Create a MDRaidArrayDevice instance. + + Arguments: + + name -- the device name (generally a device node's basename) + + Keyword Arguments: + + level -- the device's RAID level (a string, eg: '1' or 'raid1') + parents -- list of member devices (StorageDevice instances) + size -- the device's size (units/format TBD) + uuid -- the device's UUID + minor -- the device minor + bitmap -- whether to use a bitmap (boolean) + sysfsPath -- sysfs device path + format -- a DeviceFormat instance + exists -- indicates whether this is an existing device + """ + StorageDevice.__init__(self, name, format=format, exists=exists, + major=major, minor=minor, size=size, + parents=parents, sysfsPath=sysfsPath) + + self.level = level + if level == "container": + self._type = "mdcontainer" + elif level is not None: + self.level = mdraid.raidLevel(level) + + # For new arrays check if we have enough members + if (not exists and parents and + len(parents) < mdraid.get_raid_min_members(self.level)): + raise ValueError, _("A RAID%d set requires atleast %d members") % ( + self.level, mdraid.get_raid_min_members(self.level)) + + self.uuid = uuid + self._totalDevices = numeric_type(totalDevices) + self._memberDevices = numeric_type(memberDevices) + self.sysfsPath = "/devices/virtual/block/%s" % name + self.chunkSize = 512.0 / 1024.0 # chunk size in MB + self.superBlockSize = 2.0 # superblock size in MB + + # For container members probe size now, as we cannot determine it + # when teared down. + if self.parents and self.parents[0].type == "mdcontainer": + self._size = self.currentSize + self._type = "mdbiosraidarray" + + # FIXME: Bitmap is more complicated than this. + # It can be internal or external. External requires a filename. + self.bitmap = bitmap + + self.formatClass = get_device_format_class("mdmember") + if not self.formatClass: + raise DeviceError("cannot find class for 'mdmember'", self.name) + + if self.exists and self.uuid: + # this is a hack to work around mdadm's insistence on giving + # really high minors to arrays it has no config entry for + md_f = open("/etc/mdadm.conf", "a") + md_f.write("ARRAY %s UUID=%s\n" % (self.path, self.uuid)) + md_f.close() + + @property + def smallestMember(self): + try: + smallest = sorted(self.devices, key=lambda d: d.size)[0] + except IndexError: + smallest = None + return smallest + + @property + def size(self): + if not self.devices: + return 0 + + # For container members return probed size, as we cannot determine it + # when teared down. + if self.type == "mdbiosraidarray": + return self._size + + size = 0 + smallestMemberSize = self.smallestMember.size - self.superBlockSize + if not self.exists or not self.partedDevice: + if self.level == mdraid.RAID0: + size = self.memberDevices * smallestMemberSize + size -= size % self.chunkSize + elif self.level == mdraid.RAID1: + size = smallestMemberSize + elif self.level == mdraid.RAID4: + size = (self.memberDevices - 1) * smallestMemberSize + size -= size % self.chunkSize + elif self.level == mdraid.RAID5: + size = (self.memberDevices - 1) * smallestMemberSize + size -= size % self.chunkSize + elif self.level == mdraid.RAID6: + size = (self.memberDevices - 2) * smallestMemberSize + size -= size % self.chunkSize + elif self.level == mdraid.RAID10: + size = (self.memberDevices / 2.0) * smallestMemberSize + size -= size % self.chunkSize + else: + size = self.partedDevice.getSize() + + return size + + @property + def description(self): + if self.level == mdraid.RAID0: + levelstr = "stripe" + elif self.level == mdraid.RAID1: + levelstr = "mirror" + else: + levelstr = "raid%s" % self.level + + if self.type == "mdcontainer": + return "BIOS RAID container" + elif self.type == "mdbiosraidarray": + return "BIOS RAID set (%s)" % levelstr + else: + return "MDRAID set (%s)" % levelstr + + def __str__(self): + s = StorageDevice.__str__(self) + s += (" level = %(level)s bitmap = %(bitmap)s spares = %(spares)s\n" + " members = %(memberDevices)s\n" + " total devices = %(totalDevices)s" % + {"level": self.level, "bitmap": self.bitmap, "spares": self.spares, + "memberDevices": self.memberDevices, "totalDevices": self.totalDevices}) + return s + + @property + def dict(self): + d = super(MDRaidArrayDevice, self).dict + d.update({"level": self.level, "bitmap": self.bitmap, + "spares": self.spares, "memberDevices": self.memberDevices, + "totalDevices": self.totalDevices}) + return d + + def writeKS(self, f, preexisting=False, noformat=False, s=None): + args = ["--level=%s" % self.level, + "--device=%s" % self.name] + mems = [] + + if self.spares > 0: + args.append("--spares=%s" % self.spares) + if preexisting: + args.append("--useexisting") + if noformat: + args.append("--noformat") + + for mem in self.parents: + mems.append("raid.%s" % mem.format.uuid) + + f.write("#raid ") + self.format.writeKS(f) + f.write(" %s" % " ".join(args)) + f.write(" %s" % " ".join(mems)) + if s: + f.write(" %s" % s) + + @property + def mdadmConfEntry(self): + """ This array's mdadm.conf entry. """ + if self.level is None or self.memberDevices is None or not self.uuid: + raise DeviceError("array is not fully defined", self.name) + + # containers and the sets within must only have a UUID= parameter + if self.type == "mdcontainer" or self.type == "mdbiosraidarray": + fmt = "ARRAY %s UUID=%s\n" + return fmt % (self.path, self.uuid) + + fmt = "ARRAY %s level=raid%d num-devices=%d UUID=%s\n" + return fmt % (self.path, self.level, self.memberDevices, self.uuid) + + @property + def totalDevices(self): + """ Total number of devices in the array, including spares. """ + count = len(self.parents) + if not self.exists: + count = self._totalDevices + return count + + def _getMemberDevices(self): + return self._memberDevices + + def _setMemberDevices(self, number): + if not isinstance(number, int): + raise ValueError("memberDevices is an integer") + + if number > self.totalDevices: + raise ValueError("memberDevices cannot be greater than totalDevices") + self._memberDevices = number + + memberDevices = property(_getMemberDevices, _setMemberDevices, + doc="number of member devices") + + def _getSpares(self): + spares = 0 + if self.memberDevices is not None: + if self.totalDevices is not None: + spares = self.totalDevices - self.memberDevices + else: + spares = self.memberDevices + self._totalDevices = self.memberDevices + return spares + + def _setSpares(self, spares): + # FIXME: this is too simple to be right + if self.totalDevices > spares: + self.memberDevices = self.totalDevices - spares + + spares = property(_getSpares, _setSpares) + + def probe(self): + """ Probe for any missing information about this device. + + I'd like to avoid paying any attention to "Preferred Minor" + as it seems problematic. + """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + try: + self.devices[0].setup() + except Exception: + return + + info = mdraid.mdexamine(self.devices[0].path) + if self.level is None: + self.level = mdraid.raidLevel(info['level']) + + def updateSysfsPath(self): + """ Update this device's sysfs path. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if self.status: + self.sysfsPath = "/devices/virtual/block/%s" % self.name + else: + self.sysfsPath = '' + + def _addDevice(self, device): + """ Add a new member device to the array. + + XXX This is for use when probing devices, not for modification + of arrays. + """ + log_method_call(self, + self.name, + device=device.name, + status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if not isinstance(device.format, self.formatClass): + raise ValueError("invalid device format for mdraid member") + + if self.uuid and device.format.mdUuid != self.uuid: + raise ValueError("cannot add member with non-matching UUID") + + if device in self.devices: + raise ValueError("device is already a member of this array") + + # we added it, so now set up the relations + self.devices.append(device) + device.addChild() + + device.setup() + udev_settle() + try: + mdraid.mdadd(device.path) + # mdadd causes udev events + udev_settle() + except MDRaidError as e: + log.warning("failed to add member %s to md array %s: %s" + % (device.path, self.path, e)) + + if self.status: + # we always probe since the device may not be set up when we want + # information about it + self._size = self.currentSize + + def _removeDevice(self, device): + """ Remove a component device from the array. + + XXX This is for use by clearpart, not for reconfiguration. + """ + log_method_call(self, + self.name, + device=device.name, + status=self.status) + + if device not in self.devices: + raise ValueError("cannot remove non-member device from array") + + self.devices.remove(device) + device.removeChild() + + @property + def status(self): + """ This device's status. + + For now, this should return a boolean: + True the device is open and ready for use + False the device is not open + """ + # check the status in sysfs + status = False + if not self.exists: + return status + + state_file = "/sys/%s/md/array_state" % self.sysfsPath + if os.access(state_file, os.R_OK): + state_f = open(state_file) + state = state_f.read().strip() + state_f.close() + log.debug("%s state is %s" % (self.name, state)) + if state in ("clean", "active", "active-idle", "readonly", "read-auto"): + status = True + # mdcontainers have state inactive when started (clear if stopped) + if self.type == "mdcontainer" and state == "inactive": + status = True + + return status + + @property + def degraded(self): + """ Return True if the array is running in degraded mode. """ + rc = False + degraded_file = "/sys/%s/md/degraded" % self.sysfsPath + if os.access(degraded_file, os.R_OK): + deg_f = open(degraded_file) + val = deg_f.read().strip() + deg_f.close() + log.debug("%s degraded is %s" % (self.name, val)) + if val == "1": + rc = True + + return rc + + @property + def devices(self): + """ Return a list of this array's member device instances. """ + return self.parents + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if self.status: + return + + disks = [] + for member in self.devices: + member.setup(orig=orig) + disks.append(member.path) + + update_super_minor = True + if self.type == "mdcontainer" or self.type == "mdbiosraidarray": + update_super_minor = False + + mdraid.mdactivate(self.path, + members=disks, + super_minor=self.minor, + update_super_minor=update_super_minor, + uuid=self.uuid) + + udev_settle() + + # we always probe since the device may not be set up when we want + # information about it + self._size = self.currentSize + + def teardown(self, recursive=None): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists and not recursive: + raise DeviceError("device has not been created", self.name) + + if self.status: + if self.originalFormat.exists: + self.originalFormat.teardown() + if self.format.exists: + self.format.teardown() + udev_settle() + + # Since BIOS RAID sets (containers in mdraid terminology) never change + # there is no need to stop them and later restart them. Not stopping + # (and thus also not starting) them also works around bug 523334 + if self.type == "mdcontainer" or self.type == "mdbiosraidarray": + return + + # We don't really care what the array's state is. If the device + # file exists, we want to deactivate it. mdraid has too many + # states. + if self.exists and os.path.exists(self.path): + mdraid.mddeactivate(self.path) + + if recursive: + self.teardownParents(recursive=recursive) + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + if self.exists: + raise DeviceError("device already exists", self.name) + + w = None + if intf: + w = intf.progressWindow(_("Creating"), + _("Creating device %s") + % (self.path,), + 100, pulse = True) + try: + self.createParents() + self.setupParents() + + disks = [disk.path for disk in self.devices] + spares = len(self.devices) - self.memberDevices + + # Figure out format specific options + metadata="1.1" + # bitmaps are not meaningful on raid0 according to mdadm-3.0.3 + bitmap = self.level != 0 + if getattr(self.format, "mountpoint", None) == "/boot": + metadata="1.0" + bitmap=False + elif self.format.type == "swap": + bitmap=False + + mdraid.mdcreate(self.path, + self.level, + disks, + spares, + metadataVer=metadata, + bitmap=bitmap, + progress=w) + except Exception: + raise + else: + self.exists = True + # the array is automatically activated upon creation, but... + self.setup() + udev_settle() + self.updateSysfsPath() + info = udev_get_block_device(self.sysfsPath) + self.uuid = udev_device_get_md_uuid(info) + for member in self.devices: + member.mdUuid = self.uuid + finally: + if w: + w.pop() + + @property + def formatArgs(self): + formatArgs = [] + if self.format.type == "ext2": + if self.level == mdraid.RAID5: + formatArgs = ['-R', + 'stride=%d' % ((self.memberDevices - 1) * 16)] + if self.level == mdraid.RAID4: + formatArgs = ['-R', + 'stride=%d' % ((self.memberDevices - 1) * 16)] + elif self.level == mdraid.RAID0: + formatArgs = ['-R', + 'stride=%d' % (self.memberDevices * 16)] + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + self.teardown() + + # The destruction of the formatting on the member devices does the + # real work, but it isn't our place to do it from here. + self.exists = False + + @property + def mediaPresent(self): + # Containers should not get any format handling done + # (the device node does not allow read / write calls) + if self.type == "mdcontainer": + return False + # BIOS RAID sets should show as present even when teared down + elif self.type == "mdbiosraidarray": + return True + else: + return self.partedDevice is not None + + @property + def model(self): + return self.description + + @property + def partitionable(self): + return self.type == "mdbiosraidarray" + + @property + def isDisk(self): + return self.type == "mdbiosraidarray" + + def dracutSetupString(self): + return "rd_MD_UUID=%s" % self.uuid + + +class DMRaidArrayDevice(DMDevice): + """ A dmraid (device-mapper RAID) device """ + _type = "dm-raid array" + _packages = ["dmraid"] + _partitionable = True + _isDisk = True + + def __init__(self, name, raidSet=None, format=None, + size=None, parents=None, sysfsPath=''): + """ Create a DMRaidArrayDevice instance. + + Arguments: + + name -- the dmraid name also the device node's basename + + Keyword Arguments: + + raidSet -- the RaidSet object from block + parents -- a list of the member devices + sysfsPath -- sysfs device path + size -- the device's size + format -- a DeviceFormat instance + """ + if isinstance(parents, list): + for parent in parents: + if not parent.format or parent.format.type != "dmraidmember": + raise ValueError("parent devices must contain dmraidmember format") + DMDevice.__init__(self, name, format=format, size=size, + parents=parents, sysfsPath=sysfsPath, exists=True) + + self.formatClass = get_device_format_class("dmraidmember") + if not self.formatClass: + raise StorageError("cannot find class for 'dmraidmember'") + + self._raidSet = raidSet + + @property + def raidSet(self): + return self._raidSet + + def _addDevice(self, device): + """ Add a new member device to the array. + + XXX This is for use when probing devices, not for modification + of arrays. + """ + log_method_call(self, self.name, device=device.name, status=self.status) + + if not self.exists: + raise DeviceError("device has not been created", self.name) + + if not isinstance(device.format, self.formatClass): + raise ValueError("invalid device format for dmraid member") + + if device in self.members: + raise ValueError("device is already a member of this array") + + # we added it, so now set up the relations + self.devices.append(device) + device.addChild() + + @property + def members(self): + return self.parents + + @property + def devices(self): + """ Return a list of this array's member device instances. """ + return self.parents + + def deactivate(self): + """ Deactivate the raid set. """ + log_method_call(self, self.name, status=self.status) + # This call already checks if the set is not active. + self._raidSet.deactivate() + + def activate(self): + """ Activate the raid set. """ + log_method_call(self, self.name, status=self.status) + # This call already checks if the set is active. + self._raidSet.activate(mknod=True) + udev_settle() + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + StorageDevice.setup(self, intf=intf, orig=orig) + self.activate() + + def teardown(self, recursive=None): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists and not recursive: + raise DeviceError("device has not been created", self.name) + + log.debug("not tearing down dmraid device %s" % self.name) + + @property + def description(self): + return "BIOS RAID set (%s)" % self._raidSet.rs.set_type + + @property + def model(self): + return self.description + + def dracutSetupString(self): + return "rd_DM_UUID=%s" % self.name + +class MultipathDevice(DMDevice): + """ A multipath device """ + _type = "dm-multipath" + _packages = ["device-mapper-multipath"] + _partitionable = True + _isDisk = True + + def __init__(self, name, info, format=None, size=None, + parents=None, sysfsPath=''): + """ Create a MultipathDevice instance. + + Arguments: + + name -- the device name (generally a device node's basename) + info -- the udev info for this device + + Keyword Arguments: + + sysfsPath -- sysfs device path + size -- the device's size + format -- a DeviceFormat instance + parents -- a list of the backing devices (Device instances) + """ + + self._info = info + self.setupIdentity() + DMDevice.__init__(self, name, format=format, size=size, + parents=parents, sysfsPath=sysfsPath, + exists=True) + + self.config = { + 'wwid' : self.identity, + 'alias' : self.name, + 'mode' : '0600', + 'uid' : '0', + 'gid' : '0', + } + + def setupIdentity(self): + """ Adds identifying remarks to MultipathDevice object. + + May be overridden by a sub-class for e.g. RDAC handling. + """ + self._identity_short = self._info['ID_SERIAL_SHORT'] + self._identity = self._info['ID_SERIAL'] + + @property + def identity(self): + """ Get identity set with setupIdentityFromInfo() + + May be overridden by a sub-class for e.g. RDAC handling. + """ + if not hasattr(self, "_identity"): + raise RuntimeError, "setupIdentityFromInfo() has not been called." + return self._identity + + @property + def wwid(self): + identity = self._identity_short + ret = [] + while identity: + ret.append(identity[:2]) + identity = identity[2:] + return ":".join(ret) + + @property + def model(self): + if not self.parents: + return "" + return self.parents[0].model + + @property + def vendor(self): + if not self.parents: + return "" + return self.parents[0].vendor + + @property + def description(self): + return "WWID %s" % (self.wwid,) + + def addParent(self, parent): + """ Add a parent device to the mpath. """ + log_method_call(self, self.name, status=self.status) + if self.status: + self.teardown() + self.parents.append(parent) + self.setup() + else: + self.parents.append(parent) + + def setupPartitions(self): + log_method_call(self, name=self.name, kids=self.kids) + rc = iutil.execWithRedirect("kpartx", + ["-a", "-p", "p", "/dev/mapper/%s" % self.name], + stdout = "/dev/tty5", + stderr = "/dev/tty5") + if rc: + raise MPathError("multipath partition activation failed for '%s'" % + self.name) + udev_settle() + + def teardown(self, recursive=None): + """ Tear down the mpath device. """ + log_method_call(self, self.name, status=self.status) + + if not self.exists and not recursive: + raise DeviceError("device has not been created", self.name) + + if self.exists and os.path.exists(self.path): + #self.teardownPartitions() + #rc = iutil.execWithRedirect("multipath", + # ['-f', self.name], + # stdout = "/dev/tty5", + # stderr = "/dev/tty5") + #if rc: + # raise MPathError("multipath deactivation failed for '%s'" % + # self.name) + bdev = block.getDevice(self.name) + devmap = block.getMap(major=bdev[0], minor=bdev[1]) + if devmap.open_count: + return + try: + block.removeDeviceMap(devmap) + except Exception as e: + raise MPathError("failed to tear down multipath device %s: %s" + % (self.name, e)) + + if recursive: + self.teardownParents(recursive=recursive) + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + + if self.status: + return + + StorageDevice.setup(self, intf=intf, orig=orig) + udev_settle() + rc = iutil.execWithRedirect("multipath", + [self.name], + stdout = "/dev/tty5", + stderr = "/dev/tty5") + if rc: + raise MPathError("multipath activation failed for '%s'" % + self.name) + udev_settle() + self.setupPartitions() + udev_settle() + +class NoDevice(StorageDevice): + """ A nodev device for nodev filesystems like tmpfs. """ + _type = "nodev" + + def __init__(self, format=None): + """ Create a NoDevice instance. + + Arguments: + + Keyword Arguments: + + format -- a DeviceFormat instance + """ + if format: + name = format.type + else: + name = "none" + + StorageDevice.__init__(self, name, format=format) + + @property + def path(self): + """ Device node representing this device. """ + return self.name + + def probe(self): + """ Probe for any missing information about this device. """ + log_method_call(self, self.name, status=self.status) + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + + def teardown(self, recursive=False): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + self.setupParents() + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + + +class FileDevice(StorageDevice): + """ A file on a filesystem. + + This exists because of swap files. + """ + _type = "file" + _devDir = "" + + def __init__(self, path, format=None, size=None, + exists=None, parents=None): + """ Create a FileDevice instance. + + Arguments: + + path -- full path to the file + + Keyword Arguments: + + format -- a DeviceFormat instance + size -- the file size (units TBD) + parents -- a list of required devices (Device instances) + exists -- indicates whether this is an existing device + """ + StorageDevice.__init__(self, path, format=format, size=size, + exists=exists, parents=parents) + + def probe(self): + """ Probe for any missing information about this device. """ + pass + + @property + def fstabSpec(self): + return self.name + + @property + def path(self): + path = self.name + root = "" + try: + status = self.parents[0].format.status + except (AttributeError, IndexError): + status = False + + if status: + # this is the actual active mountpoint + root = self.parents[0].format._mountpoint + # trim the mountpoint down to the chroot since we already have + # the otherwise fully-qualified path + mountpoint = self.parents[0].format.mountpoint + if mountpoint.endswith("/"): + mountpoint = mountpoint[:-1] + if mountpoint: + root = root[:-len(mountpoint)] + + return os.path.normpath("%s/%s" % (root, path)) + + def setup(self, intf=None, orig=False): + StorageDevice.setup(self, orig=orig) + if self.format and self.format.exists and not self.format.status: + self.format.device = self.path + + for parent in self.parents: + if orig: + parent.originalFormat.setup() + else: + parent.format.setup() + + def teardown(self, recursive=None): + StorageDevice.teardown(self) + if self.format and self.format.exists and not self.format.status: + self.format.device = self.path + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + if self.exists: + raise DeviceError("device already exists", self.name) + + w = None + if intf: + w = intf.waitWindow(_("Creating"), + _("Creating file %s") % (self.path,)) + + try: + # this only checks that parents exist + self.createParents() + self.setupParents() + + fd = os.open(self.path, os.O_RDWR) + buf = '\0' * 1024 * 1024 * self.size + os.write(fd, buf) + except (OSError, TypeError) as e: + log.error("error writing out %s: %s" % (self.path, e)) + raise DeviceError(e, self.name) + else: + self.exists = True + finally: + os.close(fd) + if w: + w.pop() + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + os.unlink(self.path) + self.exists = False + + +class DirectoryDevice(FileDevice): + """ A directory on a filesystem. + + This exists because of bind mounts. + """ + _type = "directory" + + def create(self): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + if self.exists: + raise DeviceError("device already exists", self.name) + + self.createParents() + self.setupParents() + try: + iutil.mkdirChain(self.path) + except Exception, e: + raise DeviceError(e, self.name) + + self.exists = True + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + os.unlink(self.path) + self.exists = False + + +class iScsiDiskDevice(DiskDevice, NetworkStorageDevice): + """ An iSCSI disk. """ + _type = "iscsi" + _packages = ["iscsi-initiator-utils", "dracut-network"] + + def __init__(self, device, **kwargs): + self.node = kwargs.pop("node") + self.ibft = kwargs.pop("ibft") + self.initiator = kwargs.pop("initiator") + DiskDevice.__init__(self, device, **kwargs) + NetworkStorageDevice.__init__(self, host_address=self.node.address) + log.debug("created new iscsi disk %s %s:%d" % (self.node.name, self.node.address, self.node.port)) + + def dracutSetupString(self): + if self.ibft: + return "iscsi_firmware" + + netroot="netroot=iscsi:" + auth = self.node.getAuth() + if auth: + netroot += "%s:%s" % (auth.username, auth.password) + if len(auth.reverse_username) or len(auth.reverse_password): + netroot += ":%s:%s" % (auth.reverse_username, + auth.reverse_password) + + netroot += "@%s::%d::%s" % (self.node.address, self.node.port, + self.node.name) + + netroot += " iscsi_initiator=%s" % self.initiator + + return netroot + +class FcoeDiskDevice(DiskDevice, NetworkStorageDevice): + """ An FCoE disk. """ + _type = "fcoe" + _packages = ["fcoe-utils", "dracut-network"] + + def __init__(self, device, **kwargs): + self.nic = kwargs.pop("nic") + self.identifier = kwargs.pop("identifier") + DiskDevice.__init__(self, device, **kwargs) + NetworkStorageDevice.__init__(self, nic=self.nic) + log.debug("created new fcoe disk %s @ %s" % (device, self.nic)) + + def dracutSetupString(self): + dcb = True + + from .fcoe import fcoe + for nic, dcb in fcoe().nics: + if nic == self.nic: + break + + if dcb: + dcbOpt = "dcb" + else: + dcbOpt = "nodcb" + + return "netroot=fcoe:%s:%s" % (self.nic, dcbOpt) + + +class OpticalDevice(StorageDevice): + """ An optical drive, eg: cdrom, dvd+r, &c. + + XXX Is this useful? + """ + _type = "cdrom" + + def __init__(self, name, major=None, minor=None, exists=None, + format=None, parents=None, sysfsPath='', vendor="", + model=""): + StorageDevice.__init__(self, name, format=format, + major=major, minor=minor, exists=True, + parents=parents, sysfsPath=sysfsPath, + vendor=vendor, model=model) + + @property + def mediaPresent(self): + """ Return a boolean indicating whether or not the device contains + media. + """ + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + try: + fd = os.open(self.path, os.O_RDONLY) + except OSError as e: + # errno 123 = No medium found + if e.errno == 123: + return False + else: + return True + else: + os.close(fd) + return True + + def eject(self): + """ Eject the drawer. """ + import _isys + + log_method_call(self, self.name, status=self.status) + if not self.exists: + raise DeviceError("device has not been created", self.name) + + #try to umount and close device before ejecting + self.teardown() + + # Make a best effort attempt to do the eject. If it fails, it's not + # critical. + fd = os.open(self.path, os.O_RDONLY | os.O_NONBLOCK) + + try: + _isys.ejectcdrom(fd) + except SystemError as e: + log.warning("error ejecting cdrom %s: %s" % (self.name, e)) + + os.close(fd) + + +class ZFCPDiskDevice(DiskDevice): + """ A mainframe ZFCP disk. """ + _type = "zfcp" + + def __init__(self, device, **kwargs): + self.hba_id = kwargs.pop("hba_id") + self.wwpn = kwargs.pop("wwpn") + self.fcp_lun = kwargs.pop("fcp_lun") + DiskDevice.__init__(self, device, **kwargs) + + def __str__(self): + s = DiskDevice.__str__(self) + s += (" hba_id = %(hba_id)s wwpn = %(wwpn)s fcp_lun = %(fcp_lun)s" % + {"hba_id": self.hba_id, + "wwpn": self.wwpn, + "fcp_lun": self.fcp_lun}) + return s + + def dracutSetupString(self): + return "rd_ZFCP=%s,%s,%s" % (self.hba_id, self.wwpn, self.fcp_lun,) + + +class DASDDevice(DiskDevice): + """ A mainframe DASD. """ + _type = "dasd" + + def __init__(self, device, **kwargs): + self.busid = kwargs.pop('busid') + self.opts = kwargs.pop('opts') + self.dasd = kwargs.pop('dasd') + DiskDevice.__init__(self, device, **kwargs) + + if self.dasd: + self.dasd.addDASD(self) + + def getOpts(self): + return map(lambda (k, v): "%s=%s" % (k, v,), self.opts.items()) + + def dracutSetupString(self): + args = ["rd_DASD=%s" % (self.busid,)] + self.getOpts() + return ",".join(args) + + +class NFSDevice(StorageDevice, NetworkStorageDevice): + """ An NFS device """ + _type = "nfs" + _packages = ["dracut-network"] + + def __init__(self, device, format=None, parents=None): + # we could make host/ip, path, &c but will anything use it? + StorageDevice.__init__(self, device, format=format, parents=parents) + NetworkStorageDevice.__init__(self, device.split(":")[0]) + + @property + def path(self): + """ Device node representing this device. """ + return self.name + + def setup(self, intf=None, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status) + + def teardown(self, recursive=None): + """ Close, or tear down, a device. """ + log_method_call(self, self.name, status=self.status) + + def create(self, intf=None): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + self.createParents() + self.setupParents() + + def destroy(self): + """ Destroy the device. """ + log_method_call(self, self.name, status=self.status) |