David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 1 | # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 5 | import logging |
| 6 | import os |
| 7 | import pipes |
| 8 | import threading |
| 9 | |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 10 | |
| 11 | class _HelperThread(threading.Thread): |
| 12 | """Make a thread to run the command in.""" |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 13 | def __init__(self, host, cmd): |
| 14 | super(_HelperThread, self).__init__() |
| 15 | self._host = host |
| 16 | self._cmd = cmd |
| 17 | self._result = None |
| 18 | self.daemon = True |
| 19 | |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 20 | |
| 21 | def run(self): |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 22 | logging.info('Helper thread running: %s', self._cmd) |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 23 | # NB: set ignore_status as we're always terminated w/ pkill |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 24 | self._result = self._host.run(self._cmd, ignore_status=True) |
| 25 | |
| 26 | |
| 27 | @property |
| 28 | def result(self): |
| 29 | """ |
| 30 | @returns string result of running our command if the command has |
| 31 | finished, and None otherwise. |
| 32 | |
| 33 | """ |
| 34 | return self._result |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 35 | |
| 36 | |
| 37 | class Command(object): |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 38 | """ |
| 39 | Encapsulates a command run on a remote machine. |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 40 | |
| 41 | Future work is to have this get the PID (by prepending 'echo $$; |
| 42 | exec' to the command and parsing the output). |
| 43 | |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 44 | """ |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 45 | def __init__(self, host, cmd, pkill_argument=None): |
| 46 | """ |
| 47 | Run a command on a remote host in the background. |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 48 | |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 49 | @param host Host object representing the remote machine. |
| 50 | @param cmd String command to run on the remote machine. |
| 51 | @param pkill_argument String argument to pkill to kill the remote |
| 52 | process. |
| 53 | |
| 54 | """ |
| 55 | if pkill_argument is None: |
| 56 | pkill_argument = os.path.basename(cmd) |
| 57 | self._command_name = pipes.quote(pkill_argument) |
| 58 | self._host = host |
| 59 | self._thread = _HelperThread(self._host, cmd) |
| 60 | self._thread.start() |
| 61 | |
| 62 | |
| 63 | def join(self, signal=None): |
| 64 | """ |
| 65 | Kills the remote command and waits until it dies. Takes an optional |
| 66 | signal argument to control which signal to send the process to be |
| 67 | killed. |
| 68 | |
| 69 | @param signal Signal string to give to pkill (e.g. SIGNAL_INT). |
| 70 | """ |
| 71 | if signal is None: |
| 72 | signal_arg = '' |
| 73 | else: |
| 74 | # In theory, it should be hard to pass something evil for signal if |
| 75 | # we make sure it's an integer before passing it to pkill. |
| 76 | signal_arg = '-' + str(int(signal)) |
| 77 | |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 78 | # Ignore status because the command may have exited already |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 79 | self._host.run("pkill %s %s" % (signal_arg, self._command_name), |
| 80 | ignore_status=True) |
| 81 | self._thread.join() |
| 82 | |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 83 | |
| 84 | def __enter__(self): |
| 85 | return self |
| 86 | |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 87 | |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 88 | def __exit__(self, exception, value, traceback): |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 89 | self.join() |
David Rochberg | 410ea82 | 2011-11-18 11:39:59 -0500 | [diff] [blame] | 90 | return False |
Christopher Wiley | ffffab7 | 2013-03-06 16:14:11 -0800 | [diff] [blame^] | 91 | |
| 92 | |
| 93 | @property |
| 94 | def result(self): |
| 95 | """ |
| 96 | @returns string result of running our command if the command has |
| 97 | finished, and None otherwise. |
| 98 | |
| 99 | """ |
| 100 | return self._thread.result |