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