source: palm/trunk/SCRIPTS/palm_wd @ 1753

Last change on this file since 1753 was 1753, checked in by maronga, 9 years ago

bugfixes in watchdog and mrun

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