qubes-installer-qubes-os/anaconda/pyanaconda/storage/devices.py
2013-01-24 01:45:53 +01:00

4214 lines
143 KiB
Python

# 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 *
from pyanaconda.iutil import notify_kernel, numeric_type
from pyanaconda.flags import flags
from pyanaconda.anaconda_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 = numeric_type(size)
self.major = numeric_type(major)
self.minor = numeric_type(minor)
self.sysfsPath = sysfsPath
self._serial = serial
self._vendor = vendor
self._model = model
self.bus = bus
self.protected = False
self.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:
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 = 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 iutil.isS390():
return
if self.flagAvailable(parted.PARTITION_BOOT):
if bootable:
self.setFlag(parted.PARTITION_BOOT)
else:
self.unsetFlag(parted.PARTITION_BOOT)
else:
raise DeviceError("boot flag not available for this partition", self.name)
self._bootable = bootable
else:
self.req_bootable = bootable
def _getBootable(self):
return self._bootable or self.req_bootable
bootable = property(_getBootable, _setBootable)
def flagAvailable(self, flag):
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 = iutil.execWithRedirect("kpartx",
["-a", "-s", self.path],
stdout = "/dev/tty5",
stderr = "/dev/tty5")
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 = iutil.execWithRedirect("kpartx",
["-d", "-s", self.path],
stdout = "/dev/tty5",
stderr = "/dev/tty5")
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 = numeric_type(free)
self.peSize = numeric_type(peSize)
self.peCount = numeric_type(peCount)
self.peFree = numeric_type(peFree)
self.pvCount = 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 = 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 = numeric_type(maxsize)
# XXX should we enforce that req_size be pe-aligned?
self.req_size = self._size
self.req_percent = 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(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 = numeric_type(totalDevices)
self._memberDevices = 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 = iutil.execWithRedirect("multipath",
# ['-f', self.name],
# stdout = "/dev/tty5",
# stderr = "/dev/tty5")
#if rc:
# raise MPathError("multipath deactivation failed for '%s'" %
# self.name)
bdev = block.getDevice(self.name)
devmap = block.getMap(major=bdev[0], minor=bdev[1])
if devmap.open_count:
return
try:
block.removeDeviceMap(devmap)
except Exception as e:
raise MPathError("failed to tear down multipath device %s: %s"
% (self.name, e))
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 = iutil.execWithRedirect("multipath",
[self.name],
stdout = "/dev/tty5",
stderr = "/dev/tty5")
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)
iutil.mkdirChain(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. """
from pyanaconda import _isys
log_method_call(self, self.name, status=self.status)
if not self.exists:
raise DeviceError("device has not been created", self.name)
#try to umount and close device before ejecting
self.teardown()
if flags.cmdline.has_key('noeject'):
log.info("noeject in effect, not ejecting cdrom")
return
# Make a best effort attempt to do the eject. If it fails, it's not
# critical.
fd = os.open(self.path, os.O_RDONLY | os.O_NONBLOCK)
try:
_isys.ejectcdrom(fd)
except SystemError as e:
log.warning("error ejecting cdrom %s: %s" % (self.name, e))
os.close(fd)
class ZFCPDiskDevice(DiskDevice):
""" A mainframe ZFCP disk. """
_type = "zfcp"
def __init__(self, device, **kwargs):
self.hba_id = kwargs.pop("hba_id")
self.wwpn = kwargs.pop("wwpn")
self.fcp_lun = kwargs.pop("fcp_lun")
DiskDevice.__init__(self, device, **kwargs)
def __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()