Package pyanaconda :: Package iw :: Module filter_gui
[hide private]
[frames] | no frames]

Source Code for Module pyanaconda.iw.filter_gui

  1  # 
  2  # Storage filtering UI 
  3  # 
  4  # Copyright (C) 2009  Red Hat, Inc. 
  5  # All rights reserved. 
  6  # 
  7  # This program is free software; you can redistribute it and/or modify 
  8  # it under the terms of the GNU General Public License as published by 
  9  # the Free Software Foundation; either version 2 of the License, or 
 10  # (at your option) any later version. 
 11  # 
 12  # This program is distributed in the hope that it will be useful, 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 15  # GNU General Public License for more details. 
 16  # 
 17  # You should have received a copy of the GNU General Public License 
 18  # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 19  # 
 20   
 21  import block 
 22  import collections 
 23  import gtk, gobject 
 24  import gtk.glade 
 25  from pyanaconda import gui 
 26  from pyanaconda import iutil 
 27  import itertools 
 28  import parted 
 29  import _ped 
 30  from DeviceSelector import * 
 31  from pyanaconda.baseudev import * 
 32  from pyanaconda.constants import * 
 33  from iw_gui import * 
 34  from pyanaconda.storage.devices import devicePathToName 
 35  from pyanaconda.storage.udev import * 
 36  from pyanaconda.storage.devicelibs.mpath\ 
 37      import MultipathTopology, MultipathConfigWriter, flush_mpaths 
 38  from pyanaconda.flags import flags 
 39  from pyanaconda.storage import iscsi 
 40  from pyanaconda.storage import fcoe 
 41  from pyanaconda.storage import zfcp 
 42  from pyanaconda.storage import dasd 
 43   
 44  import gettext 
 45  _ = lambda x: gettext.ldgettext("anaconda", x) 
 46   
 47  DEVICE_COL = 4 
 48  MODEL_COL = 5 
 49  CAPACITY_COL = 6 
 50  VENDOR_COL = 7 
 51  INTERCONNECT_COL = 8 
 52  SERIAL_COL = 9 
 53  ID_COL = 10 
 54  MEMBERS_COL = 11 
 55  PORT_COL = 12 
 56  TARGET_COL = 13 
 57  LUN_COL = 14 
 58   
 59  # This is kind of a magic class that is used for populating the device store. 
 60  # It mostly acts like a list except for some funny behavior on adding/getting. 
 61  # You must add udev dicts to this list, but when you go to examine the list 
 62  # (by pulling items out, checking membership, etc.) you are comparing based 
 63  # on names. 
 64  # 
 65  # The only reason to have this is to prevent needing two lists in a variety 
 66  # of places throughout FilterWindow. 
