diff options
Diffstat (limited to 'meta/lib')
-rw-r--r-- | meta/lib/oeqa/utils/qemurunner.py | 303 |
1 files changed, 136 insertions, 167 deletions
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py index 6ee52a2f1e..9073315fba 100644 --- a/meta/lib/oeqa/utils/qemurunner.py +++ b/meta/lib/oeqa/utils/qemurunner.py | |||
@@ -53,6 +53,7 @@ class QemuRunner: | |||
53 | self.use_kvm = use_kvm | 53 | self.use_kvm = use_kvm |
54 | 54 | ||
55 | self.runqemutime = 120 | 55 | self.runqemutime = 120 |
56 | self.qemu_pidfile = 'pidfile_'+str(os.getpid()) | ||
56 | self.host_dumper = HostDumper(dump_host_cmds, dump_dir) | 57 | self.host_dumper = HostDumper(dump_host_cmds, dump_dir) |
57 | 58 | ||
58 | self.logger = logging.getLogger("BitBake.QemuRunner") | 59 | self.logger = logging.getLogger("BitBake.QemuRunner") |
@@ -143,7 +144,11 @@ class QemuRunner: | |||
143 | if extra_bootparams: | 144 | if extra_bootparams: |
144 | bootparams = bootparams + ' ' + extra_bootparams | 145 | bootparams = bootparams + ' ' + extra_bootparams |
145 | 146 | ||
146 | self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1}"'.format(bootparams, threadport) | 147 | # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes |
148 | # and analyze descendents in order to determine it. | ||
149 | if os.path.exists(self.qemu_pidfile): | ||
150 | os.remove(self.qemu_pidfile) | ||
151 | self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1} -pidfile {2}"'.format(bootparams, threadport, self.qemu_pidfile) | ||
147 | if qemuparams: | 152 | if qemuparams: |
148 | self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' | 153 | self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' |
149 | 154 | ||
@@ -199,125 +204,14 @@ class QemuRunner: | |||
199 | self.stop() | 204 | self.stop() |
200 | self.logger.info("Output from runqemu:\n%s" % self.getOutput(output)) | 205 | self.logger.info("Output from runqemu:\n%s" % self.getOutput(output)) |
201 | return False | 206 | return False |
202 | time.sleep(1) | 207 | time.sleep(0.5) |
203 | 208 | ||
204 | out = self.getOutput(output) | 209 | if not self.is_alive(): |
205 | netconf = False # network configuration is not required by default | ||
206 | if self.is_alive(): | ||
207 | self.logger.info("qemu started in %s seconds - qemu procces pid is %s" % (time.time() - (endtime - self.runqemutime), self.qemupid)) | ||
208 | if get_ip: | ||
209 | cmdline = '' | ||
210 | with open('/proc/%s/cmdline' % self.qemupid) as p: | ||
211 | cmdline = p.read() | ||
212 | # It is needed to sanitize the data received | ||
213 | # because is possible to have control characters | ||
214 | cmdline = re_control_char.sub('', cmdline) | ||
215 | try: | ||
216 | ips = re.findall("((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1]) | ||
217 | self.ip = ips[0] | ||
218 | self.server_ip = ips[1] | ||
219 | self.logger.info("qemu cmdline used:\n{}".format(cmdline)) | ||
220 | except (IndexError, ValueError): | ||
221 | # Try to get network configuration from runqemu output | ||
222 | match = re.match('.*Network configuration: ([0-9.]+)::([0-9.]+):([0-9.]+)$.*', | ||
223 | out, re.MULTILINE|re.DOTALL) | ||
224 | if match: | ||
225 | self.ip, self.server_ip, self.netmask = match.groups() | ||
226 | # network configuration is required as we couldn't get it | ||
227 | # from the runqemu command line, so qemu doesn't run kernel | ||
228 | # and guest networking is not configured | ||
229 | netconf = True | ||
230 | else: | ||
231 | self.logger.error("Couldn't get ip from qemu command line and runqemu output! " | ||
232 | "Here is the qemu command line used:\n%s\n" | ||
233 | "and output from runqemu:\n%s" % (cmdline, out)) | ||
234 | self._dump_host() | ||
235 | self.stop() | ||
236 | return False | ||
237 | |||
238 | self.logger.info("Target IP: %s" % self.ip) | ||
239 | self.logger.info("Server IP: %s" % self.server_ip) | ||
240 | |||
241 | self.thread = LoggingThread(self.log, threadsock, self.logger) | ||
242 | self.thread.start() | ||
243 | if not self.thread.connection_established.wait(self.boottime): | ||
244 | self.logger.error("Didn't receive a console connection from qemu. " | ||
245 | "Here is the qemu command line used:\n%s\nand " | ||
246 | "output from runqemu:\n%s" % (cmdline, out)) | ||
247 | self.stop_thread() | ||
248 | return False | ||
249 | |||
250 | self.logger.info("Output from runqemu:\n%s", out) | ||
251 | self.logger.info("Waiting at most %d seconds for login banner" % self.boottime) | ||
252 | endtime = time.time() + self.boottime | ||
253 | socklist = [self.server_socket] | ||
254 | reachedlogin = False | ||
255 | stopread = False | ||
256 | qemusock = None | ||
257 | bootlog = '' | ||
258 | data = b'' | ||
259 | while time.time() < endtime and not stopread: | ||
260 | try: | ||
261 | sread, swrite, serror = select.select(socklist, [], [], 5) | ||
262 | except InterruptedError: | ||
263 | continue | ||
264 | for sock in sread: | ||
265 | if sock is self.server_socket: | ||
266 | qemusock, addr = self.server_socket.accept() | ||
267 | qemusock.setblocking(0) | ||
268 | socklist.append(qemusock) | ||
269 | socklist.remove(self.server_socket) | ||
270 | self.logger.info("Connection from %s:%s" % addr) | ||
271 | else: | ||
272 | data = data + sock.recv(1024) | ||
273 | if data: | ||
274 | try: | ||
275 | data = data.decode("utf-8", errors="surrogateescape") | ||
276 | bootlog += data | ||
277 | data = b'' | ||
278 | if re.search(".* login:", bootlog): | ||
279 | self.server_socket = qemusock | ||
280 | stopread = True | ||
281 | reachedlogin = True | ||
282 | self.logger.info("Reached login banner") | ||
283 | except UnicodeDecodeError: | ||
284 | continue | ||
285 | else: | ||
286 | socklist.remove(sock) | ||
287 | sock.close() | ||
288 | stopread = True | ||
289 | |||
290 | if not reachedlogin: | ||
291 | self.logger.info("Target didn't reached login boot in %d seconds" % self.boottime) | ||
292 | lines = "\n".join(bootlog.splitlines()[-25:]) | ||
293 | self.logger.info("Last 25 lines of text:\n%s" % lines) | ||
294 | self.logger.info("Check full boot log: %s" % self.logfile) | ||
295 | self._dump_host() | ||
296 | self.stop() | ||
297 | return False | ||
298 | |||
299 | # If we are not able to login the tests can continue | ||
300 | try: | ||
301 | (status, output) = self.run_serial("root\n", raw=True) | ||
302 | if re.search("root@[a-zA-Z0-9\-]+:~#", output): | ||
303 | self.logged = True | ||
304 | self.logger.info("Logged as root in serial console") | ||
305 | if netconf: | ||
306 | # configure guest networking | ||
307 | cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) | ||
308 | output = self.run_serial(cmd, raw=True)[1] | ||
309 | if re.search("root@[a-zA-Z0-9\-]+:~#", output): | ||
310 | self.logger.info("configured ip address %s", self.ip) | ||
311 | else: | ||
312 | self.logger.info("Couldn't configure guest networking") | ||
313 | else: | ||
314 | self.logger.info("Couldn't login into serial console" | ||
315 | " as root using blank password") | ||
316 | except: | ||
317 | self.logger.info("Serial console failed while trying to login") | ||
318 | |||
319 | else: | ||
320 | self.logger.error("Qemu pid didn't appear in %s seconds" % self.runqemutime) | 210 | self.logger.error("Qemu pid didn't appear in %s seconds" % self.runqemutime) |
211 | # Dump all processes to help us to figure out what is going on... | ||
212 | ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command '], stdout=subprocess.PIPE).communicate()[0] | ||
213 | processes = ps.decode("utf-8") | ||
214 | self.logger.info("Running processes:\n%s" % processes) | ||
321 | self._dump_host() | 215 | self._dump_host() |
322 | self.stop() | 216 | self.stop() |
323 | op = self.getOutput(output) | 217 | op = self.getOutput(output) |
@@ -327,7 +221,121 @@ class QemuRunner: | |||
327 | self.logger.error("No output from runqemu.\n") | 221 | self.logger.error("No output from runqemu.\n") |
328 | return False | 222 | return False |
329 | 223 | ||
330 | return self.is_alive() | 224 | # We are alive: qemu is running |
225 | out = self.getOutput(output) | ||
226 | netconf = False # network configuration is not required by default | ||
227 | self.logger.info("qemu started in %s seconds - qemu procces pid is %s" % (time.time() - (endtime - self.runqemutime), self.qemupid)) | ||
228 | if get_ip: | ||
229 | cmdline = '' | ||
230 | with open('/proc/%s/cmdline' % self.qemupid) as p: | ||
231 | cmdline = p.read() | ||
232 | # It is needed to sanitize the data received | ||
233 | # because is possible to have control characters | ||
234 | cmdline = re_control_char.sub(' ', cmdline) | ||
235 | try: | ||
236 | ips = re.findall("((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1]) | ||
237 | self.ip = ips[0] | ||
238 | self.server_ip = ips[1] | ||
239 | self.logger.info("qemu cmdline used:\n{}".format(cmdline)) | ||
240 | except (IndexError, ValueError): | ||
241 | # Try to get network configuration from runqemu output | ||
242 | match = re.match('.*Network configuration: ([0-9.]+)::([0-9.]+):([0-9.]+)$.*', | ||
243 | out, re.MULTILINE|re.DOTALL) | ||
244 | if match: | ||
245 | self.ip, self.server_ip, self.netmask = match.groups() | ||
246 | # network configuration is required as we couldn't get it | ||
247 | # from the runqemu command line, so qemu doesn't run kernel | ||
248 | # and guest networking is not configured | ||
249 | netconf = True | ||
250 | else: | ||
251 | self.logger.error("Couldn't get ip from qemu command line and runqemu output! " | ||
252 | "Here is the qemu command line used:\n%s\n" | ||
253 | "and output from runqemu:\n%s" % (cmdline, out)) | ||
254 | self._dump_host() | ||
255 | self.stop() | ||
256 | return False | ||
257 | |||
258 | self.logger.info("Target IP: %s" % self.ip) | ||
259 | self.logger.info("Server IP: %s" % self.server_ip) | ||
260 | |||
261 | self.thread = LoggingThread(self.log, threadsock, self.logger) | ||
262 | self.thread.start() | ||
263 | if not self.thread.connection_established.wait(self.boottime): | ||
264 | self.logger.error("Didn't receive a console connection from qemu. " | ||
265 | "Here is the qemu command line used:\n%s\nand " | ||
266 | "output from runqemu:\n%s" % (cmdline, out)) | ||
267 | self.stop_thread() | ||
268 | return False | ||
269 | |||
270 | self.logger.info("Output from runqemu:\n%s", out) | ||
271 | self.logger.info("Waiting at most %d seconds for login banner" % self.boottime) | ||
272 | endtime = time.time() + self.boottime | ||
273 | socklist = [self.server_socket] | ||
274 | reachedlogin = False | ||
275 | stopread = False | ||
276 | qemusock = None | ||
277 | bootlog = '' | ||
278 | data = b'' | ||
279 | while time.time() < endtime and not stopread: | ||
280 | try: | ||
281 | sread, swrite, serror = select.select(socklist, [], [], 5) | ||
282 | except InterruptedError: | ||
283 | continue | ||
284 | for sock in sread: | ||
285 | if sock is self.server_socket: | ||
286 | qemusock, addr = self.server_socket.accept() | ||
287 | qemusock.setblocking(0) | ||
288 | socklist.append(qemusock) | ||
289 | socklist.remove(self.server_socket) | ||
290 | self.logger.info("Connection from %s:%s" % addr) | ||
291 | else: | ||
292 | data = data + sock.recv(1024) | ||
293 | if data: | ||
294 | try: | ||
295 | data = data.decode("utf-8", errors="surrogateescape") | ||
296 | bootlog += data | ||
297 | data = b'' | ||
298 | if re.search(".* login:", bootlog): | ||
299 | self.server_socket = qemusock | ||
300 | stopread = True | ||
301 | reachedlogin = True | ||
302 | self.logger.info("Reached login banner") | ||
303 | except UnicodeDecodeError: | ||
304 | continue | ||
305 | else: | ||
306 | socklist.remove(sock) | ||
307 | sock.close() | ||
308 | stopread = True | ||
309 | |||
310 | if not reachedlogin: | ||
311 | self.logger.info("Target didn't reached login boot in %d seconds" % self.boottime) | ||
312 | lines = "\n".join(bootlog.splitlines()[-25:]) | ||
313 | self.logger.info("Last 25 lines of text:\n%s" % lines) | ||
314 | self.logger.info("Check full boot log: %s" % self.logfile) | ||
315 | self._dump_host() | ||
316 | self.stop() | ||
317 | return False | ||
318 | |||
319 | # If we are not able to login the tests can continue | ||
320 | try: | ||
321 | (status, output) = self.run_serial("root\n", raw=True) | ||
322 | if re.search("root@[a-zA-Z0-9\-]+:~#", output): | ||
323 | self.logged = True | ||
324 | self.logger.info("Logged as root in serial console") | ||
325 | if netconf: | ||
326 | # configure guest networking | ||
327 | cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) | ||
328 | output = self.run_serial(cmd, raw=True)[1] | ||
329 | if re.search("root@[a-zA-Z0-9\-]+:~#", output): | ||
330 | self.logger.info("configured ip address %s", self.ip) | ||
331 | else: | ||
332 | self.logger.info("Couldn't configure guest networking") | ||
333 | else: | ||
334 | self.logger.info("Couldn't login into serial console" | ||
335 | " as root using blank password") | ||
336 | except: | ||
337 | self.logger.info("Serial console failed while trying to login") | ||
338 | return True | ||
331 | 339 | ||
332 | def stop(self): | 340 | def stop(self): |
333 | self.stop_thread() | 341 | self.stop_thread() |
@@ -355,6 +363,8 @@ class QemuRunner: | |||
355 | self.server_socket = None | 363 | self.server_socket = None |
356 | self.qemupid = None | 364 | self.qemupid = None |
357 | self.ip = None | 365 | self.ip = None |
366 | if os.path.exists(self.qemu_pidfile): | ||
367 | os.remove(self.qemu_pidfile) | ||
358 | 368 | ||
359 | def stop_qemu_system(self): | 369 | def stop_qemu_system(self): |
360 | if self.qemupid: | 370 | if self.qemupid: |
@@ -380,56 +390,15 @@ class QemuRunner: | |||
380 | def is_alive(self): | 390 | def is_alive(self): |
381 | if not self.runqemu: | 391 | if not self.runqemu: |
382 | return False | 392 | return False |
383 | qemu_child = self.find_child(str(self.runqemu.pid)) | 393 | if os.path.isfile(self.qemu_pidfile): |
384 | if qemu_child: | 394 | f = open(self.qemu_pidfile, 'r') |
385 | self.qemupid = qemu_child[0] | 395 | qemu_pid = f.read() |
386 | if os.path.exists("/proc/" + str(self.qemupid)): | 396 | f.close() |
387 | return True | 397 | #logger.info("qemu_pid: %s" % qemu_pid) |
398 | self.qemupid = int(qemu_pid) | ||
399 | return True | ||
388 | return False | 400 | return False |
389 | 401 | ||
390 | def find_child(self,parent_pid): | ||
391 | # | ||
392 | # Walk the process tree from the process specified looking for a qemu-system. Return its [pid'cmd] | ||
393 | # | ||
394 | ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command'], stdout=subprocess.PIPE).communicate()[0] | ||
395 | processes = ps.decode("utf-8").split('\n') | ||
396 | nfields = len(processes[0].split()) - 1 | ||
397 | pids = {} | ||
398 | commands = {} | ||
399 | for row in processes[1:]: | ||
400 | data = row.split(None, nfields) | ||
401 | if len(data) != 3: | ||
402 | continue | ||
403 | if data[1] not in pids: | ||
404 | pids[data[1]] = [] | ||
405 | |||
406 | pids[data[1]].append(data[0]) | ||
407 | commands[data[0]] = data[2] | ||
408 | |||
409 | if parent_pid not in pids: | ||
410 | return [] | ||
411 | |||
412 | parents = [] | ||
413 | newparents = pids[parent_pid] | ||
414 | while newparents: | ||
415 | next = [] | ||
416 | for p in newparents: | ||
417 | if p in pids: | ||
418 | for n in pids[p]: | ||
419 | if n not in parents and n not in next: | ||
420 | next.append(n) | ||
421 | if p not in parents: | ||
422 | parents.append(p) | ||
423 | newparents = next | ||
424 | #print("Children matching %s:" % str(parents)) | ||
425 | for p in parents: | ||
426 | # Need to be careful here since runqemu runs "ldd qemu-system-xxxx" | ||
427 | # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx" | ||
428 | basecmd = commands[p].split()[0] | ||
429 | basecmd = os.path.basename(basecmd) | ||
430 | if "qemu-system" in basecmd and "-serial tcp" in commands[p]: | ||
431 | return [int(p),commands[p]] | ||
432 | |||
433 | def run_serial(self, command, raw=False, timeout=5): | 402 | def run_serial(self, command, raw=False, timeout=5): |
434 | # We assume target system have echo to get command status | 403 | # We assume target system have echo to get command status |
435 | if not raw: | 404 | if not raw: |