source: palm/trunk/SCRIPTS/palm_wd @ 1751

Last change on this file since 1751 was 1751, checked in by maronga, 6 years ago

watchdog changes and bugfixes

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 39.2 KB
Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#--------------------------------------------------------------------------------#
4# This file is part of PALM.
5#
6# PALM is free software: you can redistribute it and/or modify it under the terms
7# of the GNU General Public License as published by the Free Software Foundation,
8# either version 3 of the License, or (at your option) any later version.
9#
10# PALM is distributed in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along with
15# PALM. If not, see <http://www.gnu.org/licenses/>.
16#
17# Copyright 1997-2015  Leibniz Universitaet Hannover
18#--------------------------------------------------------------------------------#
19#
20# Current revisions:
21# -----------------
22# Bugfixes: runs on multiple hosts caused crash of the watchdog. Progress bar
23# occasionally showed wrong progress.
24# New: Hosts can be switched on/off and update_frequency can be modified via
25# File->Options. Security check for cancelation of jobs.
26#
27# Former revisions:
28# -----------------
29# $Id: palm_wd 1751 2016-02-15 07:44:16Z maronga $
30#
31# 1618 2015-07-13 06:52:15Z maronga
32# Added steering via configuration file .wd.config, to be place in the local
33# palm directory. Watchdog save files are automatically created upon first start.
34#
35# 1613 2015-07-08 14:53:29Z maronga
36# Bugfix: tooltip for queuing name did not show up on first update.
37# New: added contect menu for showing the parameter file and the run control
38# output
39#
40# 1611 2015-07-07 12:23:22Z maronga
41# Initial revision
42#
43#
44# Description:
45# ------------
46# PALM watchdog client for monitoring batch jobs on a variety of hosts specified
47# by the user
48#
49# Instructions:
50# -------------
51# 1) Modify the header section of palm_wd
52# 2) Move .wd.olddata and .wd.newdata to your palm directory
53#    (e.g. /home/user/current_version/.wd.newdata etc.)
54# 3) Modify a copy of palm_wdd for each host to be monitored and move it to the
55#    respective hosts
56# 4) Start the client either from mrungui or from shell by "nohup palm_wd&"
57
58# To do:
59# ------
60# 1) Add "Options", "Help" and "Manual"
61# 2) Move user settings to a configuration file
62#------------------------------------------------------------------------------!
63
64import ConfigParser
65import datetime
66import os
67from PyQt4 import QtGui, QtCore, uic
68from PyQt4.QtCore import pyqtSlot,SIGNAL,SLOT
69import shutil
70import subprocess as sub
71import sys
72import time
73
74global update_frequency
75
76# Determine PALM directories
77try: 
78   devnull = open(os.devnull, 'w')
79   out = sub.check_output("echo $PALM_BIN", shell=True, stderr=sub.STDOUT)
80   palm_bin = out.rstrip()
81   palm_dir = out.split("palm")[0] + "palm/" + out.split("palm")[1].split("/")[1]
82   out = None
83except:   
84   print "Error. $PALM_BIN is not set."
85   raise SystemExit
86
87
88# Read configuration file
89# First check if the configuration file exists
90if ( os.path.exists(palm_dir + '/.wd.config') == False ):
91    print "Error. No configuration file .wd.config found in " + palm_dir
92    raise SystemExit     
93
94config = ConfigParser.RawConfigParser()
95config.read(palm_dir + '/.wd.config')
96
97description = []
98hostname = []
99username = []
100
101for i in range(0,len(config.sections())):
102
103    description_tmp = config.sections()[i]
104 
105    if ( description_tmp != 'Settings' ):
106     
107       if ( config.get(description_tmp, 'enabled') == 'true' ):
108          description.append(description_tmp)
109          hostname.append(config.get(description_tmp, 'hostname'))
110          username.append(config.get(description_tmp, 'username')) 
111    else:
112       update_frequency = int(config.get(description_tmp, 'update_frequency'))*60000
113
114# Check if .wd.olddata and .wd.newdata exist. Otherwise create the files
115if ( os.path.exists(palm_dir + '/.wd.olddata') == False ):
116   print "No .wd.olddata found. Creating..."
117   file1 = open(palm_dir + '/.wd.olddata', 'a')
118   file1.close()
119   
120
121if ( os.path.exists(palm_dir + '/.wd.newdata') == False ):
122   print "No .wd.newdata found. Creating..." 
123   file1 = open(palm_dir + '/.wd.newdata', 'a')
124   file1.close()
125
126# Dummy variables for the jobname and the progressbars
127jobname = ""
128timestamp = ""
129pbars = []
130job_data_list = []
131
132# Message box for showing RUN_CONTROL output
133class MessageBoxScroll(QtGui.QMessageBox):
134    def __init__(self):
135        QtGui.QMessageBox.__init__(self)
136        self.setSizeGripEnabled(True)
137
138    def event(self, e):
139        result = QtGui.QMessageBox.event(self, e)
140
141        self.setMinimumHeight(100)
142        self.setMaximumHeight(16777215)
143        self.setMinimumWidth(400)
144        self.setMaximumWidth(16777215)
145        self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
146
147        textEdit = self.findChild(QtGui.QTextEdit)
148        if textEdit != None :
149            textEdit.setMinimumHeight(800)
150            textEdit.setMaximumHeight(16777215)
151            textEdit.setMinimumWidth(1000)
152            textEdit.setMaximumWidth(16777215)
153            textEdit.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
154            f = QtGui.QFont('not_a_font') #
155            f.setStyleHint(f.TypeWriter, f.PreferDefault)
156            textEdit.setFont(f)
157
158        return result
159
160
161# Message box for showing RUN_CONTROL output
162class OptionBox(QtGui.QDialog):
163    def __init__(self):
164     
165        super(OptionBox, self).__init__()
166     
167        uic.loadUi(palm_bin + '/palm_wd_files/wdoptions.ui', self)
168       
169        hostname = []
170        self.checkbox = []
171        j = -1
172        ypos = 0
173   
174        self.vbox = QtGui.QVBoxLayout(self)
175        self.vbox.setSpacing(0)
176        self.vbox.setMargin(0)       
177   
178        config.read(palm_dir + '/.wd.config')
179   
180        for i in range(0,len(config.sections())):
181
182           description_tmp = config.sections()[i]
183 
184           if ( description_tmp != 'Settings' ):
185              description.append(description_tmp)
186              hostname.append(config.get(description_tmp, 'hostname'))
187             
188              j = j + 1
189              self.checkbox.append(j)
190              self.checkbox[j] = QtGui.QCheckBox(description_tmp, self)   
191              ypos = ypos + 20
192              self.checkbox[j].move(10,0)
193              if ( config.get(description_tmp, 'enabled') == 'true' ):
194                 self.checkbox[j].toggle()
195                       
196              self.vbox.addWidget(self.checkbox[j])
197           else:
198              self.update_spin.setValue(int(config.get(description_tmp, 'update_frequency')))
199     
200        self.hostBox.setLayout(self.vbox)
201        self.hostBox.setGeometry(0, 0, 272, 50+ypos)
202        self.settingBox.setGeometry(281, 0, 214, 50+ypos)
203        self.ok.setGeometry(385, 50+ypos, 111, 24)
204        self.cancel.setGeometry(270, 50+ypos, 111, 24)
205       
206        self.setGeometry(0, 0, 500, 80+ypos)
207        self.show()
208       
209        return
210
211#   save updtes to file
212    def SaveAndClose(self):
213     
214       for i in range(0,len(self.checkbox)):
215         
216          if ( self.checkbox[i].checkState() == 0 ):
217             config.set(str(self.checkbox[i].text()),'enabled','false')
218          else:
219             config.set(str(self.checkbox[i].text()),'enabled','true')       
220       
221       config.set('Settings','update_frequency',self.update_spin.text()) 
222
223       with open(palm_dir + '/.wd.config', 'wb') as configfile:
224          config.write(configfile)
225
226
227       self.close()
228     
229       return
230
231
232
233# MainWindow class
234class Watchdog(QtGui.QMainWindow):
235   
236    def __init__(self):
237        super(Watchdog, self).__init__()
238       
239        self.InitUi()   
240       
241   
242    # Initialized MainWindow UI
243    def InitUi(self):
244
245        # Load predefined mainwindow
246        uic.loadUi(palm_bin + '/palm_wd_files/wd.ui', self)
247
248        # Resize columns and set number of rows to zero. Column 6 is hidden and
249        # contains the remote jobname (e.g. hannover.174610)
250        self.table.setColumnWidth(0,230)
251        self.table.setColumnWidth(1,80)
252        self.table.setColumnWidth(2,50)
253        self.table.setColumnWidth(3,80)     
254        self.table.setColumnWidth(4,180)
255        self.table.setColumnWidth(5,115)   
256        self.table.setColumnHidden(6, True)
257        self.table.setRowCount(0) 
258   
259        # Display MainWindow
260        self.show()
261        QtGui.QApplication.processEvents()
262
263        # Start refresh timer. On timeout perform update
264        self.timer = QtCore.QTimer(self)
265        self.timer.timeout.connect(self.Refresh)
266        self.timer.setSingleShot(False)
267        self.timer.start(update_frequency)
268
269        # The timetimer counts the time since last update
270        self.timetimer= QtCore.QElapsedTimer()
271        self.timetimer.start()
272
273        # The labeltimer induces the update of the remaining time in the UI
274        self.labeltimer = QtCore.QTimer(self)
275        self.labeltimer.timeout.connect(self.UpdateLabel)
276        self.labeltimer.setSingleShot(False)
277
278        # Update in the UI will be performed at each 1/10 of the update interval
279        self.labeltimer.start(update_frequency/10)   
280        self.label.setText("Next update in " + str(update_frequency/1000/60) + "min")
281
282        # Update the list now
283        self.Refresh()
284       
285
286    # Add a custom context menu
287    def OpenContextMenu(self, position):
288        menu = QtGui.QMenu()
289       
290        # "Show details" -> fetch details for the selected job
291        detailsjobAction = menu.addAction('Show job details')     
292        detailsjobAction.setStatusTip('Display detailed info for job')
293        detailsjobAction.triggered.connect(self.ShowDetails)
294
295        # "Show parameter file" -> show the contents of PARIN
296        parinAction = menu.addAction('Show parameter file')     
297        parinAction.setStatusTip('Display the parameter file of the job (e.g. PARIN / _p3d)')
298        parinAction.triggered.connect(self.ShowPARIN)
299       
300        rcAction = menu.addAction('Show run control file')     
301        rcAction.setStatusTip('Display the current run control file (e.g. RUN_CONTROL / _rc)')
302        rcAction.triggered.connect(self.ShowRC)
303       
304        # "Cancel job" -> remove job from queuing system
305        canceljobAction = menu.addAction('Cancel job')
306        canceljobAction.setStatusTip('Remove job from queueing system')
307        canceljobAction.triggered.connect(self.CancelJob)
308       
309        # "Force stop" -> force termination of job
310        stopjobAction = menu.addAction('Force stop')
311        stopjobAction.setStatusTip('Terminate job properly')
312        stopjobAction.triggered.connect(self.DoStop)
313   
314        # "Force restart" -> force rermination and restart of job
315        restartjobAction = menu.addAction('Force restart')
316        restartjobAction.setStatusTip('Terminate job properly and initiate restart')
317        restartjobAction.triggered.connect(self.DoRestart)
318 
319        # "Remove from list" -> remove a completed job from the list
320        removefromlistAction = menu.addAction('Remove from list')     
321        removefromlistAction.setStatusTip('Remove finished job')
322        removefromlistAction.triggered.connect(lambda: self.RemoveFromList(-1))     
323
324       
325        # Activate/deactive contect menu items based on the status of the runs
326        state = self.table.item(self.table.currentRow(),3).text()
327        if (state == "Canceled" or state == "Completed" or state == "Terminated"):
328            detailsjobAction.setEnabled(False)
329            canceljobAction.setEnabled(False) 
330            removefromlistAction.setEnabled(True) 
331        else:
332            detailsjobAction.setEnabled(True)
333            canceljobAction.setEnabled(True) 
334            removefromlistAction.setEnabled(False)   
335 
336        if ( state == "Running" ):
337            stopjobAction.setEnabled(True) 
338            restartjobAction.setEnabled(True)   
339            parinAction.setEnabled(True)
340            rcAction.setEnabled(True)         
341        else:
342            stopjobAction.setEnabled(False) 
343            restartjobAction.setEnabled(False)   
344            parinAction.setEnabled(False)
345            rcAction.setEnabled(False) 
346           
347        # Show the context menu
348        action = menu.exec_(self.table.mapToGlobal(position))
349
350
351    # Collecting watchdog data and display in the UI
352    def Refresh(self):
353     
354        # Use global variables
355        global pbars
356        global update_frequency
357
358        # Deactivate timer processes until update is finished
359        # QtGui.QApplication.processEvents() updates the UI whenever needed
360        self.labeltimer.stop() 
361        self.label.setText("Updating...") 
362        QtGui.QApplication.processEvents()
363        self.setEnabled(False)
364        QtGui.QApplication.processEvents()
365
366        # Get current timestamp
367        timestamp = datetime.datetime.now().strftime("%d/%m/%Y %H:%M")
368       
369        # Open an empty file for the watchdog data       
370        file = open(palm_dir + '/.wd.newdata', 'w')
371       
372 
373        # Set all required variables to their initial values. Sorting must be
374        # disabled.
375        self.table.setRowCount(0) 
376        self.table.setSortingEnabled(False)
377        i = -1
378        job_data_list = []
379        pbars = []
380       
381        # Scan all hosts in the variable hostname. For each host perform the
382        # update loop
383        for h in range(0,len(hostname)):
384         
385            # Perform ssh command
386            host    = username[h] + "@" + hostname[h]
387            ssh = sub.Popen(["ssh", "%s" % host, "/sw/tools/python/2.7.6/generic/bin/python palm_wdd queue " + username[h]],
388                           shell=False,
389                           stdout=sub.PIPE,
390                           stderr=sub.PIPE)
391            result = ssh.stdout.readlines()
392            result = filter(None, result)
393 
394 
395            # Delete all job data
396            job_data_tmp = []
397            job_data = []
398            job_data_n = []   
399            job_progress = 0
400
401            # In case of errors display error message
402            if ( len(result) < 1 ):
403                error = ssh.stderr.readlines()
404                if ( error != [] ):
405                    notify = QtGui.QMessageBox.warning(self,'Collect data',"Error. An error occured during read of job data for user " + username[h] +" for host " + description[h] + ".\n\nError message:\n" + ''.join(error))
406
407            # No error -> save job data
408            else:       
409
410                # Successively write to job_data
411                for j in range(0,len(result)):
412   
413                    job_data_tmp.append(j)
414                    job_data_tmp[j] = result[j].split(" ")
415                    job_data_tmp[j] = filter(None, job_data_tmp[j])
416 
417                    job_data.append(j)   
418                    job_data[j] = [job_data_tmp[j][0], description[h], job_data_tmp[j][2],                 
419                                   job_data_tmp[j][3], int(float(job_data_tmp[j][4])*100), job_data_tmp[j][5], 
420                                   job_data_tmp[j][1], job_data_tmp[j][6].rstrip()]
421                    job_data_n.append(j)
422                    job_data_n[j] = job_data_tmp[j][1]
423
424            del(result)
425
426
427
428            # Now read the data from the last update from file. These data are
429            # already in the prefered format
430            file2 = open(palm_dir + '/.wd.olddata', 'r')
431            result = file2.readlines()
432            file2.close()
433
434            job_data_old = []
435            job_data_old_n = []
436            k = -1
437            for j in range(0,len(result)):
438                if ( result[j].split()[1] == description[h] ):
439                    k = k + 1
440                    job_data_old.append(k)
441                    job_data_old[k] = result[j].split(" ")
442                    job_data_old[k] = filter(None, job_data_old[k])
443                    job_data_old[k] = [line.rstrip('\n') for line in job_data_old[k]]
444                    job_data_old_n.append(k)
445                    job_data_old_n[k] = job_data_old[k][6]
446
447            # Merge the job_data and job_data_old to find completed, new and
448            # still queued jobs     
449            if ( len(job_data_n) > 0 and len(job_data_old_n) > 0 ):
450               jobs_full  = list(set(job_data_old_n) | set(job_data_n))
451               jobs_known    = list(set(job_data_old_n) & set(job_data_n))
452               jobs_complete = set(job_data_old_n) - set(job_data_n)
453               jobs_new      = set(job_data_n) - set(job_data_old_n)
454            elif ( len(job_data_n) > 0 ):
455               jobs_full  = job_data_n
456               jobs_known    = []
457               jobs_new = job_data_n
458               jobs_complete = []
459            elif ( len(job_data_old_n) > 0 ):
460               jobs_full  = job_data_old_n
461               jobs_known    = []
462               jobs_new = []
463               jobs_complete = job_data_old_n   
464            else:
465               jobs_full  = []
466               jobs_known    = []         
467               jobs_new = []
468               jobs_complete = []
469
470            # Display all known jobs
471            to_display = [list(job_data_n).index(item) for item in list(jobs_known) if list(jobs_known) and list(job_data_n)]
472            for j in range(0,len(to_display)):
473                i = i + 1
474                self.table.insertRow(i)
475                item = QtGui.QTableWidgetItem(job_data[to_display[j]][0])
476                item.setToolTip("Queuing name: " + job_data[to_display[j]][6])
477                self.table.setItem(i, 0, item)           
478                self.table.setItem(i, 1, QtGui.QTableWidgetItem(job_data[to_display[j]][1])) 
479                self.table.setItem(i, 2, QtGui.QTableWidgetItem(job_data[to_display[j]][2]))   
480                self.table.setItem(i, 3, QtGui.QTableWidgetItem(job_data[to_display[j]][3]))
481                item = QtGui.QTableWidgetItem(job_data[to_display[j]][5])
482                item.setToolTip("Estimated job start: " + job_data[to_display[j]][7])               
483                self.table.setItem(i, 5, item) 
484                self.table.setItem(i, 6, QtGui.QTableWidgetItem(job_data[to_display[j]][6])) 
485                self.table.item(i,2).setTextAlignment(QtCore.Qt.AlignRight)
486                self.table.item(i,5).setTextAlignment(QtCore.Qt.AlignRight)
487                pbars.append(i)
488                pbars[i] = QtGui.QProgressBar(self)
489                pbars[i].setValue(int(job_data[to_display[j]][4])) 
490 
491                # Apply a color depending on the status of the job
492                if ( job_data[to_display[j]][3] == "Running" ):
493                    self.table.setCellWidget(i,4,pbars[i]) 
494                    self.table.item(i,0).setBackground(QtGui.QColor(255, 251, 168))
495                    self.table.item(i,1).setBackground(QtGui.QColor(255, 251, 168))
496                    self.table.item(i,2).setBackground(QtGui.QColor(255, 251, 168))
497                    self.table.item(i,3).setBackground(QtGui.QColor(255, 251, 168))
498                    self.table.item(i,5).setBackground(QtGui.QColor(255, 251, 168))
499                    self.table.item(i,6).setBackground(QtGui.QColor(255, 251, 168))
500                else:
501                    pbars[j].setEnabled(False)
502                    self.table.setCellWidget(i,4,pbars[i])
503                    self.table.item(i,0).setBackground(QtGui.QColor(255, 112, 118))
504                    self.table.item(i,1).setBackground(QtGui.QColor(255, 112, 118))
505                    self.table.item(i,2).setBackground(QtGui.QColor(255, 112, 118))
506                    self.table.item(i,3).setBackground(QtGui.QColor(255, 112, 118))
507                    self.table.item(i,5).setBackground(QtGui.QColor(255, 112, 118))
508                    self.table.item(i,6).setBackground(QtGui.QColor(255, 112, 118))
509               
510                # Save the job data to file
511                job_data_list.append(i)
512                job_data_list[i] = job_data[to_display[j]][0]
513               
514                printstring = str(job_data[to_display[j]][0]) + " " + \
515                              str(job_data[to_display[j]][1]) + " " + \
516                              str(job_data[to_display[j]][2]) + " " + \
517                              str(job_data[to_display[j]][3]) + " " + \
518                              str(job_data[to_display[j]][4]) + " " + \
519                              str(job_data[to_display[j]][5]) + " " + \
520                              str(job_data[to_display[j]][6]) + " " + \
521                              str(job_data[to_display[j]][7]) + "\n"
522                file.write(printstring)
523
524
525            # Display all new jobs
526            to_display = [list(job_data_n).index(item) for item in list(jobs_new) if list(jobs_new) and list(job_data_n)]
527            for j in range(0,len(to_display)):
528                i = i + 1
529                self.table.insertRow(i)
530                item = QtGui.QTableWidgetItem(job_data[to_display[j]][0])
531                item.setToolTip("Queuing name: " + job_data[to_display[j]][6])
532                self.table.setItem(i, 0, item)   
533                self.table.setItem(i, 1, QtGui.QTableWidgetItem(job_data[to_display[j]][1])) 
534                self.table.setItem(i, 2, QtGui.QTableWidgetItem(job_data[to_display[j]][2]))   
535                self.table.setItem(i, 3, QtGui.QTableWidgetItem(job_data[to_display[j]][3])) 
536                item = QtGui.QTableWidgetItem(job_data[to_display[j]][5])
537                item.setToolTip("Estimated job start: " + job_data[to_display[j]][7])               
538                self.table.setItem(i, 5, item) 
539                self.table.setItem(i, 6, QtGui.QTableWidgetItem(job_data[to_display[j]][6])) 
540                self.table.item(i,2).setTextAlignment(QtCore.Qt.AlignRight)
541                self.table.item(i,5).setTextAlignment(QtCore.Qt.AlignRight)
542               
543                pbars.append(i)
544                pbars[i] = QtGui.QProgressBar(self)
545                pbars[i].setValue(int(job_data[to_display[j]][4]))   
546
547                # Apply a color depending on the status of the job
548                if ( job_data[to_display[j]][3] == "Running" ):
549                    self.table.setCellWidget(i,4,pbars[i])           
550                    self.table.item(i,0).setBackground(QtGui.QColor(255, 251, 168))
551                    self.table.item(i,1).setBackground(QtGui.QColor(255, 251, 168))
552                    self.table.item(i,2).setBackground(QtGui.QColor(255, 251, 168))
553                    self.table.item(i,3).setBackground(QtGui.QColor(255, 251, 168))
554                    self.table.item(i,5).setBackground(QtGui.QColor(255, 251, 168))
555                    self.table.item(i,6).setBackground(QtGui.QColor(255, 251, 168))
556                else:
557                    pbars[j].setEnabled(False)
558                    self.table.setCellWidget(i,4,pbars[i])
559                    self.table.item(i,0).setBackground(QtGui.QColor(255, 112, 118))
560                    self.table.item(i,1).setBackground(QtGui.QColor(255, 112, 118))
561                    self.table.item(i,2).setBackground(QtGui.QColor(255, 112, 118))
562                    self.table.item(i,3).setBackground(QtGui.QColor(255, 112, 118))
563                    self.table.item(i,5).setBackground(QtGui.QColor(255, 112, 118))
564                    self.table.item(i,6).setBackground(QtGui.QColor(255, 112, 118))
565         
566                # Save job data to file
567                job_data_list.append(i)
568                job_data_list[i] = job_data[to_display[j]][0]
569               
570                printstring = str(job_data[to_display[j]][0]) + " " + \
571                              str(job_data[to_display[j]][1]) + " " + \
572                              str(job_data[to_display[j]][2]) + " " + \
573                              str(job_data[to_display[j]][3]) + " " + \
574                              str(job_data[to_display[j]][4]) + " " + \
575                              str(job_data[to_display[j]][5]) + " " + \
576                              str(job_data[to_display[j]][6]) + " " + \
577                              str(job_data[to_display[j]][7]) + "\n"
578                file.write(printstring)
579
580
581            # Display all completed/canceled/aborted jobs. The watchdog cannot
582            # distinguish why the job has been finished
583            to_display = [list(job_data_old_n).index(item) for item in list(jobs_complete) if list(jobs_complete) and list(job_data_old_n)]
584            for j in range(0,len(to_display)):
585                i = i + 1
586                self.table.insertRow(i)
587                item = QtGui.QTableWidgetItem(job_data_old[to_display[j]][0])
588                item.setToolTip("Queuing name: " + job_data_old[to_display[j]][6])
589                self.table.setItem(i, 0, item)         
590                self.table.setItem(i, 1, QtGui.QTableWidgetItem(job_data_old[to_display[j]][1])) 
591                self.table.setItem(i, 2, QtGui.QTableWidgetItem(job_data_old[to_display[j]][2]))   
592                self.table.setItem(i, 3, QtGui.QTableWidgetItem(job_data_old[to_display[j]][3])) 
593                pbars.append(i)
594                pbars[j] = QtGui.QProgressBar(self)
595                pbars[j].setValue(int(job_data_old[to_display[j]][4])) 
596                pbars[j].setEnabled(False)
597                self.table.setCellWidget(i,4,pbars[j]) 
598                item = QtGui.QTableWidgetItem(job_data_old[to_display[j]][5])
599                item.setToolTip("Estimated job start: " + job_data_old[to_display[j]][7])               
600                self.table.setItem(i, 5, item) 
601                self.table.setItem(i, 6, QtGui.QTableWidgetItem(job_data_old[to_display[j]][6])) 
602                self.table.item(i,2).setTextAlignment(QtCore.Qt.AlignRight)
603                self.table.item(i,5).setTextAlignment(QtCore.Qt.AlignRight)
604                self.table.setItem(i, 3, QtGui.QTableWidgetItem("Completed"))
605               
606                # Apply a color depending on the status of the job
607                self.table.item(i,0).setBackground(QtGui.QColor(172, 252, 175))
608                self.table.item(i,1).setBackground(QtGui.QColor(172, 252, 175))
609                self.table.item(i,2).setBackground(QtGui.QColor(172, 252, 175))
610                self.table.item(i,3).setBackground(QtGui.QColor(172, 252, 175))
611                self.table.item(i,5).setBackground(QtGui.QColor(172, 252, 175))
612                self.table.item(i,6).setBackground(QtGui.QColor(172, 252, 175))
613
614         
615                # Save job data to file
616                job_data_list.append(i)
617                job_data_list[i] = job_data_old[to_display[j]][0]
618
619                printstring = str(job_data_old[to_display[j]][0]) + " " + \
620                              str(job_data_old[to_display[j]][1]) + " " + \
621                              str(job_data_old[to_display[j]][2]) + " " + \
622                              str(job_data_old[to_display[j]][3]) + " " + \
623                              str(job_data_old[to_display[j]][4]) + " " + \
624                              str(job_data_old[to_display[j]][5]) + " " + \
625                              str(job_data_old[to_display[j]][6]) + " " + \
626                              str(job_data_old[to_display[j]][7]) + "\n"
627                file.write(printstring)
628
629            del(jobs_complete)
630            del(jobs_new)
631            del(result)
632            del(job_data_old)
633            del(job_data_old_n)
634           
635        file.close()
636
637       
638        # Update is complete, sorting can thus be re-enabled               
639        self.table.setSortingEnabled(True) 
640
641        # Update the logfile
642        if ( os.path.isfile(palm_dir + '/.wd.newdata') ):
643            shutil.copy(palm_dir + '/.wd.newdata',palm_dir + '/.wd.olddata')
644
645        # Re-enable timer processes             
646        self.setEnabled(True)
647        self.label2.setText("Last update: " + timestamp)
648        self.label.setText("Update complete.")
649        self.timetimer.start()
650        self.timer.start(update_frequency)
651        self.labeltimer.start(update_frequency/10)
652        self.UpdateLabel()
653        QtGui.QApplication.processEvents()
654       
655    # Canceling selected job from table
656    def CancelJob(self):
657
658        # Return internal jobname
659        jobname = self.table.item(self.table.currentRow(),6).text()
660 
661        # Check description of job in order to login on the correct host
662        descr = self.table.item(self.table.currentRow(),1).text()
663       
664        querybox = QtGui.QMessageBox()
665        querybox.setText("Attention!\nYou are trying to cancel a job. It will not be able to terminate properly.")
666        querybox.setInformativeText("Do you really want to cancel " + jobname + " on host " + descr + "?")
667        querybox.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
668        querybox.setDefaultButton(QtGui.QMessageBox.No)
669        returnvalue = querybox.exec_()
670       
671        if ( returnvalue == QtGui.QMessageBox.Yes ):
672       
673           for h in range(0,len(description)):
674               if ( descr == description[h] ):
675                  host = username[h] + "@" + hostname[h]
676
677           ssh = sub.Popen(["ssh", "%s" % host, "/sw/tools/python/2.7.6/generic/bin/python palm_wdd cancel " + jobname],
678                              shell=False,
679                              stdout=sub.PIPE,
680                              stderr=sub.PIPE)
681           result = ssh.stdout.readlines()
682           result = filter(None, result)
683       
684           # In case of error display a warning message
685           if ( len(result) == 0 ):
686               error = ssh.stderr.readlines()
687               notify = QtGui.QMessageBox.warning(self,'Cancel job',"Error. Could not cancel job " + jobname + ".\n\nError message:\n" + ''.join(error))
688
689           # Otherwise inform the user and mark the job in the table
690           else:
691               self.table.setItem(self.table.currentRow(),3,QtGui.QTableWidgetItem("Canceled"))
692               notify = QtGui.QMessageBox.information(self,'Cancel job',"Job" + jobname + " canceled!\n\nServer message:\n" + ''.join(result))
693
694
695    # Show detailed information on job. For documentation see above
696    def ShowDetails(self):
697
698        jobname = self.table.item(self.table.currentRow(),6).text()
699        descr   = self.table.item(self.table.currentRow(),1).text()
700        for h in range(0,len(description)):
701            if ( descr == description[h] ):
702                host = username[h] + "@" + hostname[h]
703
704        ssh = sub.Popen(["ssh", "%s" % host, "/sw/tools/python/2.7.6/generic/bin/python palm_wdd check " + jobname],
705                           shell=False,
706                           stdout=sub.PIPE,
707                           stderr=sub.PIPE)
708        result = ssh.stdout.readlines()
709        result = filter(None, result)
710
711        if ( len(result) == 0 ):
712            error = ssh.stderr.readlines()
713            notify = QtGui.QMessageBox.warning(self,'Show job details',"Error. Could not fetch job details for " + jobname + ".\n\nError message:\n" + ''.join(error))
714        else:
715            notify = QtGui.QMessageBox.information(self,'Job details',"Details for job " + jobname + ":\n\n" + ''.join(result))
716
717
718    # Perform a forced stop on the job
719    def DoStop(self):
720
721        # Return internal jobname
722        jobname = self.table.item(self.table.currentRow(),6).text()
723 
724        # Check description of job in order to login on the correct host
725        descr = self.table.item(self.table.currentRow(),1).text()
726        for h in range(0,len(description)):
727            if ( descr == description[h] ):
728                host = username[h] + "@" + hostname[h]
729                user = username[h]
730
731        ssh = sub.Popen(["ssh", "%s" % host, "/sw/tools/python/2.7.6/generic/bin/python palm_wdd stop " + user + " " + jobname],
732                           shell=False,
733                           stdout=sub.PIPE,
734                           stderr=sub.PIPE)
735        result = ssh.stdout.readlines()
736        result = filter(None, result) 
737       
738        # In case of error display a warning message
739        if ( len(result) == 0 ):
740            error = ssh.stderr.readlines()
741            notify = QtGui.QMessageBox.warning(self,'Proper termination of job',"Error. Could not stop job " + jobname + ".\n\nError message:\n" + ''.join(error))
742
743        # Otherwise inform the user and mark the job in the table
744        else:
745            self.table.setItem(self.table.currentRow(),3,QtGui.QTableWidgetItem("Terminated"))
746            notify = QtGui.QMessageBox.information(self,'Terminate job',"Termination of job " + jobname + " was initiated!\n\nServer message:\n" + ''.join(result))
747
748
749    # Perform a forced stop on the job
750    def DoRestart(self):
751
752        # Return internal jobname
753        jobname = self.table.item(self.table.currentRow(),6).text()
754 
755        # Check description of job in order to login on the correct host
756        descr = self.table.item(self.table.currentRow(),1).text()
757        for h in range(0,len(description)):
758            if ( descr == description[h] ):
759                host = username[h] + "@" + hostname[h]
760                user = username[h]
761
762        ssh = sub.Popen(["ssh", "%s" % host, "/sw/tools/python/2.7.6/generic/bin/python palm_wdd restart " + user + " " + jobname],
763                           shell=False,
764                           stdout=sub.PIPE,
765                           stderr=sub.PIPE)
766        result = ssh.stdout.readlines()
767        result = filter(None, result)
768       
769        # In case of error display a warning message
770        if ( len(result) == 0 ):
771            error = ssh.stderr.readlines()
772            notify = QtGui.QMessageBox.warning(self,'Proper termination of job',"Error. Could not stop job " + jobname + ".\n\nError message:\n" + ''.join(error))
773
774        # Otherwise inform the user and mark the job in the table
775        else:
776            self.table.setItem(self.table.currentRow(),3,QtGui.QTableWidgetItem("Terminated"))
777            notify = QtGui.QMessageBox.information(self,'Terminate job for restart',"Restart for job" + jobname + " was initiated!\n\nServer message:\n" + ''.join(result))
778           
779
780    # Read the PARIN file of the job
781    def ShowPARIN(self):
782
783        # Return internal jobname
784        jobname = self.table.item(self.table.currentRow(),6).text()
785        jobrealname = self.table.item(self.table.currentRow(),0).text()     
786 
787        # Check description of job in order to login on the correct host
788        descr = self.table.item(self.table.currentRow(),1).text()
789        for h in range(0,len(description)):
790            if ( descr == description[h] ):
791                host = username[h] + "@" + hostname[h]
792                user = username[h]
793
794        ssh = sub.Popen(["ssh", "%s" % host, "/sw/tools/python/2.7.6/generic/bin/python palm_wdd parin " + user + " " + jobname],
795                           shell=False,
796                           stdout=sub.PIPE,
797                           stderr=sub.PIPE)
798        result = ssh.stdout.readlines()
799        result = filter(None, result)
800       
801        # In case of error display a warning message
802        if ( len(result) == 0 ):
803            error = ssh.stderr.readlines()
804            notify = QtGui.QMessageBox.warning(self,'Showing parameter file',"Error. Could not fetch parameter file for job " + jobrealname + " (" + jobname + ").\n\nError message:\n" + ''.join(error))
805
806        # Otherwise inform the user and mark the job in the table
807        else:
808           mb = MessageBoxScroll()
809           mb.setText("Parameter file for job: " + jobrealname + "")
810           mb.setDetailedText(''.join(result))
811           mb.exec_()
812
813    # Read the PARIN file of the job
814    def ShowRC(self):
815
816        # Return internal jobname and real job name
817        jobname = self.table.item(self.table.currentRow(),6).text()
818        jobrealname = self.table.item(self.table.currentRow(),0).text()     
819 
820        # Check description of job in order to login on the correct host
821        descr = self.table.item(self.table.currentRow(),1).text()
822        for h in range(0,len(description)):
823            if ( descr == description[h] ):
824                host = username[h] + "@" + hostname[h]
825                user = username[h]
826
827        ssh = sub.Popen(["ssh", "%s" % host, "/sw/tools/python/2.7.6/generic/bin/python palm_wdd rc " + user + " " + jobname],
828                           shell=False,
829                           stdout=sub.PIPE,
830                           stderr=sub.PIPE)
831        result = ssh.stdout.readlines()
832        result = filter(None, result)
833       
834        # In case of error display a warning message
835        if ( len(result) == 0 ):
836            error = ssh.stderr.readlines()
837            notify = QtGui.QMessageBox.warning(self,'Showing run control',"Error. Could not fetch run control file for job " + jobrealname + "(" + jobname + ").\n\nError message:\n" + ''.join(error))
838
839        # Otherwise inform the user and mark the job in the table
840        else:
841           mb = MessageBoxScroll()
842           lastline = result[len(result)-2].split()[2]
843           mb.setText("Simulated time for job " + jobrealname + " is currently: " + lastline)
844           mb.setDetailedText(''.join(result))
845           mb.exec_()
846
847    # Remove a job from list - removes the current row from the table and from
848    # save file
849    def RemoveFromList(self, row):
850        if ( row == -1 ):
851            row = self.table.currentRow()
852
853        # Read data from save file
854        job_to_delete = self.table.item(row,6).text()
855        self.table.removeRow(row)
856        file = open(palm_dir + '/.wd.olddata', 'r')
857        result = file.readlines()
858        result = filter(None, result)
859        file.close()
860        file = open(palm_dir + '/.wd.olddata', 'w')
861       
862        job_data_old = []
863       
864        if ( len(result) == 0 ):
865            notify = QtGui.QMessageBox.warning(self,'Read from .wd.olddata',"Error message:\n\nCould not read from file. I/O error.")
866        else: 
867            # Save data in array job_data_old
868            for j in range(0,len(result)):
869                job_data_old.append(j)
870                job_data_old[j] = result[j].split(" ")
871                job_data_old[j] = filter(None, job_data_old[j])
872                job_data_old[j] = [line.rstrip('\n') for line in job_data_old[j]]
873               
874                # Check if line j is the selected job, if not -> save to file
875                if ( job_data_old[j][6] != job_to_delete ):
876                    printstring = str(job_data_old[j][0]) + " " + \
877                                  str(job_data_old[j][1]) + " " + \
878                                  str(job_data_old[j][2]) + " " + \
879                                  str(job_data_old[j][3]) + " " + \
880                                  str(job_data_old[j][4]) + " " + \
881                                  str(job_data_old[j][5]) + " " + \
882                                  str(job_data_old[j][6]) + " " + \
883                                  str(job_data_old[j][7]) + "\n"
884                    file.write(printstring)
885           
886        file.close() 
887
888    # Remove all completed jobs from list
889    def ClearList(self):
890     
891       num_of_lines = self.table.rowCount()
892       
893       # Delete all lines with completed/canceled jobs from list. The counter
894       # must decrease as the line numbers would be messed up otherwise
895       for j in range(num_of_lines-1,-1,-1): 
896           state = self.table.item(j,3).text()
897           if ( state == "Completed" or state == "Canceled" or state == "Terminated"):
898              self.RemoveFromList(j)
899         
900    # Update the label
901    def UpdateLabel(self):
902        remaining_time = (update_frequency - self.timetimer.elapsed()) / 1000 / 60
903        self.label.setText("Next update in " + str(remaining_time) + " min")
904       
905
906    # Enter Options menu
907    def Options(self):
908 
909#       disable mainwindow 
910        self.setEnabled(False)
911     
912#       show Options Dialog     
913        opt = OptionBox()
914        opt.exec_()
915
916        self.UpdateHosts()
917        self.setEnabled(True)
918
919# Update settings
920    def UpdateHosts(self):
921
922       global update_frequency
923       description = []
924       hostname = []
925       username = []
926
927       for i in range(0,len(config.sections())):
928
929          description_tmp = config.sections()[i]
930 
931          if ( description_tmp != 'Settings' ):
932     
933             if ( config.get(description_tmp, 'enabled') == 'true' ):
934                description.append(description_tmp)
935                hostname.append(config.get(description_tmp, 'hostname'))
936                username.append(config.get(description_tmp, 'username')) 
937          else:
938             update_frequency = int(config.get(description_tmp, 'update_frequency'))*60000
939
940
941# Main loop       
942def main():
943   
944    app = QtGui.QApplication(sys.argv)
945    res = Watchdog() 
946    sys.exit(app.exec_())
947
948
949if __name__ == '__main__':
950    main()   
Note: See TracBrowser for help on using the repository browser.