1 # Copyright 2015 Intel Corporation.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 """Task management helper functions and classes.
27 from conf import settings
31 _MY_ENCODING = locale.getdefaultlocale()[1]
34 """Get stdout value for ``subprocess`` calls.
38 if settings.getValue('VERBOSITY') != 'debug':
39 stdout = open(os.devnull, 'wb')
44 def run_task(cmd, logger, msg=None, check_error=False):
45 """Run task, report errors and log overall status.
47 Run given task using ``subprocess.Popen``. Log the commands
48 used and any errors generated. Prints stdout to screen if
49 in verbose mode and returns it regardless. Prints stderr to
52 :param cmd: Exact command to be executed
53 :param logger: Logger to write details to
54 :param msg: Message to be shown to user
55 :param check_error: Throw exception on error
57 :returns: (stdout, stderr)
59 def handle_error(exception):
60 """Handle errors by logging and optionally raising an exception.
63 'Unable to execute %(cmd)s. Exception: %(exception)s',
64 {'cmd': ' '.join(cmd), 'exception': exception})
74 logger.debug('%s%s', CMD_PREFIX, ' '.join(cmd))
77 proc = subprocess.Popen(
78 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
81 reads = [proc.stdout.fileno(), proc.stderr.fileno()]
82 ret = select.select(reads, [], [])
85 if file_d == proc.stdout.fileno():
86 line = proc.stdout.readline()
87 if settings.getValue('VERBOSITY') == 'debug':
88 sys.stdout.write(line.decode(_MY_ENCODING))
90 if file_d == proc.stderr.fileno():
91 line = proc.stderr.readline()
92 sys.stderr.write(line.decode(_MY_ENCODING))
95 if proc.poll() is not None:
101 ex = subprocess.CalledProcessError(proc.returncode, cmd, stderr)
104 return ('\n'.join(sout.decode(_MY_ENCODING).strip() for sout in stdout),
105 ('\n'.join(sout.decode(_MY_ENCODING).strip() for sout in stderr)))
107 def run_background_task(cmd, logger, msg):
108 """Run task in background and log when started.
110 Run given task using ``subprocess.Popen``. Log the command
111 used. Print stdout to screen if in verbose mode. Prints stderr
114 :param cmd: Exact command to be executed
115 :param logger: Logger to write details to
116 :param msg: Message to be shown to user
118 :returns: Process PID
121 logger.debug('%s%s', CMD_PREFIX, ' '.join(cmd))
123 proc = subprocess.Popen(cmd, stdout=_get_stdout(), bufsize=0)
128 def run_interactive_task(cmd, logger, msg):
129 """Run a task interactively and log when started.
131 Run given task using ``pexpect.spawn``. Log the command used.
132 Performs neither validation of the process - if the process
133 successfully started or is still running - nor killing of the
134 process. The user must do both.
136 :param cmd: Exact command to be executed
137 :param logger: Logger to write details to
138 :param msg: Message to be shown to user
140 :returns: ``pexpect.child`` object
143 logger.debug('%s%s', CMD_PREFIX, cmd)
144 child = pexpect.spawnu(cmd)
146 if settings.getValue('VERBOSITY') == 'debug':
147 child.logfile_read = sys.stdout
152 class Process(object):
153 """Control an instance of a long-running process.
155 This is basically a context-manager wrapper around the
156 ``run_interactive_task`` function above (with some extra helper
162 _logger = logging.getLogger(__name__)
165 _proc_name = 'unnamed process'
166 _relinquish_thread = None
171 """Start process instance using context manager.
176 def __exit__(self, type_, value, traceback):
177 """Shutdown process instance.
184 """Start process instance.
186 self._start_process()
187 if self._timeout > 0:
188 self._expect_process()
190 def _start_process(self):
191 """Start process instance.
193 cmd = ' '.join(settings.getValue('SHELL_CMD') +
194 ['"%s"' % ' '.join(self._cmd)])
196 self._child = run_interactive_task(cmd, self._logger,
197 'Starting %s...' % self._proc_name)
198 self._child.logfile = open(self._logfile, 'w')
200 def expect(self, msg, timeout=None):
201 """Expect string from process.
203 Expect string and die if not received.
205 :param msg: String to expect.
206 :param timeout: Time to wait for string.
210 self._expect_process(msg, timeout)
212 def _expect_process(self, msg=None, timeout=None):
213 """Expect string from process.
218 timeout = self._timeout
220 # we use exceptions rather than catching conditions in ``expect`` list
221 # as we want to fail catastrophically after handling; there is likely
222 # little we can do from within the scripts to fix issues such as
223 # hugepages not being mounted
225 self._child.expect([msg], timeout=timeout)
226 except pexpect.EOF as exc:
227 self._logger.critical(
228 'An error occurred. Please check the logs (%s) for more'
229 ' information. Exiting...', self._logfile)
231 except pexpect.TIMEOUT as exc:
232 self._logger.critical(
233 'Failed to execute in \'%d\' seconds. Please check the logs'
234 ' (%s) for more information. Exiting...',
235 timeout, self._logfile)
238 except (Exception, KeyboardInterrupt) as exc:
239 self._logger.critical('General exception raised. Exiting...')
244 """Kill process instance if it is alive.
246 if self._child and self._child.isalive():
247 run_task(['sudo', 'kill', '-2', str(self._child.pid)],
250 if self.is_relinquished():
251 self._relinquish_thread.join()
254 'Log available at %s', self._logfile)
256 def is_relinquished(self):
257 """Returns True if process is relinquished.
259 If relinquished the process is no longer controllable and can
262 :returns: True if process is relinquished, else False.
264 return self._relinquish_thread
266 def is_running(self):
267 """Returns True if process is running.
269 :returns: True if process is running, else False
271 return self._child is not None
273 def _affinitize_pid(self, core, pid):
274 """Affinitize a process with ``pid`` to ``core``.
276 :param core: Core to affinitize process to.
277 :param pid: Process ID to affinitize.
281 run_task(['sudo', 'taskset', '-c', '-p', str(core),
285 def affinitize(self, core):
286 """Affinitize process to a specific ``core``.
288 :param core: Core to affinitize process to.
292 self._logger.info('Affinitizing process')
294 if self._child and self._child.isalive():
295 self._affinitize_pid(core, self._child.pid)
297 class ContinueReadPrintLoop(threading.Thread):
298 """Thread to read output from child and log.
300 Taken from: https://github.com/pexpect/pexpect/issues/90
302 def __init__(self, child):
304 threading.Thread.__init__(self)
309 self.child.read_nonblocking()
310 except (pexpect.EOF, pexpect.TIMEOUT):
313 def relinquish(self):
314 """Relinquish control of process.
316 Give up control of application in order to ensure logging
317 continues for the application. After relinquishing control it
318 will no longer be possible to :func:`expect` anything.
320 This works around an issue described here:
322 https://github.com/pexpect/pexpect/issues/90
324 It is hoped that future versions of pexpect will avoid this
327 self._relinquish_thread = self.ContinueReadPrintLoop(self._child)
328 self._relinquish_thread.start()
331 class CustomProcess(Process):
332 """An sample implementation of ``Process``.
334 This is essentially a more detailed version of the
335 ``run_interactive_task`` function that checks for process execution
336 and kills the process (assuming use of the context manager).
338 def __init__(self, cmd, timeout, logfile, expect, name):
339 """Initialise process state.
341 :param cmd: Command to execute.
342 :param timeout: Time to wait for ``expect``.
343 :param logfile: Path to logfile.
344 :param expect: String to expect indicating startup. This is a
345 regex and should be escaped as such.
346 :param name: Name of process to use in logs.
351 self._logfile = logfile
352 self._expect = expect
353 self._proc_name = name
354 self._timeout = timeout