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