summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa
diff options
context:
space:
mode:
authorStefan Stanacar <stefanx.stanacar@intel.com>2014-01-28 15:55:32 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2014-02-02 11:22:10 +0000
commit1dd7fd6b615a87c2e88a0f18bc7efde7afdbd574 (patch)
tree89f0ed10ebef56ad68eaec97a66b0e47ff6e00d4 /meta/lib/oeqa
parente54c1ad651886cd7e7c4cc395dbe78dcda7c5347 (diff)
downloadpoky-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>
Diffstat (limited to 'meta/lib/oeqa')
-rw-r--r--meta/lib/oeqa/utils/sshcontrol.py175
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
11import os 11import os
12import select 12import select
13 13
14class SSHControl(object):
15 14
16 def __init__(self, ip=None, timeout=300, logfile=None, port=None): 15class 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
67class 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)