diff options
author | Stefan Stanacar <stefanx.stanacar@intel.com> | 2014-01-28 15:55:32 +0200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2014-02-02 11:22:10 +0000 |
commit | 1dd7fd6b615a87c2e88a0f18bc7efde7afdbd574 (patch) | |
tree | 89f0ed10ebef56ad68eaec97a66b0e47ff6e00d4 | |
parent | e54c1ad651886cd7e7c4cc395dbe78dcda7c5347 (diff) | |
download | poky-1dd7fd6b615a87c2e88a0f18bc7efde7afdbd574.tar.gz |
oeqa/utils: sshcontrol: rewrite the SSHControl class
Split the class in two, one to handle the process and the
timeout based on output and one for the actual ssh/scp commands.
The ssh/scp methods now use the same run method.
It does the same thing as before but:
- it looks cleaner.
- adds support for using a different user than root
- optionally, raises an exception when exit code != 0
(that's useful for code outside of tests, where you wouldn't want
to check the return code every time as the tests do)
(From OE-Core rev: bb14a7598d3c0636dc249f719bde0d9d65b2694d)
Signed-off-by: Stefan Stanacar <stefanx.stanacar@intel.com>
Signed-off-by: Saul Wold <sgw@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r-- | meta/lib/oeqa/utils/sshcontrol.py | 175 |
1 files changed, 92 insertions, 83 deletions
diff --git a/meta/lib/oeqa/utils/sshcontrol.py b/meta/lib/oeqa/utils/sshcontrol.py index 891325048c..d355d5e8e9 100644 --- a/meta/lib/oeqa/utils/sshcontrol.py +++ b/meta/lib/oeqa/utils/sshcontrol.py | |||
@@ -11,108 +11,117 @@ import time | |||
11 | import os | 11 | import os |
12 | import select | 12 | import select |
13 | 13 | ||
14 | class SSHControl(object): | ||
15 | 14 | ||
16 | def __init__(self, ip=None, timeout=300, logfile=None, port=None): | 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): | ||
17 | self.ip = ip | 69 | self.ip = ip |
18 | self.timeout = timeout | 70 | self.defaulttimeout = timeout |
19 | self._starttime = None | 71 | self.ignore_status = True |
20 | self._out = '' | ||
21 | self._ret = 126 | ||
22 | self.logfile = logfile | 72 | self.logfile = logfile |
73 | self.user = user | ||
23 | self.ssh_options = [ | 74 | self.ssh_options = [ |
24 | '-o', 'UserKnownHostsFile=/dev/null', | 75 | '-o', 'UserKnownHostsFile=/dev/null', |
25 | '-o', 'StrictHostKeyChecking=no', | 76 | '-o', 'StrictHostKeyChecking=no', |
26 | '-o', 'LogLevel=ERROR' | 77 | '-o', 'LogLevel=ERROR' |
27 | ] | 78 | ] |
28 | self.ssh = ['ssh', '-l', 'root'] + self.ssh_options | 79 | self.ssh = ['ssh', '-l', self.user ] + self.ssh_options |
80 | self.scp = ['scp'] + self.ssh_options | ||
29 | if port: | 81 | if port: |
30 | self.ssh = self.ssh + ['-p', str(port)] | 82 | self.ssh = self.ssh + [ '-p', port ] |
83 | self.scp = self.scp + [ '-P', port ] | ||
31 | 84 | ||
32 | def log(self, msg): | 85 | def log(self, msg): |
33 | if self.logfile: | 86 | if self.logfile: |
34 | with open(self.logfile, "a") as f: | 87 | with open(self.logfile, "a") as f: |
35 | f.write("%s\n" % msg) | 88 | f.write("%s\n" % msg) |
36 | 89 | ||
37 | def _internal_run(self, cmd): | 90 | def _internal_run(self, command, timeout=None, ignore_status = True): |
38 | # We need this for a proper PATH | ||
39 | cmd = ". /etc/profile; " + cmd | ||
40 | command = self.ssh + [self.ip, cmd] | ||
41 | self.log("[Running]$ %s" % " ".join(command)) | 91 | self.log("[Running]$ %s" % " ".join(command)) |
42 | self._starttime = time.time() | ||
43 | # ssh hangs without os.setsid | ||
44 | proc = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, preexec_fn=os.setsid) | ||
45 | return proc | ||
46 | |||
47 | def run(self, cmd, timeout=None): | ||
48 | """Run cmd and get it's return code and output. | ||
49 | Let it run for timeout seconds and then terminate/kill it, | ||
50 | if time is 0 will let cmd run until it finishes. | ||
51 | Time can be passed to here or can be set per class instance.""" | ||
52 | |||
53 | if self.ip: | ||
54 | sshconn = self._internal_run(cmd) | ||
55 | else: | ||
56 | raise Exception("Remote IP hasn't been set, I can't run ssh without one.") | ||
57 | 92 | ||
58 | # run the command forever | 93 | proc = SSHProcess() |
59 | if timeout == 0: | 94 | status, output = proc.run(command, timeout) |
60 | output = sshconn.communicate()[0] | ||
61 | else: | ||
62 | # use the default timeout | ||
63 | if timeout is None: | ||
64 | tdelta = self.timeout | ||
65 | # use the specified timeout | ||
66 | else: | ||
67 | tdelta = timeout | ||
68 | endtime = self._starttime + tdelta | ||
69 | output = '' | ||
70 | eof = False | ||
71 | while time.time() < endtime and not eof: | ||
72 | if select.select([sshconn.stdout], [], [], 5)[0] != []: | ||
73 | data = os.read(sshconn.stdout.fileno(), 1024) | ||
74 | if not data: | ||
75 | sshconn.stdout.close() | ||
76 | eof = True | ||
77 | else: | ||
78 | output += data | ||
79 | endtime = time.time() + tdelta | ||
80 | 95 | ||
81 | # process hasn't returned yet | 96 | self.log("%s" % output) |
82 | if not eof: | 97 | self.log("[Command returned '%d' after %.2f seconds]" % (status, time.time() - proc.starttime)) |
83 | sshconn.terminate() | 98 | |
84 | time.sleep(3) | 99 | if status and not ignore_status: |
85 | try: | 100 | raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, status, output)) |
86 | sshconn.kill() | 101 | |
87 | except OSError: | 102 | return (status, output) |
88 | pass | 103 | |
89 | output += "\n[!!! SSH command killed - no output for %d seconds. Total running time: %d seconds." % (tdelta, time.time() - self._starttime) | 104 | def run(self, command, timeout=None): |
90 | 105 | """ | |
91 | self._ret = sshconn.wait() | 106 | command - ssh command to run |
92 | # strip the last LF so we can test the output | 107 | timeout=<val> - kill command if there is no output after <val> seconds |
93 | self._out = output.rstrip() | 108 | timeout=None - kill command if there is no output after a default value seconds |
94 | self.log("%s" % self._out) | 109 | timeout=0 - no timeout, let command run until it returns |
95 | self.log("[SSH command returned after %d seconds]: %s" % (time.time() - self._starttime, self._ret)) | 110 | """ |
96 | return (self._ret, self._out) | 111 | |
97 | 112 | # We need to source /etc/profile for a proper PATH on the target | |
98 | def _internal_scp(self, cmd): | 113 | command = self.ssh + [self.ip, ' . /etc/profile; ' + command] |
99 | cmd = ['scp'] + self.ssh_options + cmd | 114 | |
100 | self.log("[Running SCP]$ %s" % " ".join(cmd)) | 115 | if timeout is None: |
101 | self._starttime = time.time() | 116 | return self._internal_run(command, self.defaulttimeout, self.ignore_status) |
102 | scpconn = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, preexec_fn=os.setsid) | 117 | if timeout == 0: |
103 | out = scpconn.communicate()[0] | 118 | return self._internal_run(command, None, self.ignore_status) |
104 | ret = scpconn.poll() | 119 | return self._internal_run(command, timeout, self.ignore_status) |
105 | self.log("%s" % out) | ||
106 | self.log("[SCP command returned after %d seconds]: %s" % (time.time() - self._starttime, ret)) | ||
107 | if ret != 0: | ||
108 | # we raise an exception so that tests fail in setUp and setUpClass without a need for an assert | ||
109 | raise Exception("Error running %s, output: %s" % ( " ".join(cmd), out)) | ||
110 | return (ret, out) | ||
111 | 120 | ||
112 | def copy_to(self, localpath, remotepath): | 121 | def copy_to(self, localpath, remotepath): |
113 | actualcmd = [localpath, 'root@%s:%s' % (self.ip, remotepath)] | 122 | command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)] |
114 | return self._internal_scp(actualcmd) | 123 | return self._internal_run(command, ignore_status=False) |
115 | 124 | ||
116 | def copy_from(self, remotepath, localpath): | 125 | def copy_from(self, remotepath, localpath): |
117 | actualcmd = ['root@%s:%s' % (self.ip, remotepath), localpath] | 126 | command = self.scp + ['%s@%s:%s' % (self.user, self.ip, remotepath), localpath] |
118 | return self._internal_scp(actualcmd) | 127 | return self._internal_run(command, ignore_status=False) |