summaryrefslogtreecommitdiffstats
path: root/pyanaconda/storage/devices.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyanaconda/storage/devices.py')
-rw-r--r--pyanaconda/storage/devices.py4190
1 files changed, 0 insertions, 4190 deletions
diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py
deleted file mode 100644
index 1a4b4d071..000000000
--- a/pyanaconda/storage/devices.py
+++ /dev/null
@@ -1,4190 +0,0 @@
-# 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 pprint
-import tempfile
-
-# device backend modules
-from devicelibs import mdraid
-from devicelibs import lvm
-from devicelibs import dm
-from devicelibs import loop
-from devicelibs import btrfs
-from devicelibs import crypto
-import parted
-import _ped
-import block
-
-from errors import *
-import util
-import arch
-from flags import flags
-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)
-P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)
-
-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:]
-
- if name.startswith("md/"):
- name = name[3:]
-
- return name
-
-
-def deviceNameToDiskByPath(deviceName=None):
- if not deviceName:
- return ""
-
- ret = None
- for dev in udev_get_block_devices():
- if udev_device_get_name(dev) == deviceName:
- ret = udev_device_get_by_path(dev)
- break
-
- if ret:
- return ret
- raise DeviceNotFoundError(deviceName)
-
-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 = "device"
- _packages = []
- _services = []
-
- 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 __repr__(self):
- s = ("%(type)s instance (%(id)s) --\n"
- " name = %(name)s status = %(status)s"
- " kids = %(kids)s id = %(dev_id)s\n"
- " parents = %(parents)s\n" %
- {"type": self.__class__.__name__, "id": "%#x" % id(self),
- "name": self.name, "kids": self.kids, "status": self.status,
- "dev_id": self.id,
- "parents": pprint.pformat([str(p) for p in self.parents])})
- return s
-
- def __str__(self):
- s = "%s %s (%d)" % (self.type, self.name, 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 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):
- """ 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):
- """ 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 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 dracutSetupArgs(self):
- return set()
-
- @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 ancestors(self):
- l = set([self])
- for p in [d for d in self.parents if d not in l]:
- l.update(set(p.ancestors))
- return list(l)
-
- @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 services(self):
- """ List of services required to manage devices of this type.
-
- This list includes the services required by its parent devices."
- """
- services = self._services
- for parent in self.parents:
- for service in parent.services:
- if service not in services:
- services.append(service)
-
- return services
-
- @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"
- _devDir = "/dev"
- sysfsBlockDir = "class/block"
- _resizable = False
- _partitionable = False
- _isDisk = False
-
- def __init__(self, name, format=None, uuid=None,
- size=None, major=None, minor=None,
- sysfsPath='', parents=None, exists=False, serial=None,
- vendor="", model="", bus=""):
- """ Create a StorageDevice instance.
-
- Arguments:
-
- name -- 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
- uuid -- universally unique identifier
- 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, name, parents=parents)
-
- self.uuid = uuid
- self._format = None
- self._size = util.numeric_type(size)
- self.major = util.numeric_type(major)
- self.minor = util.numeric_type(minor)
- self.sysfsPath = sysfsPath
- self._serial = serial
- self._vendor = vendor
- self._model = model
- self.bus = bus
-
- self.protected = False
- self.controllable = not flags.testing
-
- self.format = format
- self.originalFormat = copy.copy(self.format)
- self.fstabComment = ""
- self._targetSize = self._size
-
- self._partedDevice = None
-
- if self.exists and flags.testing and not self._size:
- def read_int_from_sys(path):
- return int(open(path).readline().strip())
-
- device_root = "/sys/class/block/%s" % self.name
- if os.path.exists("%s/queue" % device_root):
- sector_size = read_int_from_sys("%s/queue/logical_block_size"
- % device_root)
- size = read_int_from_sys("%s/size" % device_root)
- self._size = (size * sector_size) / (1024.0 * 1024.0)
-
- def __str__(self):
- exist = "existing"
- if not self.exists:
- exist = "non-existent"
- s = "%s %dMB %s" % (exist, self.size, super(StorageDevice, self).__str__())
- if self.format.type:
- s += " with %s" % self.format
-
- return s
-
- @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 services(self):
- """ List of services required to manage devices of this type.
-
- This list includes the services required by this device's
- format type as well those required by all of its parent
- devices.
- """
- services = super(StorageDevice, self).services
- services.extend(self.format.services)
- for parent in self.parents:
- for service in parent.format.services:
- if service not in services:
- services.append(service)
-
- return services
-
- @property
- def disks(self):
- """ A list of all disks this device depends on, including itself. """
- _disks = []
- for parent in self.parents:
- for disk in parent.disks:
- if disk not in _disks:
- _disks.append(disk)
-
- if self.isDisk and not self.format.hidden:
- _disks.append(self)
-
- return _disks
-
- @property
- def encrypted(self):
- """ True if this device, or any it requires, is encrypted. """
- crypted = False
- for parent in self.parents:
- if parent.encrypted:
- crypted = True
- break
-
- if not crypted and isinstance(self, DMCryptDevice):
- crypted = True
-
- return crypted
-
- @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 __repr__(self):
- s = Device.__repr__(self)
- s += (" uuid = %(uuid)s size = %(size)s\n"
- " format = %(format)s\n"
- " major = %(major)s minor = %(minor)s exists = %(exists)s"
- " protected = %(protected)s\n"
- " sysfs path = %(sysfs)s partedDevice = %(partedDevice)s\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,
- "protected": self.protected,
- "formatArgs": self.formatArgs, "origFmt": self.originalFormat.type})
- 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.resizable)
-
- 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:
- util.notify_kernel(path, action="change")
- except (ValueError, IOError) as 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):
- """ Resize the device.
-
- New size should already be set.
- """
- raise NotImplementedError("resize method not defined for StorageDevice")
-
- #
- # setup
- #
- def _preSetup(self, orig=False):
- """ Preparation and pre-condition checking for device setup.
-
- Return True if setup should proceed or False if not.
- """
- if not self.exists:
- raise DeviceError("device has not been created", self.name)
-
- if self.status or not self.controllable:
- return False
-
- self.setupParents(orig=orig)
- return True
-
- def _setup(self, orig=False):
- """ Perform device-specific setup operations. """
- pass
-
- def setup(self, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
- if not self._preSetup(orig=orig):
- return
-
- self._setup(orig=orig)
- self._postSetup()
-
- def _postSetup(self):
- """ Perform post-setup operations. """
- udev_settle()
- # we always probe since the device may not be set up when we want
- # information about it
- self._size = self.currentSize
-
- #
- # teardown
- #
- def _preTeardown(self, recursive=None):
- """ Preparation and pre-condition checking for device teardown.
-
- Return True if teardown should proceed or False if not.
- """
- if not self.exists and not recursive:
- raise DeviceError("device has not been created", self.name)
-
- if not self.status or not self.controllable:
- return False
-
- if self.originalFormat.exists:
- self.originalFormat.teardown()
- self.format.cacheMajorminor()
- if self.format.exists:
- self.format.teardown()
- udev_settle()
- return True
-
- def _teardown(self, recursive=None):
- """ Perform device-specific teardown operations. """
- pass
-
- def teardown(self, recursive=None):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
- if not self._preTeardown(recursive=recursive):
- return
-
- self._teardown(recursive=recursive)
- self._postTeardown(recursive=recursive)
-
- def _postTeardown(self, recursive=None):
- """ Perform post-teardown operations. """
- if recursive:
- self.teardownParents(recursive=recursive)
-
- #
- # create
- #
- def _preCreate(self):
- """ Preparation and pre-condition checking for device creation. """
- if self.exists:
- raise DeviceError("device has already been created", self.name)
-
- self.setupParents()
-
- def _create(self):
- """ Perform device-specific create operations. """
- pass
-
- def create(self):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
- self._preCreate()
- try:
- self._create()
- except Exception as e:
- raise DeviceCreateError(str(e), self.name)
- else:
- self._postCreate()
-
- def _postCreate(self):
- """ Perform post-create operations. """
- self.exists = True
- self.setup()
- self.updateSysfsPath()
- udev_settle()
-
- #
- # destroy
- #
- def _preDestroy(self):
- """ Preparation and precondition checking for device destruction. """
- 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.teardown()
-
- def _destroy(self):
- """ Perform device-specific destruction operations. """
- pass
-
- def destroy(self):
- """ Destroy the device. """
- log_method_call(self, self.name, status=self.status)
- self._preDestroy()
- self._destroy()
- self._postDestroy()
-
- def _postDestroy(self):
- """ Perform post-destruction operations. """
- self.exists = False
-
- 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)
- if orig:
- _format = parent.originalFormat
- else:
- _format = parent.format
-
- # set up the formatting, if present
- if _format.type and _format.exists:
- _format.setup()
-
- 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
- self._format.device = self.path
-
- def _getFormat(self):
- return self._format
-
- format = property(lambda d: d._getFormat(),
- lambda d,f: d._setFormat(f),
- doc="The device's formatting.")
-
- def preCommitFixup(self, *args, **kwargs):
- """ Do any necessary pre-commit fixups."""
- pass
-
- @property
- def removable(self):
- devpath = os.path.normpath("/sys/%s" % self.sysfsPath)
- remfile = os.path.normpath("%s/removable" % devpath)
- return (self.sysfsPath and os.path.exists(devpath) and
- os.access(remfile, os.R_OK) and
- open(remfile).readline().strip() == "1")
-
- @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
-
- @property
- def growable(self):
- """ True if this device or it's component devices are growable. """
- grow = getattr(self, "req_grow", False)
- if not grow:
- for parent in self.parents:
- grow = parent.growable
- if grow:
- break
- return grow
-
- def checkSize(self):
- """ Check to make sure the size of the device is allowed by the
- format used.
-
- Returns:
- 0 - ok
- 1 - Too large
- -1 - Too small
- """
- if self.format.maxSize and self.size > self.format.maxSize:
- return 1
- elif self.format.minSize and self.size < self.format.minSize:
- return -1
- return 0
-
-class DiskDevice(StorageDevice):
- """ A disk """
- _type = "disk"
- _partitionable = True
- _isDisk = True
-
- def __init__(self, name, format=None,
- size=None, major=None, minor=None, sysfsPath='',
- parents=None, serial=None, vendor="", model="", bus="",
- exists=True):
- """ Create a DiskDevice instance.
-
- Arguments:
-
- name -- 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, name, format=format, size=size,
- major=major, minor=minor, exists=exists,
- sysfsPath=sysfsPath, parents=parents,
- serial=serial, model=model,
- vendor=vendor, bus=bus)
-
- def __repr__(self):
- s = StorageDevice.__repr__(self)
- s += (" removable = %(removable)s partedDevice = %(partedDevice)r" %
- {"removable": self.removable, "partedDevice": self.partedDevice})
- return s
-
- @property
- def mediaPresent(self):
- if flags.testing:
- return True
-
- 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 _preDestroy(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)
-
- StorageDevice._preDestroy(self)
-
-
-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=False,
- 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 and not flags.testing:
- 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)
- elif self.getFlag(parted.PARTITION_BIOS_GRUB):
- # the only way to identify a BIOS Boot partition is to
- # check the partition type/flags, so do it here.
- self.format = getFormat("biosboot", 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 = util.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 __repr__(self):
- s = StorageDevice.__repr__(self)
- s += (" grow = %(grow)s max size = %(maxsize)s bootable = %(bootable)s\n"
- " part type = %(partType)s primary = %(primary)s\n"
- " partedPartition = %(partedPart)s\n"
- " disk = %(disk)s\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 _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):
- if not self.parents:
- devDir = StorageDevice._devDir
- else:
- devDir = self.parents[0]._devDir
-
- return "%s/%s" % (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.name)
- 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" % (self.name, partition))
- self._partedPartition = partition
- self.updateName()
-
- partedPartition = property(lambda d: d._getPartedPartition(),
- lambda d,p: d._setPartedPartition(p))
-
- def preCommitFixup(self, *args, **kwargs):
- """ 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 isinstance(self.parents[0], DMDevice):
- 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:]
- elif isinstance(self.parents[0], MDRaidArrayDevice):
- md_node = mdraid.md_node_from_name(self.name)
- path = os.path.join("/sys", self.sysfsBlockDir, md_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 arch.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):
- 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)
-
- @property
- def isMagic(self):
- if not self.disk:
- return False
-
- number = getattr(self.partedPartition, "number", -1)
- magic = self.disk.format.magicPartitionNumber
- return (number == magic)
-
- 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):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
- 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
-
- def _postCreate(self):
- if self.isExtended:
- partition = self.disk.format.extendedPartition
- else:
- start = self.partedPartition.geometry.start
- partition = self.disk.format.partedDisk.getPartitionBySector(start)
-
- log.debug("post-commit partition path is %s" % getattr(partition,
- "path", None))
- self.partedPartition = partition
- if not self.isExtended:
- # 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()
-
- StorageDevice._postCreate(self)
- self._currentSize = self.partedPartition.getSize()
-
- 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):
- """ Resize the device.
-
- self.targetSize must be set to the new size.
- """
- log_method_call(self, self.name, status=self.status)
- self._preDestroy()
- 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 _preDestroy(self):
- StorageDevice._preDestroy(self)
- if not self.sysfsPath:
- return
-
- self.setupParents(orig=True)
-
- def _destroy(self):
- """ Destroy the device. """
- log_method_call(self, self.name, status=self.status)
- # 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
-
- if self.disk.format.exists and \
- self.disk.format.partedDisk != self.disk.originalFormat.partedDisk:
- # If the new/current disklabel is the same as the original one, we
- # have to duplicate the removal on the other copy of the DiskLabel.
- part = self.disk.format.partedDisk.getPartitionByPath(self.path)
- self.disk.format.removePartition(part)
- self.disk.format.commit()
-
- def deactivate(self):
- """
- This is never called. For instructional purposes only.
-
- We do not want multipath partitions disappearing upon their 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()
-
- 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 Only allow growth up to the amount of free space following this
- # partition on disk. We don't care about leading free space --
- # a filesystem cannot be relocated, so if you want to use space
- # before and after your partition, remove it and create a new one.
- sector = self.partedPartition.geometry.end + 1
- maxPartSize = self.size
- try:
- partition = self.partedPartition.disk.getPartitionBySector(sector)
- except _ped.PartitionException:
- pass
- else:
- if partition.type == parted.PARTITION_FREESPACE:
- maxPartSize = self.size + math.floor(partition.getSize())
-
- return min(self.format.maxSize, maxPartSize)
-
- @property
- def currentSize(self):
- """ The device's actual size. """
- if self.exists:
- return self._currentSize
- else:
- return 0
-
- @property
- def resizable(self):
- """ Can this type of device be resized? """
- return super(PartitionDevice, self).resizable and \
- self.disk.type != 'dasd'
-
- def checkSize(self):
- """ Check to make sure the size of the device is allowed by the
- format used.
-
- Returns:
- 0 - ok
- 1 - Too large
- -1 - Too small
- """
- if self.format.maxSize and self.size > self.format.maxSize:
- return 1
- elif (self.format.minSize and
- (not self.req_grow and
- self.size < self.format.minSize) or
- (self.req_grow and self.req_max_size and
- self.req_max_size < self.format.minSize)):
- return -1
- 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=False, 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 __repr__(self):
- s = StorageDevice.__repr__(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 setupPartitions(self):
- log_method_call(self, name=self.name, kids=self.kids)
- rc = util.run_program(["kpartx", "-a", "-s", self.path])
- if rc:
- raise DMError("partition activation failed for '%s'" % self.name)
- udev_settle()
-
- def teardownPartitions(self):
- log_method_call(self, name=self.name, kids=self.kids)
- rc = util.run_program(["kpartx", "-d", "-s", self.path])
- if rc:
- raise DMError("partition deactivation failed for '%s'" % self.name)
- udev_settle()
-
- 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))
-
- @property
- def slave(self):
- """ This device's backing device. """
- return self.parents[0]
-
-
-class DMLinearDevice(DMDevice):
- _type = "dm-linear"
- _partitionable = True
- _isDisk = True
-
- def __init__(self, name, format=None, size=None, dmUuid=None,
- exists=False, parents=None, sysfsPath=''):
- """ Create a DMLinearDevice instance.
-
- Arguments:
-
- name -- the device name (generally a device node's basename)
-
- Keyword Arguments:
-
- 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
- """
- if not parents:
- raise ValueError("DMLinearDevice requires a backing block device")
-
- DMDevice.__init__(self, name, format=format, size=size,
- parents=parents, sysfsPath=sysfsPath,
- exists=exists, target="linear", dmUuid=dmUuid)
-
- def _setup(self, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
- slave_length = self.slave.partedDevice.length
- dm.dm_create_linear(self.name, self.slave.path, slave_length,
- self.dmUuid)
-
- def _postSetup(self):
- StorageDevice._postSetup(self)
- self.setupPartitions()
- udev_settle()
-
- def _teardown(self, recursive=False):
- self.teardownPartitions()
- udev_settle()
- dm.dm_remove(self.name)
- udev_settle()
-
- def deactivate(self, recursive=False):
- StorageDevice.teardown(self, recursive=recursive)
-
- def teardown(self, recursive=None):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
- if not self._preTeardown(recursive=recursive):
- return
-
- log.debug("not tearing down dm-linear device %s" % self.name)
-
- @property
- def description(self):
- return self.model
-
-
-class DMCryptDevice(DMDevice):
- """ A dm-crypt device """
- _type = "dm-crypt"
-
- def __init__(self, name, format=None, size=None, uuid=None,
- exists=False, 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"
- _packages = ["cryptsetup-luks"]
-
- def __init__(self, name, format=None, size=None, uuid=None,
- exists=False, 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)
-
- @property
- def size(self):
- if not self.exists or not self.partedDevice:
- size = float(self.slave.size) - crypto.LUKS_METADATA_SIZE
- else:
- size = self.partedDevice.getSize()
- return size
-
- def _postCreate(self):
- self._name = self.slave.format.mapName
- StorageDevice._postCreate(self)
-
- def _postTeardown(self, recursive=False):
- if not recursive:
- # this is handled by StorageDevice._postTeardown if recursive
- # is True
- self.teardownParents(recursive=recursive)
-
- StorageDevice._postTeardown(self, recursive=recursive)
-
- def dracutSetupArgs(self):
- return set(["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"
- _packages = ["lvm2"]
-
- def __init__(self, name, parents=None, size=None, free=None,
- peSize=None, peCount=None, peFree=None, pvCount=None,
- uuid=None, exists=False, 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
- 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 = util.numeric_type(free)
- self.peSize = util.numeric_type(peSize)
- self.peCount = util.numeric_type(peCount)
- self.peFree = util.numeric_type(peFree)
- self.pvCount = util.numeric_type(pvCount)
- self.lv_names = []
- self.lv_uuids = []
- self.lv_sizes = []
- self.lv_attr = []
- self.hasDuplicate = False
- self.reserved_percent = 0
- self.reserved_space = 0
-
- # circular references, here I come
- self._lvs = []
-
- # TODO: validate peSize if given
- if not self.peSize:
- self.peSize = lvm.LVM_PE_SIZE # MB
-
- if not self.exists:
- self.pvCount = len(self.parents)
-
- # Some snapshots don't have a proper LV as an origin (--vorigin).
- # They still occupy space in the VG.
- self.voriginSnapshots = {}
-
- def __repr__(self):
- s = DMDevice.__repr__(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 = %(lv_names)s modified = %(modified)s\n"
- " extents = %(extents)s free space = %(freeSpace)s\n"
- " free extents = %(freeExtents)s"
- " reserved percent = %(rpct)s reserved space = %(res)s\n"
- " PVs = %(pvs)s\n"
- " LVs = %(lvs)s" %
- {"free": self.free, "peSize": self.peSize, "peCount": self.peCount,
- "peFree": self.peFree, "pvCount": self.pvCount,
- "lv_names": self.lv_names, "modified": self.isModified,
- "extents": self.extents, "freeSpace": self.freeSpace,
- "freeExtents": self.freeExtents,
- "rpct": self.reserved_percent, "res": self.reserved_space,
- "pvs": pprint.pformat([str(p) for p in self.pvs]),
- "lvs": pprint.pformat([str(l) for l in 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,
- "lv_names": self.lv_names,
- "lv_uuids": self.lv_uuids,
- "lv_sizes": self.lv_sizes,
- "lv_attr": self.lv_attr,
- "reserved_percent": self.reserved_percent,
- "reserved_space": self.reserved_space,
- "lvNames": [lv.name for lv in self.lvs]})
- return d
-
- @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 not self.complete:
- 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:
- # this means there is another vg with the same name on the system
- # set hasDuplicate which will make complete return False
- # and let devicetree._handleInconsistencies() further handle this.
- # Note we still add the device to our parents for use by
- # devicetree._handleInconsistencies()
- self.hasDuplicate = True
-
- 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 self.complete:
- 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:
- raise ValueError("cannot remove non-member PV device from VG")
-
- device.removeChild()
-
- def _preSetup(self, orig=False):
- if self.exists and not self.complete:
- raise DeviceError("cannot activate VG with missing PV(s)", self.name)
- return StorageDevice._preSetup(self, orig=orig)
-
- def _teardown(self, recursive=None):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
- lvm.vgdeactivate(self.name)
-
- def _create(self):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
- pv_list = [pv.path for pv in self.parents]
- lvm.vgcreate(self.name, pv_list, self.peSize)
-
- def _preDestroy(self):
- StorageDevice._preDestroy(self)
- # set up the pvs since lvm needs access to them to do the vgremove
- self.setupParents(orig=True)
-
- def _destroy(self):
- """ Destroy the device. """
- log_method_call(self, self.name, status=self.status)
- if not self.complete:
- for pv in self.pvs:
- # Remove the PVs from the ignore filter so we can wipe them.
- lvm.lvm_cc_removeFilterRejectRegexp(pv.name)
-
- # Don't run vgremove or vgreduce since there may be another VG with
- # the same name that we want to keep/use.
- return
-
- lvm.vgreduce(self.name, [], rm=True)
- lvm.vgdeactivate(self.name)
- lvm.vgremove(self.name)
-
- 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 self.growable and lv.size > self.freeSpace:
- raise DeviceError("new lv is too large to fit in free space", self.name)
-
- log.debug("Adding %s/%dMB to %s" % (lv.name, lv.size, 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()
-
- # and update our pv count
- self.pvCount = len(self.parents)
-
- def addMember(self, member):
- self._addPV(member)
-
- 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()
-
- # and update our pv count
- self.pvCount = len(self.parents)
-
- def removeMember(self, member):
- self._removePV(member)
-
- # 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 snapshotSpace(self):
- """ Total space used by snapshots in this volume group. """
- used = 0
- for lv in self.lvs:
- used += self.align(lv.snapshotSpace, roundup=True)
-
- for (vname, vsize) in self.voriginSnapshots.items():
- used += self.align(vsize, roundup=True)
-
- return used
-
- @property
- def reservedSpace(self):
- """ Reserved space in this VG, in MB """
- reserved = 0
- if self.reserved_percent > 0:
- reserved = self.reserved_percent * 0.01 * self.size
- elif self.reserved_space > 0:
- reserved = self.reserved_space
-
- return self.align(reserved, roundup=True)
-
- @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
- log.debug("%s size is %dMB" % (self.name, self.size))
- used = sum(lv.vgSpaceUsed for lv in self.lvs) + self.snapshotSpace
- used += self.reservedSpace
- 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 = util.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.
- """
- # vgs with duplicate names are overcomplete, which is not what we want
- if self.hasDuplicate:
- return False
-
- return len(self.pvs) == self.pvCount or not self.exists
-
-
-class LVMLogicalVolumeDevice(DMDevice):
- """ An LVM Logical Volume """
- _type = "lvmlv"
- _resizable = True
- _packages = ["lvm2"]
-
- def __init__(self, name, parents=None, size=None, uuid=None,
- stripes=1, logSize=0, snapshotSpace=0,
- format=None, exists=False, sysfsPath='',
- grow=None, maxsize=None, percent=None,
- singlePV=False):
- """ 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
- singlePV -- if true, maps this lv to a single pv
-
- 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(parents, list):
- if len(parents) != 1:
- raise ValueError("constructor requires a single LVMVolumeGroupDevice instance")
- elif not isinstance(parents[0], LVMVolumeGroupDevice):
- raise ValueError("constructor requires a LVMVolumeGroupDevice instance")
- elif not isinstance(parents, LVMVolumeGroupDevice):
- raise ValueError("constructor requires a LVMVolumeGroupDevice instance")
- DMDevice.__init__(self, name, size=size, format=format,
- sysfsPath=sysfsPath, parents=parents,
- exists=exists)
-
- self.singlePVerr = ("%(mountpoint)s is restricted to a single "
- "physical volume on this platform. No physical "
- "volumes available in volume group %(vgname)s "
- "with %(size)d MB of available space." %
- {'mountpoint': getattr(self.format, "mountpoint",
- "A proposed logical volume"),
- 'vgname': self.vg.name,
- 'size': self.size})
-
- self.uuid = uuid
- self.snapshotSpace = snapshotSpace
- self.stripes = stripes
- self.logSize = logSize
- self.singlePV = singlePV
-
- 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 = util.numeric_type(maxsize)
- # XXX should we enforce that req_size be pe-aligned?
- self.req_size = self._size
- self.req_percent = util.numeric_type(percent)
-
- if self.singlePV:
- # make sure there is at least one PV that can hold this LV
- validpvs = filter(lambda x: float(x.size) >= self.req_size,
- self.vg.pvs)
- if not validpvs:
- for dev in self.parents:
- dev.removeChild()
- raise SinglePhysicalVolumeError(self.singlePVerr)
-
- # here we go with the circular references
- self.vg._addLogVol(self)
-
- def __repr__(self):
- s = DMDevice.__repr__(self)
- s += (" VG device = %(vgdev)r\n"
- " 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
-
- @property
- def mirrored(self):
- return self.stripes > 1
-
- def _setSize(self, size):
- size = self.vg.align(util.numeric_type(size))
- log.debug("trying to set lv %s size to %dMB" % (self.name, size))
- if size <= self.vg.freeSpace + self.vgSpaceUsed:
- self._size = size
- self.targetSize = size
- else:
- log.debug("failed to set size: %dMB short" % (size - (self.vg.freeSpace + self.vgSpaceUsed),))
- raise ValueError("not enough free space in volume group")
-
- size = property(StorageDevice._getSize, _setSize)
-
- @property
- def vgSpaceUsed(self):
- """ Space occupied by this LV, not including snapshots. """
- return (self.vg.align(self.size, roundup=True) * self.stripes
- + self.logSize)
-
- @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 setupParents(self, orig=False):
- # parent is a vg, which has no formatting (or device for that matter)
- Device.setupParents(self, orig=orig)
-
- def _setup(self, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
- lvm.lvactivate(self.vg.name, self._name)
-
- def _teardown(self, recursive=None):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
- lvm.lvdeactivate(self.vg.name, self._name)
-
- def _postTeardown(self, recursive=False):
- try:
- # 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.
- StorageDevice._postTeardown(self, recursive=recursive)
- except StorageError:
- if recursive:
- log.debug("vg %s teardown failed; continuing" % self.vg.name)
- else:
- raise
-
- def _create(self):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
- # should we use --zero for safety's sake?
- if self.singlePV:
- lvm.lvcreate(self.vg.name, self._name, self.size,
- pvs=self._getSinglePV())
- else:
- lvm.lvcreate(self.vg.name, self._name, self.size)
-
- def _preDestroy(self):
- StorageDevice._preDestroy(self)
- # set up the vg's pvs so lvm can remove the lv
- self.vg.setupParents(orig=True)
-
- def _destroy(self):
- """ Destroy the device. """
- log_method_call(self, self.name, status=self.status)
- lvm.lvremove(self.vg.name, self._name)
-
- def _getSinglePV(self):
- validpvs = filter(lambda x: float(x.size) >= self.size, self.vg.pvs)
-
- if not validpvs:
- raise SinglePhysicalVolumeError(self.singlePVerr)
-
- return [validpvs[0].path]
-
- def resize(self):
- log_method_call(self, self.name, status=self.status)
- self._preDestroy()
-
- # 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 dracutSetupArgs(self):
- # Note no mapName usage here, this is a lvm cmdline name, which
- # is different (ofcourse)
- return set(["rd.lvm.lv=%s/%s" % (self.vg.name, self._name)])
-
- def checkSize(self):
- """ Check to make sure the size of the device is allowed by the
- format used.
-
- Returns:
- 0 - ok
- 1 - Too large
- -1 - Too small
- """
- if self.format.maxSize and self.size > self.format.maxSize:
- return 1
- elif (self.format.minSize and
- (not self.req_grow and
- self.size < self.format.minSize) or
- (self.req_grow and self.req_max_size and
- self.req_max_size < self.format.minSize)):
- return -1
- return 0
-
-class MDRaidArrayDevice(StorageDevice):
- """ An mdraid (Linux RAID) device. """
- _type = "mdarray"
- _packages = ["mdadm"]
- _devDir = "/dev/md"
-
- def __init__(self, name, level=None, major=None, minor=None, size=None,
- memberDevices=None, totalDevices=None,
- uuid=None, format=None, exists=False, metadataVersion=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')
- metadataVersion -- the version of the device's md metadata
- parents -- list of member devices (StorageDevice instances)
- size -- the device's size (units/format TBD)
- uuid -- the device's UUID
- minor -- the device minor
- 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)):
- for dev in self.parents:
- dev.removeChild()
- raise DeviceError, P_("A RAID%(raidLevel)d set requires at least %(minMembers)d member",
- "A RAID%(raidLevel)d set requires at least %(minMembers)d members",
- mdraid.get_raid_min_members(self.level)) % \
- {"raidLevel": self.level, "minMembers": mdraid.get_raid_min_members(self.level)}
-
- self.uuid = uuid
- self._totalDevices = util.numeric_type(totalDevices)
- self._memberDevices = util.numeric_type(memberDevices)
-
- self.chunkSize = 512.0 / 1024.0 # chunk size in MB
-
- if not self.exists and not isinstance(metadataVersion, str):
- self.metadataVersion = "default"
- else:
- self.metadataVersion = metadataVersion
-
- # bitmaps are not meaningful on raid0 according to mdadm-3.0.3
- self.createBitmap = self.level != 0
-
- # 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"
-
- self.formatClass = get_device_format_class("mdmember")
- if not self.formatClass:
- for dev in self.parents:
- dev.removeChild()
- raise DeviceError("cannot find class for 'mdmember'", self.name)
-
- if self.exists and self.uuid and not flags.testing:
- # this is a hack to work around mdadm's insistence on giving
- # really high minors to arrays it has no config entry for
- open("/etc/mdadm.conf", "a").write("ARRAY %s UUID=%s\n"
- % (self.path, self.uuid))
-
- @property
- def rawArraySize(self):
- """ Calculate the raw array size without taking into account space
- reserved for metadata or chunkSize alignment.
-
- This is used to calculate the superBlockSize for v1.1 and v1.2
- metadata.
-
- Returns the raw size in MB
- """
- smallestMemberSize = self.smallestMember.size
- if self.level == mdraid.RAID0:
- size = self.memberDevices * smallestMemberSize
- elif self.level == mdraid.RAID1:
- size = smallestMemberSize
- elif self.level == mdraid.RAID4:
- size = (self.memberDevices - 1) * smallestMemberSize
- elif self.level == mdraid.RAID5:
- size = (self.memberDevices - 1) * smallestMemberSize
- elif self.level == mdraid.RAID6:
- size = (self.memberDevices - 2) * smallestMemberSize
- elif self.level == mdraid.RAID10:
- size = (self.memberDevices / 2.0) * smallestMemberSize
- else:
- size = smallestMemberSize
- log.error("unknown RAID level %s" % (self.level))
- log.debug("raw RAID %s size == %s" % (self.level, size))
- return size
-
- @property
- def superBlockSize(self):
- """ mdadm has different amounts of space reserved for its use depending
- on the metadata type and size of the array.
-
- 0.9 use 2.0 MB
- 1.0 use 2.0 MB
- 1.1 or 1.2 use the formula lifted from mdadm/super1.c to calculate it
- based on the array size.
- """
- # mdadm 3.2.4 made a major change in the amount of space used for 1.1 and 1.2
- # in order to reserve space for reshaping. See commit 508a7f16 in the
- # upstream mdadm repository.
- if self.metadataVersion not in ["default", "1.1", "1.2"]:
- headroom = 2.0
- else:
- array_size = self.rawArraySize
- # MDADM: We try to leave 0.1% at the start for reshape
- # MDADM: operations, but limit this to 128Meg (0.1% of 10Gig)
- # MDADM: which is plenty for efficient reshapes
- # NOTE: In the mdadm code this is in 512b sectors. Converted to use MB
- headroom = 128
- while headroom << 10 > array_size:
- headroom >>= 1
- log.info("Using %sMB superBlockSize" % (headroom))
- return headroom
-
- @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
- log.debug("non-existent RAID %s size == %s" % (self.level, size))
- else:
- size = self.partedDevice.getSize()
- log.debug("existing RAID %s size == %s" % (self.level, size))
-
- 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 __repr__(self):
- s = StorageDevice.__repr__(self)
- s += (" level = %(level)s spares = %(spares)s\n"
- " members = %(memberDevices)s\n"
- " total devices = %(totalDevices)s"
- " metadata version = %(metadataVersion)s" %
- {"level": self.level, "spares": self.spares,
- "memberDevices": self.memberDevices,
- "totalDevices": self.totalDevices,
- "metadataVersion": self.metadataVersion})
- return s
-
- @property
- def dict(self):
- d = super(MDRaidArrayDevice, self).dict
- d.update({"level": self.level,
- "spares": self.spares, "memberDevices": self.memberDevices,
- "totalDevices": self.totalDevices,
- "metadataVersion": self.metadataVersion})
- return d
-
- @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 and \
- self.totalDevices > self.memberDevices:
- spares = self.totalDevices - self.memberDevices
- elif self.totalDevices is None:
- 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 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)
-
- # We don't use self.status here because self.status requires a valid
- # sysfs path to function correctly.
- if os.path.exists(self.path):
- md_node = mdraid.md_node_from_name(self.name)
- self.sysfsPath = "/devices/virtual/block/%s" % md_node
- 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()
-
- if self.spares > 0:
- # mdadm doesn't like it when you try to incrementally add spares
- return
-
- 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()
-
- def addMember(self, member):
- if member in self.parents:
- raise ValueError("member is already part of this array")
-
- # for the time being we will not allow adding members to existing arrays
- if self.exists:
- raise DeviceError("cannot add member to existing array", self.name)
-
- self.parents.append(member)
- member.addChild()
- self.memberDevices += 1
-
- def removeMember(self, member):
- self._removeDevice(member)
- self.memberDevices -= 1
-
- @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 = open(state_file).read().strip()
- 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):
- val = open(degraded_file).read().strip()
- if val == "1":
- rc = True
-
- return rc
-
- @property
- def complete(self):
- if self.type == "mdbiosraidarray":
- members = len(self.parents[0].parents)
- else:
- members = len(self.parents)
-
- return (self.memberDevices <= members) or not self.exists
-
- @property
- def devices(self):
- """ Return a list of this array's member device instances. """
- return self.parents
-
- def _postSetup(self):
- super(MDRaidArrayDevice, self)._postSetup()
- self.updateSysfsPath()
-
- def _setup(self, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
- disks = []
- for member in self.devices:
- member.setup(orig=orig)
- disks.append(member.path)
-
- mdraid.mdactivate(self.path,
- members=disks,
- uuid=self.uuid)
-
- def teardown(self, recursive=None):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
- # we don't really care about the return value of _preTeardown here.
- # see comment just above mddeactivate call
- self._preTeardown(recursive=recursive)
-
- # 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)
-
- self._postTeardown(recursive=recursive)
-
- def preCommitFixup(self, *args, **kwargs):
- """ Determine create parameters for this set """
- mountpoints = kwargs.pop("mountpoints")
- log_method_call(self, self.name, mountpoints)
-
- if "/boot" in mountpoints:
- bootmountpoint = "/boot"
- else:
- bootmountpoint = "/"
-
- # If we are used to boot from we cannot use 1.1 metadata
- if getattr(self.format, "mountpoint", None) == bootmountpoint or \
- getattr(self.format, "mountpoint", None) == "/boot/efi" or \
- self.format.type == "prepboot":
- self.metadataVersion = "1.0"
-
- # Bitmaps are not useful for swap and small partitions
- if self.size < 1000 or self.format.type == "swap":
- self.createBitmap = False
-
- def _postCreate(self):
- # this is critical since our status method requires a valid sysfs path
- md_node = mdraid.md_node_from_name(self.name)
- self.sysfsPath = "/devices/virtual/block/%s" % md_node
- self.exists = True # I think we can remove this.
-
- StorageDevice._postCreate(self)
-
- # update our uuid attribute with the new array's UUID
- info = udev_get_block_device(self.sysfsPath)
- self.uuid = udev_device_get_md_uuid(info)
- for member in self.devices:
- member.mdUuid = self.uuid
-
- def _create(self):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
- disks = [disk.path for disk in self.devices]
- spares = len(self.devices) - self.memberDevices
- mdraid.mdcreate(self.path,
- self.level,
- disks,
- spares,
- metadataVer=self.metadataVersion,
- bitmap=self.createBitmap)
-
- @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)]
-
- @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
- elif flags.testing:
- 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 dracutSetupArgs(self):
- return set(["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, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
- self.activate()
-
- def teardown(self, recursive=None):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
- if not self._preTeardown(recursive=recursive):
- return
-
- 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 dracutSetupArgs(self):
- return set(["rd.dm.uuid=%s" % self.name])
-
-class MultipathDevice(DMDevice):
- """ A multipath device """
- _type = "dm-multipath"
- _packages = ["device-mapper-multipath"]
- _services = ["multipathd"]
- _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,
- '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 = self._info.get("ID_SERIAL_RAW", self._info.get("ID_SERIAL_SHORT"))
-
- @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
- 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 deactivate(self):
- """
- This is never called, included just for documentation.
-
- If we called this during teardown(), we wouldn't be able to get parted
- object because /dev/mapper/mpathX wouldn't exist.
- """
- if self.exists and os.path.exists(self.path):
- #self.teardownPartitions()
- #rc = util.run_program(["multipath", '-f', self.name])
- #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))
-
- def _setup(self, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
- udev_settle()
- rc = util.run_program(["multipath", self.name])
- if rc:
- raise MPathError("multipath activation failed for '%s'" %
- self.name, hardware_fault=True)
-
- def _postSetup(self):
- StorageDevice._postSetup(self)
- 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, exists=True)
-
- @property
- def path(self):
- """ Device node representing this device. """
- return self.name
-
- def setup(self, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
-
- def teardown(self, recursive=False):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
- # just make sure the format is unmounted
- self._preTeardown(recursive=recursive)
-
- def create(self):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
-
- def destroy(self):
- """ Destroy the device. """
- log_method_call(self, self.name, status=self.status)
- self._preDestroy()
-
-
-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=False, 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
- """
- if not path.startswith("/"):
- raise ValueError("FileDevice requires an absolute path")
-
- StorageDevice.__init__(self, path, format=format, size=size,
- exists=exists, parents=parents)
-
- @property
- def fstabSpec(self):
- return self.name
-
- @property
- def path(self):
- root = ""
- try:
- status = self.parents[0].format.status
- except (AttributeError, IndexError):
- # either this device has no parents or something is wrong with
- # the first one
- status = (os.access(self.name, os.R_OK) and
- self.parents in ([], None))
- else:
- # 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
- while mountpoint.endswith("/"):
- mountpoint = mountpoint[:-1]
- if mountpoint:
- root = root[:-len(mountpoint)]
-
- return os.path.normpath("%s%s" % (root, self.name))
-
- def _preSetup(self, orig=False):
- if self.format and self.format.exists and not self.format.status:
- self.format.device = self.path
-
- return StorageDevice._preSetup(self, orig=orig)
-
- def _preTeardown(self, recursive=None):
- if self.format and self.format.exists and not self.format.status:
- self.format.device = self.path
-
- return StorageDevice._preTeardown(self, recursive=recursive)
-
- def _create(self):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
- fd = os.open(self.path, os.O_WRONLY|os.O_CREAT|os.O_TRUNC)
- buf = "\0" * 1024 * 1024
- for n in range(self.size):
- os.write(fd, buf)
- os.close(fd)
-
- def _destroy(self):
- """ Destroy the device. """
- log_method_call(self, self.name, status=self.status)
- os.unlink(self.path)
-
-
-class SparseFileDevice(FileDevice):
- """A sparse file on a filesystem.
- This exists for sparse disk images."""
- _type = "sparse file"
- def _create(self):
- """Create a sparse file."""
- log_method_call(self, self.name, status=self.status)
- fd = os.open(self.path, os.O_WRONLY|os.O_CREAT|os.O_TRUNC)
- os.ftruncate(fd, 1024*1024*self.size)
- os.close(fd)
-
-
-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)
- util.makedirs(self.path)
-
-
-class LoopDevice(StorageDevice):
- """ A loop device. """
- _type = "loop"
-
- def __init__(self, name=None, format=None, size=None, sysfsPath=None,
- exists=False, parents=None):
- """ Create a LoopDevice instance.
-
- Arguments:
-
- name -- the device's name
-
- Keyword Arguments:
-
- format -- a DeviceFormat instance
- size -- the device's size in MB
- parents -- a list of required devices (Device instances)
- exists -- indicates whether this is an existing device
-
-
- Loop devices always exist.
- """
- if not parents:
- raise ValueError("LoopDevice requires a backing device")
-
- if not name:
- # set up a temporary name until we've activated the loop device
- name = "tmploop%d" % Device._id
-
- StorageDevice.__init__(self, name, format=format, size=size,
- exists=True, parents=parents)
-
- def updateName(self):
- """ Update this device's name. """
- if not self.slave.status:
- # if the backing device is inactive, so are we
- return self.name
-
- if self.name.startswith("loop"):
- # if our name is loopN we must already be active
- return self.name
-
- name = loop.get_loop_name(self.slave.path)
- if name.startswith("loop"):
- self._name = name
-
- return self.name
-
- @property
- def status(self):
- return (self.slave.status and
- self.name.startswith("loop") and
- loop.get_loop_name(self.slave.path) == self.name)
-
- @property
- def size(self):
- return self.slave.size
-
- def _setup(self, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
- loop.loop_setup(self.slave.path)
-
- def _postSetup(self):
- StorageDevice._postSetup(self)
- self.updateName()
- self.updateSysfsPath()
-
- def _teardown(self, recursive=False):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
- loop.loop_teardown(self.path)
-
- def _postTeardown(self, recursive=False):
- StorageDevice._postTeardown(self, recursive=recursive)
- self._name = "tmploop%d" % self.id
- self.sysfsPath = ''
-
- @property
- def slave(self):
- return self.parents[0]
-
-
-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.nic = kwargs.pop("nic")
- self.initiator = kwargs.pop("initiator")
-
- if self.node is None:
- # qla4xxx partial offload
- name = kwargs.pop("fw_name")
- address = kwargs.pop("fw_address")
- port = kwargs.pop("fw_port")
- DiskDevice.__init__(self, device, **kwargs)
- NetworkStorageDevice.__init__(self,
- host_address=address,
- nic=self.nic)
- log.debug("created new iscsi disk %s %s:%s using fw initiator %s"
- % (name, address, port, self.initiator))
- else:
- DiskDevice.__init__(self, device, **kwargs)
- NetworkStorageDevice.__init__(self, host_address=self.node.address,
- nic=self.nic)
- log.debug("created new iscsi disk %s %s:%d via %s:%s" % (self.node.name,
- self.node.address,
- self.node.port,
- self.node.iface,
- self.nic))
-
- def dracutSetupArgs(self):
- if self.ibft:
- return set(["iscsi_firmware"])
-
- # qla4xxx partial offload
- if self.node is None:
- return set()
-
- address = self.node.address
- # surround ipv6 addresses with []
- if ":" in address:
- address = "[%s]" % address
-
- 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)
-
- iface_spec = ""
- if self.nic != "default":
- iface_spec = ":%s:%s" % (self.node.iface, self.nic)
- netroot += "@%s::%d%s::%s" % (address,
- self.node.port,
- iface_spec,
- self.node.name)
-
- initiator = "iscsi_initiator=%s" % self.initiator
-
- return set([netroot, initiator])
-
-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) @ %s" %
- (device, self.identifier, self.nic))
-
- def dracutSetupArgs(self):
- dcb = True
-
- from .fcoe import fcoe
- for nic, dcb, auto_vlan in fcoe().nics:
- if nic == self.nic:
- break
-
- if dcb:
- dcbOpt = "dcb"
- else:
- dcbOpt = "nodcb"
-
- return set(["fcoe=edd:%s" % 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=False,
- 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. """
- 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()
-
- try:
- util.run_program(["eject", self.name])
- except OSError as e:
- log.warning("error ejecting cdrom %s: %s" % (self.name, e))
-
-
-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 __repr__(self):
- s = DiskDevice.__repr__(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
-
- @property
- def description(self):
- return "FCP device %(device)s with WWPN %(wwpn)s and LUN %(lun)s" \
- % {'device': self.hba_id,
- 'wwpn': self.wwpn,
- 'lun': self.fcp_lun}
-
- def dracutSetupArgs(self):
- return set(["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)
-
- @property
- def description(self):
- return "DASD device %s" % self.busid
-
- def getOpts(self):
- return ["%s=%s" % (k, v) for k, v in self.opts.items() if v == '1']
-
- def dracutSetupArgs(self):
- conf = "/etc/dasd.conf"
- line = None
- if os.path.isfile(conf):
- f = open(conf)
- # grab the first line that starts with our busID
- line = [line for line in f.readlines()
- if line.startswith(self.busid)][:1]
- f.close()
-
- # See if we got a line. If not, grab our getOpts
- if not line:
- line = self.busid
- for devopt in self.getOpts():
- line += " %s" % devopt
-
- # Create a translation mapping from dasd.conf format to module format
- translate = {'use_diag': 'diag',
- 'readonly': 'ro',
- 'erplog': 'erplog',
- 'failfast': 'failfast'}
-
- # this is a really awkward way of determining if the
- # feature found is actually desired (1, not 0), plus
- # translating that feature into the actual kernel module
- # value
- opts = []
- parts = line.split()
- for chunk in parts[1:]:
- try:
- feat, val = chunk.split('=')
- if int(val):
- opts.append(translate[feat])
- except:
- # If we don't know what the feature is (feat not in translate
- # or if we get a val that doesn't cleanly convert to an int
- # we can't do anything with it.
- log.warning("failed to parse dasd feature %s" % chunk)
-
- if opts:
- return set(["rd.dasd=%s(%s)" % (self.busid,
- ":".join(opts))])
- else:
- return set(["rd.dasd=%s" % self.busid])
-
-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, orig=False):
- """ Open, or set up, a device. """
- log_method_call(self, self.name, orig=orig, status=self.status,
- controllable=self.controllable)
-
- def teardown(self, recursive=None):
- """ Close, or tear down, a device. """
- log_method_call(self, self.name, status=self.status,
- controllable=self.controllable)
-
- def create(self):
- """ Create the device. """
- log_method_call(self, self.name, status=self.status)
- self._preCreate()
-
- def destroy(self):
- """ Destroy the device. """
- log_method_call(self, self.name, status=self.status)
-
-
-class BTRFSDevice(StorageDevice):
- """ Base class for BTRFS volume and sub-volume devices. """
- _type = "btrfs"
- _packages = ["btrfs-progs"]
-
- def __init__(self, *args, **kwargs):
- """ Passing None or no name means auto-generate one like btrfs.%d """
- if not args or not args[0]:
- args = ("btrfs.%d" % Device._id,)
-
- self.req_size = kwargs.pop("size", None)
- super(BTRFSDevice, self).__init__(*args, **kwargs)
-
- def updateSysfsPath(self):
- """ Update this device's sysfs path. """
- log_method_call(self, self.name, status=self.status)
- self.parents[0].updateSysfsPath()
- self.sysfsPath = self.parents[0].sysfsPath
- log.debug("%s sysfsPath set to %s" % (self.name, self.sysfsPath))
-
- def _postCreate(self):
- super(BTRFSDevice, self)._postCreate()
- self.format.exists = True
- self.format.device = self.path
-
- def _preDestroy(self):
- """ Preparation and precondition checking for device destruction. """
- super(BTRFSDevice, self)._preDestroy()
- self.setupParents(orig=True)
-
- def _getSize(self):
- size = sum([d.size for d in self.parents])
- return size
-
- def _setSize(self, size):
- raise RuntimeError("cannot directly set size of btrfs volume")
-
- @property
- def status(self):
- return not any([not d.status for d in self.parents])
-
- @property
- def _temp_dir_prefix(self):
- return "btrfs-tmp.%s" % self.id
-
- def _do_temp_mount(self, orig=False):
- if self.format.status or not self.exists:
- return
-
- tmpdir = tempfile.mkdtemp(prefix=self._temp_dir_prefix)
- if orig:
- fmt = self.originalFormat
- else:
- fmt = self.format
-
- fmt.mount(mountpoint=tmpdir)
-
- def _undo_temp_mount(self):
- if getattr(self.format, "_mountpoint", None):
- fmt = self.format
- elif getattr(self.originalFormat, "_mountpoint", None):
- fmt = self.originalFormat
- else:
- return
-
- mountpoint = fmt._mountpoint
-
- if os.path.basename(mountpoint).startswith(self._temp_dir_prefix):
- fmt.unmount()
- os.rmdir(mountpoint)
-
- @property
- def path(self):
- return self.parents[0].path
-
-
-class BTRFSVolumeDevice(BTRFSDevice):
- _type = "btrfs volume"
-
- def __init__(self, *args, **kwargs):
- self.dataLevel = kwargs.pop("dataLevel", None)
- self.metaDataLevel = kwargs.pop("metaDataLevel", None)
-
- super(BTRFSVolumeDevice, self).__init__(*args, **kwargs)
-
- self.subvolumes = []
-
- for parent in self.parents:
- if parent.format.type != "btrfs":
- raise ValueError("member device %s is not BTRFS" % parent.name)
-
- if parent.format.exists and self.exists and \
- parent.format.volUUID != self.uuid:
- raise ValueError("BTRFS member device %s UUID %s does not "
- "match volume UUID %s" % (parent.name,
- parent.format.volUUID, self.uuid))
-
- if self.parents and not self.format.type:
- label = getattr(self.parents[0].format, "label", None)
- self.format = getFormat("btrfs",
- exists=self.exists,
- label=label,
- volUUID=self.uuid,
- device=self.path)
- self.originalFormat = copy.copy(self.format)
-
- def _setFormat(self, format):
- """ Set the Device's format. """
- super(BTRFSVolumeDevice, self)._setFormat(format)
- self._name = "btrfs.%d" % self.id
- label = getattr(self.format, "label", None)
- if label:
- self._name = label
-
- def _getSize(self):
- size = sum([d.size for d in self.parents])
- if self.dataLevel in ("raid1", "raid10"):
- size /= len(self.parents)
-
- return size
-
- def _addDevice(self, device):
- """ Add a new device to this volume.
-
- XXX This is for use by device probing routines and is not
- intended for modification of the volume.
- """
- 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 device.format.type != "btrfs":
- raise ValueError("addDevice requires a btrfs device as sole arg")
-
- if device.format.volUUID != self.uuid:
- raise ValueError("device UUID does not match the volume UUID")
-
- if device in self.parents:
- raise ValueError("device is already a member of this volume")
-
- self.parents.append(device)
- device.addChild()
-
- def _removeDevice(self, device):
- """ Remove a device from the volume.
-
- 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:
- raise ValueError("cannot remove non-member device from volume")
-
- device.removeChild()
-
- def addMember(self, member):
- if member in self.parents:
- raise ValueError("member is already part of this volume")
-
- # for the time being we will not allow adding members to existing vols
- if self.exists:
- raise DeviceError("cannot add member to existing volume", self.name)
-
- self.parents.append(member)
- member.addChild()
-
- def removeMember(self, member):
- if member not in self.parents:
- raise ValueError("member is not part of this volume")
-
- if self.exists:
- raise DeviceError("cannot remove member from an existing volume")
-
- self.parents.remove(member)
- member.removeChild()
-
- def _addSubVolume(self, vol):
- if vol.name in [v.name for v in self.subvolumes]:
- raise ValueError("subvolume %s already exists" % vol.name)
-
- self.subvolumes.append(vol)
-
- def _removeSubVolume(self, name):
- if name not in [v.name for v in self.subvolumes]:
- raise ValueError("cannot remove non-existent subvolume %s" % name)
-
- names = [v.name for v in self.subvolumes]
- self.subvolumes.pop(names.index(name))
-
- def listSubVolumes(self):
- subvols = []
- self.setup(orig=True)
- try:
- self._do_temp_mount(orig=True)
- except FSError as e:
- log.debug("btrfs temp mount failed: %s" % e)
- return subvols
-
- try:
- subvols = btrfs.list_subvolumes(self.originalFormat._mountpoint)
- except BTRFSError as e:
- log.debug("failed to list subvolumes: %s" % e)
- finally:
- self._undo_temp_mount()
-
- return subvols
-
- def createSubVolumes(self):
- self._do_temp_mount()
- for name, subvol in self.subvolumes:
- if subvol.exists:
- continue
- subvol.create(mountpoint=self._temp_dir_prefix)
- self._undo_temp_mount()
-
- def removeSubVolume(self, name):
- raise NotImplementedError()
-
- def _create(self):
- log_method_call(self, self.name, status=self.status)
- btrfs.create_volume(devices=[d.path for d in self.parents],
- label=self.format.label,
- data=self.dataLevel,
- metadata=self.metaDataLevel)
-
- def _destroy(self):
- log_method_call(self, self.name, status=self.status)
- for device in self.parents:
- device.setup(orig=True)
- DeviceFormat(device=device.path, exists=True).destroy()
-
-class BTRFSSubVolumeDevice(BTRFSDevice):
- """ A btrfs subvolume pseudo-device. """
- _type = "btrfs subvolume"
-
- def __init__(self, *args, **kwargs):
- self.vol_id = kwargs.pop("vol_id", None)
- super(BTRFSSubVolumeDevice, self).__init__(*args, **kwargs)
-
- self.volume._addSubVolume(self)
-
- @property
- def volume(self):
- return self.parents[0]
-
- def setupParents(self, orig=False):
- """ Run setup method of all parent devices. """
- log_method_call(self, name=self.name, orig=orig, kids=self.kids)
- self.volume.setup(orig=orig)
-
- def _create(self):
- log_method_call(self, self.name, status=self.status)
- self.volume._do_temp_mount()
- mountpoint = self.volume.format._mountpoint
- if not mountpoint:
- raise RuntimeError("btrfs subvol create requires mounted volume")
-
- btrfs.create_subvolume(mountpoint, self.name)
- self.volume._undo_temp_mount()
-
- def _destroy(self):
- log_method_call(self, self.name, status=self.status)
- self.volume._do_temp_mount(orig=True)
- mountpoint = self.volume.originalFormat._mountpoint
- if not mountpoint:
- raise RuntimeError("btrfs subvol destroy requires mounted volume")
- btrfs.delete_subvolume(mountpoint, self.name)
- self.volume._undo_temp_mount()