Setting tag sface-0.9-9
[sface.git] / sface / screens / mainscreen.py
1
2 import datetime
3 import os
4 import urlparse
5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
7
8 #from sfa.util.rspecHelper import RSpec
9 from sfa.util.xrn import Xrn
10 from sface.config import config
11 from sface.sfirenew import RenewWindow
12 from sface.sfiprocess import SfiProcess
13 from sface.screens.sfascreen import SfaScreen
14 from sface.sfidata import SfiData
15
16 from sface.clislicemgr import ClientSliceManager
17
18 already_in_nodes = []
19
20 node_status = { "in": "Already Selected",
21                 "out": "Not Selected",
22                 "add": "To be Added",
23                 "remove": "To be Removed"}
24
25 tag_status = { "in": "Already Set",
26                 "out": "Not Set",
27                 "add": "To be Added",
28                 "remove": "To be Removed"}
29
30 color_status = { "in": QColor.fromRgb(0, 250, 250),
31                  "add": QColor.fromRgb(0, 250, 0),
32                  "remove": QColor.fromRgb(250, 0, 0) }
33
34 default_tags = "Default tags"
35 settable_tags = ['delegations', 'initscript']
36
37 NAME_COLUMN = 0
38 NODE_TYPE_COLUMN = 1
39 NODE_STATUS_COLUMN = 2
40 MEMBERSHIP_STATUS_COLUMN = 3
41 KIND_COLUMN = 4
42
43 # maximum length of a name to display before clipping
44 NAME_MAX_LEN = 48
45
46 def itemType(index):
47     if index.parent().parent().isValid():
48         return "tag"
49     else:
50         return "node"
51
52
53 class NodeView(QTreeView):
54     def __init__(self, parent):
55         QTreeView.__init__(self, parent)
56
57         self.setAnimated(True)
58         self.setItemsExpandable(True)
59         self.setRootIsDecorated(True)
60         self.setAlternatingRowColors(True)
61 #        self.setSelectionMode(self.MultiSelection)
62         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
63         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
64         self.setToolTip("Double click on a row to change its status.  Right click on a host to add a tag.")
65
66     def keyPressEvent(self, event):
67         if (event.key() == Qt.Key_Space):
68             self.toggleSelection()
69         else:
70             QTreeView.keyPressEvent(self, event)
71
72     def mouseDoubleClickEvent(self, event):
73         self.toggleSelection()
74
75     def toggleSelection(self):
76         index = self.currentIndex()
77         model = index.model()
78
79         if (model == None):
80             # probably no rspec downloaded yet
81             return
82
83         status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
84         status_data = status_index.data().toString()
85         node_index = model.index(index.row(), NAME_COLUMN, index.parent())
86         node_data = node_index.data().toString()
87
88         if itemType(node_index) == "tag":
89             data = node_index.data().toString()
90             tagname, value = data.split(": ")
91             if tagname not in settable_tags:
92                 # Pop up error msg
93                 QMessageBox.warning(self, "Not settable", "Insufficient permission to change '%s' tag" % tagname)
94                 return
95             if status_data == tag_status['in']:
96                 model.setData(status_index, QString(tag_status['remove']))
97             elif status_data == tag_status['add']:
98                 model.setData(status_index, QString(tag_status['out']))
99             elif status_data == tag_status['remove']:
100                 model.setData(status_index, QString(tag_status['in']))
101             else: model.setData(status_index, QString(node_status['out']))
102         else:
103             # This is a hostname
104             if status_data == node_status['in']:
105                 model.setData(status_index, QString(node_status['remove']))
106             elif status_data == node_status['out']:
107                 model.setData(status_index, QString(node_status['add']))
108             elif status_data in (node_status['add'], node_status['remove']):
109                 if node_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
110                 else: model.setData(status_index, QString(node_status['out']))
111
112         model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
113
114     def appendRow(self, parent, name, nodeStatus="", nodeType="", membership="", kind = ""):
115         # row: name nodeStatus nodeType membership kind
116         item = QStandardItem(QString(str(name)))
117         row = [item,
118                 QStandardItem(QString(str(nodeType))),
119                 QStandardItem(QString(str(nodeStatus))),
120                 QStandardItem(QString(str(membership))),
121                 QStandardItem(QString(str(kind)))]
122         parent.appendRow(row)
123         return item
124
125     def mousePressEvent(self, event):
126         QTreeView.mousePressEvent(self, event)
127         if event.button() == Qt.LeftButton:
128             return
129
130         # Right click
131         index = self.currentIndex()
132         model = index.model()
133
134         if (model == None):
135             # probably no rspec downloaded yet
136             return
137
138         status_index = model.index(index.row(), 1, index.parent())
139         status_data = status_index.data().toString()
140         node_index = model.index(index.row(), 0, index.parent())
141         node_data = node_index.data().toString()
142
143         if itemType(node_index) == "node":
144             # This is a hostname
145             if status_data in (node_status['in'], node_status['add'], ""):
146                 # Pop up a dialog box for adding a new attribute
147                 tagname, ok = QInputDialog.getItem(self, "Add tag",
148                                                    "Tag name:", settable_tags)
149                 if ok:
150                     value, ok = QInputDialog.getText(self, "Add tag",
151                                                      "Value for tag '%s'" % tagname)
152                     if ok:
153                         # We're using the QSortFilterProxyModel here
154                         src_index = model.mapToSource(index)
155                         src_model = src_index.model()
156                         nodeItem = src_model.itemFromIndex(src_index)
157
158                         self.appendRow(nodeItem, "%s: %s" % (tagname, value), membership=tag_status['add'], kind="attribute")
159
160             elif status_data in (node_status['out'], node_status['remove']):
161                 QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
162                 return
163
164         model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
165
166     def currentChanged(self, current, previous):
167         model = current.model()
168         node_index = model.index(current.row(), 0, current.parent())
169         node_data = node_index.data().toString()
170         self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
171         
172                 
173
174 class NodeNameDelegate(QStyledItemDelegate):
175     def __init__(self, parent):
176         QStyledItemDelegate.__init__(self, parent)
177
178     def displayText(self, value, locale):
179         try:
180             data = str(QStyledItemDelegate.displayText(self, value, locale))
181         except UnicodeEncodeError:
182             data = "<UnicodeDecodeError when generating displaytext>"
183         if (len(data)>NAME_MAX_LEN):
184             data = data[:(NAME_MAX_LEN-3)] + "..."
185         return QString(data)
186
187     def paint(self, painter, option, index):
188         model = index.model()
189         data = str(self.displayText(index.data(), QLocale()))
190         status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
191         status_data = status_index.data().toString()
192
193         fm = QFontMetrics(option.font)
194         rect = QRect(option.rect)
195
196         rect.setHeight(rect.height() - 2)
197         rect.setWidth(fm.width(QString(data)) + 6)
198         rect.setX(rect.x() + 5)
199         rect.setY(rect.y() - 1)
200
201         x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
202
203         path = QPainterPath()
204         path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
205
206         painter.save()
207         painter.setRenderHint(QPainter.Antialiasing)
208
209         if option.state & QStyle.State_Selected:
210             painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
211
212         if itemType(index) == "node":
213             for x in node_status.keys():
214                 if (node_status[x] == status_data) and (x in color_status):
215                     painter.fillPath(path, color_status[x])
216
217             painter.setPen(QColor.fromRgb(0, 0, 0))
218             painter.drawText(rect, 0, QString(data))
219
220         else:
221             for x in tag_status.keys():
222                 if (tag_status[x] == status_data) and (x in color_status):
223                     painter.fillPath(path, color_status[x])
224
225             painter.setPen(QColor.fromRgb(0, 0, 0))
226             painter.drawText(rect, 0, QString(data))
227
228         painter.restore()
229
230 class NodeStatusDelegate(QStyledItemDelegate):
231     def __init__(self, parent):
232         QStyledItemDelegate.__init__(self, parent)
233
234     def paint(self, painter, option, index):
235         model = index.model()
236         nodestatus_index = model.index(index.row(), NODE_STATUS_COLUMN, index.parent())
237         nodestatus_data = nodestatus_index.data().toString()
238
239         fm = QFontMetrics(option.font)
240         rect = QRect(option.rect)
241
242         data = index.data().toString()
243         rect.setHeight(rect.height() - 2)
244         rect.setWidth(fm.width(QString(data)) + 6)
245         rect.setX(rect.x() + 5)
246         rect.setY(rect.y() - 1)
247
248         x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
249
250         path = QPainterPath()
251         path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
252
253         painter.save()
254         painter.setRenderHint(QPainter.Antialiasing)
255
256         if option.state & QStyle.State_Selected:
257             painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
258
259         if (nodestatus_data == ""):
260                 painter.setPen(QColor.fromRgb(0, 0, 0))
261                 painter.drawText(rect, 0, QString(data))
262         elif (nodestatus_data == "boot"):
263                 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
264                 painter.setPen(QColor.fromRgb(0, 0, 0))
265                 painter.drawText(rect, 0, QString(data))
266         else:
267                 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
268                 painter.setPen(QColor.fromRgb(0, 0, 0))
269                 painter.drawText(rect, 0, QString(data))
270
271         painter.restore()
272
273 class NodeFilterProxyModel(QSortFilterProxyModel):
274     def __init__(self, parent=None):
275         QSortFilterProxyModel.__init__(self, parent)
276         self.hostname_filter_regex = None
277         self.nodestatus_filter = None
278
279     def setHostNameFilter(self, hostname):
280         self.hostname_filter_regex = QRegExp(hostname)
281         self.invalidateFilter()
282
283     def setNodeStatusFilter(self, status):
284         if (status == "all"):
285             self.nodestatus_filter = None
286         else:
287             self.nodestatus_filter = status
288         self.invalidateFilter()
289
290     def filterAcceptsRow(self, sourceRow, source_parent):
291         kind_data = self.sourceModel().index(sourceRow, KIND_COLUMN, source_parent).data().toString()
292         if (kind_data == "node"):
293             if self.hostname_filter_regex:
294                 name_data = self.sourceModel().index(sourceRow, NAME_COLUMN, source_parent).data().toString()
295                 if (self.hostname_filter_regex.indexIn(name_data) < 0):
296                     return False
297             if self.nodestatus_filter:
298                 nodestatus_data = self.sourceModel().index(sourceRow, NODE_STATUS_COLUMN, source_parent).data().toString()
299                 if (nodestatus_data != self.nodestatus_filter):
300                     return False
301         return True
302
303     def lessThan(self, left, right):
304         l_str = str(left.data().toString())
305         r_str = str(right.data().toString())
306
307         # make sure default_tags appears before everything else
308         if l_str.startswith(default_tags):
309             return True
310
311         if r_str.startswith(default_tags):
312             return False
313
314         return (l_str < r_str)
315
316
317 class SliceWidget(QWidget):
318     def __init__(self, parent):
319         QWidget.__init__(self, parent)
320
321         self.network_names = []
322         self.process = SfiProcess(self)
323
324         self.slicename = QLabel("", self)
325         self.updateSliceName()
326         self.slicename.setScaledContents(False)
327         filterlabel = QLabel ("Filter: ", self)
328         filterbox = QComboBox(self)
329         filterbox.addItems(["all", "boot", "disabled", "reinstall", "safeboot"])
330         searchlabel = QLabel ("Search: ", self)
331         searchlabel.setScaledContents(False)
332         searchbox = QLineEdit(self)
333         searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
334
335         toplayout = QHBoxLayout()
336         toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
337         toplayout.addStretch()
338         toplayout.addWidget(filterlabel, 0, Qt.AlignRight)
339         toplayout.addWidget(filterbox, 0, Qt.AlignRight)
340         toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
341         toplayout.addWidget(searchbox, 0, Qt.AlignRight)
342
343         self.nodeView = NodeView(self)
344         self.nodeModel = QStandardItemModel(0, 4, self)
345         self.filterModel = NodeFilterProxyModel(self)
346
347         self.nodeNameDelegate = NodeNameDelegate(self)
348         self.nodeStatusDelegate = NodeStatusDelegate(self)
349
350         refresh = QPushButton("Refresh Slice Data", self)
351         refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
352         renew = QPushButton("Renew Slice", self)
353         renew.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
354         submit = QPushButton("Submit", self)
355         submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
356
357         bottomlayout = QHBoxLayout()
358         bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
359         bottomlayout.addWidget(renew, 0, Qt.AlignLeft)
360         bottomlayout.addStretch()
361         bottomlayout.addWidget(submit, 0, Qt.AlignRight)
362         self.bottomlayout = bottomlayout
363
364         layout = QVBoxLayout()
365         layout.addLayout(toplayout)
366         layout.addWidget(self.nodeView)
367         layout.addLayout(bottomlayout)
368         self.setLayout(layout)
369         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
370
371         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
372         self.connect(renew, SIGNAL('clicked()'), self.renew)
373         self.connect(submit, SIGNAL('clicked()'), self.submit) # _pg_compat)
374         self.connect(searchbox, SIGNAL('textChanged(QString)'), self.search)
375         self.connect(filterbox, SIGNAL('currentIndexChanged(QString)'), self.filter)
376         self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
377                      self.nodeSelectionChanged)
378
379         self.updateView()
380
381     def submitFinished(self):
382         self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
383
384         faultString = self.process.getFaultString()
385         if not faultString:
386             self.setStatus("<font color='green'>Slice data submitted.</font>")
387         else:
388             self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
389
390         self.updateView()
391         self.parent().signalAll("rspecUpdated")
392
393     def deleteSliversFinished(self):
394         self.disconnect(self.process, SIGNAL('finished()'), self.deleteSliversFinished)
395
396         faultString = self.process.getFaultString()
397         if not faultString:
398             self.setStatus("<font color='green'>Slice data submitted.</font>")
399             QTimer.singleShot(2500, self.refresh)
400         else:
401             self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
402
403     def refreshResourcesFinished(self):
404         self.disconnect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
405
406         faultString = self.process.getFaultString()
407         if not faultString:
408             self.setStatus("Refreshing slice RSpec.")
409             self.connect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
410             self.process.retrieveRspec()
411         else:
412             self.setStatus("<font color='red'>Resources refresh failed: %s</font>" % (faultString))
413
414     def refreshRSpecFinished(self):
415         self.disconnect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
416
417         faultString = self.process.getFaultString()
418         if not faultString:
419             self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
420         else:
421             self.setStatus("<font color='red'>Slice refresh failed: %s</font>" % (faultString))
422
423         self.updateView()
424         self.parent().signalAll("rspecUpdated")
425
426     def setStatus(self, msg, timeout=None):
427         self.parent().setStatus(msg, timeout)
428
429     def checkRunningProcess(self):
430         if self.process.isRunning():
431             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
432             return True
433         return False
434
435     def search(self, search_string):
436         self.filterModel.setHostNameFilter(str(search_string))
437
438     def filter(self, filter_string):
439         self.filterModel.setNodeStatusFilter(str(filter_string))
440
441     def itemStatus(self, item):
442         statusItem = item.parent().child(item.row(), MEMBERSHIP_STATUS_COLUMN)
443         return str(statusItem.data(Qt.DisplayRole).toString())
444
445     def itemText(self, item):
446         return str(item.data(Qt.DisplayRole).toString())
447
448     # Recursively walk the tree, making changes to the RSpec
449     def process_subtree(self, rspec, rspec_node_names, resource_node_names, item, depth = 0):
450         changeAdd = 0
451         changeRemove = 0
452         changeAddTag = 0
453         changeRemoveTag = 0
454         model = self.nodeModel
455
456         if depth in [0, 1]:
457             pass
458         elif depth == 2: # Hostname
459             hostname = self.itemText(item)
460             testbed = self.itemText(item.parent())
461             status = self.itemStatus(item)
462             if status == node_status['add']:
463                 print "Add hostname: %s" % hostname
464
465                 resource_node = resource_node_names.get(hostname, None)
466
467                 if resource_node==None:
468                     print "Error: Failed to find %s in resources rspec" % hostname
469                 else:
470                     if not (hostname in rspec_node_names):
471                         network_name = Xrn(resource_node['component_manager_id']).get_hrn()
472                         rspec.version.add_network(network_name)
473                         rspec.version.add_nodes([resource_node])
474                     rspec.version.add_slivers([str(hostname)])
475                     changeAdd += 1
476             elif status == node_status['remove']:
477                 print "Remove hostname: %s" % hostname
478                 rspec.version.remove_slivers([str(hostname)])
479                 changeRemove += 1
480         elif depth == 3: # Tag
481             tag, value = self.itemText(item).split(": ")
482             status = self.itemStatus(item)
483             tag = "%s" % tag     # Prevent weird error from lxml
484             value = "%s" % value # Prevent weird error from lxml
485             hostname = self.itemText(item.parent())
486             testbed = self.itemText(item.parent().parent())
487             if status == tag_status['add']:
488                 print "Add tag to (%s, %s): %s/%s " % (testbed, hostname, tag, value)
489                 if hostname.startswith(default_tags):
490                     rspec.version.add_default_sliver_attribute(tag, value, testbed)
491                 else:
492                     node = rspec_node_names.get(hostname, None)
493                     if node:
494                         rspec.version.add_sliver_attribute(node['component_id'], tag, value, testbed)
495                 changeAddTag += 1
496             elif status == tag_status['remove']:
497                 print "Remove tag from (%s, %s): %s/%s " % (testbed, hostname, tag, value)
498                 if hostname.startswith(default_tags):
499                     rspec.version.remove_default_sliver_attribute(tag, value, testbed)
500                 else:
501                     node = rspec_node_names.get(hostname, None)
502                     if node:
503                         rspec.version.remove_sliver_attribute(node['component_id'], tag, value, testbed)
504                 changeRemoveTag += 1
505
506         children = item.rowCount()
507         for row in range(0, children):
508             (c, ca, cr, cat, crt) = self.process_subtree(rspec, rspec_node_names, resource_node_names, item.child(row), depth + 1)
509             changeAdd = changeAdd + ca
510             changeRemove = changeRemove + cr
511             changeAddTag = changeAddTag + cat
512             changeRemoveTag = changeRemoveTag + crt
513
514         change = changeAdd + changeRemove + changeAddTag + changeRemoveTag
515
516         return (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag)
517
518     def submit(self):
519         if self.checkRunningProcess():
520             return
521
522         rspec = SfiData().getSliceRSpec()
523         resources = SfiData().getResourcesRSpec()
524
525         resource_node_names = self.nodesByName(resources.version.get_nodes())
526         rspec_node_names = self.nodesByName(rspec.version.get_nodes_with_slivers())
527
528         (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag) = \
529             self.process_subtree(rspec, rspec_node_names, resource_node_names, self.nodeModel.invisibleRootItem())
530
531         if (change <= 0):
532             self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
533             return
534
535         if (changeRemove > 0) and (rspec.version.get_nodes_with_slivers() == []):
536             # removing the last sliver in a slice requires us to call deleteSlivers
537             self.connect(self.process, SIGNAL('finished()'), self.deleteSliversFinished)
538             self.process.deleteSlivers()
539             return
540
541         # Several aggregates have issues with the <statistics> section in the
542         # rspec, so make sure it's not there.
543         stats_elems = rspec.xml.xpath("//statistics")
544         if len(stats_elems)>0:
545             stats_elem = stats_elems[0]
546             parent = stats_elem.xpath("..")[0]
547             parent.remove(stats_elem)
548
549         self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
550         self.process.applyRSpec(rspec)
551         self.setStatus("Sending slice data (RSpec). This will take some time...")
552
553     def submit_pg_compat(self):
554         if self.checkRunningProcess():
555             return
556
557         rspec = SfiData().getSliceRSpec()
558         resources = SfiData().getResourcesRSpec()
559
560         (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag) = \
561             self.process_subtree(rspec, rspec, resources, self.nodeModel.invisibleRootItem())
562
563         if (change <= 0):
564             self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
565             return
566
567         dlg = ClientSliceManager(self)
568         dlg.submit_pg_compat(rspec)
569         dlg.exec_()
570
571         self.setStatus("<font color='green'>Finished submitting. %d/%d aggs succeeded.</font>" %
572                       (dlg.submit_aggSuccessCount,dlg.submit_aggSuccessCount+dlg.submit_aggFailCount))
573         QTimer.singleShot(2500, self.refresh)
574
575     def renew(self):
576         dlg = RenewWindow(parent=self)
577         dlg.exec_()
578
579     def refresh(self):
580         if not config.getSlice():
581             self.setStatus("<font color='red'>Slice not set yet!</font>")
582             return
583
584         if self.process.isRunning():
585             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
586             return
587
588         self.connect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
589
590         self.process.retrieveResources()
591         self.setStatus("Refreshing resources. This will take some time...")
592
593     def nodesByNetwork(self, nodeList):
594         netDict = {}
595         for node in nodeList:
596             network_name = Xrn(node['component_manager_id']).get_hrn()
597             if network_name:
598                 net = netDict.get(network_name, [])
599                 if net == []:
600                     netDict[network_name] = net
601
602                 net.append(node)
603
604         return netDict
605
606     def nodesByName(self, nodeList, nameDict=None):
607         if nameDict==None:
608             nameDict = {}
609         for node in nodeList:
610             hostname = node.get("component_name", None)
611             if hostname and (not hostname in nameDict):
612                 nameDict[hostname] = node
613
614         return nameDict
615
616     def updateView(self):
617         global already_in_nodes
618         already_in_nodes = []
619         self.network_names = []
620         self.nodeModel.clear()
621
622         rspec = SfiData().getSliceRSpec()
623         if not rspec:
624             return None
625
626         resources = SfiData().getResourcesRSpec()
627         if not resources:
628             return None
629
630         rootItem = self.nodeModel.invisibleRootItem()
631
632         networks = []
633         for network in rspec.version.get_networks():
634             network_name = network.get("name", None)
635             if (network_name != None) and (not network_name in networks):
636                 networks.append(network_name)
637         for network in resources.version.get_networks():
638             network_name = network.get("name", None)
639             if (network_name != None) and (not network_name in networks):
640                 networks.append(network_name)
641
642         resources_nodes = self.nodesByNetwork(resources.version.get_nodes())
643         rspec_nodes = self.nodesByNetwork(rspec.version.get_nodes_with_slivers())
644
645         for network in networks:
646             self.network_names.append(network)
647
648             all_nodes = resources_nodes.get(network, [])
649             sliver_nodes = rspec_nodes.get(network, [])
650
651             sliver_node_names = self.nodesByName(sliver_nodes)
652
653             available_nodes = [ node for node in all_nodes if node["component_name"] not in sliver_node_names ]
654
655             msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
656             networkItem = self.nodeView.appendRow(rootItem, network, membership=msg, kind="network")
657
658             already_in_nodes += sliver_node_names.keys()
659
660             # Add default slice tags
661             nodeItem = self.nodeView.appendRow(networkItem, "%s for %s" % (default_tags, network), kind="defaults")
662             attrs = rspec.version.get_default_sliver_attributes(network)
663             for attr in attrs:
664                     name = attr.get("name", None)
665                     value = attr.get("value", None)
666                     tagstring = QString("%s: %s" % (name, value))
667                     self.nodeView.appendRow(nodeItem, tagstring, membership=tag_status['in'], kind = "attribute")
668
669             for node in sliver_nodes:
670                 nodeType = None
671                 if ("hardware_types" in node):
672                     hardware_types = [x["name"] for x in node["hardware_types"]]
673                     nodeType = ",".join(hardware_types)
674                 nodeStatus = node.get("boot_state", "")
675                 if nodeStatus == None:
676                     nodeStatus=""
677                 nodeItem = self.nodeView.appendRow(networkItem,
678                                node["component_name"],
679                                nodeStatus=nodeStatus,
680                                nodeType=nodeType,
681                                membership=node_status['in'],
682                                kind="node")
683
684                 attrs = rspec.version.get_sliver_attributes(node['component_id'], network)
685                 for attr in attrs:
686                     name = attr.get("name", None)
687                     value = attr.get("value", None)
688                     self.nodeView.appendRow(nodeItem,
689                                             "%s: %s" % (name, value),
690                                             membership=tag_status['in'],
691                                             kind="attribute")
692                 disk_images = node.get("disk_image", [])
693                 for disk_image in disk_images:
694                     name = disk_image.get("name", None)
695                     self.noveView.appendRow(nodeItem, name, 
696                                             membership=node_status['in'],
697                                             kind="attribute")                                                                   
698                 
699             for node in available_nodes:
700                 nodeType = None
701                 if ("hardware_types" in node):
702                     hardware_types = [x["name"] for x in node["hardware_types"]]
703                     nodeType = ",".join(hardware_types)
704                 nodeStatus = node.get("boot_state", "")
705                 if nodeStatus == None:
706                     nodeStatus=""
707                 self.nodeView.appendRow(networkItem,
708                                node["component_name"],
709                                nodeStatus=nodeStatus,
710                                nodeType=nodeType,
711                                membership=node_status['out'],
712                                kind="node")
713
714         self.filterModel.setSourceModel(self.nodeModel)
715         self.filterModel.setDynamicSortFilter(True)
716         self.filterModel.sort(NAME_COLUMN)
717
718         headers = QStringList() << "Hostname or Tag" << "Node Type" << "Node Status" << "Membership Status" << "Kind"
719         self.nodeModel.setHorizontalHeaderLabels(headers)
720
721         self.nodeView.setItemDelegateForColumn(NAME_COLUMN, self.nodeNameDelegate)
722         self.nodeView.setItemDelegateForColumn(NODE_STATUS_COLUMN, self.nodeStatusDelegate)
723         self.nodeView.setModel(self.filterModel)
724         self.nodeView.hideColumn(KIND_COLUMN)
725         self.nodeView.expandAll()
726         self.nodeView.resizeColumnToContents(0)
727         self.nodeView.collapseAll()
728
729     def updateSliceName(self):
730         self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
731
732     def nodeSelectionChanged(self, hostname):
733         self.parent().nodeSelectionChanged(hostname)
734
735 class MainScreen(SfaScreen):
736     def __init__(self, parent):
737         SfaScreen.__init__(self, parent)
738
739         self.sliceWidget = SliceWidget(self)
740         self.init(self.sliceWidget, "Nodes", "OneLab SFA crawler")
741
742     def rspecUpdated(self):
743         self.mainwin.rspecWindow.updateView()
744
745     def configurationChanged(self):
746         self.widget.updateSliceName()
747         self.widget.updateView()
748         self.mainwin.rspecWindow.updateView()
749
750     def nodeSelectionChanged(self, hostname):
751         self.mainwin.nodeSelectionChanged(hostname)
752
753     def remoteSliceChanged(self):
754         # we're being notified the slice was changed remotely. Download a new
755         # rspec.
756         QTimer.singleShot(2500, self.sliceWidget.refresh)
757
758     def addMainScreenButton(self, caption, action):
759         # allows another screen to add a button to mainscreen
760         button = QPushButton(caption, self)
761         self.sliceWidget.bottomlayout.insertWidget(2, button)
762         self.sliceWidget.connect(button, SIGNAL('clicked()'), action)
763         return button
764
765