diff options
Diffstat (limited to 'meta/lib/oeqa/utils/sshcontrol.py')
-rw-r--r-- | meta/lib/oeqa/utils/sshcontrol.py | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/meta/lib/oeqa/utils/sshcontrol.py b/meta/lib/oeqa/utils/sshcontrol.py new file mode 100644 index 0000000000..d355d5e8e9 --- /dev/null +++ b/meta/lib/oeqa/utils/sshcontrol.py | |||
@@ -0,0 +1,127 @@ | |||
1 | # Copyright (C) 2013 Intel Corporation | ||
2 | # | ||
3 | # Released under the MIT license (see COPYING.MIT) | ||
4 | |||
5 | # Provides a class for setting up ssh connections, | ||
6 | # running commands and copying files to/from a target. | ||
7 | # It's used by testimage.bbclass and tests in lib/oeqa/runtime. | ||
8 | |||
9 | import subprocess | ||
10 | import time | ||
11 | import os | ||
12 | import select | ||
13 | |||
14 | |||
15 | class SSHProcess(object): | ||
16 | def __init__(self, **options): | ||
17 | |||
18 | self.defaultopts = { | ||
19 | "stdout": subprocess.PIPE, | ||
20 | "stderr": subprocess.STDOUT, | ||
21 | "stdin": None, | ||
22 | "shell": False, | ||
23 | "bufsize": -1, | ||
24 | "preexec_fn": os.setsid, | ||
25 | } | ||
26 | self.options = dict(self.defaultopts) | ||
27 | self.options.update(options) | ||
28 | self.status = None | ||
29 | self.output = None | ||
30 | self.process = None | ||
31 | self.starttime = None | ||
32 | |||
33 | def run(self, command, timeout=None): | ||
34 | self.starttime = time.time() | ||
35 | output = '' | ||
36 | self.process = subprocess.Popen(command, **self.options) | ||
37 | if timeout: | ||
38 | endtime = self.starttime + timeout | ||
39 | eof = False | ||
40 | while time.time() < endtime and not eof: | ||
41 | if select.select([self.process.stdout], [], [], 5)[0] != []: | ||
42 | data = os.read(self.process.stdout.fileno(), 1024) | ||
43 | if not data: | ||
44 | self.process.stdout.close() | ||
45 | eof = True | ||
46 | else: | ||
47 | output += data | ||
48 | endtime = time.time() + timeout | ||
49 | |||
50 | # process hasn't returned yet | ||
51 | if not eof: | ||
52 | self.process.terminate() | ||
53 | time.sleep(5) | ||
54 | try: | ||
55 | self.process.kill() | ||
56 | except OSError: | ||
57 | pass | ||
58 | output += "\nProcess killed - no output for %d seconds. Total running time: %d seconds." % (timeout, time.time() - self.starttime) | ||
59 | else: | ||
60 | output = self.process.communicate()[0] | ||
61 | |||
62 | self.status = self.process.wait() | ||
63 | self.output = output.rstrip() | ||
64 | return (self.status, self.output) | ||
65 | |||
66 | |||
67 | class SSHControl(object): | ||
68 | def __init__(self, ip, logfile=None, timeout=300, user='root', port=None): | ||
69 | self.ip = ip | ||
70 | self.defaulttimeout = timeout | ||
71 | self.ignore_status = True | ||
72 | self.logfile = logfile | ||
73 | self.user = user | ||
74 | self.ssh_options = [ | ||
75 | '-o', 'UserKnownHostsFile=/dev/null', | ||
76 | '-o', 'StrictHostKeyChecking=no', | ||
77 | '-o', 'LogLevel=ERROR' | ||
78 | ] | ||
79 | self.ssh = ['ssh', '-l', self.user ] + self.ssh_options | ||
80 | self.scp = ['scp'] + self.ssh_options | ||
81 | if port: | ||
82 | self.ssh = self.ssh + [ '-p', port ] | ||
83 | self.scp = self.scp + [ '-P', port ] | ||
84 | |||
85 | def log(self, msg): | ||
86 | if self.logfile: | ||
87 | with open(self.logfile, "a") as f: | ||
88 | f.write("%s\n" % msg) | ||
89 | |||
90 | def _internal_run(self, command, timeout=None, ignore_status = True): | ||
91 | self.log("[Running]$ %s" % " ".join(command)) | ||
92 | |||
93 | proc = SSHProcess() | ||
94 | status, output = proc.run(command, timeout) | ||
95 | |||
96 | self.log("%s" % output) | ||
97 | self.log("[Command returned '%d' after %.2f seconds]" % (status, time.time() - proc.starttime)) | ||
98 | |||
99 | if status and not ignore_status: | ||
100 | raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, status, output)) | ||
101 | |||
102 | return (status, output) | ||
103 | |||
104 | def run(self, command, timeout=None): | ||
105 | """ | ||
106 | command - ssh command to run | ||
107 | timeout=<val> - kill command if there is no output after <val> seconds | ||
108 | timeout=None - kill command if there is no output after a default value seconds | ||
109 | timeout=0 - no timeout, let command run until it returns | ||
110 | """ | ||
111 | |||
112 | # We need to source /etc/profile for a proper PATH on the target | ||
113 | command = self.ssh + [self.ip, ' . /etc/profile; ' + command] | ||
114 | |||
115 | if timeout is None: | ||
116 | return self._internal_run(command, self.defaulttimeout, self.ignore_status) | ||
117 | if timeout == 0: | ||
118 | return self._internal_run(command, None, self.ignore_status) | ||
119 | return self._internal_run(command, timeout, self.ignore_status) | ||
120 | |||
121 | def copy_to(self, localpath, remotepath): | ||
122 | command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)] | ||
123 | return self._internal_run(command, ignore_status=False) | ||
124 | |||
125 | def copy_from(self, remotepath, localpath): | ||
126 | command = self.scp + ['%s@%s:%s' % (self.user, self.ip, remotepath), localpath] | ||
127 | return self._internal_run(command, ignore_status=False) | ||