diff options
Diffstat (limited to 'storage/devicelibs/mpath.py')
-rw-r--r-- | storage/devicelibs/mpath.py | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/storage/devicelibs/mpath.py b/storage/devicelibs/mpath.py new file mode 100644 index 0000000..adb2644 --- /dev/null +++ b/storage/devicelibs/mpath.py @@ -0,0 +1,228 @@ +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 |