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