3 from PyQt4.QtCore import *
4 from PyQt4.QtGui import *
6 from sfa.util.rspecHelper import RSpec
7 from sface.config import config
8 from sface.sfiprocess import SfiProcess
9 from sface.screens.sfascreen import SfaScreen
13 node_status = { "in": "Already Selected",
14 "out": "Not Selected",
16 "remove": "To be Removed"}
18 tag_status = { "in": "Already Set",
21 "remove": "To be Removed"}
23 default_tags = "Default tags"
24 settable_tags = ['delegations', 'initscript']
27 if index.parent().parent().isValid():
33 class NodeView(QTreeView):
34 def __init__(self, parent):
35 QTreeView.__init__(self, parent)
37 self.setAnimated(True)
38 self.setItemsExpandable(True)
39 self.setRootIsDecorated(True)
40 self.setAlternatingRowColors(True)
41 # self.setSelectionMode(self.MultiSelection)
42 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
43 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
44 self.setToolTip("Double click on a row to change its status")
46 def mouseDoubleClickEvent(self, event):
47 index = self.currentIndex()
49 status_index = model.index(index.row(), 2, index.parent())
50 status_data = status_index.data().toString()
51 node_index = model.index(index.row(), 1, index.parent())
52 node_data = node_index.data().toString()
54 if itemType(node_index) == "tag":
55 tagname, value = node_index.data().toStringList()
56 if tagname not in settable_tags:
58 QMessageBox.warning(self, "Not settable", "Insufficient permission to change '%s' tag" % tagname)
60 if status_data == tag_status['in']:
61 model.setData(status_index, QString(tag_status['remove']))
62 elif status_data == tag_status['add']:
63 model.setData(status_index, QString(tag_status['out']))
64 elif status_data == tag_status['remove']:
65 model.setData(status_index, QString(tag_status['in']))
66 else: model.setData(status_index, QString(node_status['out']))
69 if status_data == node_status['in']:
70 model.setData(status_index, QString(node_status['remove']))
71 elif status_data == node_status['out']:
72 model.setData(status_index, QString(node_status['add']))
73 elif status_data in (node_status['add'], node_status['remove']):
74 if node_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
75 else: model.setData(status_index, QString(node_status['out']))
77 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
79 def currentChanged(self, current, previous):
80 model = current.model()
81 node_index = model.index(current.row(), 1, current.parent())
82 node_data = node_index.data().toString()
83 self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
87 class NodeNameDelegate(QStyledItemDelegate):
88 def __init__(self, parent):
89 QStyledItemDelegate.__init__(self, parent)
91 def paint(self, painter, option, index):
93 status_index = model.index(index.row(), 2, index.parent())
94 status_data = status_index.data().toString()
96 fm = QFontMetrics(option.font)
99 if itemType(index) == "node":
100 data = "%s" % index.data().toString()
101 #if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
103 # QStyledItemDelegate.paint(self, painter, option, index)
106 rect.setHeight(rect.height() - 2)
107 rect.setWidth(fm.width(QString(data)) + 6)
108 rect.setX(rect.x() + 5)
109 rect.setY(rect.y() - 1)
110 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
112 path = QPainterPath()
113 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
116 painter.setRenderHint(QPainter.Antialiasing)
118 if status_data == node_status['in']: # already in the slice
119 painter.fillPath(path, QColor("cyan"))
120 painter.setPen(QColor.fromRgb(0, 0, 0))
121 painter.drawText(option.rect, 0, QString(data))
123 elif status_data == node_status['add']: # newly added to the slice
124 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
125 painter.setPen(QColor.fromRgb(0, 0, 0))
126 painter.drawText(option.rect, 0, QString(data))
128 elif status_data == node_status['remove']: # removed from the slice
129 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
130 painter.setPen(QColor.fromRgb(0, 0, 0))
131 painter.drawText(option.rect, 0, QString(data))
134 painter.setPen(QColor.fromRgb(0, 0, 0))
135 painter.drawText(option.rect, 0, QString(data))
139 tag = index.data().toStringList()
140 data = "%s: %s" % (tag[0], tag[1])
141 rect.setHeight(rect.height() - 2)
142 rect.setWidth(fm.width(QString(data)) + 6 + indent)
143 rect.setX(rect.x() + 4 + indent)
144 rect.setY(rect.y() - 1)
146 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
148 path = QPainterPath()
149 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
152 painter.setRenderHint(QPainter.Antialiasing)
154 if status_data == tag_status['in']: # already in the slice
155 painter.fillPath(path, QColor("cyan"))
156 painter.setPen(QColor.fromRgb(0, 0, 0))
157 painter.drawText(option.rect, 0, QString(data))
159 elif status_data == tag_status['add']: # newly added to the slice
160 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
161 painter.setPen(QColor.fromRgb(0, 0, 0))
162 painter.drawText(option.rect, 0, QString(data))
164 elif status_data == tag_status['remove']: # removed from the slice
165 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
166 painter.setPen(QColor.fromRgb(0, 0, 0))
167 painter.drawText(option.rect, 0, QString(data))
170 painter.setPen(QColor.fromRgb(0, 0, 0))
171 painter.drawText(option.rect, 0, QString(data))
177 def __init__(self, data, parent=None):
178 self.parentItem = parent
183 for child in self.childItems:
189 def allChildItems(self):
191 for c in self.childItems:
194 all.extend(c.allChildItems())
197 def appendChild(self, child):
198 self.childItems.append(child)
200 def child(self, row):
201 return self.childItems[row]
203 def childCount(self):
204 return len(self.childItems)
206 def childNumber(self):
208 return self.parentItem.childItems.index(self)
211 def columnCount(self):
212 return len(self.itemData)
214 def data(self, column):
215 return self.itemData[column]
217 def insertChildren(self, position, count, columns):
218 if position < 0 or position > len(self.childItems):
221 for row in range(count):
222 data = self.data(columns)
223 item = TreeItem(data, self)
224 self.childItems.insert(position, item)
228 def insertColumns(self, position, columns):
229 if position < 0 or position > len(self.itemData):
232 for column in range(columns):
233 self.itemData.insert(position, QVariant())
235 for child in self.childItems:
236 child.insertColumns(position, columns)
240 def setData(self, column, value):
241 if column < 0 or column >= len(self.itemData):
244 self.itemData[column] = value
248 return self.parentItem
251 class NodeModel(QAbstractItemModel):
252 def __init__(self, parent):
253 QAbstractItemModel.__init__(self, parent)
257 self.rootItem.clear()
260 def __initRoot(self):
261 self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status")])
263 def getItem(self, index):
265 item = index.internalPointer()
266 if isinstance(item, TreeItem):
270 def headerData(self, section, orientation, role):
271 if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
272 return self.rootItem.data(section)
275 def index(self, row, column, parent):
276 if not self.hasIndex(row, column, parent):
279 parentItem = self.getItem(parent)
280 childItem = parentItem.child(row)
282 return self.createIndex(row, column, childItem)
286 def insertColumns(self, position, columns, parent):
287 self.beginInsertColumns(parent, position, position + columns -1)
288 ret = self.rootItem.insertColumns(position, columns)
289 self.endInsertColumns()
292 def insertRows(self, position, rows, parent):
293 parentItem = self.getItem(parent)
294 self.beginInsertRows(parent, position, position + rows -1)
295 ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
299 def parent(self, index):
300 if not index.isValid():
303 childItem = self.getItem(index)
304 parentItem = childItem.parent()
305 if parentItem is self.rootItem:
308 return self.createIndex(parentItem.childNumber(), 0, parentItem)
310 def rowCount(self, parent=QModelIndex()):
311 parentItem = self.getItem(parent)
312 return parentItem.childCount()
314 def columnCount(self, parent=None):
315 return self.rootItem.columnCount()
317 def data(self, index, role):
318 if not index.isValid():
321 if role != Qt.DisplayRole and role != Qt.EditRole:
324 item = self.getItem(index)
325 return item.data(index.column())
327 def nodestatus(self, index):
328 if not index.isValid():
331 item = self.getItem(index)
332 return item.nodestatus(index.column())
334 def flags(self, index):
335 if not index.isValid():
337 return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
339 def setData(self, index, value, role):
340 if role != Qt.EditRole:
343 item = self.getItem(index)
344 ret = item.setData(index.column(), value)
346 self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
350 class SliceWidget(QWidget):
351 def __init__(self, parent):
352 QWidget.__init__(self, parent)
354 self.network_names = []
355 self.process = SfiProcess(self)
357 self.slicename = QLabel("", self)
358 self.updateSliceName()
359 self.slicename.setScaledContents(False)
360 searchlabel = QLabel ("Search: ", self)
361 searchlabel.setScaledContents(False)
362 searchbox = QLineEdit(self)
363 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
365 toplayout = QHBoxLayout()
366 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
367 toplayout.addStretch()
368 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
369 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
371 self.nodeView = NodeView(self)
372 self.nodeModel = NodeModel(self)
373 self.filterModel = QSortFilterProxyModel(self) # enable filtering
375 self.nodeNameDelegate = NodeNameDelegate(self)
377 refresh = QPushButton("Update Slice Data", self)
378 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
379 submit = QPushButton("Submit", self)
380 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
382 bottomlayout = QHBoxLayout()
383 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
384 bottomlayout.addStretch()
385 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
387 layout = QVBoxLayout()
388 layout.addLayout(toplayout)
389 layout.addWidget(self.nodeView)
390 layout.addLayout(bottomlayout)
391 self.setLayout(layout)
392 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
394 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
395 self.connect(submit, SIGNAL('clicked()'), self.submit)
396 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
397 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
398 self.nodeSelectionChanged)
402 def submitFinished(self):
403 self.setStatus("<font color='green'>Slice data submitted.</font>")
404 QTimer.singleShot(1000, self.refresh)
406 def refreshFinished(self):
407 self.setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
409 self.parent().signalAll("rspecUpdated")
411 def readSliceRSpec(self):
412 rspec_file = config.getSliceRSpecFile()
413 if os.path.exists(rspec_file):
414 xml = open(rspec_file).read()
418 def setStatus(self, msg, timeout=None):
419 self.parent().setStatus(msg, timeout)
421 def checkRunningProcess(self):
422 if self.process.isRunning():
423 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
427 def filter(self, filter_string):
428 # for hierarchical models QSortFilterProxyModel applies the
429 # sort recursively. if the parent doesn't match the criteria
430 # we won't be able to match the children. so we need to match
431 # parent (by matching the network_names)
432 networks = ["^%s$" % n for n in self.network_names]
433 filters = networks + [str(filter_string)]
434 self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
436 def levels_down(self, item):
437 if item == self.nodeModel.rootItem:
440 return self.levels_down(item.parent()) + 1
443 if self.checkRunningProcess():
446 rspec = self.readSliceRSpec()
449 all_child = self.nodeModel.rootItem.allChildItems()
451 testbed, name, status = c.itemData
453 if isinstance(status, QVariant):
454 status = status.toString()
456 treelevel = self.levels_down(c)
459 testbed = c.parent().itemData[0]
460 if status == node_status['add']:
461 #print "Add hostname: %s" % name
462 rspec.add_sliver(name, testbed)
464 elif status == node_status['remove']:
465 #print "Remove hostname: %s" % name
466 rspec.remove_sliver(name, testbed)
471 tag = "%s" % tag # Prevent weird error from lxml
472 node = c.parent().itemData[1]
473 testbed = c.parent().parent().itemData[0]
474 if status == tag_status['add']:
475 #print "Add tag: %s/%s " % (tag, value)
476 if node.startswith(default_tags):
477 rspec.add_default_sliver.attribute(tag, value, testbed)
479 rspec.add_sliver_attribute(tag, value, node, testbed)
481 elif status == node_status['remove']:
482 #print "Remove tag: %s/%s" % (tag, value)
483 if node.startsWith(default_tags):
484 rspec.remove_default_sliver_attribute(tag, value, testbed)
486 rspec.remove_sliver_attribute(node, tag, value, testbed)
490 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
493 self.disconnect(self.process, SIGNAL('finished()'), self.refreshFinished)
494 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
496 self.process.applyRSpec(rspec)
497 self.setStatus("Sending slice data (RSpec). This will take some time...")
501 if not config.getSlice():
502 self.setStatus("<font color='red'>Slice not set yet!</font>")
505 if self.process.isRunning():
506 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
509 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
510 self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
512 self.process.getRSpecFromSM()
513 self.setStatus("Updating slice data. This will take some time...")
515 def updateView(self):
516 global already_in_nodes
517 already_in_nodes = []
518 self.network_names = []
519 self.nodeModel.clear()
521 rspec = self.readSliceRSpec()
525 networks = rspec.get_network_list()
526 for network in networks:
527 self.network_names.append(network)
528 data = [QString(network), QString(""), QString("")]
529 networkItem = TreeItem(data, self.nodeModel.rootItem)
530 all_nodes = rspec.get_node_list(network)
531 sliver_nodes = rspec.get_sliver_list(network)
532 available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
534 already_in_nodes += sliver_nodes
536 # Add default slice tags
537 data = [QString(""), QString("%s for %s" % (default_tags, network)),
539 nodeItem = TreeItem(data, networkItem)
540 networkItem.appendChild(nodeItem)
541 attrs = rspec.get_default_sliver_attributes(network)
542 for (name, value) in attrs:
543 tagstring = QStringList([name, value])
544 data = [QString(""), tagstring, QString(tag_status['in'])]
545 tagItem = TreeItem(data, nodeItem)
546 nodeItem.appendChild(tagItem)
548 for node in sliver_nodes:
549 data = [QString(""), QString(node), QString(node_status['in'])]
550 nodeItem = TreeItem(data, networkItem)
551 networkItem.appendChild(nodeItem)
553 attrs = rspec.get_sliver_attributes(node, network)
554 for (name, value) in attrs:
555 tagstring = QStringList([name, value])
556 data = [QString(""), tagstring, QString(tag_status['in'])]
557 tagItem = TreeItem(data, nodeItem)
558 nodeItem.appendChild(tagItem)
560 for node in available_nodes:
561 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out'])], networkItem)
562 networkItem.appendChild(nodeItem)
564 self.nodeModel.rootItem.appendChild(networkItem)
566 self.filterModel.setSourceModel(self.nodeModel)
567 self.filterModel.setFilterKeyColumn(-1)
568 self.filterModel.setDynamicSortFilter(True)
570 self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
571 self.nodeView.setModel(self.filterModel)
572 self.nodeView.expandAll()
573 self.nodeView.resizeColumnToContents(1)
575 def updateSliceName(self):
576 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
578 def nodeSelectionChanged(self, hostname):
579 self.parent().nodeSelectionChanged(hostname)
581 class MainScreen(SfaScreen):
582 def __init__(self, parent):
583 SfaScreen.__init__(self, parent)
585 slice = SliceWidget(self)
586 self.init(slice, "Main Window", "OneLab Federation GUI")
588 def rspecUpdated(self):
589 self.mainwin.rspecWindow.updateView()
591 def configurationChanged(self):
592 self.widget.updateSliceName()
593 self.widget.updateView()
594 self.mainwin.rspecWindow.updateView()
596 def nodeSelectionChanged(self, hostname):
597 self.mainwin.nodeSelectionChanged(hostname)