diff options
Diffstat (limited to 'meta/lib/oeqa/utils/sshcontrol.py')
-rw-r--r-- | meta/lib/oeqa/utils/sshcontrol.py | 138 |
1 files changed, 138 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..1c81795a87 --- /dev/null +++ b/meta/lib/oeqa/utils/sshcontrol.py | |||
@@ -0,0 +1,138 @@ | |||
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 | self.logfile = None | ||
33 | |||
34 | def log(self, msg): | ||
35 | if self.logfile: | ||
36 | with open(self.logfile, "a") as f: | ||
37 | f.write("%s" % msg) | ||
38 | |||
39 | def run(self, command, timeout=None, logfile=None): | ||
40 | self.logfile = logfile | ||
41 | self.starttime = time.time() | ||
42 | output = '' | ||
43 | self.process = subprocess.Popen(command, **self.options) | ||
44 | if timeout: | ||
45 | endtime = self.starttime + timeout | ||
46 | eof = False | ||
47 | while time.time() < endtime and not eof: | ||
48 | if select.select([self.process.stdout], [], [], 5)[0] != []: | ||
49 | data = os.read(self.process.stdout.fileno(), 1024) | ||
50 | if not data: | ||
51 | self.process.stdout.close() | ||
52 | eof = True | ||
53 | else: | ||
54 | output += data | ||
55 | self.log(data) | ||
56 | endtime = time.time() + timeout | ||
57 | |||
58 | |||
59 | # process hasn't returned yet | ||
60 | if not eof: | ||
61 | self.process.terminate() | ||
62 | time.sleep(5) | ||
63 | try: | ||
64 | self.process.kill() | ||
65 | except OSError: | ||
66 | pass | ||
67 | lastline = "\nProcess killed - no output for %d seconds. Total running time: %d seconds." % (timeout, time.time() - self.starttime) | ||
68 | self.log(lastline) | ||
69 | output += lastline | ||
70 | else: | ||
71 | output = self.process.communicate()[0] | ||
72 | self.log(output.rstrip()) | ||
73 | |||
74 | self.status = self.process.wait() | ||
75 | self.output = output.rstrip() | ||
76 | return (self.status, self.output) | ||
77 | |||
78 | |||
79 | class SSHControl(object): | ||
80 | def __init__(self, ip, logfile=None, timeout=300, user='root', port=None): | ||
81 | self.ip = ip | ||
82 | self.defaulttimeout = timeout | ||
83 | self.ignore_status = True | ||
84 | self.logfile = logfile | ||
85 | self.user = user | ||
86 | self.ssh_options = [ | ||
87 | '-o', 'UserKnownHostsFile=/dev/null', | ||
88 | '-o', 'StrictHostKeyChecking=no', | ||
89 | '-o', 'LogLevel=ERROR' | ||
90 | ] | ||
91 | self.ssh = ['ssh', '-l', self.user ] + self.ssh_options | ||
92 | self.scp = ['scp'] + self.ssh_options | ||
93 | if port: | ||
94 | self.ssh = self.ssh + [ '-p', port ] | ||
95 | self.scp = self.scp + [ '-P', port ] | ||
96 | |||
97 | def log(self, msg): | ||
98 | if self.logfile: | ||
99 | with open(self.logfile, "a") as f: | ||
100 | f.write("%s\n" % msg) | ||
101 | |||
102 | def _internal_run(self, command, timeout=None, ignore_status = True): | ||
103 | self.log("[Running]$ %s" % " ".join(command)) | ||
104 | |||
105 | proc = SSHProcess() | ||
106 | status, output = proc.run(command, timeout, logfile=self.logfile) | ||
107 | |||
108 | self.log("[Command returned '%d' after %.2f seconds]" % (status, time.time() - proc.starttime)) | ||
109 | |||
110 | if status and not ignore_status: | ||
111 | raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, status, output)) | ||
112 | |||
113 | return (status, output) | ||
114 | |||
115 | def run(self, command, timeout=None): | ||
116 | """ | ||
117 | command - ssh command to run | ||
118 | timeout=<val> - kill command if there is no output after <val> seconds | ||
119 | timeout=None - kill command if there is no output after a default value seconds | ||
120 | timeout=0 - no timeout, let command run until it returns | ||
121 | """ | ||
122 | |||
123 | # We need to source /etc/profile for a proper PATH on the target | ||
124 | command = self.ssh + [self.ip, ' . /etc/profile; ' + command] | ||
125 | |||
126 | if timeout is None: | ||
127 | return self._internal_run(command, self.defaulttimeout, self.ignore_status) | ||
128 | if timeout == 0: | ||
129 | return self._internal_run(command, None, self.ignore_status) | ||
130 | return self._internal_run(command, timeout, self.ignore_status) | ||
131 | |||
132 | def copy_to(self, localpath, remotepath): | ||
133 | command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)] | ||
134 | return self._internal_run(command, ignore_status=False) | ||
135 | |||
136 | def copy_from(self, remotepath, localpath): | ||
137 | command = self.scp + ['%s@%s:%s' % (self.user, self.ip, remotepath), localpath] | ||
138 | return self._internal_run(command, ignore_status=False) | ||