3 from PyQt4.QtCore import *
4 from PyQt4.QtGui import *
6 from sfa.util.rspecHelper import RSpec
7 from sface.sfahelper import *
8 from sface.config import config
9 from sface.sfiprocess import SfiProcess
10 from sface.screens.sfascreen import SfaScreen
14 node_status = { "in": "Already Selected",
15 "out": "Not Selected",
17 "remove": "To be Removed"}
19 class NodeView(QTreeView):
20 def __init__(self, parent):
21 QTreeView.__init__(self, parent)
23 self.setAnimated(True)
24 self.setItemsExpandable(True)
25 self.setRootIsDecorated(True)
26 self.setAlternatingRowColors(True)
27 # self.setSelectionMode(self.MultiSelection)
28 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
29 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
30 self.setToolTip("Double click on a row to change its status")
32 def mouseDoubleClickEvent(self, event):
33 index = self.currentIndex()
35 status_index = model.index(index.row(), 2, index.parent())
36 status_data = status_index.data().toString()
37 hostname_index = model.index(index.row(), 1, index.parent())
38 hostname_data = hostname_index.data().toString()
40 if status_data == node_status['in']:
41 model.setData(status_index, QString(node_status['remove']))
42 elif status_data == node_status['out']:
43 model.setData(status_index, QString(node_status['add']))
44 elif status_data in (node_status['add'], node_status['remove']):
45 if hostname_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
46 else: model.setData(status_index, QString(node_status['out']))
48 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), hostname_index, hostname_index)
50 def currentChanged(self, current, previous):
51 model = current.model()
52 hostname_index = model.index(current.row(), 1, current.parent())
53 hostname_data = hostname_index.data().toString()
54 self.emit(SIGNAL('hostnameClicked(QString)'), hostname_data)
58 class NodeNameDelegate(QStyledItemDelegate):
59 def __init__(self, parent):
60 QStyledItemDelegate.__init__(self, parent)
62 def paint(self, painter, option, index):
64 data = "%s" % index.data().toString()
65 status_index = model.index(index.row(), 2, index.parent())
66 status_data = status_index.data().toString()
68 if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
70 QStyledItemDelegate.paint(self, painter, option, index)
73 fm = QFontMetrics(option.font)
75 rect.setWidth(fm.width(QString(data)) + 8)
76 rect.setHeight(rect.height() - 2)
77 rect.setX(rect.x() + 4)
78 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
81 path.addRoundedRect(x, y, w, h, 4, 4)
84 painter.setRenderHint(QPainter.Antialiasing)
85 painter.drawRoundedRect(rect, 4, 4)
87 if status_data == node_status['in']: # already in the slice
88 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
89 painter.setPen(QColor.fromRgb(0, 0, 0))
90 painter.drawText(option.rect, 0, QString(data))
92 elif status_data == node_status['add']: # newly added to the slice
93 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
94 painter.setPen(QColor.fromRgb(0, 0, 0))
95 painter.drawText(option.rect, 0, QString(data))
96 painter.drawRect(x + w + 10, y + 3, 10, 10)
97 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(0, 250, 0))
99 elif status_data == node_status['remove']: # removed from the slice
100 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
101 painter.setPen(QColor.fromRgb(0, 0, 0))
102 painter.drawText(option.rect, 0, QString(data))
103 painter.drawRect(x + w + 10, y + 3, 10, 10)
104 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(250, 0, 0))
110 def __init__(self, data, parent=None):
111 self.parentItem = parent
116 for child in self.childItems:
122 def allChildItems(self):
124 for c in self.childItems:
127 for cc in c.childItems:
131 def appendChild(self, child):
132 self.childItems.append(child)
134 def child(self, row):
135 return self.childItems[row]
137 def childCount(self):
138 return len(self.childItems)
140 def childNumber(self):
142 return self.parentItem.childItems.index(self)
145 def columnCount(self):
146 return len(self.itemData)
148 def data(self, column):
149 return self.itemData[column]
151 def insertChildren(self, position, count, columns):
152 if position < 0 or position > len(self.childItems):
155 for row in range(count):
156 data = self.data(columns)
157 item = TreeItem(data, self)
158 self.childItems.insert(position, item)
162 def insertColumns(self, position, columns):
163 if position < 0 or position > len(self.itemData):
166 for column in range(columns):
167 self.itemData.insert(position, QVariant())
169 for child in self.childItems:
170 child.insertColumns(position, columns)
174 def setData(self, column, value):
175 if column < 0 or column >= len(self.itemData):
178 self.itemData[column] = value
182 return self.parentItem
185 class NodeModel(QAbstractItemModel):
186 def __init__(self, parent):
187 QAbstractItemModel.__init__(self, parent)
191 self.rootItem.clear()
194 def __initRoot(self):
195 self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status")])
197 def getItem(self, index):
199 item = index.internalPointer()
200 if isinstance(item, TreeItem):
204 def headerData(self, section, orientation, role):
205 if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
206 return self.rootItem.data(section)
209 def index(self, row, column, parent):
210 if not self.hasIndex(row, column, parent):
213 parentItem = self.getItem(parent)
214 childItem = parentItem.child(row)
216 return self.createIndex(row, column, childItem)
220 def insertColumns(self, position, columns, parent):
221 self.beginInsertColumns(parent, position, position + columns -1)
222 ret = self.rootItem.insertColumns(position, columns)
223 self.endInsertColumns()
226 def insertRows(self, position, rows, parent):
227 parentItem = self.getItem(parent)
228 self.beginInsertRows(parent, position, position + rows -1)
229 ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
233 def parent(self, index):
234 if not index.isValid():
237 childItem = self.getItem(index)
238 parentItem = childItem.parent()
239 if parentItem is self.rootItem:
242 return self.createIndex(parentItem.childNumber(), 0, parentItem)
244 def rowCount(self, parent=QModelIndex()):
245 parentItem = self.getItem(parent)
246 return parentItem.childCount()
248 def columnCount(self, parent=None):
249 return self.rootItem.columnCount()
251 def data(self, index, role):
252 if not index.isValid():
255 if role != Qt.DisplayRole and role != Qt.EditRole:
258 item = self.getItem(index)
259 return item.data(index.column())
261 def flags(self, index):
262 if not index.isValid():
264 return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
266 def setData(self, index, value, role):
267 if role != Qt.EditRole:
270 item = self.getItem(index)
271 ret = item.setData(index.column(), value)
273 self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
277 class SliceWidget(QWidget):
278 def __init__(self, parent):
279 QWidget.__init__(self, parent)
281 self.network_names = []
282 self.process = SfiProcess(self)
284 self.slicename = QLabel("", self)
285 self.updateSliceName()
286 self.slicename.setScaledContents(False)
287 searchlabel = QLabel ("Search: ", self)
288 searchlabel.setScaledContents(False)
289 searchbox = QLineEdit(self)
290 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
292 toplayout = QHBoxLayout()
293 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
294 toplayout.addStretch()
295 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
296 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
298 self.nodeView = NodeView(self)
299 self.nodeModel = NodeModel(self)
300 self.filterModel = QSortFilterProxyModel(self) # enable filtering
302 self.nodeNameDelegate = NodeNameDelegate(self)
304 refresh = QPushButton("Update Slice Data", self)
305 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
306 submit = QPushButton("Submit", self)
307 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
309 bottomlayout = QHBoxLayout()
310 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
311 bottomlayout.addStretch()
312 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
314 layout = QVBoxLayout()
315 layout.addLayout(toplayout)
316 layout.addWidget(self.nodeView)
317 layout.addLayout(bottomlayout)
318 self.setLayout(layout)
319 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
321 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
322 self.connect(submit, SIGNAL('clicked()'), self.submit)
323 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
324 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
325 self.nodeSelectionChanged)
329 def submitFinished(self):
330 self.setStatus("<font color='green'>Slice data submitted.</font>")
331 QTimer.singleShot(1000, self.refresh)
333 def refreshFinished(self):
334 self.setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
336 self.parent().signalAll("rspecUpdated")
338 def readSliceRSpec(self):
339 rspec_file = config.getSliceRSpecFile()
340 if os.path.exists(rspec_file):
341 xml = open(rspec_file).read()
345 def setStatus(self, msg, timeout=None):
346 self.parent().setStatus(msg, timeout)
348 def checkRunningProcess(self):
349 if self.process.isRunning():
350 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
354 def filter(self, filter_string):
355 # for hierarchical models QSortFilterProxyModel applies the
356 # sort recursively. if the parent doesn't match the criteria
357 # we won't be able to match the children. so we need to match
358 # parent (by matching the network_names)
359 networks = ["^%s$" % n for n in self.network_names]
360 filters = networks + [str(filter_string)]
361 self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
364 if self.checkRunningProcess():
367 rspec = RSpec(self.readSliceRSpec())
370 all_child = self.nodeModel.rootItem.allChildItems()
372 testbed, hostname, status = c.itemData
373 if isinstance(status, QVariant):
374 status = status.toString()
376 if status == node_status['add']:
377 rspec.add_sliver(hostname)
379 elif str(status) == node_status['remove']:
380 rspec.remove_sliver(hostname)
384 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
387 self.disconnect(self.process, SIGNAL('finished()'), self.refreshFinished)
388 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
390 self.process.applyRSpec(rspec)
391 self.setStatus("Sending slice data (RSpec). This will take some time...")
395 if not config.getSlice():
396 self.setStatus("<font color='red'>Slice not set yet!</font>")
399 if self.process.isRunning():
400 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
403 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
404 self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
406 self.process.getRSpecFromSM()
407 self.setStatus("Updating slice data. This will take some time...")
409 def updateView(self):
410 global already_in_nodes
411 already_in_nodes = []
412 self.network_names = []
413 self.nodeModel.clear()
415 rspec_string = self.readSliceRSpec()
419 networks = rspec_get_networks(rspec_string)
420 for network in networks:
421 self.network_names.append(network)
422 networkItem = TreeItem([QString(network), QString(""), QString("")], self.nodeModel.rootItem)
424 all_nodes = rspec_get_nodes_from_network(rspec_string, network)
425 sliver_nodes = rspec_get_sliver_nodes_from_network(rspec_string, network)
426 available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
428 already_in_nodes += sliver_nodes
430 for node in sliver_nodes:
431 nodeItem = TreeItem([QString(""), QString("%s" % node), QString(node_status['in'])], networkItem)
432 networkItem.appendChild(nodeItem)
434 for node in available_nodes:
435 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out'])], networkItem)
436 networkItem.appendChild(nodeItem)
438 self.nodeModel.rootItem.appendChild(networkItem)
440 self.filterModel.setSourceModel(self.nodeModel)
441 self.filterModel.setFilterKeyColumn(-1)
442 self.filterModel.setDynamicSortFilter(True)
444 self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
445 self.nodeView.setModel(self.filterModel)
446 self.nodeView.expandAll()
447 self.nodeView.resizeColumnToContents(1)
449 def updateSliceName(self):
450 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
452 def nodeSelectionChanged(self, hostname):
453 self.parent().nodeSelectionChanged(hostname)
455 class MainScreen(SfaScreen):
456 def __init__(self, parent):
457 SfaScreen.__init__(self, parent)
459 slice = SliceWidget(self)
460 self.init(slice, "Main Window", "OneLab Federation GUI")
462 def rspecUpdated(self):
463 self.mainwin.rspecWindow.updateView()
465 def configurationChanged(self):
466 self.widget.updateSliceName()
467 self.widget.updateView()
468 self.mainwin.rspecWindow.updateView()
470 def nodeSelectionChanged(self, hostname):
471 self.mainwin.nodeSelectionChanged(hostname)