qubes-installer-qubes-os/storage/devicelibs/mpath.py
2011-01-18 04:24:57 -05:00

229 lines
7.6 KiB
Python

from ..udev import *
import iutil
def parseMultipathOutput(output):
# this function parses output from "multipath -d", so we can use its
# logic for our topology.
# The input looks like:
# create: mpathb (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: mpatha (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
#
# (In anaconda, the first one there won't be included because we blacklist
# "ATA" as a vendor.)
#
# It returns a structure like:
# [ {'mpatha':['sdb','sdc']}, ... ]
mpaths = {}
if output is None:
return mpaths
name = None
devices = []
lines = output.split('\n')
for line in lines:
lexemes = line.split()
if not lexemes:
break
if lexemes[0] == 'create:':
if name and devices:
mpaths[name] = devices
name = None
devices = []
name = lexemes[1]
elif lexemes[0].startswith('size='):
pass
elif lexemes[0] == '`-+-':
pass
elif lexemes[0] in ['|-','`-']:
devices.append(lexemes[2].replace('!', '/'))
if name and devices:
mpaths[name] = devices
return mpaths
def identifyMultipaths(devices):
# this function does a couple of things
# 1) identifies multipath disks
# 2) sets their ID_FS_TYPE to multipath_member
# 3) removes the individual members of an mpath's partitions
# sample input with multipath pair [sdb,sdc]
# [sr0, sda, sda1, sdb, sdb1, sdb2, sdc, sdc1, sdd, sdd1, sdd2]
# sample output:
# [sda, sdd], [[sdb, sdc]], [sr0, sda1, sdd1, sdd2]]
log.info("devices to scan for multipath: %s" % [d['name'] for d in devices])
topology = parseMultipathOutput(iutil.execWithCapture("multipath", ["-d",]))
# find the devices that aren't in topology, and add them into it...
topodevs = reduce(lambda x,y: x.union(y), topology.values(), set())
for name in set([d['name'] for d in devices]).difference(topodevs):
topology[name] = [name]
devmap = {}
non_disk_devices = {}
for d in devices:
if not udev_device_is_disk(d):
non_disk_devices[d['name']] = d
log.info("adding %s to non_disk_device list" % (d['name'],))
continue
devmap[d['name']] = d
singlepath_disks = []
multipaths = []
for name, disks in topology.items():
if len(disks) == 1:
if not non_disk_devices.has_key(disks[0]):
log.info("adding %s to singlepath_disks" % (disks[0],))
singlepath_disks.append(devmap[disks[0]])
else:
# 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)
all_usb = True
# see if we've got any non-disk devices on our mpath list.
# If so, they're probably false-positives.
non_disks = False
for disk in disks:
d = devmap[disk]
if d.get("ID_USB_DRIVER") != "usb-storage":
all_usb = False
if (not devmap.has_key(disk)) and non_disk_devices.has_key(disk):
log.warning("non-disk device %s is part of an mpath" %
(disk,))
non_disks = True
if all_usb:
log.info("adding multi lun usb mass storage device to singlepath_disks: %s" %
(disks,))
singlepath_disks.extend([devmap[d] for d in disks])
continue
if non_disks:
for disk in disks:
if devmap.has_key(disk):
del devmap[disk]
if topology.has_key(disk):
del topology[disk]
continue
log.info("found multipath set: %s" % (disks,))
for disk in disks:
d = devmap[disk]
log.info("adding %s to multipath_disks" % (disk,))
d["ID_FS_TYPE"] = "multipath_member"
d["ID_MPATH_NAME"] = name
multipaths.append([devmap[d] for d in disks])
non_disk_serials = {}
for name,device in non_disk_devices.items():
serial = udev_device_get_serial(device)
non_disk_serials.setdefault(serial, [])
non_disk_serials[serial].append(device)
for mpath in multipaths:
for serial in [d.get('ID_SERIAL_SHORT') for d in mpath]:
if non_disk_serials.has_key(serial):
log.info("filtering out non disk devices [%s]" % [d['name'] for d in non_disk_serials[serial]])
for name in [d['name'] for d in non_disk_serials[serial]]:
if non_disk_devices.has_key(name):
del non_disk_devices[name]
partition_devices = []
for device in non_disk_devices.values():
partition_devices.append(device)
# this is the list of devices we want to keep from the original
# device list, but we want to maintain its original order.
singlepath_disks = filter(lambda d: d in devices, singlepath_disks)
#multipaths = filter(lambda d: d in devices, multipaths)
partition_devices = filter(lambda d: d in devices, partition_devices)
mpathStr = "["
for mpath in multipaths:
mpathStr += str([d['name'] for d in mpath])
mpathStr += "]"
s = "(%s, %s, %s)" % ([d['name'] for d in singlepath_disks], \
mpathStr, \
[d['name'] for d in partition_devices])
log.info("devices post multipath scan: %s" % s)
return (singlepath_disks, multipaths, partition_devices)
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):
# 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 yes
}
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"
}
"""
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'
ret += '}\n'
ret += 'multipaths {\n'
for mpath in self.mpaths:
ret += '\tmultipath {\n'
for k,v in mpath.config.items():
ret += '\t\t%s %s\n' % (k, v)
ret += '\t}\n'
ret += '}\n'
return ret