67 -class NameCache(collections.MutableSequence):
68 - def __init__(self, iterable):
69 self._lst = list(iterable)
70
71 - def __contains__(self, item):
72 return item["name"] in iter(self)
73
74 - def __delitem__(self, index):
75 return self._lst.__delitem__(index)
76
77 - def __getitem__(self, index):
78 return self._lst.__getitem__(index)["name"]
79
80 - def __iter__(self):
81 for d in self._lst: 82 yield d["name"]
83
84 - def __len__(self):
85 return len(self._lst)
86
87 - def __setitem__(self, index, value):
88 return self._lst.__setitem__(index, value)
89
90 - def insert(self, index, value):
91 return self._lst.insert(index, value)
92 93 # These are global because they need to be accessible across all Callback 94 # objects as the same values, and from the AdvancedFilterWindow object to add 95 # and remove devices when populating scrolled windows. 96 totalDevices = 0 97 selectedDevices = 0 98 totalSize = 0 99 selectedSize = 0 100 101 # These are global so they can be accessed from all Callback objects. The 102 # basic callback defines its membership as anything that doesn't pass the 103 # is* methods.
104 -def isCCISS(info):
105 return udev_device_is_cciss(info)
106
107 -def isRAID(info):
108 if flags.dmraid: 109 return udev_device_is_biosraid_member(info) 110 111 return False
112
113 -def isMultipath(info):
114 return udev_device_is_multipath_member(info)
115
116 -def isOther(info):
117 return udev_device_is_iscsi(info) or udev_device_is_fcoe(info)
118
119 -class Callbacks(object):
120 - def __init__(self, xml):
121 self.model = None 122 self.xml = xml 123 124 self.sizeLabel = self.xml.get_widget("sizeLabel") 125 self.sizeLabel.connect("realize", self.update)
126
127 - def addToUI(self, tuple):
128 pass
129
130 - def deviceToggled(self, set, device):
131 global selectedDevices, totalDevices 132 global selectedSize, totalSize 133 134 if set: 135 selectedDevices += 1 136 selectedSize += device["XXX_SIZE"] 137 else: 138 selectedDevices -= 1 139 selectedSize -= device["XXX_SIZE"] 140 141 self.update()
142
143 - def isMember(self, info):
144 return info and not isRAID(info) and not isCCISS(info) and \ 145 not isMultipath(info) and not isOther(info)
146
147 - def update(self, *args, **kwargs):
148 global selectedDevices, totalDevices 149 global selectedSize, totalSize 150 151 self.sizeLabel.set_markup(_("Selected devices: %(selectedDevices)s (%(selectedSize)s MB) out of %(totalDevices)s (%(totalSize)s MB).") % {"selectedDevices": selectedDevices, "selectedSize": selectedSize, "totalDevices": totalDevices, "totalSize": totalSize})
152
153 - def visible(self, model, iter, view):
154 # Most basic visibility function - does the model say this row 155 # should be visible? Subclasses can define their own more specific 156 # visibility function, though they should also take a look at this 157 # one to see what the model says. 158 return self.isMember(model.get_value(iter, OBJECT_COL)) and \ 159 model.get_value(iter, VISIBLE_COL)
160
161 -class RAIDCallbacks(Callbacks):
162 - def isMember(self, info):
163 return info and (isRAID(info) or isCCISS(info))
164
165 -class FilteredCallbacks(Callbacks):
166 - def __init__(self, *args, **kwargs):
167 Callbacks.__init__(self, *args, **kwargs) 168 169 # Are we even applying the filtering UI? This is False when 170 # whateverFilterBy is empty, True the rest of the time. 171 self.filtering = False
172
173 - def reset(self):
174 self.notebook.set_current_page(0) 175 self.filtering = False
176
177 - def set(self, num):
178 self.notebook.set_current_page(num) 179 self.filtering = True
180
181 -class MPathCallbacks(FilteredCallbacks):
182 - def __init__(self, *args, **kwargs):
183 FilteredCallbacks.__init__(self, *args, **kwargs) 184 185 self._vendors = [] 186 self._interconnects = [] 187 188 self.filterBy = self.xml.get_widget("mpathFilterBy") 189 self.notebook = self.xml.get_widget("mpathNotebook") 190 191 self.vendorEntry = self.xml.get_widget("mpathVendorEntry") 192 self.interconnectEntry = self.xml.get_widget("mpathInterconnectEntry") 193 self.IDEntry = self.xml.get_widget("mpathIDEntry") 194 195 self.mpathFilterHBox = self.xml.get_widget("mpathFilterHBox") 196 self.mpathFilterHBox.connect("realize", self._populateUI) 197 198 self.vendorEntry.connect("changed", lambda entry: self.model.get_model().refilter()) 199 self.interconnectEntry.connect("changed", lambda entry: self.model.get_model().refilter()) 200 self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())
201
202 - def addToUI(self, tuple):
203 if not tuple[VENDOR_COL] in self._vendors: 204 self._vendors.append(tuple[VENDOR_COL]) 205 206 if not tuple[INTERCONNECT_COL] in self._interconnects: 207 self._interconnects.append(tuple[INTERCONNECT_COL])
208
209 - def isMember(self, info):
210 return info and isMultipath(info)
211
212 - def visible(self, model, iter, view):
213 if not FilteredCallbacks.visible(self, model, iter, view): 214 return False 215 216 if self.filtering: 217 if self.notebook.get_current_page() == 0: 218 return self._visible_by_interconnect(model, iter, view) 219 elif self.notebook.get_current_page() == 1: 220 return self._visible_by_vendor(model, iter, view) 221 elif self.notebook.get_current_page() == 2: 222 return self._visible_by_wwid(model, iter, view) 223 224 return True
225
226 - def _populateUI(self, widget):
227 cell = gtk.CellRendererText() 228 229 self._vendors.sort() 230 self.vendorEntry.set_model(gtk.ListStore(gobject.TYPE_STRING)) 231 self.vendorEntry.pack_start(cell) 232 self.vendorEntry.add_attribute(cell, 'text', 0) 233 234 for v in self._vendors: 235 self.vendorEntry.append_text(v) 236 237 self.vendorEntry.show_all() 238 239 self._interconnects.sort() 240 self.interconnectEntry.set_model(gtk.ListStore(gobject.TYPE_STRING)) 241 self.interconnectEntry.pack_start(cell) 242 self.interconnectEntry.add_attribute(cell, 'text', 0) 243 244 for i in self._interconnects: 245 self.interconnectEntry.append_text(i) 246 247 self.interconnectEntry.show_all()
248
249 - def _visible_by_vendor(self, model, iter, view):
250 entered = self.vendorEntry.get_child().get_text() 251 return model.get_value(iter, VENDOR_COL).find(entered) != -1
252
253 - def _visible_by_interconnect(self, model, iter, view):
254 entered = self.interconnectEntry.get_child().get_text() 255 return model.get_value(iter, INTERCONNECT_COL).find(entered) != -1
256
257 - def _visible_by_wwid(self, model, iter, view):
258 # FIXME: make this support globs, etc. 259 entered = self.IDEntry.get_text() 260 261 return entered != "" and model.get_value(iter, ID_COL).find(entered) != -1
262
263 -class OtherCallbacks(MPathCallbacks):
264 - def __init__(self, *args, **kwargs):
265 FilteredCallbacks.__init__(self, *args, **kwargs) 266 267 self._vendors = [] 268 self._interconnects = [] 269 270 self.filterBy = self.xml.get_widget("otherFilterBy") 271 self.notebook = self.xml.get_widget("otherNotebook") 272 273 self.vendorEntry = self.xml.get_widget("otherVendorEntry") 274 self.interconnectEntry = self.xml.get_widget("otherInterconnectEntry") 275 self.IDEntry = self.xml.get_widget("otherIDEntry") 276 277 self.otherFilterHBox = self.xml.get_widget("otherFilterHBox") 278 self.otherFilterHBox.connect("realize", self._populateUI) 279 280 self.vendorEntry.connect("changed", lambda entry: self.model.get_model().refilter()) 281 self.interconnectEntry.connect("changed", lambda entry: self.model.get_model().refilter()) 282 self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())
283
284 - def isMember(self, info):
285 return info and isOther(info)
286
287 -class SearchCallbacks(FilteredCallbacks):
288 - def __init__(self, *args, **kwargs):
289 FilteredCallbacks.__init__(self, *args, **kwargs) 290 291 self._ports = [] 292 self._targets = [] 293 self._luns = [] 294 295 self.filterBy = self.xml.get_widget("searchFilterBy") 296 self.notebook = self.xml.get_widget("searchNotebook") 297 298 self.portEntry = self.xml.get_widget("searchPortEntry") 299 self.targetEntry = self.xml.get_widget("searchTargetEntry") 300 self.LUNEntry = self.xml.get_widget("searchLUNEntry") 301 self.IDEntry = self.xml.get_widget("searchIDEntry") 302 303 # When these entries are changed, we need to redo the filtering. 304 # If we don't do filter-as-you-type, we'd need a Search/Clear button. 305 self.portEntry.connect("changed", lambda entry: self.model.get_model().refilter()) 306 self.targetEntry.connect("changed", lambda entry: self.model.get_model().refilter()) 307 self.LUNEntry.connect("changed", lambda entry: self.model.get_model().refilter()) 308 self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())
309
310 - def isMember(self, info):
311 return True
312
313 - def visible(self, model, iter, view):
314 if not model.get_value(iter, VISIBLE_COL): 315 return False 316 317 if self.filtering: 318 if self.notebook.get_current_page() == 0: 319 return self._visible_by_ptl(model, iter, view) 320 else: 321 return self._visible_by_wwid(model, iter, view) 322 323 return True
324
325 - def _visible_by_ptl(self, model, iter, view):
326 rowPort = model.get_value(iter, PORT_COL) 327 rowTarget = model.get_value(iter, TARGET_COL) 328 rowLUN = model.get_value(iter, LUN_COL) 329 330 enteredPort = self.portEntry.get_text() 331 enteredTarget = self.targetEntry.get_text() 332 enteredLUN = self.LUNEntry.get_text() 333 334 return (not enteredPort or enteredPort and enteredPort == rowPort) and \ 335 (not enteredTarget or enteredTarget and enteredTarget == rowTarget) and \ 336 (not enteredLUN or enteredLUN and enteredLUN == rowLUN)
337
338 - def _visible_by_wwid(self, model, iter, view):
339 # FIXME: make this support globs, etc. 340 entered = self.IDEntry.get_text() 341 342 return entered != "" and model.get_value(iter, ID_COL).find(entered) != -1
343
344 -class NotebookPage(object):
345 - def __init__(self, store, name, xml, cb):
346 # Every page needs a ScrolledWindow to display the results in. 347 self.scroll = xml.get_widget("%sScroll" % name) 348 349 self.filteredModel = store.filter_new() 350 self.sortedModel = gtk.TreeModelSort(self.filteredModel) 351 self.treeView = gtk.TreeView(self.sortedModel) 352 353 self.scroll.add(self.treeView) 354 355 self.cb = cb 356 self.cb.model = self.sortedModel 357 358 self.ds = DeviceSelector(store, self.sortedModel, self.treeView, 359 visible=VISIBLE_COL, active=ACTIVE_COL) 360 self.ds.createMenu() 361 self.ds.createSelectionCol(toggledCB=self.cb.deviceToggled, 362 membershipCB=self.cb.isMember) 363 364 self.filteredModel.set_visible_func(self.cb.visible, self.treeView) 365 366 # Not every NotebookPage will have a filter box - just those that do 367 # some sort of filtering (obviously). 368 self.filterBox = xml.get_widget("%sFilterHBox" % name) 369 370 if self.filterBox: 371 self.filterBy = xml.get_widget("%sFilterBy" % name) 372 self.filterBy.connect("changed", self._filter_by_changed) 373 374 # However if the page has a filter box, then it must also have a 375 # notebook with an easily discoverable name. 376 self.notebook = xml.get_widget("%sNotebook" % name)
377
378 - def _filter_by_changed(self, combo):
379 active = combo.get_active() 380 381 if active == -1: 382 self.cb.reset() 383 else: 384 self.cb.set(active) 385 386 self.filteredModel.refilter()
387
388 - def getNVisible(self):
389 retval = 0 390 iter = self.filteredModel.get_iter_first() 391 392 while iter: 393 if self.cb.visible(self.filteredModel, iter, self.treeView): 394 retval += 1 395 396 iter = self.filteredModel.iter_next(iter) 397 398 return retval
399
400 -class FilterWindow(InstallWindow):
401 windowTitle = N_("Device Filter") 402
403 - def _device_size_is_nonzero(self, info):
404 path = udev_device_get_sysfs_path(info) 405 size = iutil.get_sysfs_attr(path, "size") 406 407 if not size: 408 return False 409 410 return True
411
412 - def _getFilterDisks(self):
413 """ Return a list of disks to pass to MultipathTopology. """ 414 return filter(lambda d: udev_device_is_disk(d) and \ 415 not udev_device_is_loop(d) and \ 416 not udev_device_is_dm(d) and \ 417 not udev_device_is_md(d) and \ 418 not udev_device_get_md_container(d), 419 udev_get_block_devices())
420
421 - def getNext(self):
422 # All pages use the same store, so we only need to use the first one. 423 # However, we do need to make sure all paths from multipath devices 424 # are in the list. 425 selected = set() 426 for dev in self.pages[0].ds.getSelected(): 427 info = dev[OBJECT_COL] 428 if isRAID(info): 429 selected.add(udev_device_get_name(info)) 430 members = dev[MEMBERS_COL].split("\n") 431 selected.update(set(members)) 432 if isMultipath(info): 433 if self.anaconda.storage.config.mpathFriendlyNames: 434 selected.add(udev_device_get_name(info)) 435 else: 436 selected.add(dev[SERIAL_COL]) 437 members = dev[MEMBERS_COL].split("\n") 438 selected.update(set(members)) 439 else: 440 selected.add(udev_device_get_name(info)) 441 442 if len(selected) == 0: 443 self.anaconda.intf.messageWindow(_("Error"), 444 _("You must select at least one " 445 "drive to be used for installation."), 446 custom_icon="error") 447 raise gui.StayOnScreen 448 449 self.anaconda.storage.config.exclusiveDisks = list(selected)
450
451 - def _add_advanced_clicked(self, button):
452 from advanced_storage import addDrive 453 454 if not addDrive(self.anaconda): 455 return 456 457 udev_trigger(subsystem="block", action="change") 458 new_disks = self._getFilterDisks() 459 460 mcw = MultipathConfigWriter() 461 cfg = mcw.write(friendly_names=True) 462 with open("/etc/multipath.conf", "w+") as mpath_cfg: 463 mpath_cfg.write(cfg) 464 465 topology = MultipathTopology(new_disks) 466 (new_raids, new_nonraids) = self.split_list(lambda d: isRAID(d) and not isCCISS(d), 467 topology.singlepaths_iter()) 468 469 # The end result of the loop below is that mpaths is a list of lists of 470 # components. That's what populate expects. 471 mpaths = [] 472 for mp in topology.multipaths_iter(): 473 for d in mp: 474 # If any of the multipath components are in the nonraids cache, 475 # invalidate that cache and remove it from the UI store. 476 if d in self._cachedDevices: 477 self.depopulate(d) 478 del(self._cachedDevices[:]) 479 480 # If all components of this multipath device are in the 481 # cache, skip it. Otherwise, it's a new device and needs to 482 # be populated into the UI. 483 if d not in self._cachedMPaths: 484 mpaths.append(mp) 485 break 486 487 nonraids = filter(lambda d: d not in self._cachedDevices, new_nonraids) 488 raids = filter(lambda d: d not in self._cachedRaidDevices, new_raids) 489 490 self.populate(nonraids, mpaths, raids, activeByDefault=True) 491 492 # Make sure to update the size label at the bottom. 493 self.pages[0].cb.update() 494 495 self._cachedDevices.extend(nonraids) 496 self._cachedRaidDevices.extend(raids) 497 498 # And then we need to do the same list flattening trick here as in 499 # getScreen. 500 lst = list(itertools.chain(*mpaths)) 501 self._cachedMPaths.extend(lst)
502
503 - def _makeBasic(self):
504 np = NotebookPage(self.store, "basic", self.xml, Callbacks(self.xml)) 505 506 np.ds.addColumn(_("Model"), MODEL_COL) 507 np.ds.addColumn(_("Capacity (MB)"), CAPACITY_COL) 508 np.ds.addColumn(_("Vendor"), VENDOR_COL) 509 np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL) 510 np.ds.addColumn(_("Serial Number"), SERIAL_COL) 511 np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False) 512 return np
513
514 - def _makeRAID(self):
515 np = NotebookPage(self.store, "raid", self.xml, RAIDCallbacks(self.xml)) 516 517 np.ds.addColumn(_("Model"), MODEL_COL) 518 np.ds.addColumn(_("Capacity (MB)"), CAPACITY_COL) 519 np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False) 520 return np
521
522 - def _makeMPath(self):
523 np = NotebookPage(self.store, "mpath", self.xml, MPathCallbacks(self.xml)) 524 525 np.ds.addColumn(_("Identifier"), ID_COL) 526 np.ds.addColumn(_("Capacity (MB)"), CAPACITY_COL) 527 np.ds.addColumn(_("Vendor"), VENDOR_COL) 528 np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL) 529 np.ds.addColumn(_("Paths"), MEMBERS_COL) 530 np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False) 531 return np
532
533 - def _makeOther(self):
534 np = NotebookPage(self.store, "other", self.xml, OtherCallbacks(self.xml)) 535 536 np.ds.addColumn(_("Identifier"), ID_COL) 537 np.ds.addColumn(_("Capacity (MB)"), CAPACITY_COL) 538 np.ds.addColumn(_("Vendor"), VENDOR_COL) 539 np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL) 540 np.ds.addColumn(_("Serial Number"), SERIAL_COL, displayed=False) 541 np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False) 542 return np
543
544 - def _makeSearch(self):
545 np = NotebookPage(self.store, "search", self.xml, SearchCallbacks(self.xml)) 546 547 np.ds.addColumn(_("Model"), MODEL_COL) 548 np.ds.addColumn(_("Capacity (MB)"), CAPACITY_COL, displayed=False) 549 np.ds.addColumn(_("Vendor"), VENDOR_COL) 550 np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL, displayed=False) 551 np.ds.addColumn(_("Serial Number"), SERIAL_COL, displayed=False) 552 np.ds.addColumn(_("Identifier"), ID_COL) 553 np.ds.addColumn(_("Port"), PORT_COL) 554 np.ds.addColumn(_("Target"), TARGET_COL) 555 np.ds.addColumn(_("LUN"), LUN_COL) 556 np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False) 557 return np
558
559 - def _options_clicked(self, button):
560 (xml, dialog) = gui.getGladeWidget("device-options.glade", 561 "options_dialog") 562 friendly_cb = xml.get_widget("mpath_friendly_names") 563 friendly_cb.set_active(self.anaconda.storage.config.mpathFriendlyNames) 564 if dialog.run() == gtk.RESPONSE_OK: 565 self.anaconda.storage.config.mpathFriendlyNames = friendly_cb.get_active() 566 dialog.destroy()
567
568 - def _page_switched(self, notebook, useless, page_num):
569 # When the page is switched, we need to change what is visible so the 570 # Select All button only selects/deselected things on the current page. 571 # Unfortunately, the only way to do this is iterate over all rows and 572 # check for membership. 573 for line in self.store: 574 line[VISIBLE_COL] = self.pages[page_num].cb.isMember(line[OBJECT_COL])
575
576 - def getScreen(self, anaconda):
577 # We skip the filter UI in basic storage mode 578 if anaconda.simpleFilter: 579 anaconda.storage.config.exclusiveDisks = [] 580 return None 581 582 (self.xml, self.vbox) = gui.getGladeWidget("filter.glade", "vbox") 583 self.buttonBox = self.xml.get_widget("buttonBox") 584 self.notebook = self.xml.get_widget("notebook") 585 self.addAdvanced = self.xml.get_widget("addAdvancedButton") 586 self.options = self.xml.get_widget("optionsButton") 587 588 self.notebook.connect("switch-page", self._page_switched) 589 self.addAdvanced.connect("clicked", self._add_advanced_clicked) 590 self.options.connect("clicked", self._options_clicked) 591 592 self.pages = [] 593 594 self.anaconda = anaconda 595 596 # One common store that all the views on all the notebook tabs share. 597 # Yes, this means a whole lot of columns that are going to be empty or 598 # unused much of the time. Oh well. 599 600 # Object, 601 # visible, active (checked), immutable, 602 # device, model, capacity, vendor, interconnect, serial number, wwid 603 # paths, port, target, lun 604 self.store = gtk.TreeStore(gobject.TYPE_PYOBJECT, 605 gobject.TYPE_BOOLEAN, gobject.TYPE_BOOLEAN, 606 gobject.TYPE_BOOLEAN, 607 gobject.TYPE_STRING, gobject.TYPE_STRING, 608 gobject.TYPE_LONG, gobject.TYPE_STRING, 609 gobject.TYPE_STRING, gobject.TYPE_STRING, 610 gobject.TYPE_STRING, gobject.TYPE_STRING, 611 gobject.TYPE_STRING, gobject.TYPE_STRING, 612 gobject.TYPE_STRING) 613 self.store.set_sort_column_id(MODEL_COL, gtk.SORT_ASCENDING) 614 615 # if we've already populated the device tree at least once we should 616 # do our best to make sure any active devices get deactivated 617 anaconda.storage.devicetree.teardownAll() 618 # So that drives onlined by these show up in the filter UI 619 iscsi.iscsi().startup(anaconda.intf) 620 fcoe.fcoe().startup(anaconda.intf) 621 zfcp.ZFCP().startup(anaconda.intf) 622 dasd.DASD().startup(anaconda.intf, 623 anaconda.storage.config.exclusiveDisks, 624 anaconda.storage.config.zeroMbr) 625 disks = self._getFilterDisks() 626 627 mcw = MultipathConfigWriter() 628 cfg = mcw.write(friendly_names=True) 629 with open("/etc/multipath.conf", "w+") as mpath_cfg: 630 mpath_cfg.write(cfg) 631 632 topology = MultipathTopology(disks) 633 # identifyMultipaths() uses 'multipath -d' and 'multipath -ll' to find 634 # mpath devices. In case there are devices already set up they won't 635 # show up (or show up with a wrong name). Flush those first. 636 flush_mpaths() 637 638 # The device list could be really long, so we really only want to 639 # iterate over it the bare minimum of times. Dividing this list up 640 # now means fewer elements to iterate over later. 641 singlepaths = filter(lambda info: self._device_size_is_nonzero(info), 642 topology.singlepaths_iter()) 643 (raids, nonraids) = self.split_list(lambda d: isRAID(d) and not isCCISS(d), 644 singlepaths) 645 646 self.pages = [self._makeBasic(), self._makeRAID(), 647 self._makeMPath(), self._makeOther(), 648 self._makeSearch()] 649 650 self.populate(nonraids, topology.multipaths_iter(), raids) 651 652 # If the "Add Advanced" button is ever clicked, we need to have a list 653 # of what devices previously existed so we know what's new. Then we 654 # can just add the new devices to the UI. This is going to be slow, 655 # but the user has to click a button to get to the slow part. 656 self._cachedDevices = NameCache(singlepaths) 657 self._cachedRaidDevices = NameCache(raids) 658 659 # Multipath is a little more complicated. Since mpaths is a list of 660 # lists, we can't directly store that into the cache. Instead we want 661 # to flatten it into a single list of all components of all multipaths 662 # and store that. 663 mpath_chain = itertools.chain(*topology.multipaths_iter()) 664 self._cachedMPaths = NameCache(mpath_chain) 665 666 # Switch to the first notebook page that displays any devices. 667 i = 0 668 for pg in self.pages: 669 if pg.getNVisible(): 670 self.notebook.set_current_page(i) 671 break 672 673 i += 1 674 675 return self.vbox
676
677 - def depopulate(self, component):
678 for row in self.store: 679 if row[4] == component['DEVNAME']: 680 self.store.remove(row.iter) 681 return
682
683 - def populate(self, nonraids, mpaths, raids, activeByDefault=False):
684 def _addTuple(tuple): 685 global totalDevices, totalSize 686 global selectedDevices, selectedSize 687 added = False 688 689 self.store.append(None, tuple) 690 691 for pg in self.pages: 692 if pg.cb.isMember(tuple[0]): 693 added = True 694 pg.cb.addToUI(tuple) 695 696 # Only update the size label if this device was added to any pages. 697 # This prevents situations where we're only displaying the basic 698 # filter that has one disk, but there are several advanced disks 699 # in the store that cannot be seen. 700 if added: 701 totalDevices += 1 702 totalSize += tuple[0]["XXX_SIZE"] 703 704 if tuple[ACTIVE_COL]: 705 selectedDevices += 1 706 selectedSize += tuple[0]["XXX_SIZE"]
707 708 def _isProtected(info): 709 protectedNames = map(udev_resolve_devspec, self.anaconda.protected) 710 711 sysfs_path = udev_device_get_sysfs_path(info) 712 for protected in protectedNames: 713 _p = "/sys/%s/%s" % (sysfs_path, protected) 714 if os.path.exists(os.path.normpath(_p)): 715 return True 716 717 return False
718 719 def _active(info): 720 if _isProtected(info) or activeByDefault: 721 return True 722 723 name = udev_device_get_name(info) 724 725 if self.anaconda.storage.config.exclusiveDisks and \ 726 name in self.anaconda.storage.config.exclusiveDisks: 727 return True 728 elif self.anaconda.storage.config.ignoredDisks and \ 729 name not in self.anaconda.storage.config.ignoredDisks: 730 return True 731 else: 732 return False 733 734 for d in nonraids: 735 name = udev_device_get_name(d) 736 737 # We aren't guaranteed to be able to get a device. In 738 # particular, built-in USB flash readers show up as devices but 739 # do not always have any media present, so parted won't be able 740 # to find a device. 741 try: 742 partedDevice = parted.Device(path="/dev/" + name) 743 except (_ped.IOException, _ped.DeviceException): 744 continue 745 d["XXX_SIZE"] = long(partedDevice.getSize()) 746 747 # This isn't so great, but iSCSI and s390 devices have an ID_PATH 748 # that contains a lot of useful identifying info, so that should be 749 # displayed instead of a blank WWID. 750 if udev_device_is_iscsi(d) or udev_device_is_dasd(d) or udev_device_is_zfcp(d): 751 ident = udev_device_get_path(d) 752 else: 753 ident = udev_device_get_wwid(d) 754 755 tuple = (d, True, _active(d), _isProtected(d), name, 756 partedDevice.model, long(d["XXX_SIZE"]), 757 udev_device_get_vendor(d), udev_device_get_bus(d), 758 udev_device_get_serial(d), ident, "", "", "", "") 759 _addTuple(tuple) 760 761 if raids and flags.dmraid: 762 used_raidmembers = [] 763 for rs in block.getRaidSets(): 764 # dmraid does everything in sectors 765 size = (rs.rs.sectors * 512) / (1024.0 * 1024.0) 766 fstype = "" 767 768 # get_members also returns subsets with layered raids, we only 769 # want the devices 770 members = filter(lambda m: isinstance(m, block.device.RaidDev), 771 list(rs.get_members())) 772 members = map(lambda m: m.get_devpath(), members) 773 for d in raids: 774 if udev_device_get_name(d) in members: 775 fstype = udev_device_get_format(d) 776 sysfs_path = udev_device_get_sysfs_path(d) 777 break 778 779 # Skip this set if none of its members are in the raids list 780 if not fstype: 781 continue 782 783 used_raidmembers.extend(members) 784 785 # biosraid devices don't really get udev data, at least not in a 786 # a way that's useful to the filtering UI. So we need to fake 787 # that data now so we have something to put into the store. 788 data = {"XXX_SIZE": size, "ID_FS_TYPE": fstype, 789 "DM_NAME": rs.name, "name": rs.name, 790 "sysfs_path": sysfs_path} 791 792 model = "BIOS RAID set (%s)" % rs.rs.set_type 793 tuple = (data, True, _active(data), _isProtected(data), rs.name, 794 model, long(size), "", "", "", "", 795 "\n".join(members), "", "", "") 796 _addTuple(tuple) 797 798 unused_raidmembers = [] 799 for d in raids: 800 if udev_device_get_name(d) not in used_raidmembers: 801 unused_raidmembers.append(udev_device_get_name(d)) 802 803 self.anaconda.intf.unusedRaidMembersWarning(unused_raidmembers) 804 805 for mpath in mpaths: 806 # We only need to grab information from the first device in the set. 807 name = udev_device_get_name(mpath[0]) 808 809 try: 810 partedDevice = parted.Device(path="/dev/" + name) 811 except (_ped.IOException, _ped.DeviceException): 812 continue 813 mpath[0]["XXX_SIZE"] = long(partedDevice.getSize()) 814 model = partedDevice.model 815 816 # However, we do need all the paths making up this multipath set. 817 paths = "\n".join(map(udev_device_get_name, mpath)) 818 819 # We use a copy here, so as to not modify the original udev info 820 # dict as that would break NameCache matching 821 data = mpath[0].copy() 822 data["name"] = udev_device_get_multipath_name(mpath[0]) 823 tuple = (data, True, _active(data), _isProtected(data), 824 udev_device_get_multipath_name(mpath[0]), model, 825 long(mpath[0]["XXX_SIZE"]), 826 udev_device_get_vendor(mpath[0]), 827 udev_device_get_bus(mpath[0]), 828 udev_device_get_serial(mpath[0]), 829 udev_device_get_wwid(mpath[0]), 830 paths, "", "", "") 831 _addTuple(tuple) 832
833 - def split_list(self, pred, lst):
834 pos = [] 835 neg = [] 836 837 for ele in lst: 838 if pred(ele): 839 pos.append(ele) 840 else: 841 neg.append(ele) 842 843 return (pos, neg)
844