2004-11-08 18:55:25 -05:00
|
|
|
#----------------------------------------------------------------------
|
|
|
|
# Name: taskrunner.py
|
|
|
|
# Purpose: Classes that can manage running of external processes,
|
|
|
|
# either consecutively, simultaneously, or both, and can
|
|
|
|
# log the output of those jobs
|
|
|
|
#
|
|
|
|
# Author: Robin Dunn
|
|
|
|
#
|
|
|
|
# Created: 05-Nov-2004
|
|
|
|
# RCS-ID: $Id$
|
|
|
|
# Copyright: (c) 2004 by Total Control Software
|
|
|
|
# Licence: wxWindows license
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import signal
|
|
|
|
import select
|
2006-09-02 18:00:06 -04:00
|
|
|
import time
|
|
|
|
|
2004-11-08 18:55:25 -05:00
|
|
|
from subprocess import Popen, PIPE, STDOUT
|
|
|
|
|
|
|
|
|
2006-09-02 18:00:06 -04:00
|
|
|
__all__ = ["Job", "Task", "TaskRunner", "TaskRunnerThread"]
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
2006-02-26 22:25:48 -05:00
|
|
|
# For environment settings
|
|
|
|
class Config:
|
|
|
|
def asDict(self):
|
|
|
|
return self.__dict__.copy()
|
|
|
|
|
|
|
|
def write(self, filename="config", outfile=None):
|
|
|
|
if outfile is None:
|
|
|
|
f = file(filename, "w")
|
|
|
|
else:
|
|
|
|
f = outfile
|
|
|
|
for k, v in self.__dict__.items():
|
|
|
|
f.write('%s="%s"\n' % (k, v))
|
|
|
|
|
|
|
|
def read(self, filename="config"):
|
|
|
|
myfile = open(filename, "r")
|
|
|
|
for line in myfile.readlines():
|
|
|
|
line = line.strip()
|
|
|
|
if len(line) > 0 and line[0] == "#":
|
|
|
|
continue # it's a comment, move on
|
|
|
|
data = line.split("=")
|
|
|
|
if len(data) == 2:
|
2006-07-05 01:23:28 -04:00
|
|
|
self.__dict__[data[0].strip()] = data[1].strip()
|
2006-02-26 22:25:48 -05:00
|
|
|
myfile.close()
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
class Job(object):
|
|
|
|
"""
|
|
|
|
Each Job is a monitor wrapped around an externally executing
|
|
|
|
process. It handles starting the process, polling if it is still
|
|
|
|
running, reading and logging it's output, and killing it if
|
|
|
|
needed.
|
|
|
|
"""
|
|
|
|
|
|
|
|
LOGBASE="."
|
|
|
|
|
2006-09-02 18:00:06 -04:00
|
|
|
def __init__(self, label, command, args=[], env=os.environ, verbose=True):
|
2004-11-08 18:55:25 -05:00
|
|
|
self.label = label
|
2006-02-26 22:25:48 -05:00
|
|
|
self.command = command
|
2004-11-08 18:55:25 -05:00
|
|
|
self.args = args
|
2006-02-26 22:25:48 -05:00
|
|
|
self.env = env
|
2004-11-08 18:55:25 -05:00
|
|
|
self.proc = None
|
2006-09-02 18:00:06 -04:00
|
|
|
self.startTime = None
|
|
|
|
self.stopTime = None
|
|
|
|
self.verbose = verbose
|
|
|
|
self.label = label
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
def start(self):
|
2006-02-26 22:25:48 -05:00
|
|
|
self.proc = Popen([self.command] + self.args, # the command and args to execute
|
|
|
|
stdout=PIPE, stderr=STDOUT, env=self.env,
|
|
|
|
bufsize=0 # line-buffered
|
2004-11-08 18:55:25 -05:00
|
|
|
)
|
2006-09-02 18:00:06 -04:00
|
|
|
self.startTime = time.time()
|
|
|
|
if self.label:
|
|
|
|
if not os.path.exists(self.LOGBASE):
|
|
|
|
os.makedirs(self.LOGBASE)
|
|
|
|
self.log = file("%s/%s.log" % (self.LOGBASE, self.label), "w", 0)
|
|
|
|
|
2004-11-08 18:55:25 -05:00
|
|
|
# put the file in non-blocking mode
|
|
|
|
#flags = fcntl.fcntl (self.proc.stdout, fcntl.F_GETFL, 0)
|
|
|
|
#flags = flags | os.O_NONBLOCK
|
|
|
|
#fcntl.fcntl (self.proc.stdout, fcntl.F_SETFL, flags)
|
|
|
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
if self.proc is not None and self.proc.returncode is None:
|
|
|
|
os.kill(self.proc.pid, signal.SIGTERM)
|
|
|
|
self.logLines()
|
2006-09-02 18:00:06 -04:00
|
|
|
self.stopTime = time.time()
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
|
|
|
|
def fileno(self):
|
|
|
|
if self.proc is not None:
|
|
|
|
return self.proc.stdout.fileno()
|
|
|
|
else:
|
|
|
|
return -1
|
|
|
|
|
2006-09-02 18:00:06 -04:00
|
|
|
def elapsedTime(self):
|
|
|
|
now = self.stopTime
|
|
|
|
if not now:
|
|
|
|
now = time.time()
|
|
|
|
elapsed_time = now-self.startTime
|
|
|
|
mins = elapsed_time/60
|
|
|
|
hours = mins/60
|
|
|
|
seconds = (elapsed_time - mins) % 60
|
|
|
|
return "%d:%d:%d" % (hours, mins, seconds)
|
|
|
|
|
2004-11-08 18:55:25 -05:00
|
|
|
def logLines(self):
|
|
|
|
if self.proc is not None:
|
|
|
|
while self.linesAvailable():
|
|
|
|
line = self.proc.stdout.readline()
|
|
|
|
if not line: break
|
|
|
|
if self.label:
|
|
|
|
self.log.write(line)
|
|
|
|
line = "** %s: %s" % (self.label, line)
|
2006-09-02 18:00:06 -04:00
|
|
|
if self.verbose:
|
|
|
|
sys.stdout.write(line)
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
|
|
|
|
def linesAvailable(self):
|
|
|
|
if self.proc is None:
|
|
|
|
return False
|
|
|
|
ind, outd, err = select.select([self], [], [], 0)
|
|
|
|
if ind:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def finished(self):
|
|
|
|
if self.proc is None:# or self.linesAvailable():
|
|
|
|
return False
|
|
|
|
return self.proc.poll() is not None
|
|
|
|
|
|
|
|
|
|
|
|
def wait(self):
|
|
|
|
if self.proc is None: return None
|
|
|
|
return self.proc.wait()
|
|
|
|
|
|
|
|
|
|
|
|
def poll(self):
|
|
|
|
if self.proc is None: return None
|
|
|
|
return self.proc.poll()
|
|
|
|
|
|
|
|
|
|
|
|
def returnCode(self):
|
|
|
|
if self.proc is None: return None
|
|
|
|
return self.proc.returncode
|
|
|
|
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
|
|
class Task(object):
|
|
|
|
"""
|
|
|
|
This class helps manage the running of a Task, which is a simply a
|
|
|
|
sequence of one or more Jobs, where subesquent jobs are not
|
|
|
|
started until prior ones are completed.
|
|
|
|
"""
|
|
|
|
def __init__(self, jobs=[]):
|
|
|
|
if type(jobs) != list:
|
|
|
|
jobs = [jobs]
|
|
|
|
self.jobs = jobs[:]
|
|
|
|
self.active = 0
|
|
|
|
|
|
|
|
def append(self, job):
|
|
|
|
self.jobs.append(job)
|
|
|
|
|
|
|
|
def activeJob(self):
|
|
|
|
if self.active > len(self.jobs)-1:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return self.jobs[self.active]
|
|
|
|
|
|
|
|
def next(self):
|
|
|
|
self.active += 1
|
|
|
|
if self.active < len(self.jobs):
|
|
|
|
self.jobs[self.active].start()
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
|
|
class TaskRunner(object):
|
|
|
|
"""
|
2006-09-02 18:00:06 -04:00
|
|
|
Manages the running of multiple tasks. Name can be used to identify
|
|
|
|
a specific TaskRunner instance when reporting information back to the user.
|
2004-11-08 18:55:25 -05:00
|
|
|
"""
|
2006-09-02 18:00:06 -04:00
|
|
|
def __init__(self, tasks=[], name="TaskRunner Tasks"):
|
2004-11-08 18:55:25 -05:00
|
|
|
if type(tasks) != list:
|
|
|
|
tasks = [tasks]
|
|
|
|
self.tasks = tasks[:]
|
2006-09-02 18:00:06 -04:00
|
|
|
self.name = name
|
|
|
|
self.rc = 0
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
def append(self, task):
|
|
|
|
self.tasks.append(task)
|
2006-09-02 18:00:06 -04:00
|
|
|
|
|
|
|
def errorOccurred(self):
|
|
|
|
"""
|
|
|
|
Only used for threaded TR instances. Once all TR tasks have completed,
|
|
|
|
we'll want to check to make sure there were no errors in the process.
|
|
|
|
"""
|
|
|
|
return self.rc != 0
|
|
|
|
|
2004-11-08 18:55:25 -05:00
|
|
|
def run(self):
|
|
|
|
# start all the active jobs
|
|
|
|
for task in self.tasks:
|
|
|
|
task.activeJob().start()
|
|
|
|
|
|
|
|
try:
|
|
|
|
# loop, getting output from the jobs, etc.
|
|
|
|
while True:
|
|
|
|
# get all active Jobs
|
|
|
|
jobs = [t.activeJob() for t in self.tasks if t.activeJob()]
|
|
|
|
if not jobs:
|
|
|
|
break
|
|
|
|
|
|
|
|
# wait for a job to have output ready, then log it
|
|
|
|
input, output, err = select.select(jobs, [], [], 1)
|
|
|
|
for job in input:
|
|
|
|
job.logLines()
|
|
|
|
|
|
|
|
# check for finished jobs
|
|
|
|
for task in self.tasks:
|
|
|
|
job = task.activeJob()
|
|
|
|
if job and job.finished():
|
|
|
|
if job.returnCode() != 0:
|
|
|
|
rc = job.returnCode()
|
2007-01-21 02:03:55 -05:00
|
|
|
print "'%s' JOB RETURNED FAILURE CODE! (%d)" % (job.label, rc)
|
2006-09-02 18:00:06 -04:00
|
|
|
self.rc = rc
|
2004-11-08 18:55:25 -05:00
|
|
|
self.stopAllJobs()
|
|
|
|
return rc
|
|
|
|
else:
|
|
|
|
task.next()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print "STOPPING JOBS..."
|
|
|
|
self.stopAllJobs()
|
2004-11-08 22:39:32 -05:00
|
|
|
return 1
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
except:
|
|
|
|
print "Unknown exception..."
|
|
|
|
self.stopAllJobs()
|
|
|
|
raise
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
def stopAllJobs(self):
|
|
|
|
for task in self.tasks:
|
|
|
|
job = task.activeJob()
|
|
|
|
if job:
|
|
|
|
job.stop()
|
|
|
|
|
2006-09-02 18:00:06 -04:00
|
|
|
|
|
|
|
import threading
|
|
|
|
|
|
|
|
class TaskRunnerThread(threading.Thread):
|
|
|
|
def __init__(self, taskRunner, callback=None):
|
|
|
|
self.taskRunner = taskRunner
|
|
|
|
self.startTime = None
|
|
|
|
self.stopTime = None
|
|
|
|
self.callback = callback
|
|
|
|
threading.Thread.__init__ ( self )
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
self.startTime = time.time()
|
|
|
|
self.taskRunner.run()
|
|
|
|
self.stopTime = time.time()
|
|
|
|
#if self.callback:
|
|
|
|
# self.callback
|
|
|
|
|
|
|
|
def elapsedTime(self):
|
|
|
|
now = self.stopTime
|
|
|
|
if not now:
|
|
|
|
now = time.time()
|
|
|
|
elapsed_time = now-self.startTime
|
|
|
|
mins = elapsed_time/60
|
|
|
|
hours = mins/60
|
|
|
|
seconds = (elapsed_time - mins) % 60
|
|
|
|
return "%d:%d:%d" % (hours, mins, seconds)
|
|
|
|
|
|
|
|
def totalTime(self):
|
|
|
|
if self.stopTime:
|
|
|
|
return self.elapsedTime()
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2004-11-08 18:55:25 -05:00
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
2006-09-02 18:00:06 -04:00
|
|
|
j1 = Job("label1", "./tmp/job-1.py", ["TEST-1"])
|
|
|
|
j2 = Job("label2", "./tmp/job-2.sh", ["TEST-2"])
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
t1 = Task()
|
|
|
|
t1.append(j1)
|
|
|
|
t1.append(j2)
|
|
|
|
|
2006-09-02 18:00:06 -04:00
|
|
|
j3 = Job("task2a", "./tmp/job-1.py", ["TASK-2a"])
|
|
|
|
j4 = Job("task2b", "./tmp/job-2.sh", ["TASK-2b"])
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
t2 = Task()
|
|
|
|
t2.append(j4)
|
|
|
|
t2.append(j3)
|
|
|
|
|
2006-09-02 18:00:06 -04:00
|
|
|
t3 = Task([Job("error", "./tmp/job-3.sh", ["TASK-3"])])
|
2004-11-08 18:55:25 -05:00
|
|
|
|
|
|
|
tr = TaskRunner()
|
|
|
|
tr.append(t1)
|
|
|
|
tr.append(t2)
|
|
|
|
tr.append(t3)
|
|
|
|
|
|
|
|
for task in tr.tasks:
|
|
|
|
for job in task.jobs:
|
|
|
|
print job.label
|
|
|
|
|
|
|
|
print tr.run()
|
|
|
|
|