This identifies that a device is part of a multipath, and builds an mpath device for partitioning. --- 70-anaconda.rules | 2 +- storage/devicelibs/dm.py | 8 ++ storage/devices.py | 92 +++++++++++++++++++++-- storage/devicetree.py | 173 ++++++++++++++++++++++++++++++++++++------ storage/errors.py | 3 + storage/formats/multipath.py | 88 +++++++++++++++++++++ storage/udev.py | 36 +++++++++ 7 files changed, 370 insertions(+), 32 deletions(-) create mode 100644 storage/formats/multipath.py diff --git a/70-anaconda.rules b/70-anaconda.rules index 65d3141..a42cd57 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 29df126..c118d51 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 2d6a294..2396bd5 100644 --- a/storage/devices.py +++ b/storage/devices.py @@ -416,7 +416,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 +446,7 @@ class StorageDevice(Device): self.minor = numeric_type(minor) self.sysfsPath = sysfsPath self.exists = exists + self.serial = serial self.protected = False @@ -2733,19 +2734,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 +2769,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 4ff4727..02fc40e 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 9dd121c..e0175fc 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 0000000..cad2552 --- /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@xxxxxxxxxx> +# + +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 450b54a..a8024dd 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): -- 1.6.4 _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list