catch UnicodeEncodeError
[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
363         layout = QVBoxLayout()
364         layout.addLayout(toplayout)
365         layout.addWidget(self.nodeView)
366         layout.addLayout(bottomlayout)
367         self.setLayout(layout)
368         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
369
370         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
371         self.connect(renew, SIGNAL('clicked()'), self.renew)
372         self.connect(submit, SIGNAL('clicked()'), self.submit) # _pg_compat)
373         self.connect(searchbox, SIGNAL('textChanged(QString)'), self.search)
374         self.connect(filterbox, SIGNAL('currentIndexChanged(QString)'), self.filter)
375         self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
376                      self.nodeSelectionChanged)
377
378         self.updateView()
379
380     def submitFinished(self):
381         self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
382
383         faultString = self.process.getFaultString()
384         if not faultString:
385             self.setStatus("<font color='green'>Slice data submitted.</font>")
386         else:
387             self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
388
389         self.updateView()
390         self.parent().signalAll("rspecUpdated")
391
392     def deleteSliversFinished(self):
393         self.disconnect(self.process, SIGNAL('finished()'), self.deleteSliversFinished)
394
395         faultString = self.process.getFaultString()
396         if not faultString:
397             self.setStatus("<font color='green'>Slice data submitted.</font>")
398             QTimer.singleShot(2500, self.refresh)
399         else:
400             self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
401
402     def refreshResourcesFinished(self):
403         self.disconnect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
404
405         faultString = self.process.getFaultString()
406         if not faultString:
407             self.setStatus("Refreshing slice RSpec.")
408             self.connect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
409             self.process.retrieveRspec()
410         else:
411             self.setStatus("<font color='red'>Resources refresh failed: %s</font>" % (faultString))
412
413     def refreshRSpecFinished(self):
414         self.disconnect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
415
416         faultString = self.process.getFaultString()
417         if not faultString:
418             self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
419         else:
420             self.setStatus("<font color='red'>Slice refresh failed: %s</font>" % (faultString))
421
422         self.updateView()
423         self.parent().signalAll("rspecUpdated")
424
425     def setStatus(self, msg, timeout=None):
426         self.parent().setStatus(msg, timeout)
427
428     def checkRunningProcess(self):
429         if self.process.isRunning():
430             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
431             return True
432         return False
433
434     def search(self, search_string):
435         self.filterModel.setHostNameFilter(str(search_string))
436
437     def filter(self, filter_string):
438         self.filterModel.setNodeStatusFilter(str(filter_string))
439
440     def itemStatus(self, item):
441         statusItem = item.parent().child(item.row(), MEMBERSHIP_STATUS_COLUMN)
442         return str(statusItem.data(Qt.DisplayRole).toString())
443
444     def itemText(self, item):
445         return str(item.data(Qt.DisplayRole).toString())
446
447     # Recursively walk the tree, making changes to the RSpec
448     def process_subtree(self, rspec, rspec_node_names, resource_node_names, item, depth = 0):
449         changeAdd = 0
450         changeRemove = 0
451         changeAddTag = 0
452         changeRemoveTag = 0
453         model = self.nodeModel
454
455         if depth in [0, 1]:
456             pass
457         elif depth == 2: # Hostname
458             hostname = self.itemText(item)
459             testbed = self.itemText(item.parent())
460             status = self.itemStatus(item)
461             if status == node_status['add']:
462                 print "Add hostname: %s" % hostname
463
464                 resource_node = resource_node_names.get(hostname, None)
465
466                 if resource_node==None:
467                     print "Error: Failed to find %s in resources rspec" % hostname
468                 else:
469                     if not (hostname in rspec_node_names):
470                         network_name = Xrn(resource_node['component_manager_id']).get_hrn()
471                         rspec.version.add_network(network_name)
472                         rspec.version.add_nodes([resource_node])
473                     rspec.version.add_slivers([str(hostname)])
474                     changeAdd += 1
475             elif status == node_status['remove']:
476                 print "Remove hostname: %s" % hostname
477                 rspec.version.remove_slivers([str(hostname)])
478                 changeRemove += 1
479         elif depth == 3: # Tag
480             tag, value = self.itemText(item).split(": ")
481             status = self.itemStatus(item)
482             tag = "%s" % tag     # Prevent weird error from lxml
483             value = "%s" % value # Prevent weird error from lxml
484             hostname = self.itemText(item.parent())
485             testbed = self.itemText(item.parent().parent())
486             if status == tag_status['add']:
487                 print "Add tag to (%s, %s): %s/%s " % (testbed, hostname, tag, value)
488                 if hostname.startswith(default_tags):
489                     rspec.version.add_default_sliver_attribute(tag, value, testbed)
490                 else:
491                     node = rspec_node_names.get(hostname, None)
492                     if node:
493                         rspec.version.add_sliver_attribute(node['component_id'], tag, value, testbed)
494                 changeAddTag += 1
495             elif status == tag_status['remove']:
496                 print "Remove tag from (%s, %s): %s/%s " % (testbed, hostname, tag, value)
497                 if hostname.startswith(default_tags):
498                     rspec.version.remove_default_sliver_attribute(tag, value, testbed)
499                 else:
500                     node = rspec_node_names.get(hostname, None)
501                     if node:
502                         rspec.version.remove_sliver_attribute(node['component_id'], tag, value, testbed)
503                 changeRemoveTag += 1
504
505         children = item.rowCount()
506         for row in range(0, children):
507             (c, ca, cr, cat, crt) = self.process_subtree(rspec, rspec_node_names, resource_node_names, item.child(row), depth + 1)
508             changeAdd = changeAdd + ca
509             changeRemove = changeRemove + cr
510             changeAddTag = changeAddTag + cat
511             changeRemoveTag = changeRemoveTag + crt
512
513         change = changeAdd + changeRemove + changeAddTag + changeRemoveTag
514
515         return (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag)
516
517     def submit(self):
518         if self.checkRunningProcess():
519             return
520
521         rspec = SfiData().getSliceRSpec()
522         resources = SfiData().getResourcesRSpec()
523
524         resource_node_names = self.nodesByName(resources.version.get_nodes())
525         rspec_node_names = self.nodesByName(rspec.version.get_nodes_with_slivers())
526
527         (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag) = \
528             self.process_subtree(rspec, rspec_node_names, resource_node_names, self.nodeModel.invisibleRootItem())
529
530         if (change <= 0):
531             self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
532             return
533
534         if (changeRemove > 0) and (rspec.version.get_nodes_with_slivers() == []):
535             # removing the last sliver in a slice requires us to call deleteSlivers
536             self.connect(self.process, SIGNAL('finished()'), self.deleteSliversFinished)
537             self.process.deleteSlivers()
538             return
539
540         # Several aggregates have issues with the <statistics> section in the
541         # rspec, so make sure it's not there.
542         stats_elems = rspec.xml.xpath("//statistics")
543         if len(stats_elems)>0:
544             stats_elem = stats_elems[0]
545             parent = stats_elem.xpath("..")[0]
546             parent.remove(stats_elem)
547
548         self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
549         self.process.applyRSpec(rspec)
550         self.setStatus("Sending slice data (RSpec). This will take some time...")
551
552     def submit_pg_compat(self):
553         if self.checkRunningProcess():
554             return
555
556         rspec = SfiData().getSliceRSpec()
557         resources = SfiData().getResourcesRSpec()
558
559         (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag) = \
560             self.process_subtree(rspec, rspec, resources, self.nodeModel.invisibleRootItem())
561
562         if (change <= 0):
563             self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
564             return
565
566         dlg = ClientSliceManager(self)
567         dlg.submit_pg_compat(rspec)
568         dlg.exec_()
569
570         self.setStatus("<font color='green'>Finished submitting. %d/%d aggs succeeded.</font>" %
571                       (dlg.submit_aggSuccessCount,dlg.submit_aggSuccessCount+dlg.submit_aggFailCount))
572         QTimer.singleShot(2500, self.refresh)
573
574     def renew(self):
575         dlg = RenewWindow(parent=self)
576         dlg.exec_()
577
578     def refresh(self):
579         if not config.getSlice():
580             self.setStatus("<font color='red'>Slice not set yet!</font>")
581             return
582
583         if self.process.isRunning():
584             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
585             return
586
587         self.connect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
588
589         self.process.retrieveResources()
590         self.setStatus("Refreshing resources. This will take some time...")
591
592     def nodesByNetwork(self, nodeList):
593         netDict = {}
594         for node in nodeList:
595             network_name = Xrn(node['component_manager_id']).get_hrn()
596             if network_name:
597                 net = netDict.get(network_name, [])
598                 if net == []:
599                     netDict[network_name] = net
600
601                 net.append(node)
602
603         return netDict
604
605     def nodesByName(self, nodeList, nameDict=None):
606         if nameDict==None:
607             nameDict = {}
608         for node in nodeList:
609             hostname = node.get("component_name", None)
610             if hostname and (not hostname in nameDict):
611                 nameDict[hostname] = node
612
613         return nameDict
614
615     def updateView(self):
616         global already_in_nodes
617         already_in_nodes = []
618         self.network_names = []
619         self.nodeModel.clear()
620
621         rspec = SfiData().getSliceRSpec()
622         if not rspec:
623             return None
624
625         resources = SfiData().getResourcesRSpec()
626         if not resources:
627             return None
628
629         rootItem = self.nodeModel.invisibleRootItem()
630
631         networks = []
632         for network in rspec.version.get_networks():
633             network_name = network.get("name", None)
634             if (network_name != None) and (not network_name in networks):
635                 networks.append(network_name)
636         for network in resources.version.get_networks():
637             network_name = network.get("name", None)
638             if (network_name != None) and (not network_name in networks):
639                 networks.append(network_name)
640
641         resources_nodes = self.nodesByNetwork(resources.version.get_nodes())
642         rspec_nodes = self.nodesByNetwork(rspec.version.get_nodes_with_slivers())
643
644         for network in networks:
645             self.network_names.append(network)
646
647             all_nodes = resources_nodes.get(network, [])
648             sliver_nodes = rspec_nodes.get(network, [])
649
650             sliver_node_names = self.nodesByName(sliver_nodes)
651
652             available_nodes = [ node for node in all_nodes if node["component_name"] not in sliver_node_names ]
653
654             msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
655             networkItem = self.nodeView.appendRow(rootItem, network, membership=msg, kind="network")
656
657             already_in_nodes += sliver_node_names.keys()
658
659             # Add default slice tags
660             nodeItem = self.nodeView.appendRow(networkItem, "%s for %s" % (default_tags, network), kind="defaults")
661             attrs = rspec.version.get_default_sliver_attributes(network)
662             for attr in attrs:
663                     name = attr.get("name", None)
664                     value = attr.get("value", None)
665                     tagstring = QString("%s: %s" % (name, value))
666                     self.nodeView.appendRow(nodeItem, tagstring, membership=tag_status['in'], kind = "attribute")
667
668             for node in sliver_nodes:
669                 nodeType = None
670                 if ("hardware_types" in node):
671                     hardware_types = [x["name"] for x in node["hardware_types"]]
672                     nodeType = ",".join(hardware_types)
673                 nodeStatus = node.get("boot_state", "")
674                 if nodeStatus == None:
675                     nodeStatus=""
676                 nodeItem = self.nodeView.appendRow(networkItem,
677                                node["component_name"],
678                                nodeStatus=nodeStatus,
679                                nodeType=nodeType,
680                                membership=node_status['in'],
681                                kind="node")
682
683                 attrs = rspec.version.get_sliver_attributes(node['component_id'], network)
684                 for attr in attrs:
685                     name = attr.get("name", None)
686                     value = attr.get("value", None)
687                     self.nodeView.appendRow(nodeItem,
688                                             "%s: %s" % (name, value),
689                                             membership=tag_status['in'],
690                                             kind="attribute")
691                 disk_images = node.get("disk_image", [])
692                 for disk_image in disk_images:
693                     name = disk_image.get("name", None)
694                     self.noveView.appendRow(nodeItem, name, 
695                                             membership=node_status['in'],
696                                             kind="attribute")                                                                   
697                 
698             for node in available_nodes:
699                 nodeType = None
700                 if ("hardware_types" in node):
701                     hardware_types = [x["name"] for x in node["hardware_types"]]
702                     nodeType = ",".join(hardware_types)
703                 nodeStatus = node.get("boot_state", "")
704                 if nodeStatus == None:
705                     nodeStatus=""
706                 self.nodeView.appendRow(networkItem,
707                                node["component_name"],
708                                nodeStatus=nodeStatus,
709                                nodeType=nodeType,
710                                membership=node_status['out'],
711                                kind="node")
712
713         self.filterModel.setSourceModel(self.nodeModel)
714         self.filterModel.setDynamicSortFilter(True)
715         self.filterModel.sort(NAME_COLUMN)
716
717         headers = QStringList() << "Hostname or Tag" << "Node Type" << "Node Status" << "Membership Status" << "Kind"
718         self.nodeModel.setHorizontalHeaderLabels(headers)
719
720         self.nodeView.setItemDelegateForColumn(NAME_COLUMN, self.nodeNameDelegate)
721         self.nodeView.setItemDelegateForColumn(NODE_STATUS_COLUMN, self.nodeStatusDelegate)
722         self.nodeView.setModel(self.filterModel)
723         self.nodeView.hideColumn(KIND_COLUMN)
724         self.nodeView.expandAll()
725         self.nodeView.resizeColumnToContents(0)
726         self.nodeView.collapseAll()
727
728     def updateSliceName(self):
729         self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
730
731     def nodeSelectionChanged(self, hostname):
732         self.parent().nodeSelectionChanged(hostname)
733
734 class MainScreen(SfaScreen):
735     def __init__(self, parent):
736         SfaScreen.__init__(self, parent)
737
738         self.sliceWidget = SliceWidget(self)
739         self.init(self.sliceWidget, "Nodes", "OneLab SFA crawler")
740
741     def rspecUpdated(self):
742         self.mainwin.rspecWindow.updateView()
743
744     def configurationChanged(self):
745         self.widget.updateSliceName()
746         self.widget.updateView()
747         self.mainwin.rspecWindow.updateView()
748
749     def nodeSelectionChanged(self, hostname):
750         self.mainwin.nodeSelectionChanged(hostname)
751
752     def remoteSliceChanged(self):
753         # we're being notified the slice was changed remotely. Download a new
754         # rspec.
755         QTimer.singleShot(2500, self.sliceWidget.refresh)
756