summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa/utils/qemurunner.py
diff options
context:
space:
mode:
authorRandy Witt <randy.e.witt@linux.intel.com>2015-08-20 13:01:25 -0700
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-08-24 23:47:07 +0100
commitfd164dcc31aa6c60e9834036a7cfcea5411daa3d (patch)
tree5f1b1395e7edbd9ff689fcf48f3baea5a2599c6c /meta/lib/oeqa/utils/qemurunner.py
parent8d05d8a0c46564784463d2a39c68c467714f306d (diff)
downloadpoky-fd164dcc31aa6c60e9834036a7cfcea5411daa3d.tar.gz
qemurunner: Use two serial ports and log console with a thread
qemu can freeze and stop responding if the socket buffer connected to a tcp serial connection fills up. This happens of course when the reader of the serial data doesn't actually read it. This happened in the qemurunner code, because after checking for the "login:" sentinel, data was never again read from the serial connection. This patch solves the potential freeze by adding a thread to continuously read the data from the console and log it. So it also will give a full log of the console, rather than just up to the login prompt. To simplify this patch, another serial port was also added to use for the sole purpose of watching for the sentinel as well as being the interactive serial port. This will also prevent the possibility of lots of debug data on the console preventing the sentinel value from being seen due to interleaved text. (From OE-Core rev: 2da3fee6b6d9f4dd4c4cb529f4ba393c20aa0f13) Signed-off-by: Randy Witt <randy.e.witt@linux.intel.com> Signed-off-by: Ross Burton <ross.burton@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib/oeqa/utils/qemurunner.py')
-rw-r--r--meta/lib/oeqa/utils/qemurunner.py128
1 files changed, 126 insertions, 2 deletions
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py
index c8d689900d..e976fd0819 100644
--- a/meta/lib/oeqa/utils/qemurunner.py
+++ b/meta/lib/oeqa/utils/qemurunner.py
@@ -13,6 +13,7 @@ import re
13import socket 13import socket
14import select 14import select
15import errno 15import errno
16import threading
16 17
17import logging 18import logging
18logger = logging.getLogger("BitBake.QemuRunner") 19logger = logging.getLogger("BitBake.QemuRunner")
@@ -38,6 +39,7 @@ class QemuRunner:
38 self.logfile = logfile 39 self.logfile = logfile
39 self.boottime = boottime 40 self.boottime = boottime
40 self.logged = False 41 self.logged = False
42 self.thread = None
41 43
42 self.runqemutime = 60 44 self.runqemutime = 60
43 45
@@ -81,6 +83,7 @@ class QemuRunner:
81 os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image 83 os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image
82 84
83 try: 85 try:
86 threadsock, threadport = self.create_socket()
84 self.server_socket, self.serverport = self.create_socket() 87 self.server_socket, self.serverport = self.create_socket()
85 except socket.error, msg: 88 except socket.error, msg:
86 logger.error("Failed to create listening socket: %s" % msg[1]) 89 logger.error("Failed to create listening socket: %s" % msg[1])
@@ -89,7 +92,7 @@ class QemuRunner:
89 # Set this flag so that Qemu doesn't do any grabs as SDL grabs interact 92 # Set this flag so that Qemu doesn't do any grabs as SDL grabs interact
90 # badly with screensavers. 93 # badly with screensavers.
91 os.environ["QEMU_DONT_GRAB"] = "1" 94 os.environ["QEMU_DONT_GRAB"] = "1"
92 self.qemuparams = 'bootparams="console=tty1 console=ttyS0,115200n8" qemuparams="-serial tcp:127.0.0.1:%s"' % self.serverport 95 self.qemuparams = 'bootparams="console=tty1 console=ttyS0,115200n8" qemuparams="-serial tcp:127.0.0.1:{} -serial tcp:127.0.0.1:{}"'.format(threadport, self.serverport)
93 if qemuparams: 96 if qemuparams:
94 self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' 97 self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'
95 98
@@ -138,6 +141,18 @@ class QemuRunner:
138 return False 141 return False
139 logger.info("Target IP: %s" % self.ip) 142 logger.info("Target IP: %s" % self.ip)
140 logger.info("Server IP: %s" % self.server_ip) 143 logger.info("Server IP: %s" % self.server_ip)
144
145 logger.info("Starting logging thread")
146 self.thread = LoggingThread(self.log, threadsock, logger)
147 self.thread.start()
148 if not self.thread.connection_established.wait(self.boottime):
149 logger.error("Didn't receive a console connection from qemu. "
150 "Here is the qemu command line used:\n%s\nand "
151 "output from runqemu:\n%s" % (cmdline,
152 getOutput(output)))
153 self.stop_thread()
154 return False
155
141 logger.info("Waiting at most %d seconds for login banner" % self.boottime) 156 logger.info("Waiting at most %d seconds for login banner" % self.boottime)
142 endtime = time.time() + self.boottime 157 endtime = time.time() + self.boottime
143 socklist = [self.server_socket] 158 socklist = [self.server_socket]
@@ -157,7 +172,6 @@ class QemuRunner:
157 else: 172 else:
158 data = sock.recv(1024) 173 data = sock.recv(1024)
159 if data: 174 if data:
160 self.log(data)
161 bootlog += data 175 bootlog += data
162 if re.search(".* login:", bootlog): 176 if re.search(".* login:", bootlog):
163 self.server_socket = qemusock 177 self.server_socket = qemusock
@@ -214,6 +228,12 @@ class QemuRunner:
214 self.server_socket = None 228 self.server_socket = None
215 self.qemupid = None 229 self.qemupid = None
216 self.ip = None 230 self.ip = None
231 self.stop_thread()
232
233 def stop_thread(self):
234 if self.thread and self.thread.is_alive():
235 self.thread.stop()
236 self.thread.join()
217 237
218 def restart(self, qemuparams = None): 238 def restart(self, qemuparams = None):
219 logger.info("Restarting qemu process") 239 logger.info("Restarting qemu process")
@@ -312,3 +332,107 @@ class QemuRunner:
312 if (status_cmd == "0"): 332 if (status_cmd == "0"):
313 status = 1 333 status = 1
314 return (status, str(data)) 334 return (status, str(data))
335
336# This class is for reading data from a socket and passing it to logfunc
337# to be processed. It's completely event driven and has a straightforward
338# event loop. The mechanism for stopping the thread is a simple pipe which
339# will wake up the poll and allow for tearing everything down.
340class LoggingThread(threading.Thread):
341 def __init__(self, logfunc, sock, logger):
342 self.connection_established = threading.Event()
343 self.serversock = sock
344 self.logfunc = logfunc
345 self.logger = logger
346 self.readsock = None
347 self.running = False
348
349 self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL
350 self.readevents = select.POLLIN | select.POLLPRI
351
352 threading.Thread.__init__(self, target=self.threadtarget)
353
354 def threadtarget(self):
355 try:
356 self.eventloop()
357 finally:
358 self.teardown()
359
360 def run(self):
361 self.logger.info("Starting logging thread")
362 self.readpipe, self.writepipe = os.pipe()
363 threading.Thread.run(self)
364
365 def stop(self):
366 self.logger.info("Stopping logging thread")
367 if self.running:
368 os.write(self.writepipe, "stop")
369
370 def teardown(self):
371 self.close_socket(self.serversock)
372
373 if self.readsock is not None:
374 self.close_socket(self.readsock)
375
376 self.close_ignore_error(self.readpipe)
377 self.close_ignore_error(self.writepipe)
378 self.running = False
379
380 def eventloop(self):
381 poll = select.poll()
382 eventmask = self.errorevents | self.readevents
383 poll.register(self.serversock.fileno())
384 poll.register(self.readpipe, eventmask)
385
386 breakout = False
387 self.running = True
388 self.logger.info("Starting thread event loop")
389 while not breakout:
390 events = poll.poll()
391 for event in events:
392 # An error occurred, bail out
393 if event[1] & self.errorevents:
394 raise Exception(self.stringify_event(event[1]))
395
396 # Event to stop the thread
397 if self.readpipe == event[0]:
398 self.logger.info("Stop event received")
399 breakout = True
400 break
401
402 # A connection request was received
403 elif self.serversock.fileno() == event[0]:
404 self.logger.info("Connection request received")
405 self.readsock, _ = self.serversock.accept()
406 poll.unregister(self.serversock.fileno())
407 poll.register(self.readsock.fileno())
408
409 self.logger.info("Setting connection established event")
410 self.connection_established.set()
411
412 # Actual data to be logged
413 elif self.readsock.fileno() == event[0]:
414 data = self.readsock.recv(1024)
415 if not data:
416 raise Exception("No data on read ready socket")
417
418 self.logfunc(data)
419
420 def stringify_event(self, event):
421 val = ''
422 if select.POLLERR == event:
423 val = 'POLLER'
424 elif select.POLLHUP == event:
425 val = 'POLLHUP'
426 elif select.POLLNVAL == event:
427 val = 'POLLNVAL'
428 return val
429
430 def close_socket(self, sock):
431 sock.shutdown(socket.SHUT_RDWR)
432 sock.close()
433
434 def close_ignore_error(self, fd):
435 try:
436 os.close(fd)
437 except OSError:
438 pass