import repository from arizona
[raven.git] / apps / gacksui / gacksui.py
1 import os
2 import platform
3 import sys
4 import time
5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
7
8 from ravenlib.ravenconfigparser import RavenConfigParser
9 from gackshandle import *
10 from gacksjob import *
11 import gackshelper
12
13 RESOURCES = ["cpu", "cores", "network", "disk"]
14
15 class QButtonLineEdit(QLineEdit):
16     def __init__(self, parent=None):
17         QLineEdit.__init__(self, parent)
18
19         style = qApp.style()
20
21         icon = style.standardIcon(style.SP_ComputerIcon)
22
23         b = QToolButton(parent=self)
24         b.setIcon(icon)
25         b.setMaximumWidth(16)
26         b.setMaximumHeight(16)
27         b.setCursor(Qt.ArrowCursor)
28         b.setStyleSheet("QToolButton { border: none; padding: 0px; }")
29         frameWidth = style.pixelMetric(QStyle.PM_DefaultFrameWidth)
30         self.setStyleSheet(QString("QLineEdit { padding-right: %1px; } ").arg(b.sizeHint().width() + frameWidth + 1))
31
32         self.button = b
33
34         self.connect(self.button, SIGNAL("clicked()"), self.buttonClicked)
35
36     def resizeEvent(self, re):
37         style = qApp.style()
38         sz = self.button.sizeHint()
39         frameWidth = style.pixelMetric(QStyle.PM_DefaultFrameWidth)
40         self.button.move(self.rect().right() - frameWidth - 16 -4,
41                          (self.rect().bottom() + 1 - 16)/2)
42
43     def buttonClicked(self):
44         self.emit(SIGNAL("buttonClicked()"))
45
46 def int_default(x, def_val=0):
47     try:
48         x = int(x)
49     except ValueError:
50         x = def_val;
51     return x
52
53 def qty_to_rspec(qty, id, timeStart, timeStop, lastAllocator):
54     # possible rspec formats
55     #   x,y,..,z  - an aggregate rspec
56     #   n         - a simple quantity
57     #   s/n       - a quantity of n units in the region (s, INFINITY)
58     #   s-e/n     - a quantity of n units in the region (s, e)
59     #   s-e       - everything in the region (s, e)
60
61     rspecs = []
62     majorParts = qty.split(",")
63     for majorPart in majorParts:
64         majorPart = majorPart.strip()
65         if not majorPart:
66             continue
67
68         rspec = {'id': id, 'timeStart': timeStart, 'timeStop': timeStop}
69
70         if (not "/" in majorPart) and (not "-" in majorPart):
71             # the easy case, it's just a number
72             rspec['unitQuantity'] = majorPart
73         else:
74             if "/" in majorPart:
75                 # the part after the "/" is the quantity. Split it off, then
76                 # proceed as normal.
77                 parts = majorPart.split("/",1)
78                 majorPart = parts[0]
79                 rspec["unitQuantity"] = parts[1]
80
81             if "-" in majorPart:
82                 parts = majorPart.split("-",1)
83                 rspec['unitStart'] = parts[0]
84                 rspec['unitStop'] = parts[1]
85             else:
86                 rspec['unitStart'] = majorPart
87
88             # if we're using quantiy, then we need to know which
89             # allocated is the one that holds 'free space' for us to
90             # allocate from.
91             if 'unitQuantity' in rspec:
92                 rspec['isLastAllocator'] = lastAllocator
93
94         rspecs.append(rspec)
95
96     # if we have only one rspec, then use it. otherwise, we have an aggregate
97     # rspec.
98     if (len(rspecs) == 1):
99         rspec = rspecs[0]
100     else:
101         rspec = {"aggregate": rspecs}
102
103     return rspec
104
105 class GacksRefreshThread(QThread):
106     def __init__(self, gacks, id, allocator, parent):
107         super(GacksRefreshThread, self).__init__(parent)
108
109         self.gacks = gacks
110         self.id = id
111         self.allocator = allocator
112
113         self.asapRecList = []
114         self.calRecList = []
115
116     def run(self):
117         self.calRecList = self.gacks.client().query_exact(id=self.id, hasAllocator = self.allocator)
118         self.emit(SIGNAL("receivedCal(PyQt_PyObject)"), self.calRecList)
119
120         self.asapRecList = self.gacks.client().query_asap(id=self.id, allocatorHRN=self.allocator)
121         self.emit(SIGNAL("receivedAsap(PyQt_PyObject)"), self.asapRecList)
122
123         self.account = self.gacks.client().get_account(self.gacks.get_authtoken(), self.id, "user")
124         self.emit(SIGNAL("receivedAccount(PyQt_PyObject)"), self.account)
125
126         # this doesn't change, so we only have to do it once
127         if not self.gacks.resources_checked:
128             self.gacks.resources_checked = True
129             (is_current, hash, resources_str) = self.gacks.client().get_resources(self.gacks.resources_hash)
130             if not is_current:
131                 print "Gacks: Resource directory was replaced"
132                 self.emit(SIGNAL("receivedResources(PyQt_PyObject)"), resources_str)
133             else:
134                 print "Gacks: Resource directory is current"
135
136 class GacksPickThread(QThread):
137     def __init__(self, gacks, authToken, resourceName, resourceGroup, amount, expand, parent):
138         super(GacksPickThread, self).__init__(parent)
139
140         self.gacks = gacks
141         self.authToken = authToken
142         self.resourceName = resourceName
143         self.resourceGroup = resourceGroup
144         self.amount = amount
145         self.expand = expand
146
147     def run(self):
148         try:
149             self.result = self.gacks.client().pick(self.authToken, self.resourceName, self.resourceGroup, self.amount, self.expand)
150             self.emit(SIGNAL("picked(PyQt_PyObject)"), self.result)
151         except:
152             self.emit(SIGNAL("picked(PyQt_PyObject)"), (False,[],[])) # TODO: Better error reporting
153             raise
154
155 class OverlayWidget(QWidget):
156     def __init__(self, parent):
157         super(OverlayWidget, self).__init__(parent)
158         self.font = QFont()
159         self.font.setBold(True)
160         self.font.setPixelSize(48)
161         self.text=""
162
163     def paintEvent(self, event):
164         if self.text:
165             painter = QPainter(self)
166             painter.setFont(self.font)
167             painter.setOpacity(0.2)
168             painter.drawText(self.rect(), Qt.AlignCenter, self.text)
169
170     def setText(self, text):
171         self.text = text
172         # even if we don't paint, if the widget isn't hidden, then it'll
173         # intercept the mouse.
174         if text:
175             self.show()
176         else:
177             self.hide()
178         self.update()
179
180 class TableWithOverlay(QTableWidget):
181     def __init__(self):
182         super(TableWithOverlay, self).__init__()
183         self.overlay = OverlayWidget(self)
184
185     def resizeEvent(self, event):
186         self.overlay.resize(event.size())
187
188     def setOverlayText(self, text):
189         self.overlay.setText(text)
190
191 class GacksUIConfig:
192     def __init__(self):
193         self.identity = ""
194         self.dir = os.path.expanduser("~/.sfi")
195         self.configFileName = os.path.join(self.dir, "gacksui_config")
196
197         if not os.path.exists(self.dir):
198             os.makedirs(self.dir)
199
200         self.load()
201
202     def save(self):
203         parser = RavenConfigParser()
204         parser.add_section("lastsession")
205         parser.set("lastsession", "identity", self.identity)
206         parser.write(open(self.configFileName, "w"))
207
208     def load(self):
209         if os.path.exists(self.configFileName):
210             parser = RavenConfigParser()
211             parser.Read(self.configFileName, True)
212             self.identity = parser.GetOpt("lastsession", "identity", "")
213
214 class GacksWidget(QWidget):
215     def __init__(self, gacks=None, parent=None):
216         super(GacksWidget, self).__init__(parent)
217         self.userHrn = None
218         self.keyFile = None
219         self.lastList = None
220         self.gacks = gacks
221         self.config = GacksUIConfig()
222
223         self.setup()
224
225         self.externalConfig()
226
227         if self.config.identity:
228             self.editIdentity.setText(self.config.identity)
229
230         self.update()
231
232     def setup(self):
233         style = qApp.style()
234
235         self.tableCalendar = TableWithOverlay() # QTableWidget()
236         self.tableCalendar.setSelectionBehavior(QAbstractItemView.SelectRows)
237         self.tableCalendar.setSelectionMode(QAbstractItemView.SingleSelection)
238
239         labelAsap = QLabel("ASAP queue jobs:")
240         self.tableAsap = TableWithOverlay() # QTableWidget()
241         self.tableAsap.setSelectionBehavior(QAbstractItemView.SelectRows)
242         self.tableAsap.setSelectionMode(QAbstractItemView.SingleSelection)
243
244         # identity bar
245         labelIdentity = QLabel("Identity:")
246         self.editIdentity = QButtonLineEdit()
247         self.editIdentity.setContentsMargins(0,0,0,0)
248         self.labelService = QLabel("Service:")
249         layoutIdentityBar = QHBoxLayout()
250         layoutIdentityBar.addWidget(labelIdentity)
251         layoutIdentityBar.addWidget(self.editIdentity)
252         layoutIdentityBar.addWidget(self.labelService)
253         layoutIdentityBar.addStretch()
254
255         # filter bar
256         labelCalendar = QLabel("Calendar scheduled jobs:")
257         labelAlloc = QLabel("Allocator:")
258         self.comboBoxAlloc = QComboBox()
259         self.comboBoxAlloc.addItems(["all", "grm", "user"])
260         self.comboBoxAlloc.setCurrentIndex(2)
261         labelAlloc.setBuddy(self.comboBoxAlloc)
262         labelKind = QLabel("Kind:")
263         self.comboBoxKind = QComboBox()
264         self.comboBoxKind.addItems(["all"] + RESOURCES)
265         self.comboBoxKind.setCurrentIndex(0)
266         labelKind.setBuddy(self.comboBoxKind)
267         buttonRefresh = QToolButton() #("Refresh")
268         buttonRefresh.setText("refresh")
269         buttonRefresh.setIcon(style.standardIcon(style.SP_BrowserReload))
270         buttonRefresh.setFixedHeight(26)
271         layoutFilterBar = QHBoxLayout()
272         layoutFilterBar.addWidget(labelCalendar)
273         layoutFilterBar.addStretch()
274         layoutFilterBar.addWidget(labelAlloc)
275         layoutFilterBar.addWidget(self.comboBoxAlloc)
276         layoutFilterBar.addWidget(labelKind)
277         layoutFilterBar.addWidget(self.comboBoxKind)
278         layoutFilterBar.addWidget(buttonRefresh)
279
280         # button bar
281         calButton = QPushButton("Add-&Cal")
282         asapButton = QPushButton("Add-&Asap")
283         groupButton = QPushButton("Add-&Group")
284         buttonDelete = QPushButton("&Delete")
285         buttonEdit = QPushButton("&Edit")
286         buttonLayout = QHBoxLayout()
287         buttonLayout.addWidget(calButton)
288         buttonLayout.addWidget(asapButton)
289         buttonLayout.addWidget(groupButton)
290         buttonLayout.addWidget(buttonDelete)
291         buttonLayout.addWidget(buttonEdit)
292         buttonLayout.addStretch()
293
294         # main layout
295         layout = QVBoxLayout()
296         layout.addLayout(layoutIdentityBar)
297         layout.addLayout(layoutFilterBar)
298         layout.addWidget(self.tableCalendar)
299         layout.addWidget(labelAsap)
300         layout.addWidget(self.tableAsap)
301         layout.addLayout(buttonLayout)
302
303         self.setLayout(layout)
304
305         self.connect(self.comboBoxAlloc, SIGNAL("currentIndexChanged (int)"), self.update)
306         self.connect(self.comboBoxKind, SIGNAL("currentIndexChanged (int)"), self.update)
307
308         self.connect(self.tableCalendar, SIGNAL("itemSelectionChanged ()"), self.onCalendarSelect)
309         self.connect(self.tableAsap, SIGNAL("itemSelectionChanged ()"), self.onAsapSelect)
310
311         self.connect(calButton, SIGNAL("clicked()"), self.onCalButton)
312         self.connect(asapButton, SIGNAL("clicked()"), self.onAsapButton)
313         self.connect(groupButton, SIGNAL("clicked()"), self.onGroupButton)
314         self.connect(buttonRefresh, SIGNAL("clicked()"), self.update)
315         self.connect(buttonDelete, SIGNAL("clicked()"), self.onDeleteButton)
316         self.connect(buttonEdit, SIGNAL("clicked()"), self.onEditButton)
317
318         self.connect(self.editIdentity, SIGNAL("buttonClicked()"), self.onPickButton)
319         self.connect(self.editIdentity, SIGNAL("textChanged (const QString&)"), self.onEditIdentityChanged)
320
321     def externalConfig(self):
322         # override me to load sface configuration params
323         pass
324
325     def getSubscribedNodes(self):
326         return []
327
328     def onPickButton(self):
329         pass
330
331     def onEditIdentityChanged(self, changestr):
332         self.config.identity = str(self.editIdentity.text())
333         self.config.save()
334         self.gacks.set_identity(self.keyFile, self.userHrn, self.config.identity)
335         self.update()
336
337     def onCalButton(self):
338         dlg = DialogAddCalendar(parent=self, gacks=self.gacks, subscribedNodes=self.getSubscribedNodes())
339         dlg.exec_()
340
341         self.update()
342
343     def onAsapButton(self):
344         dlg = DialogAddAsap(parent=self, gacks=self.gacks)
345         dlg.exec_()
346
347         self.update()
348
349     def onGroupButton(self):
350         dlg = DialogAddGroup(parent=self, gacks=self.gacks)
351         if (dlg.exec_() == 1):
352             self.parent().signalAll("remoteSliceChanged")
353
354     def onDeleteButton(self):
355         if (self.lastList==None) or (len(self.lastList.selectedItems()) <= 0):
356             QMessageBox.warning(self, "Warning", "Select something in the list before pressing delete.")
357             return
358
359         if (self.lastList==self.tableCalendar):
360             self.onDeleteCalendar()
361         else:
362             self.onDeleteAsap()
363
364     def onDeleteCalendar(self):
365         data = str(self.lastList.selectedItems()[0].data(Qt.UserRole).toString())
366         rec = GacksRecord(dict = eval(data))
367
368         box = QMessageBox(self)
369         box.setText("Confirm Deletion ?")
370         box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
371         ret = box.exec_()
372         if (ret != QMessageBox.Yes):
373             return
374
375         recList = self.gacks.get_matching_reclist(rec)
376
377         handles = records_to_handles(recList)
378
379         self.gacks.client().grm_delete(self.gacks.get_authtoken(), handles)
380
381         self.update()
382
383     def onDeleteAsap(self):
384         data = str(self.lastList.selectedItems()[0].data(Qt.UserRole).toString())
385         job = AsapJob(dict = eval(data))
386
387         box = QMessageBox(self)
388         box.setText("Confirm Deletion ?")
389         box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
390         ret = box.exec_()
391         if (ret != QMessageBox.Yes):
392             return
393
394         self.gacks.client().delete_asap(self.gacks.get_authtoken(), job.get_jobid(), job.get_allocator(), job.get_consumer())
395
396         self.update()
397
398     def onEditButton(self):
399         if (self.lastList==None) or (len(self.lastList.selectedItems()) <= 0):
400             QMessageBox.warning(self, "Warning", "Select something in the list before pressing edit.")
401             return
402
403         data = str(self.lastList.selectedItems()[0].data(Qt.UserRole).toString())
404         rec = GacksRecord(dict = eval(data))
405
406         dlg = DialogEditCalendar(parent=self, gacks=self.gacks, rec=rec)
407         dlg.exec_()
408
409         self.update()
410
411
412     def onCalendarSelect(self):
413         if (len(self.tableCalendar.selectedItems()) <= 0):
414             #print "oncal-nosel", len(self.tableCalendar.selectedItems())
415             return
416         #print "oncal", len(self.tableCalendar.selectedItems())
417
418         self.lastList = self.tableCalendar
419
420         # hide the asap selection
421         self.tableAsap.clearSelection() # setCurrentCell(-1,-1)
422
423     def onAsapSelect(self):
424         if (len(self.tableAsap.selectedItems()) <= 0):
425             #print "onasap-nosel", len(self.tableAsap.selectedItems())
426             return
427         #print "onasap", len(self.tableAsap.selectedItems())
428
429         # hide the calendar selection
430         self.tableCalendar.clearSelection() # setCurrentCell(-1,-1)
431
432         self.lastList = self.tableAsap
433
434     def updateAsapTable(self, id=None, allocator=None):
435         recList = self.gacks.client().query_asap(id=id, allocatorHRN=allocator)
436         self.onUpdateAsapTable(recList)
437
438     def onUpdateAsapTable(self, recList):
439         self.tableAsap.setOverlayText("")
440
441         self.tableAsap.clear()
442         self.tableAsap.setColumnCount(5)
443         self.tableAsap.setHorizontalHeaderLabels(["id", "allocator", "consumer", "duration", "resources"])
444
445         self.tableAsap.setRowCount(len(recList))
446
447         row = 0
448         for rec in recList:
449             handle = str(rec.as_dict())
450             self.addTableItem(self.tableAsap, row, 0, rec.get_jobid(), handle)
451             self.addTableItem(self.tableAsap, row, 1, rec.get_allocator(), handle)
452             self.addTableItem(self.tableAsap, row, 2, rec.get_consumer(), handle)
453             self.addTableItem(self.tableAsap, row, 3, rec.get_duration(), handle)
454             self.addTableItem(self.tableAsap, row, 4, rec.get_resources_string(), handle)
455
456             row = row + 1
457
458         self.tableCalendar.resizeColumnsToContents()
459
460     def addTableItem(self, table, row, col, val, data=None, readonly=True):
461         item = QTableWidgetItem(str(val))
462         if readonly:
463             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
464         if data:
465             item.setData(Qt.UserRole, QVariant(data))
466         table.setItem(row, col, item)
467
468     def updateCalendarTable(self, id=None, allocator=None):
469         recList = self.gacks.client().query_exact(id=id, hasAllocator = allocator)
470         self.onUpdateCalendarTable(recList)
471
472     def onUpdateCalendarTable(self, recList):
473         self.tableCalendar.setOverlayText("")
474
475         self.tableCalendar.clear()
476         self.tableCalendar.setColumnCount(6)
477         self.tableCalendar.setHorizontalHeaderLabels(["id", "start", "stop", "lastAllocator", "consumer", "qty"])
478
479         aggList = aggregate_records(recList)
480         keyList = aggList.keys()
481
482         self.tableCalendar.setRowCount(len(keyList))
483
484         row = 0
485         for key in keyList:
486             rec = aggList[key][0]
487             qty = count_records(aggList[key])
488             #rh = count_resource_hours(aggList[key])
489             #if (rh==INFINITY):
490             #rh="Infinity";
491
492             handle = str(rec.as_dict())
493
494             self.addTableItem(self.tableCalendar, row, 0, rec.id, handle)
495             self.addTableItem(self.tableCalendar, row, 1, self.gacks.time_to_str(rec.timeStart), handle)
496             self.addTableItem(self.tableCalendar, row, 2, self.gacks.time_to_str(rec.timeStop), handle)
497             self.addTableItem(self.tableCalendar, row, 3, rec.allocatorHRNs[-1], handle)
498             self.addTableItem(self.tableCalendar, row, 4, rec.consumerHRN, handle)
499             self.addTableItem(self.tableCalendar, row, 5, qty, handle)
500
501             row = row + 1
502
503         self.tableCalendar.resizeColumnsToContents()
504
505     def onUpdateAccount(self, account):
506         if account==None:
507             self.labelService.setText("Service: None")
508         else:
509             self.labelService.setText("Service: " + account.level)
510
511     #def onUpdateAsapBackground(self, t):
512     #    self.onUpdateAsap(t.asapRecList)
513
514     #def onUpdateCalBackground(self, t):
515     #    self.onUpdateCalendar(t.calRecList)
516
517     def onReceivedResources(self, resources_str):
518         self.gacks.set_resources(resources_str)
519
520     def update(self):
521         if not self.gacks.identity_okay():
522             self.tableCalendar.setOverlayText("Select identity...")
523             self.tableAsap.setOverlayText("Select identity...")
524             return
525
526         self.tableCalendar.setOverlayText("Refreshing...")
527         self.tableAsap.setOverlayText("Refreshing...")
528         filter_allocator = str(self.comboBoxAlloc.currentText())
529         if (filter_allocator=="user"):
530             allocator = self.gacks.get_hrn()
531         elif (filter_allocator=="grm"):
532             allocator = self.gacks.get_grm_hrn()
533         else:
534             allocator = None
535
536         filter_id = str(self.comboBoxKind.currentText())
537         if (filter_id == "all"):
538             filter_id = None
539
540         t = GacksRefreshThread(self.gacks, filter_id, allocator, self)
541         self.connect(t, SIGNAL("receivedAsap(PyQt_PyObject)"), self.onUpdateAsapTable)
542         self.connect(t, SIGNAL("receivedCal(PyQt_PyObject)"), self.onUpdateCalendarTable)
543         self.connect(t, SIGNAL("receivedAccount(PyQt_PyObject)"), self.onUpdateAccount)
544         self.connect(t, SIGNAL("receivedResources(PyQt_PyObject)"), self.onReceivedResources)
545         t.start()
546
547         #self.updateCalendarTable(filter_id, allocator)
548         #self.updateAsapTable(filter_id, allocator)
549
550 #dkl added for global access to rspec, without using global variables.
551 class GlobalRSpec():
552     pass
553
554 class DialogAddCalendar(QDialog):
555     def __init__(self, parent=None, gacks=None, subscribedNodes=[]):
556         super(DialogAddCalendar, self).__init__(parent)
557         self.gacks = gacks
558         self.subscribedNodes = subscribedNodes
559         self.setup()
560         self.onProviderChanged()
561         self.update()
562
563     def setup(self):
564         labelProvider = QLabel("Provider:")
565         self.comboBoxProvider = QComboBox()
566         self.comboBoxProvider.addItems(self.gacks.resources.get_providers())
567         self.comboBoxProvider.setCurrentIndex(0)
568         labelProvider.setBuddy(self.comboBoxProvider)
569
570         labelKind = QLabel("Kind:")
571         self.comboBoxKind = QComboBox()
572         labelKind.setBuddy(self.comboBoxKind)
573
574         labelDate = QLabel("Start Date:")
575         self.dateStart = QDateEdit()
576         now = QDateTime.currentDateTime()
577         now = QDate(now.date().year(), now.date().month(), now.date().day())
578         self.dateStart.setDate(now)
579         labelDate.setBuddy(self.dateStart)
580
581         labelTime = QLabel("Start Time:")
582
583         self.timeSlotRadio = []
584         self.timeAvailLabel = []
585         for i in range(0, 24):
586             self.timeSlotRadio.append( QRadioButton(str(i) + ":00:00") )
587             self.timeAvailLabel.append( QLabel("40") )
588
589         layoutTime = QGridLayout()
590         for i in range(0, 24):
591             col = i /8
592             row = i % 8
593             layoutTime.addWidget(self.timeSlotRadio[i], row, col*3)
594             layoutTime.addWidget(self.timeAvailLabel[i], row, col*3+1)
595
596         layoutTime.addWidget(QLabel("    "), 0, 2)
597         layoutTime.addWidget(QLabel("    "), 0, 5)
598
599         labelDuration = QLabel("Duration (hours):")
600         self.spinBoxDuration = QSpinBox()
601
602         labelQuantity = QLabel("Quantity or range:")
603         self.editQuantity = QLineEdit()
604         buttonQuantityWizard = QPushButton("Wizard")
605         layoutQuantity = QHBoxLayout()
606         layoutQuantity.addWidget(self.editQuantity)
607         layoutQuantity.addWidget(buttonQuantityWizard)
608
609         labelConsumerHrn = QLabel("Consumer HRN (optional):")
610         self.editConsumerHrn = QLineEdit()
611
612         # assuming we're using slice names as identities
613         self.editConsumerHrn.setText(self.gacks.get_hrn())
614
615         buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
616         buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
617
618         layout = QGridLayout()
619         layout.addWidget(labelProvider, 0, 0)
620         layout.addWidget(self.comboBoxProvider, 0, 1)
621         layout.addWidget(labelKind, 1, 0)
622         layout.addWidget(self.comboBoxKind, 1, 1)
623         layout.addWidget(labelDate, 2, 0)
624         layout.addWidget(self.dateStart, 2, 1)
625         layout.addWidget(labelTime, 3, 0)
626         layout.addLayout(layoutTime, 3, 1)
627         layout.addWidget(labelDuration, 4, 0)
628         layout.addWidget(self.spinBoxDuration, 4, 1)
629         layout.addWidget(labelQuantity, 5, 0)
630         #layout.addWidget(self.editQuantity, 5, 1)
631         layout.addLayout(layoutQuantity, 5, 1)
632         layout.addWidget(labelConsumerHrn, 6, 0)
633         layout.addWidget(self.editConsumerHrn, 6, 1)
634         layout.addWidget(buttonBox, 7, 0, 1, 2)
635
636         self.setLayout(layout)
637
638         self.connect(self.comboBoxProvider, SIGNAL("currentIndexChanged (int)"), self.onProviderChanged)
639         self.connect(self.comboBoxKind, SIGNAL("currentIndexChanged (int)"), self.update)
640         self.connect(self.dateStart, SIGNAL("dateChanged (const QDate&)"), self.update)
641
642         self.connect(buttonQuantityWizard, SIGNAL("clicked()"), self.onQuantityWizardClicked)
643
644         self.connect(buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
645         self.connect(buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
646
647     def onQuantityWizardClicked(self):
648         resource = self.gacks.resources.get_resource(str(self.comboBoxKind.currentText()))
649         dlg = DialogResourceWizard(self, self.gacks, resource, self.subscribedNodes)
650         if (dlg.exec_() == 1):
651             retvalComputeRspec,allChecked = dlg.compute_rspec()
652             if allChecked:
653                self.editQuantity.setText("All")
654                GlobalRSpec.computedRSpec = retvalComputeRspec
655             else:
656                self.editQuantity.setText(retvalComputeRspec)
657
658     def onProviderChanged(self):
659         provider = str(self.comboBoxProvider.currentText())
660         resources = self.gacks.resources.get_resource_names(provider)
661         self.comboBoxKind.clear()
662         self.comboBoxKind.addItems(resources)
663         if len(resources)>0:
664             self.comboBoxKind.setCurrentIndex(0)
665
666     def update(self):
667         id = str(self.comboBoxKind.currentText())
668         tStart = time.mktime(self.dateStart.date().toPyDate().timetuple())
669         recList = self.gacks.client().query_overlap(id, timeStart=tStart, timeStop=tStart+60*60*24, isLastAllocator = self.gacks.get_grm_hrn())
670         for i in range(0, 24):
671             tStartSlot = tStart + i*60*60
672             tStopSlot = tStartSlot + 60*60
673             qty = count_records_time(recList, tStartSlot, tStartSlot+60)
674             self.timeAvailLabel[i].setText(str(qty))
675
676             if tStopSlot < time.time():
677                 self.timeSlotRadio[i].setDisabled(True)
678                 self.timeSlotRadio[i].setCheckable(False)
679             else:
680                 self.timeSlotRadio[i].setDisabled(False)
681                 self.timeSlotRadio[i].setCheckable(True)
682
683     def accept(self):
684         id = str(self.comboBoxKind.currentText())
685
686         duration = int(self.spinBoxDuration.value())
687
688         hour = None
689         for i in range(0, 24):
690             if self.timeSlotRadio[i].isChecked():
691                 hour = i
692
693         if (hour == None):
694             QMessageBox.warning(self, "Warning", "start hour radio button is not checked")
695             return
696
697         dateStart = self.dateStart.date().toPyDate()
698         timeStart = self.gacks.timetuple_to_time(dateStart.timetuple(), hour) #time.mktime(dateStart.timetuple()) + hour*60*60
699         timeStop = timeStart + int(duration) * 60 * 60
700
701         timeStart -= int(self.gacks.get_timezone() * 60 * 60)
702         timeStop -= int(self.gacks.get_timezone() * 60 * 60)
703
704         #dkl get value out of box, and if it's "All", put back the entire
705         #computedRSpec, which is then passed to qty_to_rspec
706         valInQuantity = str(self.editQuantity.text())
707         if valInQuantity == "All":
708            valInQuantity = GlobalRSpec.computedRSpec
709         rspec = qty_to_rspec(valInQuantity, id, timeStart, timeStop, self.gacks.get_grm_hrn()) #gid().get_hrn())
710
711         consumerHRN = str(self.editConsumerHrn.text())
712         if not consumerHRN:
713             consumerHRN = None
714
715         self.gacks.client().grm_reserve(self.gacks.get_authtoken(), rspec, consumerHRN)
716
717         QDialog.accept(self)
718
719 class DialogResourceWizard(QDialog):
720     def __init__(self, parent=None, gacks=None, resource=None, subscribedNodes=[]):
721         super(DialogResourceWizard, self).__init__(parent)
722         self.gacks = gacks
723         self.resource = resource
724         self.subscribedNodes = subscribedNodes
725         self.setup()
726         self.update()
727
728     def setup(self):
729         self.checkBoxShowSubscribed = QCheckBox("Show only resources allocated to your slice")
730         self.checkBoxShowSubscribed.setChecked(True)
731         self.filterSubscribed = True
732
733         labelResources = QLabel("Resources:")
734         self.tableResources = QTableWidget()
735
736         buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
737         buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
738
739         labelQuantity = QLabel("Quantity per Resource:")
740         self.editQuantity = QLineEdit()
741         self.editQuantity.setText("1")
742
743         layout = QVBoxLayout()
744         layout.addWidget(self.checkBoxShowSubscribed)
745         layout.addWidget(labelResources)
746         layout.addWidget(self.tableResources)
747         layout.addWidget(labelQuantity)
748         layout.addWidget(self.editQuantity)
749         layout.addWidget(buttonBox)
750
751         self.setLayout(layout)
752
753         self.connect(buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
754         self.connect(buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
755
756         self.connect(self.checkBoxShowSubscribed, SIGNAL("stateChanged(int)"), self.onCheckBoxShowSubscribedChanged)
757
758     def onCheckBoxShowSubscribedChanged(self, val):
759         self.filterSubscribed = self.checkBoxShowSubscribed.isChecked()
760         self.update()
761
762     def addTableItem(self, table, row, col, val, data=None, readonly=True, checked=None):
763         item = QTableWidgetItem(str(val))
764         flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled
765         if not readonly:
766             flags = flags | Qt.ItemIsEditable
767         if data:
768             item.setData(Qt.UserRole, QVariant(data))
769         if checked:
770             flags = flags | Qt.ItemIsUserCheckable
771             item.setCheckState(Qt.Checked)
772         elif checked!=None:
773             flags = flags | Qt.ItemIsUserCheckable
774             item.setCheckState(Qt.Unchecked)
775         item.setFlags(flags)
776         table.setItem(row, col, item)
777
778     def update(self):
779         self.tableResources.clear()
780         self.tableResources.setColumnCount(2)
781         self.tableResources.setHorizontalHeaderLabels(["name"])
782
783         # if we're doing filtering, we'll reset the row count again below, but
784         # we need to set it to the maximum count now or it won't store the
785         # data.
786         self.tableResources.setRowCount(len(self.resource.resources))
787
788         offset = 0
789         row = 0
790
791         # dkl added two lines below
792         self.addTableItem(self.tableResources, row, 0, "All nodes", "No data", checked=False)
793         row = row + 1
794
795         for item in self.resource.resources:
796             if (item.name in self.subscribedNodes) or (not self.filterSubscribed):
797                 self.addTableItem(self.tableResources, row, 0, item.name, (offset, item), checked=False)
798                 row = row + 1
799             offset = offset + item.qty
800
801         self.tableResources.setRowCount(row)
802
803         self.tableResources.resizeColumnsToContents()
804
805     def compute_rspec(self):
806         qty = int(self.editQuantity.text())
807         rspecs = []
808         # dkl modified; see if all is checked
809         tableItem = self.tableResources.item(0, 0)
810         allChecked = (tableItem.checkState() == Qt.Checked)
811         
812         # dkl modified; lower range -> 1, plus added extra condition in if stmt
813         for row in range(1, self.tableResources.rowCount()):
814             tableItem = self.tableResources.item(row, 0)
815             (offset, item) = tableItem.data(Qt.UserRole).toPyObject()
816             if tableItem.checkState() == Qt.Checked or allChecked:
817                 rspecs.append(str(offset) + "-" + str(offset + item.qty) + "/" + str(qty))
818             row = row + 1
819         rspec = ",".join(rspecs)
820         return rspec, allChecked
821
822 class DialogEditCalendar(QDialog):
823     def __init__(self, parent=None, gacks=None, rec=None):
824         super(DialogEditCalendar, self).__init__(parent)
825         self.gacks = gacks
826         self.rec = rec
827         self.setup()
828         self.update()
829
830     def setup(self):
831         consumerHRN = self.rec.consumerHRN
832         if not consumerHRN:
833             consumerHRN = ""
834
835         self.origConsumerHRN = consumerHRN
836
837         labelResource = QLabel("Resource:")
838         labelResourceData = QLabel(self.rec.id)
839
840         labelTimeStart = QLabel("Time Start:")
841         labelTimeStartData = QLabel(str(self.rec.timeStart))
842
843         labelTimeStop = QLabel("Time Stop:")
844         labelTimeStopData = QLabel(str(self.rec.timeStop))
845
846         labelQuantity = QLabel("Quantity:")
847
848         labelAllocators = QLabel("Allocators:")
849         labelAllocatorsData = QLabel(", ".join(self.rec.allocatorHRNs))
850
851         labelHandles = QLabel("Handles:")
852         self.browserHandles = QTextBrowser()
853
854         recList = self.gacks.client().query_exact(id = self.rec.id, timeStart=self.rec.timeStart, timeStop=self.rec.timeStop, hasAllocator = self.rec.allocatorHRNs[-1])
855         recStr = "\n".join( [x.as_string() for x in recList] )
856         self.browserHandles.append(recStr)
857
858         labelConsumer = QLabel("Consumer:")
859         self.editConsumer = QLineEdit(consumerHRN)
860
861         buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
862         buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
863
864         layout = QGridLayout()
865         layout.addWidget(labelResource, 0, 0)
866         layout.addWidget(labelResourceData, 0, 1)
867         layout.addWidget(labelTimeStart, 1, 0)
868         layout.addWidget(labelTimeStartData, 1, 1)
869         layout.addWidget(labelTimeStop, 2, 0)
870         layout.addWidget(labelTimeStopData, 2, 1)
871         layout.addWidget(labelQuantity, 3, 0)
872         layout.addWidget(labelAllocators, 4, 0)
873         layout.addWidget(labelAllocatorsData, 4, 1)
874         layout.addWidget(labelHandles, 5, 0)
875         layout.addWidget(self.browserHandles, 6, 0, 1, 2)
876         layout.addWidget(labelConsumer, 7, 0)
877         layout.addWidget(self.editConsumer, 7, 1)
878         layout.addWidget(buttonBox, 8, 1, 1, 2)
879
880         self.setLayout(layout)
881
882         self.connect(buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
883         self.connect(buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
884
885     def accept(self):
886         consumerHRN = str(self.editConsumer.text())
887         if (consumerHRN != self.origConsumerHRN):
888             recList = self.gacks.get_matching_reclist(self.rec)
889
890             handles = records_to_handles(recList)
891
892             self.gacks.client().set_consumer_hrn(self.gacks.get_authtoken(), handles, consumerHRN)
893
894         QDialog.accept(self)
895
896 class DialogAddGroup(QDialog):
897     def __init__(self, parent=None, gacks=None):
898         super(DialogAddGroup, self).__init__(parent)
899         self.gacks = gacks
900         self.setup()
901         self.update()
902
903     def setup(self):
904         if self.gacks:
905             res = self.gacks.resources.get_resource("plc.vicci.cores")
906             if res:
907                 subgroups = res.get_subgroups_names()
908
909         subgrouplabel = QLabel ("Subgroup: ", self)
910         self.subgroupbox = QComboBox(self)
911         self.subgroupbox.addItems(subgroups)
912
913         descLabel = QLabel("Number:")
914         self.descEdit = QLineEdit()
915
916         self.checkbox=QCheckBox("Release Current Allocation")
917
918         self.status = QLabel("")
919         self.status.setMaximumWidth(640)
920
921         self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
922         self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
923
924         layout = QVBoxLayout()
925         layout.addWidget(subgrouplabel)
926         layout.addWidget(self.subgroupbox)
927
928         buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
929         buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
930
931         layout.addWidget(descLabel)
932         layout.addWidget(self.descEdit)
933         layout.addWidget(self.checkbox)
934         layout.addWidget(self.status)
935         layout.addWidget(buttonBox)
936         self.setLayout(layout)
937
938         self.connect(buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
939         self.connect(buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
940
941     def accept(self):
942         site = str(self.subgroupbox.currentText())
943         isReleaseAllocation = self.checkbox.isChecked()
944         desc = self.descEdit
945         if not desc:
946             QMessageBox.warning(self, "Invalid Number of Nodes", "Number of Nodes not set")
947             return
948
949         try:
950             numRequestedNodes = int(self.descEdit.text())
951         except ValueError:
952             # it wasn't a valid int
953             QMessageBox.warning(self, "Invalid Number of Nodes", "Number of Nodes not set")
954             return
955
956         self.status.setText("Running...")
957
958         # do it in the background, it may take a bit
959         t = GacksPickThread(self.gacks, self.gacks.get_authtoken(), "plc.vicci.cores", site, numRequestedNodes, not isReleaseAllocation, self)
960         self.connect(t, SIGNAL("picked(PyQt_PyObject)"), self.onPicked)
961         t.start()
962
963     def onPicked(self, result):
964         success=result[0]
965         if success:
966             QMessageBox.warning(self, "Success", "Nodes successfully picked. Updating node list")
967             QDialog.accept(self)
968         else:
969             QMessageBox.warning(self, "Failed", "Failed")
970             return
971
972 class DialogAddAsap(QDialog):
973     def __init__(self, parent=None, gacks=None):
974         super(DialogAddAsap, self).__init__(parent)
975         self.gacks = gacks
976         self.setup()
977         self.update()
978
979     def setup(self):
980         labelKind = QLabel("Kind:")
981         self.comboBoxKind = QComboBox()
982         self.comboBoxKind.addItems(RESOURCES)
983         self.comboBoxKind.setCurrentIndex(0)
984         labelKind.setBuddy(self.comboBoxKind)
985
986         labelDuration = QLabel("Duration (hours):")
987         self.spinBoxDuration = QSpinBox()
988
989         labelQuantity = QLabel("Quantity or range:")
990         self.editQuantity = QLineEdit()
991
992         labelConsumerHrn = QLabel("Consumer HRN (required):")
993         self.editConsumerHrn = QLineEdit()
994
995         buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
996         buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
997
998         layout = QGridLayout()
999         layout.addWidget(labelKind, 0, 0)
1000         layout.addWidget(self.comboBoxKind, 0, 1)
1001         layout.addWidget(labelDuration, 3, 0)
1002         layout.addWidget(self.spinBoxDuration, 3, 1)
1003         layout.addWidget(labelQuantity, 4, 0)
1004         layout.addWidget(self.editQuantity, 4, 1)
1005         layout.addWidget(labelConsumerHrn, 5, 0)
1006         layout.addWidget(self.editConsumerHrn, 5, 1)
1007         layout.addWidget(buttonBox, 6, 0, 1, 2)
1008
1009         self.setLayout(layout)
1010
1011         self.connect(buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
1012         self.connect(buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
1013
1014     def accept(self):
1015         id = str(self.comboBoxKind.currentText())
1016
1017         duration = int_default(self.spinBoxDuration.value(), 0)
1018
1019         qty = int(self.editQuantity.text())
1020
1021         job = AsapJob()
1022         job.add(AsapResource(id=id, qty=qty))
1023
1024         consumerHRN = str(self.editConsumerHrn.text())
1025         if not consumerHRN:
1026             QMessageBox.warning(self, "Warning", "consumer HRN must be set")
1027             return
1028
1029         job.set_consumer(str(consumerHRN))
1030         job.set_duration(int_default(duration, 0) * 60 * 60)
1031         job.set_allocator(self.gacks.get_gid().get_hrn())
1032
1033         self.gacks.client().add_asap(self.gacks.get_authtoken(), job)
1034
1035         QDialog.accept(self)
1036
1037 class GacksMainWindow(QMainWindow):
1038     def __init__(self, gacks=None, parent=None):
1039         super(GacksMainWindow, self).__init__(parent)
1040         self.widget = GacksWidget(gacks, parent)
1041
1042         self.setMinimumWidth(800)
1043
1044         self.setCentralWidget(self.widget)
1045
1046 def qty_to_rspec_test():
1047     x = "23"
1048     print x, qty_to_rspec(x,"cpu",0,1,"foo")
1049
1050     x="13-17"
1051     print x, qty_to_rspec(x,"cpu",0,1,"foo")
1052
1053     x="4-9/3"
1054     print x, qty_to_rspec(x,"cpu",0,1,"foo")
1055
1056     x="1,3,5,7"
1057     print x, qty_to_rspec(x,"cpu",0,1,"foo")
1058
1059     x="10-20/3, 20-30/3, 30-40/3"
1060     print x, qty_to_rspec(x,"cpu",0,1,"foo")
1061
1062 def main():
1063     # This is for running in standalone mode. Some stuff is hardcoded.
1064     # We expect users to be running in sface mode, not standalone mode.
1065
1066     gacks_helper = gackshelper.Gacks()
1067
1068     gacks_helper.set_identity("/home/smbaker/projects/genicred/Baker_Scott.pkey", "/home/smbaker/projects/genicred/Baker_Scott.cred")
1069
1070     app = QApplication(sys.argv)
1071     form = GacksMainWindow(gacks=gacks_helper)
1072     form.show()
1073     app.exec_()
1074
1075 if __name__=="__main__":
1076     main()