summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa/core/target/ssh.py
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oeqa/core/target/ssh.py')
-rw-r--r--meta/lib/oeqa/core/target/ssh.py81
1 files changed, 64 insertions, 17 deletions
diff --git a/meta/lib/oeqa/core/target/ssh.py b/meta/lib/oeqa/core/target/ssh.py
index 461448dbc5..09cdd14c75 100644
--- a/meta/lib/oeqa/core/target/ssh.py
+++ b/meta/lib/oeqa/core/target/ssh.py
@@ -34,12 +34,17 @@ class OESSHTarget(OETarget):
34 self.timeout = timeout 34 self.timeout = timeout
35 self.user = user 35 self.user = user
36 ssh_options = [ 36 ssh_options = [
37 '-o', 'ServerAliveCountMax=2',
38 '-o', 'ServerAliveInterval=30',
37 '-o', 'UserKnownHostsFile=/dev/null', 39 '-o', 'UserKnownHostsFile=/dev/null',
38 '-o', 'StrictHostKeyChecking=no', 40 '-o', 'StrictHostKeyChecking=no',
39 '-o', 'LogLevel=ERROR' 41 '-o', 'LogLevel=ERROR'
40 ] 42 ]
43 scp_options = [
44 '-r'
45 ]
41 self.ssh = ['ssh', '-l', self.user ] + ssh_options 46 self.ssh = ['ssh', '-l', self.user ] + ssh_options
42 self.scp = ['scp'] + ssh_options 47 self.scp = ['scp'] + ssh_options + scp_options
43 if port: 48 if port:
44 self.ssh = self.ssh + [ '-p', port ] 49 self.ssh = self.ssh + [ '-p', port ]
45 self.scp = self.scp + [ '-P', port ] 50 self.scp = self.scp + [ '-P', port ]
@@ -67,7 +72,7 @@ class OESSHTarget(OETarget):
67 72
68 return (status, output) 73 return (status, output)
69 74
70 def run(self, command, timeout=None): 75 def run(self, command, timeout=None, ignore_status=True):
71 """ 76 """
72 Runs command in target. 77 Runs command in target.
73 78
@@ -86,10 +91,9 @@ class OESSHTarget(OETarget):
86 else: 91 else:
87 processTimeout = self.timeout 92 processTimeout = self.timeout
88 93
89 status, output = self._run(sshCmd, processTimeout, True) 94 status, output = self._run(sshCmd, processTimeout, ignore_status)
90 self.logger.debug('Command: %s\nOutput: %s\n' % (command, output)) 95 self.logger.debug('Command: %s\nStatus: %d Output: %s\n' % (command, status, output))
91 if (status == 255) and (('No route to host') in output): 96
92 self.target_dumper.dump_target()
93 return (status, output) 97 return (status, output)
94 98
95 def copyTo(self, localSrc, remoteDst): 99 def copyTo(self, localSrc, remoteDst):
@@ -207,27 +211,41 @@ def SSHCall(command, logger, timeout=None, **opts):
207 def run(): 211 def run():
208 nonlocal output 212 nonlocal output
209 nonlocal process 213 nonlocal process
214 output_raw = b''
210 starttime = time.time() 215 starttime = time.time()
211 process = subprocess.Popen(command, **options) 216 process = subprocess.Popen(command, **options)
217 has_timeout = False
212 if timeout: 218 if timeout:
213 endtime = starttime + timeout 219 endtime = starttime + timeout
214 eof = False 220 eof = False
215 while time.time() < endtime and not eof: 221 os.set_blocking(process.stdout.fileno(), False)
216 logger.debug('time: %s, endtime: %s' % (time.time(), endtime)) 222 while not has_timeout and not eof:
217 try: 223 try:
224 logger.debug('Waiting for process output: time: %s, endtime: %s' % (time.time(), endtime))
218 if select.select([process.stdout], [], [], 5)[0] != []: 225 if select.select([process.stdout], [], [], 5)[0] != []:
219 reader = codecs.getreader('utf-8')(process.stdout, 'ignore') 226 # wait a bit for more data, tries to avoid reading single characters
220 data = reader.read(1024, 4096) 227 time.sleep(0.2)
228 data = process.stdout.read()
221 if not data: 229 if not data:
222 process.stdout.close()
223 eof = True 230 eof = True
224 else: 231 else:
225 output += data 232 output_raw += data
226 logger.debug('Partial data from SSH call: %s' % data) 233 # ignore errors to capture as much as possible
234 logger.debug('Partial data from SSH call:\n%s' % data.decode('utf-8', errors='ignore'))
227 endtime = time.time() + timeout 235 endtime = time.time() + timeout
228 except InterruptedError: 236 except InterruptedError:
237 logger.debug('InterruptedError')
238 continue
239 except BlockingIOError:
240 logger.debug('BlockingIOError')
229 continue 241 continue
230 242
243 if time.time() >= endtime:
244 logger.debug('SSHCall has timeout! Time: %s, endtime: %s' % (time.time(), endtime))
245 has_timeout = True
246
247 process.stdout.close()
248
231 # process hasn't returned yet 249 # process hasn't returned yet
232 if not eof: 250 if not eof:
233 process.terminate() 251 process.terminate()
@@ -235,16 +253,42 @@ def SSHCall(command, logger, timeout=None, **opts):
235 try: 253 try:
236 process.kill() 254 process.kill()
237 except OSError: 255 except OSError:
256 logger.debug('OSError when killing process')
238 pass 257 pass
239 endtime = time.time() - starttime 258 endtime = time.time() - starttime
240 lastline = ("\nProcess killed - no output for %d seconds. Total" 259 lastline = ("\nProcess killed - no output for %d seconds. Total"
241 " running time: %d seconds." % (timeout, endtime)) 260 " running time: %d seconds." % (timeout, endtime))
242 logger.debug('Received data from SSH call %s ' % lastline) 261 logger.debug('Received data from SSH call:\n%s ' % lastline)
243 output += lastline 262 output += lastline
263 process.wait()
244 264
245 else: 265 else:
246 output = process.communicate()[0].decode('utf-8', errors='ignore') 266 output_raw = process.communicate()[0]
247 logger.debug('Data from SSH call: %s' % output.rstrip()) 267
268 output = output_raw.decode('utf-8', errors='ignore')
269 logger.debug('Data from SSH call:\n%s' % output.rstrip())
270
271 # timout or not, make sure process exits and is not hanging
272 if process.returncode == None:
273 try:
274 process.wait(timeout=5)
275 except TimeoutExpired:
276 try:
277 process.kill()
278 except OSError:
279 logger.debug('OSError')
280 pass
281 process.wait()
282
283 if has_timeout:
284 # Version of openssh before 8.6_p1 returns error code 0 when killed
285 # by a signal, when the timeout occurs we will receive a 0 error
286 # code because the process is been terminated and it's wrong because
287 # that value means success, but the process timed out.
288 # Afterwards, from version 8.6_p1 onwards, the returned code is 255.
289 # Fix this behaviour by checking the return code
290 if process.returncode == 0:
291 process.returncode = 255
248 292
249 options = { 293 options = {
250 "stdout": subprocess.PIPE, 294 "stdout": subprocess.PIPE,
@@ -271,6 +315,9 @@ def SSHCall(command, logger, timeout=None, **opts):
271 # whilst running and ensure we don't leave a process behind. 315 # whilst running and ensure we don't leave a process behind.
272 if process.poll() is None: 316 if process.poll() is None:
273 process.kill() 317 process.kill()
318 if process.returncode == None:
319 process.wait()
274 logger.debug('Something went wrong, killing SSH process') 320 logger.debug('Something went wrong, killing SSH process')
275 raise 321 raise
276 return (process.wait(), output.rstrip()) 322
323 return (process.returncode, output.rstrip())