#
# Storage filtering UI
#
# Copyright (C) 2009 Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
import block
import collections
import gtk, gobject
import gtk.glade
import gui
import parted
import _ped
from DeviceSelector import *
from baseudev import *
from constants import *
from iw_gui import *
from storage.devices import devicePathToName
from storage.udev import *
from storage.devicelibs.mpath import *
from flags import flags
import storage.iscsi
import storage.fcoe
import storage.zfcp
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
DEVICE_COL = 4
MODEL_COL = 5
CAPACITY_COL = 6
VENDOR_COL = 7
INTERCONNECT_COL = 8
SERIAL_COL = 9
ID_COL = 10
PATHS_COL = 11
PORT_COL = 12
TARGET_COL = 13
LUN_COL = 14
# This is kind of a magic class that is used for populating the device store.
# It mostly acts like a list except for some funny behavior on adding/getting.
# You must add udev dicts to this list, but when you go to examine the list
# (by pulling items out, checking membership, etc.) you are comparing based
# on names.
#
# The only reason to have this is to prevent needing two lists in a variety
# of places throughout FilterWindow.
class NameCache(collections.MutableSequence):
def __init__(self, iterable):
self._lst = list(iterable)
def __contains__(self, item):
return item["name"] in iter(self)
def __delitem__(self, index):
return self._lst.__delitem__(index)
def __getitem__(self, index):
return self._lst.__getitem__(index)["name"]
def __iter__(self):
for d in self._lst:
yield d["name"]
def __len__(self):
return len(self._lst)
def __setitem__(self, index, value):
return self._lst.__setitem__(index, value)
def insert(self, index, value):
return self._lst.insert(index, value)
# These are global because they need to be accessible across all Callback
# objects as the same values, and from the AdvancedFilterWindow object to add
# and remove devices when populating scrolled windows.
totalDevices = 0
selectedDevices = 0
totalSize = 0
selectedSize = 0
# These are global so they can be accessed from all Callback objects. The
# basic callback defines its membership as anything that doesn't pass the
# is* methods.
def isCCISS(info):
return udev_device_is_cciss(info)
def isRAID(info):
if flags.dmraid:
return udev_device_is_biosraid(info)
return False
def isMultipath(info):
return udev_device_is_multipath_member(info)
def isOther(info):
return udev_device_is_iscsi(info) or udev_device_is_fcoe(info)
class Callbacks(object):
def __init__(self, xml):
self.model = None
self.xml = xml
self.sizeLabel = self.xml.get_widget("sizeLabel")
self.sizeLabel.connect("realize", self.update)
def addToUI(self, tuple):
pass
def deviceToggled(self, set, device):
global selectedDevices, totalDevices
global selectedSize, totalSize
if set:
selectedDevices += 1
selectedSize += device["XXX_SIZE"]
else:
selectedDevices -= 1
selectedSize -= device["XXX_SIZE"]
self.update()
def isMember(self, info):
return info and not isRAID(info) and not isCCISS(info) and \
not isMultipath(info) and not isOther(info)
def update(self, *args, **kwargs):
global selectedDevices, totalDevices
global selectedSize, totalSize
self.sizeLabel.set_markup(_("%s device(s) (%s MB) selected out of %s device(s) (%s MB) total.") % (selectedDevices, selectedSize, totalDevices, totalSize))
def visible(self, model, iter, view):
# Most basic visibility function - does the model say this row
# should be visible? Subclasses can define their own more specific
# visibility function, though they should also take a look at this
# one to see what the model says.
return self.isMember(model.get_value(iter, OBJECT_COL)) and \
model.get_value(iter, VISIBLE_COL)
class RAIDCallbacks(Callbacks):
def isMember(self, info):
return info and (isRAID(info) or isCCISS(info))
class FilteredCallbacks(Callbacks):
def __init__(self, *args, **kwargs):
Callbacks.__init__(self, *args, **kwargs)
# Are we even applying the filtering UI? This is False when
# whateverFilterBy is empty, True the rest of the time.
self.filtering = False
def reset(self):
self.notebook.set_current_page(0)
self.filtering = False
def set(self, num):
self.notebook.set_current_page(num)
self.filtering = True
class MPathCallbacks(FilteredCallbacks):
def __init__(self, *args, **kwargs):
FilteredCallbacks.__init__(self, *args, **kwargs)
self._vendors = []
self._interconnects = []
self.filterBy = self.xml.get_widget("mpathFilterBy")
self.notebook = self.xml.get_widget("mpathNotebook")
self.vendorEntry = self.xml.get_widget("mpathVendorEntry")
self.interconnectEntry = self.xml.get_widget("mpathInterconnectEntry")
self.IDEntry = self.xml.get_widget("mpathIDEntry")
self.mpathFilterHBox = self.xml.get_widget("mpathFilterHBox")
self.mpathFilterHBox.connect("realize", self._populateUI)
self.vendorEntry.connect("changed", lambda entry: self.model.get_model().refilter())
self.interconnectEntry.connect("changed", lambda entry: self.model.get_model().refilter())
self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())
def addToUI(self, tuple):
if not tuple[VENDOR_COL] in self._vendors:
self._vendors.append(tuple[VENDOR_COL])
if not tuple[INTERCONNECT_COL] in self._interconnects:
self._interconnects.append(tuple[INTERCONNECT_COL])
def isMember(self, info):
return info and isMultipath(info)
def visible(self, model, iter, view):
if not FilteredCallbacks.visible(self, model, iter, view):
return False
if self.filtering:
if self.notebook.get_current_page() == 0:
return self._visible_by_interconnect(model, iter, view)
elif self.notebook.get_current_page() == 1:
return self._visible_by_vendor(model, iter, view)
elif self.notebook.get_current_page() == 2:
return self._visible_by_wwid(model, iter, view)
return True
def _populateUI(self, widget):
cell = gtk.CellRendererText()
self._vendors.sort()
self.vendorEntry.set_model(gtk.ListStore(gobject.TYPE_STRING))
self.vendorEntry.pack_start(cell)
self.vendorEntry.add_attribute(cell, 'text', 0)
for v in self._vendors:
self.vendorEntry.append_text(v)
self.vendorEntry.show_all()
self._interconnects.sort()
self.interconnectEntry.set_model(gtk.ListStore(gobject.TYPE_STRING))
self.interconnectEntry.pack_start(cell)
self.interconnectEntry.add_attribute(cell, 'text', 0)
for i in self._interconnects:
self.interconnectEntry.append_text(i)
self.interconnectEntry.show_all()
def _visible_by_vendor(self, model, iter, view):
entered = self.vendorEntry.get_child().get_text()
return model.get_value(iter, VENDOR_COL).find(entered) != -1
def _visible_by_interconnect(self, model, iter, view):
entered = self.interconnectEntry.get_child().get_text()
return model.get_value(iter, INTERCONNECT_COL).find(entered) != -1
def _visible_by_wwid(self, model, iter, view):
# FIXME: make this support globs, etc.
entered = self.IDEntry.get_text()
return entered != "" and model.get_value(iter, ID_COL).find(entered) != -1
class OtherCallbacks(MPathCallbacks):
def __init__(self, *args, **kwargs):
FilteredCallbacks.__init__(self, *args, **kwargs)
self._vendors = []
self._interconnects = []
self.filterBy = self.xml.get_widget("otherFilterBy")
self.notebook = self.xml.get_widget("otherNotebook")
self.vendorEntry = self.xml.get_widget("otherVendorEntry")
self.interconnectEntry = self.xml.get_widget("otherInterconnectEntry")
self.IDEntry = self.xml.get_widget("otherIDEntry")
self.otherFilterHBox = self.xml.get_widget("otherFilterHBox")
self.otherFilterHBox.connect("realize", self._populateUI)
self.vendorEntry.connect("changed", lambda entry: self.model.get_model().refilter())
self.interconnectEntry.connect("changed", lambda entry: self.model.get_model().refilter())
self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())
def isMember(self, info):
return info and isOther(info)
class SearchCallbacks(FilteredCallbacks):
def __init__(self, *args, **kwargs):
FilteredCallbacks.__init__(self, *args, **kwargs)
self._ports = []
self._targets = []
self._luns = []
self.filterBy = self.xml.get_widget("searchFilterBy")
self.notebook = self.xml.get_widget("searchNotebook")
self.portEntry = self.xml.get_widget("searchPortEntry")
self.targetEntry = self.xml.get_widget("searchTargetEntry")
self.LUNEntry = self.xml.get_widget("searchLUNEntry")
self.IDEntry = self.xml.get_widget("searchIDEntry")
# When these entries are changed, we need to redo the filtering.
# If we don't do filter-as-you-type, we'd need a Search/Clear button.
self.portEntry.connect("changed", lambda entry: self.model.get_model().refilter())
self.targetEntry.connect("changed", lambda entry: self.model.get_model().refilter())
self.LUNEntry.connect("changed", lambda entry: self.model.get_model().refilter())
self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())
def isMember(self, info):
return True
def visible(self, model, iter, view):
if not model.get_value(iter, VISIBLE_COL):
return False
if self.filtering:
if self.notebook.get_current_page() == 0:
return self._visible_by_ptl(model, iter, view)
else:
return self._visible_by_wwid(model, iter, view)
return True
def _visible_by_ptl(self, model, iter, view):
rowPort = model.get_value(iter, PORT_COL)
rowTarget = model.get_value(iter, TARGET_COL)
rowLUN = model.get_value(iter, LUN_COL)
enteredPort = self.portEntry.get_text()
enteredTarget = self.targetEntry.get_text()
enteredLUN = self.LUNEntry.get_text()
return (not enteredPort or enteredPort and enteredPort == rowPort) and \
(not enteredTarget or enteredTarget and enteredTarget == rowTarget) and \
(not enteredLUN or enteredLUN and enteredLUN == rowLUN)
def _visible_by_wwid(self, model, iter, view):
# FIXME: make this support globs, etc.
entered = self.IDEntry.get_text()
return entered != "" and model.get_value(iter, ID_COL).find(entered) != -1
class NotebookPage(object):
def __init__(self, store, name, xml, cb):
# Every page needs a ScrolledWindow to display the results in.
self.scroll = xml.get_widget("%sScroll" % name)
self.filteredModel = store.filter_new()
self.sortedModel = gtk.TreeModelSort(self.filteredModel)
self.treeView = gtk.TreeView(self.sortedModel)
self.scroll.add(self.treeView)
self.cb = cb
self.cb.model = self.sortedModel
self.ds = DeviceSelector(store, self.sortedModel, self.treeView,
visible=VISIBLE_COL, active=ACTIVE_COL)
self.ds.createMenu()
self.ds.createSelectionCol(toggledCB=self.cb.deviceToggled,
membershipCB=self.cb.isMember)
self.filteredModel.set_visible_func(self.cb.visible, self.treeView)
# Not every NotebookPage will have a filter box - just those that do
# some sort of filtering (obviously).
self.filterBox = xml.get_widget("%sFilterHBox" % name)
if self.filterBox:
self.filterBy = xml.get_widget("%sFilterBy" % name)
self.filterBy.connect("changed", self._filter_by_changed)
# However if the page has a filter box, then it must also have a
# notebook with an easily discoverable name.
self.notebook = xml.get_widget("%sNotebook" % name)
def _filter_by_changed(self, combo):
active = combo.get_active()
if active == -1:
self.cb.reset()
else:
self.cb.set(active)
self.filteredModel.refilter()
def getNVisible(self):
retval = 0
iter = self.filteredModel.get_iter_first()
while iter:
if self.cb.visible(self.filteredModel, iter, self.treeView):
retval += 1
iter = self.filteredModel.iter_next(iter)
return retval
class FilterWindow(InstallWindow):
windowTitle = N_("Device Filter")
def getNext(self):
# All pages use the same store, so we only need to use the first one.
# However, we do need to make sure all paths from multipath devices
# are in the list.
selected = set()
for dev in self.pages[0].ds.getSelected():
selected.add(udev_device_get_name(dev[OBJECT_COL]))
if len(selected) == 0:
self.anaconda.intf.messageWindow(_("Error"),
_("You must select at least one "
"drive to be used for installation."),
custom_icon="error")
raise gui.StayOnScreen
self.anaconda.storage.exclusiveDisks = list(selected)
def _add_advanced_clicked(self, button):
from advanced_storage import addDrive
if not addDrive(self.anaconda):
return
udev_trigger(subsystem="block", action="change")
new_disks = filter(udev_device_is_disk, udev_get_block_devices())
(new_singlepaths, new_mpaths, new_partitions) = identifyMultipaths(new_disks)
(new_raids, new_nonraids) = self.split_list(lambda d: isRAID(d) and not isCCISS(d),
new_singlepaths)
nonraids = filter(lambda d: d not in self._cachedDevices, new_nonraids)
mpaths = filter(lambda d: d not in self._cachedMPaths, new_mpaths)
raids = filter(lambda d: d not in self._cachedRaidDevices, new_raids)
self.populate(nonraids, mpaths, raids)
# Make sure to update the size label at the bottom.
self.pages[0].cb.update()
self._cachedDevices.extend(nonraids)
self._cachedMPaths.extend(mpaths)
self._cachedRaidDevices.extend(raids)
def _makeBasic(self):
np = NotebookPage(self.store, "basic", self.xml, Callbacks(self.xml))
np.ds.addColumn(_("Model"), MODEL_COL)
np.ds.addColumn(_("Capacity"), CAPACITY_COL)
np.ds.addColumn(_("Vendor"), VENDOR_COL)
np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL)
np.ds.addColumn(_("Serial Number"), SERIAL_COL)
np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
return np
def _makeRAID(self):
np = NotebookPage(self.store, "raid", self.xml, RAIDCallbacks(self.xml))
np.ds.addColumn(_("Model"), MODEL_COL)
np.ds.addColumn(_("Capacity"), CAPACITY_COL)
np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
return np
def _makeMPath(self):
np = NotebookPage(self.store, "mpath", self.xml, MPathCallbacks(self.xml))
np.ds.addColumn(_("Identifier"), ID_COL)
np.ds.addColumn(_("Capacity"), CAPACITY_COL)
np.ds.addColumn(_("Vendor"), VENDOR_COL)
np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL)
np.ds.addColumn(_("Paths"), PATHS_COL)
np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
return np
def _makeOther(self):
np = NotebookPage(self.store, "other", self.xml, OtherCallbacks(self.xml))
np.ds.addColumn(_("Identifier"), ID_COL)
np.ds.addColumn(_("Capacity"), CAPACITY_COL)
np.ds.addColumn(_("Vendor"), VENDOR_COL)
np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL)
np.ds.addColumn(_("Serial Number"), SERIAL_COL, displayed=False)
np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
return np
def _makeSearch(self):
np = NotebookPage(self.store, "search", self.xml, SearchCallbacks(self.xml))
np.ds.addColumn(_("Model"), MODEL_COL)
np.ds.addColumn(_("Capacity"), CAPACITY_COL, displayed=False)
np.ds.addColumn(_("Vendor"), VENDOR_COL)
np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL, displayed=False)
np.ds.addColumn(_("Serial Number"), SERIAL_COL, displayed=False)
np.ds.addColumn(_("Identifier"), ID_COL)
np.ds.addColumn(_("Port"), PORT_COL)
np.ds.addColumn(_("Target"), TARGET_COL)
np.ds.addColumn(_("LUN"), LUN_COL)
np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
return np
def _page_switched(self, notebook, useless, page_num):
# When the page is switched, we need to change what is visible so the
# Select All button only selects/deselected things on the current page.
# Unfortunately, the only way to do this is iterate over all rows and
# check for membership.
for line in self.store:
line[VISIBLE_COL] = self.pages[page_num].cb.isMember(line[OBJECT_COL])
def _show_buttons(self, *args, **kwargs):
if self.anaconda.simpleFilter:
self.buttonBox.hide()
self.buttonBox.set_no_show_all(True)
else:
self.buttonBox.show_all()
def getScreen(self, anaconda):
(self.xml, self.vbox) = gui.getGladeWidget("filter.glade", "vbox")
self.buttonBox = self.xml.get_widget("buttonBox")
self.notebook = self.xml.get_widget("notebook")
self.addAdvanced = self.xml.get_widget("addAdvancedButton")
self.buttonBox.connect("realize", self._show_buttons)
self.notebook.connect("switch-page", self._page_switched)
self.addAdvanced.connect("clicked", self._add_advanced_clicked)
self.pages = []
self.anaconda = anaconda
# One common store that all the views on all the notebook tabs share.
# Yes, this means a whole lot of columns that are going to be empty or
# unused much of the time. Oh well.
# Object,
# visible, active (checked), immutable,
# device, model, capacity, vendor, interconnect, serial number, wwid
# paths, port, target, lun
self.store = gtk.TreeStore(gobject.TYPE_PYOBJECT,
gobject.TYPE_BOOLEAN, gobject.TYPE_BOOLEAN,
gobject.TYPE_BOOLEAN,
gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING)
self.store.set_sort_column_id(MODEL_COL, gtk.SORT_ASCENDING)
udev_trigger(subsystem="block", action="change")
# So that drives onlined by these show up in the filter UI
storage.iscsi.iscsi().startup(anaconda.intf)
storage.fcoe.fcoe().startup(anaconda.intf)
storage.zfcp.ZFCP().startup()
# Note we do NOT call dasd.startup() here, that does not online drives,
# but only checks if they need formatting.
disks = filter(udev_device_is_disk, udev_get_block_devices())
(singlepaths, mpaths, partitions) = identifyMultipaths(disks)
# The device list could be really long, so we really only want to
# iterate over it the bare minimum of times. Dividing this list up
# now means fewer elements to iterate over later.
(raids, nonraids) = self.split_list(lambda d: isRAID(d) and not isCCISS(d),
singlepaths)
if anaconda.simpleFilter:
# In the typical use case, the user likely only has one drive and
# there's no point showing either the filtering UI or the
# cleardisks UI. Unfortunately, that means we need to duplicate
# some of the getNext method.
if len(singlepaths) == 1:
anaconda.storage.exclusiveDisks = [udev_device_get_name(singlepaths[0])]
return None
self.pages = [self._makeBasic()]
self.notebook.set_show_border(False)
self.notebook.set_show_tabs(False)
else:
self.pages = [self._makeBasic(), self._makeRAID(),
self._makeMPath(), self._makeOther(),
self._makeSearch()]
self.populate(nonraids, mpaths, raids)
# If the "Add Advanced" button is ever clicked, we need to have a list
# of what devices previously existed so we know what's new. Then we
# can just add the new devices to the UI. This is going to be slow,
# but the user has to click a button to get to the slow part.
self._cachedDevices = NameCache(singlepaths)
self._cachedMPaths = NameCache(mpaths)
self._cachedRaidDevices = NameCache(raids)
# Switch to the first notebook page that displays any devices.
i = 0
for pg in self.pages:
if pg.getNVisible():
self.notebook.set_current_page(i)
break
i += 1
return self.vbox
def populate(self, nonraids, mpaths, raids):
def _addTuple(tuple):
global totalDevices, totalSize
global selectedDevices, selectedSize
added = False
self.store.append(None, tuple)
for pg in self.pages:
if pg.cb.isMember(tuple[0]):
added = True
pg.cb.addToUI(tuple)
# Only update the size label if this device was added to any pages.
# This prevents situations where we're only displaying the basic
# filter that has one disk, but there are several advanced disks
# in the store that cannot be seen.
if added:
totalDevices += 1
totalSize += tuple[0]["XXX_SIZE"]
if tuple[ACTIVE_COL]:
selectedDevices += 1
selectedSize += tuple[0]["XXX_SIZE"]
def _isProtected(info):
protectedNames = map(udev_resolve_devspec, self.anaconda.protected)
sysfs_path = udev_device_get_sysfs_path(info)
for protected in protectedNames:
_p = "/sys/%s/%s" % (sysfs_path, protected)
if os.path.exists(os.path.normpath(_p)):
return True
return False
def _active(info):
if _isProtected(info):
return True
name = udev_device_get_name(info)
if self.anaconda.storage.exclusiveDisks and \
name in self.anaconda.storage.exclusiveDisks:
return True
elif self.anaconda.storage.ignoredDisks and \
name not in self.anaconda.storage.ignoredDisks:
return True
else:
return False
for d in nonraids:
name = udev_device_get_name(d)
# We aren't guaranteed to be able to get a device. In
# particular, built-in USB flash readers show up as devices but
# do not always have any media present, so parted won't be able
# to find a device.
try:
partedDevice = parted.Device(path="/dev/" + name)
except (_ped.IOException, _ped.DeviceException):
continue
d["XXX_SIZE"] = int(partedDevice.getSize())
# cciss controllers, without any sets defined, show up as a 0 size
# blockdev, ignore these
if d["XXX_SIZE"] == 0:
continue
# This isn't so great, but iSCSI and s390 devices have an ID_PATH
# that contains a lot of useful identifying info, so that should be
# displayed instead of a blank WWID.
if udev_device_is_iscsi(d) or udev_device_is_dasd(d) or udev_device_is_zfcp(d):
ident = udev_device_get_path(d)
else:
ident = udev_device_get_wwid(d)
tuple = (d, True, _active(d), _isProtected(d), name,
partedDevice.model, str(d["XXX_SIZE"]) + " MB",
udev_device_get_vendor(d), udev_device_get_bus(d),
udev_device_get_serial(d), ident, "", "", "", "")
_addTuple(tuple)
if raids and flags.dmraid:
used_raidmembers = []
for rs in block.getRaidSets():
# dmraid does everything in sectors
size = (rs.rs.sectors * 512) / (1024.0 * 1024.0)
fstype = ""
# get_members also returns subsets with layered raids, we only
# want the devices
members = filter(lambda m: isinstance(m, block.device.RaidDev),
list(rs.get_members()))
members = map(lambda m: m.get_devpath(), members)
for d in raids:
if udev_device_get_name(d) in members:
fstype = udev_device_get_format(d)
sysfs_path = udev_device_get_sysfs_path(d)
break
# Skip this set if none of its members are in the raids list
if not fstype:
continue
used_raidmembers.extend(members)
# biosraid devices don't really get udev data, at least not in a
# a way that's useful to the filtering UI. So we need to fake
# that data now so we have something to put into the store.
data = {"XXX_SIZE": size, "ID_FS_TYPE": fstype,
"DM_NAME": rs.name, "name": rs.name,
"sysfs_path": sysfs_path}
model = "BIOS RAID set (%s)" % rs.rs.set_type
tuple = (data, True, _active(data), _isProtected(data), rs.name,
model, str(size) + " MB", "", "", "", "", "", "", "", "")
_addTuple(tuple)
unused_raidmembers = []
for d in raids:
if udev_device_get_name(d) not in used_raidmembers:
unused_raidmembers.append(udev_device_get_name(d))
self.anaconda.intf.unusedRaidMembersWarning(unused_raidmembers)
for mpath in mpaths:
# We only need to grab information from the first device in the set.
name = udev_device_get_name(mpath[0])
try:
partedDevice = parted.Device(path="/dev/" + name)
except (_ped.IOException, _ped.DeviceException):
continue
mpath[0]["XXX_SIZE"] = int(partedDevice.getSize())
model = partedDevice.model
# However, we do need all the paths making up this multipath set.
paths = "\n".join(map(udev_device_get_name, mpath))
# We use a copy here, so as to not modify the original udev info
# dict as that would break NameCache matching
data = mpath[0].copy()
data["name"] = udev_device_get_multipath_name(mpath[0])
tuple = (data, True, _active(data), _isProtected(data),
udev_device_get_multipath_name(mpath[0]), model,
str(mpath[0]["XXX_SIZE"]) + " MB",
udev_device_get_vendor(mpath[0]),
udev_device_get_bus(mpath[0]),
udev_device_get_serial(mpath[0]),
udev_device_get_wwid(mpath[0]),
paths, "", "", "")
_addTuple(tuple)
def split_list(self, pred, lst):
pos = []
neg = []
for ele in lst:
if pred(ele):
pos.append(ele)
else:
neg.append(ele)
return (pos, neg)