On Thu, 2009-08-06 at 14:55 -0400, Peter Jones wrote: > 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 Looks good now. > > 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): _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list