Let systemd/udev/multipathd do it.
This means we will now be writing a very simple multipath.conf to the
target system. It will contain only "find_multipaths yes" and an entry
for the chosen value of user_friendly_names.
---
blivet/__init__.py | 37 +-----
blivet/devicelibs/mpath.py | 277 +--------------------------------------------
blivet/devices.py | 24 +---
blivet/devicetree.py | 142 +++++++++++------------
4 files changed, 73 insertions(+), 407 deletions(-)
diff --git a/blivet/__init__.py b/blivet/__init__.py
index f52b60c..9d68410 100644
--- a/blivet/__init__.py
+++ b/blivet/__init__.py
@@ -61,7 +61,6 @@ from formats.fs import nodev_filesystems
import devicefactory
from devicelibs.dm import name_from_dm_node
from devicelibs.crypto import generateBackupPassphrase
-from devicelibs.mpath import MultipathConfigWriter
from devicelibs.edd import get_edd_dict
from udev import udev_trigger
import iscsi
@@ -2583,19 +2582,12 @@ class FSSet(object):
open(mdadm_path, "w").write(mdadm_conf)
# /etc/multipath.conf
- multipath_conf = self.multipathConf()
- if multipath_conf:
- multipath_path = os.path.normpath("%s/etc/multipath.conf" %
- ROOT_PATH)
- conf_contents = multipath_conf.write(self.devicetree.mpathFriendlyNames)
- f = open(multipath_path, "w")
- f.write(conf_contents)
- f.close()
+ if self.devicetree.getDevicesByType("dm-multipath"):
+ util.copy_to_system("/etc/multipath.conf")
+ util.copy_to_system("/etc/multipath/wwids")
+ util.copy_to_system("/etc/multipath/bindings")
else:
log.info("not writing out mpath configuration")
- util.copy_to_system("/etc/multipath/wwids")
- if self.devicetree.mpathFriendlyNames:
- util.copy_to_system("/etc/multipath/bindings")
def crypttab(self):
# if we are upgrading, do we want to update crypttab?
@@ -2648,27 +2640,6 @@ class FSSet(object):
return conf
- def multipathConf(self):
- """ Return the contents of multipath.conf. """
- mpaths = self.devicetree.getDevicesByType("dm-multipath")
- if not mpaths:
- return None
- mpaths.sort(key=lambda d: d.name)
- config = MultipathConfigWriter()
- whitelist = []
- for mpath in mpaths:
- config.addMultipathDevice(mpath)
- whitelist.append(mpath.name)
- whitelist.extend([d.name for d in mpath.parents])
-
- # blacklist everything we're not using and let the
- # sysadmin sort it out.
- for d in self.devicetree.devices:
- if not d.name in whitelist:
- config.addBlacklistDevice(d)
-
- return config
-
def fstab (self):
format = "%-23s %-23s %-7s %-15s %d %d\n"
fstab = """
diff --git a/blivet/devicelibs/mpath.py b/blivet/devicelibs/mpath.py
index 63f65a6..673f9e6 100644
--- a/blivet/devicelibs/mpath.py
+++ b/blivet/devicelibs/mpath.py
@@ -1,285 +1,14 @@
-import re
-
-from ..udev import udev_device_is_disk
from .. import util
-from ..flags import flags
-from ..storage_log import log_method_call
import logging
log = logging.getLogger("blivet")
-def parseMultipathOutput(output):
- """
- Parse output from "multipath -d" or "multipath -ll" and form a
topology.
-
- Returns a dictionary:
- {'mpatha':['sdb','sdc'], 'mpathb': ['sdd',
'sde'], ... }
-
- The 'multipath -d' output looks like:
- create: mpathc (1ATA ST3120026AS 5M)
undef ATA,ST3120026AS
- size=112G features='0' hwhandler='0' wp=undef
- `-+- policy='round-robin 0' prio=1 status=undef
- `- 2:0:0:0 sda 8:0 undef ready running
- create: mpathb (36006016092d21800703762872c60db11) undef DGC,RAID 5
- size=10G features='1 queue_if_no_path' hwhandler='1 emc' wp=undef
- `-+- policy='round-robin 0' prio=2 status=undef
- |- 6:0:0:0 sdb 8:16 undef ready running
- `- 7:0:0:0 sdc 8:32 undef ready running
- create: mpatha (36001438005deb4710000500000270000) dm-0 HP,HSV400
- size=20G features='0' hwhandler='0' wp=rw
- |-+- policy='round-robin 0' prio=-1 status=active
- | |- 7:0:0:1 sda 8:0 active undef running
- | `- 7:0:1:1 sdb 8:16 active undef running
- `-+- policy='round-robin 0' prio=-1 status=enabled
- |- 7:0:2:1 sdc 8:32 active undef running
- `- 7:0:3:1 sdd 8:48 active undef running
-
- (In anaconda, the first one there won't be included because we blacklist
- "ATA" as a vendor.)
-
- The 'multipath -ll' output looks like (notice the missing 'create'
before
- 'mpatha'):
-
- mpatha (3600a0b800067fcc9000001694b557dd1) dm-0 IBM,1726-4xx FAStT
- size=360G features='0' hwhandler='1 rdac' wp=rw
- `-+- policy='round-robin 0' prio=3 status=active
- |- 2:0:0:0 sda 8:0 active ready running
- `- 3:0:0:0 sdb 8:16 active ready running
-
- """
- mpaths = {}
- if output is None:
- return mpaths
-
- name = None
- devices = []
-
- policy = re.compile('^[|+` -]+policy')
- device = re.compile('^[|+` -]+[0-9]+:[0-9]+:[0-9]+:[0-9]+ ([a-zA-Z0-9!/]+)')
- create = re.compile('^(create: )?(mpath\w+|[a-f0-9]+)')
-
- lines = output.split('\n')
- for line in lines:
- pmatch = policy.match(line)
- dmatch = device.match(line)
- cmatch = create.match(line)
- lexemes = line.split()
- if not lexemes:
- break
- if cmatch and cmatch.group(2):
- if name and devices:
- mpaths[name] = devices
- name = None
- devices = []
- name = cmatch.group(2)
- elif lexemes[0].startswith('size='):
- pass
- elif pmatch:
- pass
- elif dmatch:
- devices.append(dmatch.groups()[0].replace('!','/'))
-
- if name and devices:
- mpaths[name] = devices
-
- return mpaths
-
-class MultipathTopology(object):
- def __init__(self, devices_list):
- self._devices = devices_list
- self._nondisks = []
- self._singlepaths = []
- self._multipaths = [] # mpath members
- self._devmap = {}
-
- self._build_topology()
-
- def _build_devmap(self):
- self._devmap = {}
- for dev in self._devices:
- self._devmap[dev['name']] = dev
-
- def _build_mpath_topology(self):
- with open("/etc/multipath.conf") as conf:
- log.debug("/etc/multipath.conf contents:")
- map(lambda line: log.debug(line.rstrip()), conf)
- log.debug("(end of /etc/multipath.conf)")
- self._mpath_topology = parseMultipathOutput(
- util.capture_output(["multipath", "-d"]))
- self._mpath_topology.update(parseMultipathOutput(
- util.capture_output(["multipath", "-ll"])))
-
- delete_keys = []
- for (mp, disks) in self._mpath_topology.items():
- # single device mpath is not really an mpath, eliminate them:
- if len(disks) < 2:
- log.info("MultipathTopology: not a multipath: %s" % disks)
- delete_keys.append(mp)
- continue
- # some usb cardreaders use multiple lun's (for different slots) and
- # report a fake disk serial which is the same for all the lun's
- # (#517603). find those mpaths and eliminate them:
- only_non_usbs = [d for d in disks if
- self._devmap[d].get("ID_USB_DRIVER") !=
"usb-storage"]
- if len(only_non_usbs) == 0:
- log.info("DeviceToppology: found multi lun usb "
- "mass storage device: %s" % disks)
- delete_keys.append(mp)
- map(lambda key: self._mpath_topology.pop(key), delete_keys)
-
- def _build_topology(self):
- log_method_call(self)
- self._build_devmap()
- self._build_mpath_topology()
-
- for dev in self._devices:
- name = dev['name']
- if not udev_device_is_disk(dev):
- self._nondisks.append(name)
- log.info("MultipathTopology: found non-disk device: %s" %
name)
- continue
- mpath_name = self.multipath_name(name)
- if mpath_name:
- dev["ID_FS_TYPE"] = "multipath_member"
- dev["ID_MPATH_NAME"] = mpath_name
- log.info("MultipathTopology: found a multipath member of %s: %s
" %
- (mpath_name, name))
- continue
- # it's a disk and not a multipath member (can be a coalesced
- # multipath)
- self._singlepaths.append(name)
- log.info("MultipathTopology: found singlepath device: %s" % name)
-
- def devices_iter(self):
- """ Generator. Yields all disk devices, mpaths members, coalesced
mpath
- devices and partitions.
-
- This property guarantees the order of the returned devices is the
- same as in the device list passed to the object's constructor.
- """
- for device in self._devices:
- yield device
-
- def singlepaths_iter(self):
- """ Generator. Yields only the singlepath disks.
- """
- for name in self._singlepaths:
- yield self._devmap[name]
-
- def multipath_name(self, mpath_member_name):
- """ If the mpath_member_name is a member of a multipath device
return
- the name of the device (e.g. mpathc).
-
- Else return None.
- """
- for (name, members) in self._mpath_topology.items():
- if mpath_member_name in members:
- return name
- return None
-
- def multipaths_iter(self):
- """Generator. Yields all the multipath members, in a topology.
-
- Every iteration returns a list of mpath member devices forming a
- multipath.
- """
- for disks in self._mpath_topology.values():
- yield [self._devmap[d] for d in disks]
-
-
-class MultipathConfigWriter:
- def __init__(self):
- self.blacklist_devices = []
- self.mpaths = []
-
- def addBlacklistDevice(self, device):
- self.blacklist_devices.append(device)
-
- def addMultipathDevice(self, mpath):
- self.mpaths.append(mpath)
-
- def write(self, friendly_names):
- # if you add anything here, be sure and also add it to anaconda's
- # multipath.conf
- ret = ''
- ret += """\
-# multipath.conf written by anaconda
-
-defaults {
- user_friendly_names %(friendly_names)s
-}
-blacklist {
- devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*"
- devnode "^hd[a-z]"
- devnode "^dcssblk[0-9]*"
- device {
- vendor "DGC"
- product "LUNZ"
- }
- device {
- vendor "IBM"
- product "S/390.*"
- }
- # don't count normal SATA devices as multipaths
- device {
- vendor "ATA"
- }
- # don't count 3ware devices as multipaths
- device {
- vendor "3ware"
- }
- device {
- vendor "AMCC"
- }
- # nor highpoint devices
- device {
- vendor "HPT"
- }
-""" % {'friendly_names' : "yes" if friendly_names else
"no"}
- for device in self.blacklist_devices:
- if device.serial:
- ret += '\twwid "%s"\n' % device.serial
- elif device.vendor and device.model:
- ret += '\tdevice {\n'
- ret += '\t\tvendor %s\n' % device.vendor
- ret += '\t\tproduct %s\n' % device.model
- ret += '\t}\n'
- if self.mpaths:
- ret += '\twwid "*"\n'
- ret += '}\n'
- ret += 'blacklist_exceptions {\n'
- for mpath in self.mpaths:
- for k,v in mpath.config.items():
- if k == 'wwid':
- ret += '\twwid "%s"\n' % v
- ret += '}\n'
- ret += 'multipaths {\n'
- for mpath in self.mpaths:
- ret += '\tmultipath {\n'
- for k,v in mpath.config.items():
- if k == 'wwid':
- ret += '\t\twwid "%s"\n' % v
- else:
- ret += '\t\t%s %s\n' % (k, v)
- ret += '\t}\n'
- ret += '}\n'
-
- return ret
-
- def writeConfig(self, friendly_names=True):
- if not flags.multipath:
- # not writing out a multipath.conf will effectively blacklist all
- # mpath which will prevent any of them from being activated during
- # install
- return
-
- cfg = self.write(friendly_names)
- with open("/etc/multipath.conf", "w+") as mpath_cfg:
- mpath_cfg.write(cfg)
-
def flush_mpaths():
util.run_program(["multipath", "-F"])
check_output = util.capture_output(["multipath", "-ll"]).strip()
if check_output:
log.error("multipath: some devices could not be flushed")
+
+def is_multipath_member(path):
+ return (util.run_program(["multipath", "-c", path]) == 0)
diff --git a/blivet/devices.py b/blivet/devices.py
index f4f37e6..84e27bd 100644
--- a/blivet/devices.py
+++ b/blivet/devices.py
@@ -3266,14 +3266,13 @@ class MultipathDevice(DMDevice):
_partitionable = True
_isDisk = True
- def __init__(self, name, info, format=None, size=None,
+ def __init__(self, name, format=None, size=None, serial=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:
@@ -3281,14 +3280,14 @@ class MultipathDevice(DMDevice):
size -- the device's size
format -- a DeviceFormat instance
parents -- a list of the backing devices (Device instances)
+ serial -- the device's serial number
"""
- self._info = info
- self.setupIdentity()
DMDevice.__init__(self, name, format=format, size=size,
parents=parents, sysfsPath=sysfsPath,
exists=True)
+ self.identity = serial
self.config = {
'wwid' : self.identity,
'mode' : '0600',
@@ -3296,23 +3295,6 @@ class MultipathDevice(DMDevice):
'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
diff --git a/blivet/devicetree.py b/blivet/devicetree.py
index f8a6f2c..97adb7a 100644
--- a/blivet/devicetree.py
+++ b/blivet/devicetree.py
@@ -98,7 +98,6 @@ class DeviceTree(object):
self.ignoredDisks = getattr(conf, "ignoredDisks", [])
self.iscsi = iscsi
self.dasd = dasd
- self.mpathFriendlyNames = getattr(conf, "mpathFriendlyNames", True)
self.diskImages = {}
images = getattr(conf, "diskImages", {})
@@ -115,9 +114,6 @@ class DeviceTree(object):
self.unusedRaidMembers = []
- self.__multipaths = {}
- self.__multipathConfigWriter = devicelibs.mpath.MultipathConfigWriter()
-
self.__passphrases = []
if passphrase:
self.__passphrases.append(passphrase)
@@ -657,6 +653,50 @@ class DeviceTree(object):
return device
+ def addUdevMultiPathDevice(self, info):
+ name = udev_device_get_name(info)
+ log_method_call(self, name=name)
+ uuid = udev_device_get_uuid(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+ device = None
+
+ slave_devs = []
+
+ # TODO: look for this device by dm-uuid?
+
+ # first, get a list of the slave devs and look them up
+ dir = os.path.normpath("/sys/%s/slaves" % sysfs_path)
+ slave_names = os.listdir(dir)
+ for slave_name in slave_names:
+ # if it's a dm-X name, resolve it to a map name first
+ if slave_name.startswith("dm-"):
+ dev_name = dm.name_from_dm_node(slave_name)
+ else:
+ dev_name = slave_name.replace("!", "/") # handles
cciss
+ slave_dev = self.getDeviceByName(dev_name)
+ path = os.path.normpath("%s/%s" % (dir, slave_name))
+ new_info = udev_get_block_device(os.path.realpath(path)[4:])
+ if not slave_dev:
+ # we haven't scanned the slave yet, so do it now
+ if new_info:
+ self.addUdevDevice(new_info)
+ slave_dev = self.getDeviceByName(dev_name)
+ if slave_dev is None:
+ # if the current slave is still not in
+ # the tree, something has gone wrong
+ log.error("failure scanning device %s: could not add slave
%s" % (name, dev_name))
+ return
+
+ slave_devs.append(slave_dev)
+
+ if slave_devs:
+ serial = info.get("ID_SERIAL_RAW",
info.get("ID_SERIAL_SHORT"))
+ device = MultipathDevice(name, parents=slave_devs,
+ serial=serial)
+ self._addDevice(device)
+
+ return device
+
def addUdevMDDevice(self, info):
name = udev_device_get_md_name(info)
log_method_call(self, name=name)
@@ -752,20 +792,18 @@ class DeviceTree(object):
is_sun_magic = (getattr(disk.format, "labelType", None) ==
"sun" and
udev_device_get_minor(info) == 3)
- # Check that the disk has partitions. If it does not, we must have
- # reinitialized the disklabel.
- #
- # Also ignore partitions on devices we do not support partitioning
- # of, like logical volumes.
- if ((not getattr(disk.format, "partitions", None) and not
is_sun_magic)
- or not disk.partitionable):
- # When we got here because the disk does not have a disklabel
- # format (ie a biosraid member), or because it is not
- # partitionable we want LVM to ignore this partition too
- if disk.format.type != "disklabel" or not disk.partitionable:
- devicelibs.lvm.lvm_cc_addFilterRejectRegexp(name)
+ if not disk.partitionable:
+ # Ignore partitions on devices we do not support partitioning of,
+ # like logical volumes.
+ devicelibs.lvm.lvm_cc_addFilterRejectRegexp(name)
log.debug("ignoring partition %s" % name)
return
+ elif disk.format.hidden:
+ # there's no need to filter these from lvm since multipath and
+ # dmraid are already active and lvm should know to ignore individual
+ # paths of an active multipath
+ log.debug("ignoring partition %s on %s" % (name,
disk.format.type))
+ return
try:
device = PartitionDevice(name, sysfsPath=sysfs_path,
@@ -777,6 +815,7 @@ class DeviceTree(object):
# which gets rejected by parted, in this case we will
# prompt to re-initialize the disk, so simply skip the
# faulty partitions.
+ # XXX not sure about this
log.error("Failed to instantiate PartitionDevice: %s" % e)
return
@@ -880,6 +919,10 @@ class DeviceTree(object):
major=udev_device_get_major(info),
minor=udev_device_get_minor(info),
sysfsPath=sysfs_path, **kwargs)
+
+ if devicelibs.mpath.is_multipath_member(device.path):
+ info["ID_FS_TYPE"] = "multipath_member"
+
self._addDevice(device)
return device
@@ -969,11 +1012,9 @@ class DeviceTree(object):
elif udev_device_is_loop(info):
log.info("%s is a loop device" % name)
device = self.addUdevLoopDevice(info)
- elif udev_device_is_multipath_member(info):
- device = self.addUdevDiskDevice(info)
- elif udev_device_is_dm(info) and udev_device_is_dm_mpath(info):
+ elif udev_device_is_dm_mpath(info):
log.info("%s is a multipath device" % name)
- device = self.addUdevDMDevice(info)
+ device = self.addUdevMultiPathDevice(info)
elif udev_device_is_dm_lvm(info):
log.info("%s is an lvm logical volume" % name)
device = self.addUdevLVDevice(info)
@@ -1409,17 +1450,6 @@ 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)
-
- name = udev_device_get_multipath_name(info)
- if self.__multipaths.has_key(name):
- mp = self.__multipaths[name]
- mp.addParent(device)
- else:
- mp = MultipathDevice(name, info, parents=[device])
- self.__multipaths[name] = mp
-
def handleUdevDMRaidMemberFormat(self, info, device):
log_method_call(self, name=device.name, type=device.format.type)
name = udev_device_get_name(info)
@@ -1554,9 +1584,7 @@ class DeviceTree(object):
"exists": True}
# set up type-specific arguments for the format constructor
- if format_type == "multipath_member":
- kwargs["multipath_members"] = self.getDevicesBySerial(serial)
- elif format_type == "crypto_LUKS":
+ if format_type == "crypto_LUKS":
# luks/dmcrypt
kwargs["name"] = "luks-%s" % uuid
elif format_type in formats.mdraid.MDRaidMember._udevTypes:
@@ -1622,8 +1650,6 @@ 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)
elif device.format.type == "btrfs":
self.handleBTRFSFormat(info, device)
@@ -1732,7 +1758,7 @@ class DeviceTree(object):
def backupConfigs(self, restore=False):
""" Create a backup copies of some storage config files.
"""
- configs = ["/etc/mdadm.conf", "/etc/multipath.conf"]
+ configs = ["/etc/mdadm.conf"]
for cfg in configs:
if restore:
src = cfg + ".anacbak"
@@ -1820,48 +1846,6 @@ class DeviceTree(object):
old_devices = {}
- if os.access("/etc/multipath.conf", os.W_OK) and flags.installer_mode:
- self.__multipathConfigWriter.writeConfig(self.mpathFriendlyNames)
- self.topology = devicelibs.mpath.MultipathTopology(udev_get_block_devices())
- log.info("devices to scan: %s" %
- [d['name'] for d in self.topology.devices_iter()])
- for dev in self.topology.devices_iter():
- # avoid the problems caused by encountering multipath devices in
- # this loop by simply skipping all dm devices here
- if dev['name'].startswith("dm-"):
- log.debug("Skipping a device mapper drive (%s) for now" %
dev['name'])
- continue
-
- old_devices[dev['name']] = dev
- self.addUdevDevice(dev)
-
- # Having found all the disks, we can now find all the multipaths built
- # upon them.
- whitelist = []
- mpaths = self.__multipaths.values()
- mpaths.sort(key=lambda d: d.name)
- for mp in mpaths:
- log.info("adding mpath device %s" % mp.name)
- mp.setup()
- mp.updateSysfsPath()
- mp_info = udev_get_block_device(mp.sysfsPath)
- if mp_info is None or self.isIgnored(mp_info):
- mp.teardown()
- continue
-
- whitelist.append(mp.name)
- for p in mp.parents:
- whitelist.append(p.name)
- self.__multipathConfigWriter.addMultipathDevice(mp)
- self._addDevice(mp)
- self.addUdevDevice(mp_info)
- for d in self.devices:
- if not d.name in whitelist:
- self.__multipathConfigWriter.addBlacklistDevice(d)
- self.__multipathConfigWriter.writeConfig(self.mpathFriendlyNames)
- else:
- log.info("Skipping multipath detection due to running as
non-root.")
-
# Now, loop and scan for devices that have appeared since the two above
# blocks or since previous iterations.
while True:
--
1.8.1.4