229 lines
7.6 KiB
Python
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
|