Passed
Push — master ( f283e8...e0fda7 )
by Peter
01:56
created

RunningProgram.get_output()   A

Complexity

Conditions 3

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 3
1
import pexpect
2
import os
3
import time
4
5
from .exceptions import WrongExitStatusException, NestedException, TimeoutException, TerminationException
6
7
import logging
8
logger = logging.getLogger('opensubmitexec')
9
10
11
def kill_longrunning(config):
12
    '''
13
        Terminate everything under the current user account
14
        that has run too long. This is a final safeguard if
15
        the subprocess timeout stuff is not working.
16
        You better have no production servers running also
17
        under the current user account ...
18
    '''
19
    import psutil
20
    ourpid = os.getpid()
21
    username = psutil.Process(ourpid).username
22
    # Check for other processes running under this account
23
    # Take the timeout definition from the config file
24
    timeout = config.getint("Execution", "timeout")
25
    for proc in psutil.process_iter():
26
        if proc.username == username and proc.pid != ourpid:
27
            runtime = time.time() - proc.create_time
28
            logger.debug("This user already runs %u for %u seconds." %
29
                         (proc.pid, runtime))
30
            if runtime > timeout:
31
                logger.debug("Killing %u due to exceeded runtime." % proc.pid)
32
                try:
33
                    proc.kill()
34
                except Exception:
35
                    logger.error("ERROR killing process %d." % proc.pid)
36
37
38
class RunningProgram(pexpect.spawn):
39
    '''
40
    A running program that can be interacted with.
41
42
    This class is a thin wrapper around the functionality
43
    of pexpect (http://pexpect.readthedocs.io/en/stable/overview.html).
44
    '''
45
    job = None
46
    name = None
47
    arguments = None
48
    _spawn = None
49
50
    def get_output(self):
51
        if self._spawn and self._spawn.before:
52
            return str(self._spawn.before, encoding='utf-8')
53
        else:
54
            return ""
55
56
    def get_exitstatus(self):
57
        logger.debug("Exit status is {0}".format(self._spawn.exitstatus))
58
        if self._spawn.exitstatus is None:
59
            logger.debug("Translating non-available exit code to -1.")
60
            return -1
61
        else:
62
            return self._spawn.exitstatus
63
64
    def __init__(self, job, name, arguments=[], timeout=30):
65
        self.job = job
66
        self.name = name
67
        self.arguments = arguments
68
69
        # Allow code to load its own libraries
70
        os.environ["LD_LIBRARY_PATH"] = job.working_dir
71
72
        logger.debug("Spawning '{0}' in {1} with the following arguments:{2}".format(
73
            name,
74
            job.working_dir,
75
            str(arguments)))
76
77
        if name.startswith('./'):
78
            name = name.replace('./', self.job.working_dir)
79
80
        try:
81
            self._spawn = pexpect.spawn(name, arguments,
82
                                        timeout=timeout,
83
                                        cwd=self.job.working_dir)
84
        except Exception as e:
85
            logger.debug("Spawning failed: " + str(e))
86
            raise NestedException(
87
                instance=self, real_exception=e, output=self.get_output())
88
89 View Code Duplication
    def expect(self, pattern, timeout=-1):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
90
        '''
91
        Expect an output pattern from the running program.
92
93
        The default timeout is the one defined on object creation.
94
95
        '''
96
        logger.debug("Expecting output '{0}' from '{1}'".format(
97
            pattern, self.name))
98
        try:
99
            return self._spawn.expect(pattern, timeout)
100
        except pexpect.exceptions.EOF as e:
101
            logger.debug("Raising termination exception.")
102
            raise TerminationException(instance=self, real_exception=e, output=self.get_output())
103
        except pexpect.exceptions.TIMEOUT as e:
104
            logger.debug("Raising timeout exception.")
105
            raise TimeoutException(instance=self, real_exception=e, output=self.get_output())
106
        except Exception as e:
107
            logger.debug("Expecting output failed: " + str(e))
108
            raise NestedException(
109
                instance=self, real_exception=e, output=self.get_output())
110
111 View Code Duplication
    def sendline(self, pattern):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
112
        '''
113
        Send input to the running program.
114
        '''
115
        logger.debug("Sending input '{0}' to '{1}'".format(pattern, self.name))
116
        try:
117
            return self._spawn.sendline(pattern)
118
        except pexpect.exceptions.EOF as e:
119
            logger.debug("Raising termination exception.")
120
            raise TerminationException(instance=self, real_exception=e, output=self.get_output())
121
        except pexpect.exceptions.TIMEOUT as e:
122
            logger.debug("Raising timeout exception.")
123
            raise TimeoutException(instance=self, real_exception=e, output=self.get_output())
124
        except Exception as e:
125
            logger.debug("Sending input failed: " + str(e))
126
            raise NestedException(
127
                instance=self, real_exception=e, output=self.get_output())
128
129
    def expect_end(self):
130
        '''
131
        Wait for the program to finish.
132
        Returns a tuple with the exit code and the output.
133
        '''
134
        logger.debug("Waiting for termination of '{0}'".format(self.name))
135
        try:
136
            # Make sure we fetch the last output bytes.
137
            # Recommendation from the pexpect docs.
138
            self._spawn.expect(pexpect.EOF)
139
            self._spawn.wait()
140
            dircontent = str(os.listdir(self.job.working_dir))
141
            logger.debug("Working directory after execution: " + dircontent)
142
            return self.get_exitstatus(), self.get_output()
143
        except pexpect.exceptions.EOF as e:
144
            logger.debug("Raising termination exception.")
145
            raise TerminationException(instance=self, real_exception=e, output=self.get_output())
146
        except pexpect.exceptions.TIMEOUT as e:
147
            logger.debug("Raising timeout exception.")
148
            raise TimeoutException(instance=self, real_exception=e, output=self.get_output())
149
        except Exception as e:
150
            logger.debug("Waiting for expected program end failed.")
151
            raise NestedException(
152
                instance=self, real_exception=e, output=self.get_output())
153
154
    def expect_exit_status(self, exit_status):
155
        '''
156
        Wait for the program to finish and expect some
157
        exit status. Throws exception otherwise.
158
        '''
159
        self.expect_end()
160
        logger.debug("Checking exit status of '{0}', output so far: {1}".format(
161
            self.name, self.get_output()))
162
        if self._spawn.exitstatus is None:
163
            raise WrongExitStatusException(
164
                instance=self, expected=exit_status, output=self.get_output())
165
166
        if self._spawn.exitstatus is not exit_status:
167
            raise WrongExitStatusException(
168
                instance=self,
169
                expected=exit_status,
170
                got=self._spawn.exitstatus,
171
                output=self.get_output())
172