summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Jones <pjones@redhat.com>2009-05-27 10:49:23 -0400
committerPeter Jones <pjones@redhat.com>2009-08-06 15:37:26 -0400
commit883d99a856ee36c8dcb5e5dfc50fa0cf181bec70 (patch)
tree1084cb2acf1c88cbeff233a4c4858b211f9c42b0
parentd8b635d3e2cfd1af7c95b246a6428adeb78fecee (diff)
downloadanaconda-883d99a856ee36c8dcb5e5dfc50fa0cf181bec70.tar.gz
anaconda-883d99a856ee36c8dcb5e5dfc50fa0cf181bec70.tar.xz
anaconda-883d99a856ee36c8dcb5e5dfc50fa0cf181bec70.zip
Recognize mpath devices when we see them.
This identifies that a device is part of a multipath, and builds an mpath device for partitioning.
-rw-r--r--70-anaconda.rules2
-rw-r--r--storage/devicelibs/dm.py8
-rw-r--r--storage/devices.py93
-rw-r--r--storage/devicetree.py173
-rw-r--r--storage/errors.py3
-rw-r--r--storage/formats/multipath.py88
-rw-r--r--storage/udev.py36
7 files changed, 371 insertions, 32 deletions
diff --git a/70-anaconda.rules b/70-anaconda.rules
index 65d3141ce..a42cd57e0 100644
--- a/70-anaconda.rules
+++ b/70-anaconda.rules
@@ -8,7 +8,7 @@ IMPORT{program}="$env{ANACBIN}/blkid -o udev -p $tempnode"
KERNEL!="dm-*", GOTO="anaconda_mdraid"
-IMPORT{program}="$env{ANACBIN}/dmsetup info -c --nameprefixes --unquoted --rows --noheadings -o name,uuid,suspended,readonly,major,minor,open,tables_loaded -j%M -m%m"
+IMPORT{program}="$env{ANACBIN}/dmsetup info -c --nameprefixes --unquoted --rows --noheadings -o name,uuid,suspended,readonly,major,minor,open,tables_loaded,names_using_dev -j%M -m%m"
ENV{DM_NAME}!="?*", GOTO="anaconda_end"
SYMLINK+="disk/by-id/dm-name-$env{DM_NAME}"
diff --git a/storage/devicelibs/dm.py b/storage/devicelibs/dm.py
index 29df1266e..c118d510c 100644
--- a/storage/devicelibs/dm.py
+++ b/storage/devicelibs/dm.py
@@ -67,6 +67,14 @@ def dm_node_from_name(map_name):
log.debug("dm_node_from_name(%s) returning '%s'" % (map_name, dm_node))
return dm_node
+def dm_is_multipath(major, minor):
+ for map in block.dm.maps():
+ dev = map.dev
+ if dev.major == int(major) and dev.minor == int(minor):
+ for table in map.table:
+ if table.type == 'multipath':
+ return True
+
def _get_backing_devnums_from_map(map_name):
ret = []
buf = iutil.execWithCapture("dmsetup",
diff --git a/storage/devices.py b/storage/devices.py
index 2d6a2942b..bc6b0f8b2 100644
--- a/storage/devices.py
+++ b/storage/devices.py
@@ -104,6 +104,7 @@ from devicelibs import dm
import parted
import _ped
import platform
+import block
from errors import *
from iutil import log_method_call, notify_kernel, numeric_type
@@ -416,7 +417,7 @@ class StorageDevice(Device):
def __init__(self, device, format=None,
size=None, major=None, minor=None,
- sysfsPath='', parents=None, exists=None):
+ sysfsPath='', parents=None, exists=None, serial=None):
""" Create a StorageDevice instance.
Arguments:
@@ -446,6 +447,7 @@ class StorageDevice(Device):
self.minor = numeric_type(minor)
self.sysfsPath = sysfsPath
self.exists = exists
+ self.serial = serial
self.protected = False
@@ -2733,19 +2735,34 @@ class DMRaidArrayDevice(DiskDevice):
# information about it
self._size = self.currentSize
+class _MultipathDeviceNameGenerator:
+ def __init__(self):
+ self.number = 0
+ def get(self):
+ ret = self.number
+ self.number += 1
+ return ret
+_multipathDeviceNameGenerator = _MultipathDeviceNameGenerator()
-class MultipathDevice(DMDevice):
+def generateMultipathDeviceName():
+ number = _multipathDeviceNameGenerator.get()
+ return "mpath%s" % (number, )
+
+class MultipathDevice(DiskDevice):
""" A multipath device """
_type = "dm-multipath"
_packages = ["device-mapper-multipath"]
+ _devDir = "/dev/mapper"
- def __init__(self, name, format=None, size=None,
- exists=None, parents=None, sysfsPath=''):
+ def __init__(self, name, info, format=None, size=None,
+ parents=None, sysfsPath='', initcb=None,
+ initlabel=None):
""" Create a MultipathDevice instance.
Arguments:
name -- the device name (generally a device node's basename)
+ info -- the udev info for this device
Keyword Arguments:
@@ -2753,12 +2770,74 @@ class MultipathDevice(DMDevice):
size -- the device's size
format -- a DeviceFormat instance
parents -- a list of the backing devices (Device instances)
- exists -- indicates whether this is an existing device
+ initcb -- the call back to be used when initiating disk.
+ initlabel -- whether to start with a fresh disklabel
"""
- DMDevice.__init__(self, name, format=format, size=size,
+
+ self._info = info
+ self._isUp = False
+ self._pyBlockMultiPath = None
+ self.setupIdentity()
+ DiskDevice.__init__(self, name, format=format, size=size,
parents=parents, sysfsPath=sysfsPath,
- exists=exists)
+ initcb=initcb, initlabel=initlabel)
+
+ def setupIdentity(self):
+ """ Adds identifying remarks to MultipathDevice object.
+
+ May be overridden by a sub-class for e.g. RDAC handling.
+ """
+ self._serial = self._info['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, "_serial"):
+ raise RuntimeError, "setupIdentityFromInfo() has not been called."
+ return self._serial
+
+ @property
+ def status(self):
+ return self._isUp
+
+ @property
+ def wwid(self):
+ serial = self.identity
+ ret = []
+ while serial:
+ ret.append(serial[:2])
+ serial = serial[2:]
+ return ":".join(ret)
+
+ @property
+ def description(self):
+ return "WWID %s" % (self.wwid,)
+
+ def addParent(self, parent):
+ if self.status:
+ self.teardown()
+ self.parents.append(parent)
+ self.setup()
+ else:
+ self.parents.append(parent)
+
+ def setup(self, intf=None):
+ if self.status:
+ self.teardown()
+ self._isUp = True
+ parents = []
+ for p in self.parents:
+ parents.append(p.path)
+ self._pyBlockMultiPath = block.device.MultiPath(*parents)
+ def teardown(self, recursive=None):
+ if not self.status:
+ return
+ self._isUp = False
+ self._pyBlockMultiPath = None
class NoDevice(StorageDevice):
""" A nodev device for nodev filesystems like tmpfs. """
diff --git a/storage/devicetree.py b/storage/devicetree.py
index 4ff4727cc..02fc40e6d 100644
--- a/storage/devicetree.py
+++ b/storage/devicetree.py
@@ -31,6 +31,7 @@ from partitioning import shouldClear
from pykickstart.constants import *
import formats
import devicelibs.mdraid
+import devicelibs.dm
from udev import *
from iutil import log_method_call
@@ -230,6 +231,7 @@ class DeviceTree(object):
self.__passphrase = passphrase
self.__luksDevs = {}
+ self.__multipaths = {}
if luksDict and isinstance(luksDict, dict):
self.__luksDevs = luksDict
self._ignoredDisks = []
@@ -982,16 +984,25 @@ class DeviceTree(object):
# try to get the device again now that we've got all the slaves
device = self.getDeviceByName(name)
- if device is None and \
- udev_device_is_dmraid_partition(info, self):
- diskname = udev_device_get_dmraid_partition_disk(info)
- disk = self.getDeviceByName(diskname)
- device = PartitionDevice(name, sysfsPath=sysfs_path,
- major=udev_device_get_major(info),
- minor=udev_device_get_minor(info),
- exists=True, parents=[disk])
- # DWL FIXME: call self.addUdevPartitionDevice here instead
- self._addDevice(device)
+ if device is None:
+ if udev_device_is_multipath_partition(info, self):
+ diskname = udev_device_get_multipath_partition_disk(info)
+ disk = self.getDeviceByName(diskname)
+ device = PartitionDevice(name, sysfsPath=sysfs_path,
+ major=udev_device_get_major(info),
+ minor=udev_device_get_minor(info),
+ exists=True, parents=[disk])
+ elif udev_device_is_dmraid_partition(info, self):
+ diskname = udev_device_get_dmraid_partition_disk(info)
+ disk = self.getDeviceByName(diskname)
+ device = PartitionDevice(name, sysfsPath=sysfs_path,
+ major=udev_device_get_major(info),
+ minor=udev_device_get_minor(info),
+ exists=True, parents=[disk])
+ if not device is None:
+ # DWL FIXME: call self.addUdevPartitionDevice here instead
+ self._addDevice(device)
+
# if we get here, we found all of the slave devices and
# something must be wrong -- if all of the slaves are in
@@ -1104,6 +1115,7 @@ class DeviceTree(object):
log_method_call(self, name=name)
uuid = udev_device_get_uuid(info)
sysfs_path = udev_device_get_sysfs_path(info)
+ serial = udev_device_get_serial(info)
device = None
kwargs = {}
@@ -1193,7 +1205,18 @@ class DeviceTree(object):
#
# The first step is to either look up or create the device
#
- if udev_device_is_dm(info):
+ if udev_device_is_multipath_member(info):
+ device = StorageDevice(name,
+ major=udev_device_get_major(info),
+ minor=udev_device_get_minor(info),
+ sysfsPath=sysfs_path, exists=True,
+ serial=udev_device_get_serial(info))
+ self._addDevice(device)
+ elif udev_device_is_dm(info) and \
+ devicelibs.dm.dm_is_multipath(info["DM_MAJOR"], info["DM_MINOR"]):
+ log.debug("%s is a multipath device" % name)
+ self.addUdevDMDevice(info)
+ elif udev_device_is_dm(info):
log.debug("%s is a device-mapper device" % name)
# try to look up the device
if device is None and uuid:
@@ -1413,6 +1436,44 @@ class DeviceTree(object):
md_array._addDevice(device)
self._addDevice(md_array)
+ def handleMultipathMemberFormat(self, info, device):
+ log_method_call(self, name=device.name, type=device.format.type)
+
+ serial = udev_device_get_serial(info)
+ found = False
+ if self.__multipaths.has_key(serial):
+ mp = self.__multipaths[serial]
+ mp.addParent(device)
+ else:
+ name = generateMultipathDeviceName()
+ devname = "/dev/mapper/%s" % (name,)
+
+ if self.zeroMbr:
+ cb = lambda: True
+ else:
+ desc = []
+ serialtmp = serial
+ while serialtmp:
+ desc.append(serialtmp[:2])
+ serialtmp = serialtmp[2:]
+ desc = "WWID %s" % (":".join(desc),)
+
+ cb = lambda: questionInitializeDisk(self.intf, devname, desc)
+
+ initlabel = False
+ if not self.clearPartDisks or \
+ rs.name in self.clearPartDisks:
+ initlabel = self.reinitializeDisks
+ for protected in self.protectedDevNames:
+ disk_name = re.sub(r'p\d+$', '', protected)
+ if disk_name != protected and \
+ disk_name == name:
+ initlabel = False
+ break
+ mp = MultipathDevice(name, info, parents=[device], initcb=cb,
+ initlabel=initlabel)
+ self.__multipaths[serial] = mp
+
def handleUdevDMRaidMemberFormat(self, info, device):
log_method_call(self, name=device.name, type=device.format.type)
name = udev_device_get_name(info)
@@ -1504,6 +1565,7 @@ class DeviceTree(object):
uuid = udev_device_get_uuid(info)
label = udev_device_get_label(info)
format_type = udev_device_get_format(info)
+ serial = udev_device_get_serial(info)
format = None
if (not device) or (not format_type) or device.format.type:
@@ -1517,10 +1579,13 @@ class DeviceTree(object):
kwargs = {"uuid": uuid,
"label": label,
"device": device.path,
+ "serial": serial,
"exists": True}
# set up type-specific arguments for the format constructor
- if format_type == "crypto_LUKS":
+ if format_type == "multipath_member":
+ kwargs["multipath_members"] = self.getDevicesBySerial(serial)
+ elif format_type == "crypto_LUKS":
# luks/dmcrypt
kwargs["name"] = "luks-%s" % uuid
elif format_type in formats.mdraid.MDRaidMember._udevTypes:
@@ -1590,6 +1655,8 @@ class DeviceTree(object):
self.handleUdevDMRaidMemberFormat(info, device)
elif device.format.type == "lvmpv":
self.handleUdevLVMPVFormat(info, device)
+ elif device.format.type == "multipath_member":
+ self.handleMultipathMemberFormat(info, device)
def _handleInconsistencies(self):
def reinitializeVG(vg):
@@ -1708,6 +1775,49 @@ class DeviceTree(object):
(disk.name, disk.format, format))
disk.format = format
+ def identifyMultipaths(self, devices):
+ log.info("devices to scan for multipath: %s" % [d['name'] for d in devices])
+ serials = {}
+ non_disk_devices = {}
+ for d in devices:
+ serial = udev_device_get_serial(d)
+ if not udev_device_is_disk(d):
+ non_disk_devices.setdefault(serial, [])
+ non_disk_devices[serial].append(d)
+ log.info("adding %s to non_disk_device list" % (d['name'],))
+ continue
+
+ serials.setdefault(serial, [])
+ serials[serial].append(d)
+
+ singlepath_disks = []
+ multipath_disks = []
+ for serial, disks in serials.items():
+ if len(disks) == 1:
+ log.info("adding %s to singlepath_disks" % (disks[0]['name'],))
+ singlepath_disks.append(disks[0])
+ else:
+ multipath_members = {}
+ for d in disks:
+ log.info("adding %s to multipath_disks" % (d['name'],))
+ d["ID_FS_TYPE"] = "multipath_member"
+ multipath_disks.append(d)
+
+ multipath_members[d['name']] = { 'info': d,
+ 'found': False }
+ log.info("found multipath set: [%s]" % [d['name'] for d in disks])
+
+ for serial in [d['ID_SERIAL_SHORT'] for d in multipath_disks]:
+ if non_disk_devices.has_key(serial):
+ log.info("filtering out non disk devices [%s]" % [d['name'] for d in non_disk_devices[serial]])
+ del non_disk_devices[serial]
+
+ partition_devices = []
+ for devices in non_disk_devices.values():
+ partition_devices += devices
+
+ return partition_devices + multipath_disks + singlepath_disks
+
def populate(self):
""" Locate all storage devices. """
@@ -1723,27 +1833,32 @@ class DeviceTree(object):
# each iteration scans any devices that have appeared since the
# previous iteration
- old_devices = []
+ old_devices = {}
ignored_devices = []
+ first_iteration = True
+ handled_mpaths = False
while True:
devices = []
new_devices = udev_get_block_devices()
for new_device in new_devices:
- found = False
- for old_device in old_devices:
- if old_device['name'] == new_device['name']:
- found = True
- break
-
- if not found:
+ if not old_devices.has_key(new_device['name']):
+ old_devices[new_device['name']] = new_device
devices.append(new_device)
if len(devices) == 0:
- # nothing is changing -- we are finished building devices
- break
-
- old_devices = new_devices
+ if handled_mpaths:
+ # nothing is changing -- we are finished building devices
+ break
+ for mp in self.__multipaths.values():
+ log.info("adding mpath device %s" % (mp.name,))
+ mp.setup()
+ self._addDevice(mp)
+ handled_mpaths = True
+
+ if first_iteration:
+ devices = self.identifyMultipaths(devices)
+ first_iteration = False
log.info("devices to scan: %s" % [d['name'] for d in devices])
for dev in devices:
self.addUdevDevice(dev)
@@ -1803,6 +1918,16 @@ class DeviceTree(object):
return found
+ def getDevicesBySerial(self, serial):
+ devices = []
+ for device in self._devices:
+ if not hasattr(device, "serial"):
+ log.warning("device %s has no serial attr" % device.name)
+ continue
+ if device.serial == serial:
+ devices.append(device)
+ return devices
+
def getDeviceByLabel(self, label):
if not label:
return None
diff --git a/storage/errors.py b/storage/errors.py
index 9dd121c89..e0175fc17 100644
--- a/storage/errors.py
+++ b/storage/errors.py
@@ -64,6 +64,9 @@ class FormatTeardownError(DeviceFormatError):
class DMRaidMemberError(DeviceFormatError):
pass
+class MultipathMemberError(DeviceFormatError):
+ pass
+
class FSError(DeviceFormatError):
pass
diff --git a/storage/formats/multipath.py b/storage/formats/multipath.py
new file mode 100644
index 000000000..cad255231
--- /dev/null
+++ b/storage/formats/multipath.py
@@ -0,0 +1,88 @@
+# multipath.py
+# multipath device formats
+#
+# Copyright (C) 2009 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# 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): Peter Jones <pjones@redhat.com>
+#
+
+from iutil import log_method_call
+from ..errors import *
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+class MultipathMember(DeviceFormat):
+ """ A multipath member disk. """
+ _type = "multipath_member"
+ _name = "multipath member device"
+ _udev_types = ["multipath_member"]
+ _formattable = False # can be formatted
+ _supported = True # is supported
+ _linuxNative = False # for clearpart
+ _packages = ["device-mapper-multipath"] # required packages
+ _resizable = False # can be resized
+ _bootable = False # can be used as boot
+ _maxSize = 0 # maximum size in MB
+ _minSize = 0 # minimum size in MB
+
+ def __init__(self, *args, **kwargs):
+ """ Create a DeviceFormat instance.
+
+ Keyword Arguments:
+
+ device -- path to the underlying device
+ uuid -- this format's UUID
+ exists -- indicates whether this is an existing format
+
+ On initialization this format is like DeviceFormat
+
+ """
+ log_method_call(self, *args, **kwargs)
+ DeviceFormat.__init__(self, *args, **kwargs)
+
+ # Initialize the attribute that will hold the block object.
+ self._member = None
+
+ @property
+ def member(self):
+ return self._member
+
+ @member.setter
+ def member(self, member):
+ self._member = member
+
+ def create(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ raise MultipathMemberError("creation of multipath members is non-sense")
+
+ def destroy(self, *args, **kwargs):
+ log_method_call(self, device=self.device,
+ type=self.type, status=self.status)
+ raise MultipathMemberError("destruction of multipath members is non-sense")
+
+register_device_format(MultipathMember)
+
diff --git a/storage/udev.py b/storage/udev.py
index 450b54aa6..a8024ddfb 100644
--- a/storage/udev.py
+++ b/storage/udev.py
@@ -134,6 +134,10 @@ def udev_device_get_label(udev_info):
""" Get the label from the device's format as reported by udev. """
return udev_info.get("ID_FS_LABEL")
+def udev_device_is_multipath_member(info):
+ """ Return True if the device is part of a multipath. """
+ return info.get("ID_FS_TYPE") == "multipath_member"
+
def udev_device_is_dm(info):
""" Return True if the device is a device-mapper device. """
return info.has_key("DM_NAME")
@@ -158,6 +162,10 @@ def udev_device_is_partition(info):
has_start = os.path.exists("/sys/%s/start" % info['sysfs_path'])
return info.get("DEVTYPE") == "partition" or has_start
+def udev_device_get_serial(udev_info):
+ """ Get the serial number/UUID from the device as reported by udev. """
+ return udev_info.get("ID_SERIAL_SHORT")
+
def udev_device_get_sysfs_path(info):
return info['sysfs_path']
@@ -276,6 +284,34 @@ def udev_device_is_dmraid_partition(info, devicetree):
return False
+def udev_device_is_multipath_partition(info, devicetree):
+ """ Return True if the device is a partition of a multipath device. """
+ if not udev_device_is_dm(info):
+ return False
+ if not info["DM_NAME"].startswith("mpath"):
+ return False
+ diskname = udev_device_get_dmraid_partition_disk(info)
+ if diskname is None:
+ return False
+
+ # this is sort of a lame check, but basically, if diskname gave us "mpath0"
+ # and we start with "mpath" but we're not "mpath0", then we must be
+ # "mpath0" plus some non-numeric crap.
+ if diskname != info["DM_NAME"]:
+ return True
+
+ return False
+
+def udev_device_get_multipath_partition_disk(info):
+ """ Return True if the device is a partition of a multipath device. """
+ # XXX PJFIX This whole function is crap.
+ if not udev_device_is_dm(info):
+ return False
+ if not info["DM_NAME"].startswith("mpath"):
+ return False
+ diskname = udev_device_get_dmraid_partition_disk(info)
+ return diskname
+
# iscsi disks have ID_PATH in the form of:
# ip-${iscsi_address}:${iscsi_port}-iscsi-${iscsi_tgtname}-lun-${lun}
def udev_device_is_iscsi(info):