# Copyright (C) 2013 Intel Corporation # # Released under the MIT license (see COPYING.MIT) # Provides a class for setting up ssh connections, # running commands and copying files to/from a target. # It's used by testimage.bbclass and tests in lib/oeqa/runtime. import subprocess import time import os import select class SSHProcess(object): def __init__(self, **options): self.defaultopts = { "stdout": subprocess.PIPE, "stderr": subprocess.STDOUT, "stdin": None, "shell": False, "bufsize": -1, "preexec_fn": os.setsid, } self.options = dict(self.defaultopts) self.options.update(options) self.status = None self.output = None self.process = None self.starttime = None self.logfile = None # Unset DISPLAY which means we won't trigger SSH_ASKPASS env = os.environ.copy() if "DISPLAY" in env: del env['DISPLAY'] self.options['env'] = env def log(self, msg): if self.logfile: with open(self.logfile, "a") as f: f.write("%s" % msg) def _run(self, command, timeout=None, logfile=None): self.logfile = logfile self.starttime = time.time() output = '' self.process = subprocess.Popen(command, **self.options) if timeout: endtime = self.starttime + timeout eof = False while time.time() < endtime and not eof: if select.select([self.process.stdout], [], [], 5)[0] != []: data = os.read(self.process.stdout.fileno(), 1024) if not data: self.process.stdout.close() eof = True else: output += data self.log(data) endtime = time.time() + timeout # process hasn't returned yet if not eof: self.process.terminate() time.sleep(5) try: self.process.kill() except OSError: pass lastline = "\nProcess killed - no output for %d seconds. Total running time: %d seconds." % (timeout, time.time() - self.starttime) self.log(lastline) output += lastline else: output = self.process.communicate()[0] self.log(output.rstrip()) self.status = self.process.wait() self.output = output.rstrip() def run(self, command, timeout=None, logfile=None): try: self._run(command, timeout, logfile) except: # Need to guard against a SystemExit or other exception occuring whilst running # and ensure we don't leave a process behind. if self.process.poll() is None: self.process.kill() self.status = self.process.wait() raise return (self.status, self.output) class SSHControl(object): def __init__(self, ip, logfile=None, timeout=300, user='root', port=None): self.ip = ip self.defaulttimeout = timeout self.ignore_status = True self.logfile = logfile self.user = user self.ssh_options = [ '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-o', 'LogLevel=ERROR' ] self.ssh = ['ssh', '-l', self.user ] + self.ssh_options self.scp = ['scp'] + self.ssh_options if port: self.ssh = self.ssh + [ '-p', port ] self.scp = self.scp + [ '-P', port ] def log(self, msg): if self.logfile: with open(self.logfile, "a") as f: f.write("%s\n" % msg) def _internal_run(self, command, timeout=None, ignore_status = True): self.log("[Running]$ %s" % " ".join(command)) proc = SSHProcess() status, output = proc.run(command, timeout, logfile=self.logfile) self.log("[Command returned '%d' after %.2f seconds]" % (status, time.time() - proc.starttime)) if status and not ignore_status: raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, status, output)) return (status, output) def run(self, command, timeout=None): """ command - ssh command to run timeout= - kill command if there is no output after seconds timeout=None - kill command if there is no output after a default value seconds timeout=0 - no timeout, let command run until it returns """ # We need to source /etc/profile for a proper PATH on the target command = self.ssh + [self.ip, ' . /etc/profile; ' + command] if timeout is None: return self._internal_run(command, self.defaulttimeout, self.ignore_status) if timeout == 0: return self._internal_run(command, None, self.ignore_status) return self._internal_run(command, timeout, self.ignore_status) def copy_to(self, localpath, remotepath): command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)] return self._internal_run(command, ignore_status=False) def copy_from(self, remotepath, localpath): command = self.scp + ['%s@%s:%s' % (self.user, self.ip, remotepath), localpath] return self._internal_run(command, ignore_status=False)