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

286 lines
9.4 KiB
Python

import re
from ..udev import udev_device_is_disk
from pyanaconda import iutil
from pyanaconda.flags import flags
from pyanaconda.anaconda_log import log_method_call
import logging
log = logging.getLogger("storage")
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(
iutil.execWithCapture("multipath", ["-d",]))
self._mpath_topology.update(parseMultipathOutput(
iutil.execWithCapture("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.mpath:
# 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():
iutil.execWithRedirect("multipath", ["-F"])
check_output = iutil.execWithCapture("multipath", ["-ll"]).strip()
if check_output:
log.error("multipath: some devices could not be flushed")