diff options
| -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: |
