source: palm/trunk/SCRIPTS/palm_wd @ 1752

Last change on this file since 1752 was 1752, checked in by maronga, 8 years ago

last commit documented

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