1eb8dc403SDave Cobbley# Copyright (C) 2013 Intel Corporation 2eb8dc403SDave Cobbley# 3eb8dc403SDave Cobbley# Released under the MIT license (see COPYING.MIT) 4eb8dc403SDave Cobbley 5eb8dc403SDave Cobbley# This module provides a class for starting qemu images using runqemu. 6eb8dc403SDave Cobbley# It's used by testimage.bbclass. 7eb8dc403SDave Cobbley 8eb8dc403SDave Cobbleyimport subprocess 9eb8dc403SDave Cobbleyimport os 10eb8dc403SDave Cobbleyimport sys 11eb8dc403SDave Cobbleyimport time 12eb8dc403SDave Cobbleyimport signal 13eb8dc403SDave Cobbleyimport re 14eb8dc403SDave Cobbleyimport socket 15eb8dc403SDave Cobbleyimport select 16eb8dc403SDave Cobbleyimport errno 17eb8dc403SDave Cobbleyimport string 18eb8dc403SDave Cobbleyimport threading 19eb8dc403SDave Cobbleyimport codecs 20eb8dc403SDave Cobbleyimport logging 21eb8dc403SDave Cobbleyfrom oeqa.utils.dump import HostDumper 22eb8dc403SDave Cobbley 23eb8dc403SDave Cobbley# Get Unicode non printable control chars 24eb8dc403SDave Cobbleycontrol_range = list(range(0,32))+list(range(127,160)) 25eb8dc403SDave Cobbleycontrol_chars = [chr(x) for x in control_range 26eb8dc403SDave Cobbley if chr(x) not in string.printable] 27eb8dc403SDave Cobbleyre_control_char = re.compile('[%s]' % re.escape("".join(control_chars))) 28eb8dc403SDave Cobbley 29eb8dc403SDave Cobbleyclass QemuRunner: 30eb8dc403SDave Cobbley 31eb8dc403SDave Cobbley def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds, use_kvm, logger): 32eb8dc403SDave Cobbley 33eb8dc403SDave Cobbley # Popen object for runqemu 34eb8dc403SDave Cobbley self.runqemu = None 35eb8dc403SDave Cobbley # pid of the qemu process that runqemu will start 36eb8dc403SDave Cobbley self.qemupid = None 37eb8dc403SDave Cobbley # target ip - from the command line or runqemu output 38eb8dc403SDave Cobbley self.ip = None 39eb8dc403SDave Cobbley # host ip - where qemu is running 40eb8dc403SDave Cobbley self.server_ip = None 41eb8dc403SDave Cobbley # target ip netmask 42eb8dc403SDave Cobbley self.netmask = None 43eb8dc403SDave Cobbley 44eb8dc403SDave Cobbley self.machine = machine 45eb8dc403SDave Cobbley self.rootfs = rootfs 46eb8dc403SDave Cobbley self.display = display 47eb8dc403SDave Cobbley self.tmpdir = tmpdir 48eb8dc403SDave Cobbley self.deploy_dir_image = deploy_dir_image 49eb8dc403SDave Cobbley self.logfile = logfile 50eb8dc403SDave Cobbley self.boottime = boottime 51eb8dc403SDave Cobbley self.logged = False 52eb8dc403SDave Cobbley self.thread = None 53eb8dc403SDave Cobbley self.use_kvm = use_kvm 54eb8dc403SDave Cobbley self.msg = '' 55eb8dc403SDave Cobbley 56eb8dc403SDave Cobbley self.runqemutime = 120 57eb8dc403SDave Cobbley self.qemu_pidfile = 'pidfile_'+str(os.getpid()) 58eb8dc403SDave Cobbley self.host_dumper = HostDumper(dump_host_cmds, dump_dir) 59eb8dc403SDave Cobbley 60eb8dc403SDave Cobbley self.logger = logger 61eb8dc403SDave Cobbley 62eb8dc403SDave Cobbley def create_socket(self): 63eb8dc403SDave Cobbley try: 64eb8dc403SDave Cobbley sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 65eb8dc403SDave Cobbley sock.setblocking(0) 66eb8dc403SDave Cobbley sock.bind(("127.0.0.1",0)) 67eb8dc403SDave Cobbley sock.listen(2) 68eb8dc403SDave Cobbley port = sock.getsockname()[1] 69eb8dc403SDave Cobbley self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port) 70eb8dc403SDave Cobbley return (sock, port) 71eb8dc403SDave Cobbley 72eb8dc403SDave Cobbley except socket.error: 73eb8dc403SDave Cobbley sock.close() 74eb8dc403SDave Cobbley raise 75eb8dc403SDave Cobbley 76eb8dc403SDave Cobbley def log(self, msg): 77eb8dc403SDave Cobbley if self.logfile: 78eb8dc403SDave Cobbley # It is needed to sanitize the data received from qemu 79eb8dc403SDave Cobbley # because is possible to have control characters 80eb8dc403SDave Cobbley msg = msg.decode("utf-8", errors='ignore') 81eb8dc403SDave Cobbley msg = re_control_char.sub('', msg) 82eb8dc403SDave Cobbley self.msg += msg 83eb8dc403SDave Cobbley with codecs.open(self.logfile, "a", encoding="utf-8") as f: 84eb8dc403SDave Cobbley f.write("%s" % msg) 85eb8dc403SDave Cobbley 86eb8dc403SDave Cobbley def getOutput(self, o): 87eb8dc403SDave Cobbley import fcntl 88eb8dc403SDave Cobbley fl = fcntl.fcntl(o, fcntl.F_GETFL) 89eb8dc403SDave Cobbley fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK) 90eb8dc403SDave Cobbley return os.read(o.fileno(), 1000000).decode("utf-8") 91eb8dc403SDave Cobbley 92eb8dc403SDave Cobbley 93eb8dc403SDave Cobbley def handleSIGCHLD(self, signum, frame): 94eb8dc403SDave Cobbley if self.runqemu and self.runqemu.poll(): 95eb8dc403SDave Cobbley if self.runqemu.returncode: 96eb8dc403SDave Cobbley self.logger.debug('runqemu exited with code %d' % self.runqemu.returncode) 97eb8dc403SDave Cobbley self.logger.debug("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout)) 98eb8dc403SDave Cobbley self.stop() 99eb8dc403SDave Cobbley self._dump_host() 100eb8dc403SDave Cobbley raise SystemExit 101eb8dc403SDave Cobbley 102eb8dc403SDave Cobbley def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True): 103eb8dc403SDave Cobbley env = os.environ.copy() 104eb8dc403SDave Cobbley if self.display: 105eb8dc403SDave Cobbley env["DISPLAY"] = self.display 106eb8dc403SDave Cobbley # Set this flag so that Qemu doesn't do any grabs as SDL grabs 107eb8dc403SDave Cobbley # interact badly with screensavers. 108eb8dc403SDave Cobbley env["QEMU_DONT_GRAB"] = "1" 109eb8dc403SDave Cobbley if not os.path.exists(self.rootfs): 110eb8dc403SDave Cobbley self.logger.error("Invalid rootfs %s" % self.rootfs) 111eb8dc403SDave Cobbley return False 112eb8dc403SDave Cobbley if not os.path.exists(self.tmpdir): 113eb8dc403SDave Cobbley self.logger.error("Invalid TMPDIR path %s" % self.tmpdir) 114eb8dc403SDave Cobbley return False 115eb8dc403SDave Cobbley else: 116eb8dc403SDave Cobbley env["OE_TMPDIR"] = self.tmpdir 117eb8dc403SDave Cobbley if not os.path.exists(self.deploy_dir_image): 118eb8dc403SDave Cobbley self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image) 119eb8dc403SDave Cobbley return False 120eb8dc403SDave Cobbley else: 121eb8dc403SDave Cobbley env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image 122eb8dc403SDave Cobbley 123eb8dc403SDave Cobbley if not launch_cmd: 124eb8dc403SDave Cobbley launch_cmd = 'runqemu %s %s ' % ('snapshot' if discard_writes else '', runqemuparams) 125eb8dc403SDave Cobbley if self.use_kvm: 126eb8dc403SDave Cobbley self.logger.debug('Using kvm for runqemu') 127eb8dc403SDave Cobbley launch_cmd += ' kvm' 128eb8dc403SDave Cobbley else: 129eb8dc403SDave Cobbley self.logger.debug('Not using kvm for runqemu') 130eb8dc403SDave Cobbley if not self.display: 131eb8dc403SDave Cobbley launch_cmd += ' nographic' 132eb8dc403SDave Cobbley launch_cmd += ' %s %s' % (self.machine, self.rootfs) 133eb8dc403SDave Cobbley 134eb8dc403SDave Cobbley return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env) 135eb8dc403SDave Cobbley 136eb8dc403SDave Cobbley def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None): 137eb8dc403SDave Cobbley try: 138f86d0556SBrad Bishop self.threadsock, threadport = self.create_socket() 139eb8dc403SDave Cobbley self.server_socket, self.serverport = self.create_socket() 140eb8dc403SDave Cobbley except socket.error as msg: 141eb8dc403SDave Cobbley self.logger.error("Failed to create listening socket: %s" % msg[1]) 142eb8dc403SDave Cobbley return False 143eb8dc403SDave Cobbley 144eb8dc403SDave Cobbley bootparams = 'console=tty1 console=ttyS0,115200n8 printk.time=1' 145eb8dc403SDave Cobbley if extra_bootparams: 146eb8dc403SDave Cobbley bootparams = bootparams + ' ' + extra_bootparams 147eb8dc403SDave Cobbley 148eb8dc403SDave Cobbley # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes 149eb8dc403SDave Cobbley # and analyze descendents in order to determine it. 150eb8dc403SDave Cobbley if os.path.exists(self.qemu_pidfile): 151eb8dc403SDave Cobbley os.remove(self.qemu_pidfile) 152eb8dc403SDave Cobbley self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1} -pidfile {2}"'.format(bootparams, threadport, self.qemu_pidfile) 153eb8dc403SDave Cobbley if qemuparams: 154eb8dc403SDave Cobbley self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' 155eb8dc403SDave Cobbley 156eb8dc403SDave Cobbley launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams) 157eb8dc403SDave Cobbley 158eb8dc403SDave Cobbley self.origchldhandler = signal.getsignal(signal.SIGCHLD) 159eb8dc403SDave Cobbley signal.signal(signal.SIGCHLD, self.handleSIGCHLD) 160eb8dc403SDave Cobbley 161eb8dc403SDave Cobbley self.logger.debug('launchcmd=%s'%(launch_cmd)) 162eb8dc403SDave Cobbley 163eb8dc403SDave Cobbley # FIXME: We pass in stdin=subprocess.PIPE here to work around stty 164eb8dc403SDave Cobbley # blocking at the end of the runqemu script when using this within 165eb8dc403SDave Cobbley # oe-selftest (this makes stty error out immediately). There ought 166eb8dc403SDave Cobbley # to be a proper fix but this will suffice for now. 167eb8dc403SDave Cobbley self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp, env=env) 168eb8dc403SDave Cobbley output = self.runqemu.stdout 169eb8dc403SDave Cobbley 170eb8dc403SDave Cobbley # 171eb8dc403SDave Cobbley # We need the preexec_fn above so that all runqemu processes can easily be killed 172eb8dc403SDave Cobbley # (by killing their process group). This presents a problem if this controlling 173eb8dc403SDave Cobbley # process itself is killed however since those processes don't notice the death 174eb8dc403SDave Cobbley # of the parent and merrily continue on. 175eb8dc403SDave Cobbley # 176eb8dc403SDave Cobbley # Rather than hack runqemu to deal with this, we add something here instead. 177eb8dc403SDave Cobbley # Basically we fork off another process which holds an open pipe to the parent 178eb8dc403SDave Cobbley # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills 179eb8dc403SDave Cobbley # the process group. This is like pctrl's PDEATHSIG but for a process group 180eb8dc403SDave Cobbley # rather than a single process. 181eb8dc403SDave Cobbley # 182eb8dc403SDave Cobbley r, w = os.pipe() 183eb8dc403SDave Cobbley self.monitorpid = os.fork() 184eb8dc403SDave Cobbley if self.monitorpid: 185eb8dc403SDave Cobbley os.close(r) 186eb8dc403SDave Cobbley self.monitorpipe = os.fdopen(w, "w") 187eb8dc403SDave Cobbley else: 188eb8dc403SDave Cobbley # child process 189eb8dc403SDave Cobbley os.setpgrp() 190eb8dc403SDave Cobbley os.close(w) 191eb8dc403SDave Cobbley r = os.fdopen(r) 192eb8dc403SDave Cobbley x = r.read() 193eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) 194eb8dc403SDave Cobbley sys.exit(0) 195eb8dc403SDave Cobbley 196eb8dc403SDave Cobbley self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid) 197eb8dc403SDave Cobbley self.logger.debug("waiting at most %s seconds for qemu pid (%s)" % 198eb8dc403SDave Cobbley (self.runqemutime, time.strftime("%D %H:%M:%S"))) 199eb8dc403SDave Cobbley endtime = time.time() + self.runqemutime 200eb8dc403SDave Cobbley while not self.is_alive() and time.time() < endtime: 201eb8dc403SDave Cobbley if self.runqemu.poll(): 202eb8dc403SDave Cobbley if self.runqemu.returncode: 203eb8dc403SDave Cobbley # No point waiting any longer 204eb8dc403SDave Cobbley self.logger.debug('runqemu exited with code %d' % self.runqemu.returncode) 205eb8dc403SDave Cobbley self._dump_host() 206eb8dc403SDave Cobbley self.logger.debug("Output from runqemu:\n%s" % self.getOutput(output)) 207f86d0556SBrad Bishop self.stop() 208eb8dc403SDave Cobbley return False 209eb8dc403SDave Cobbley time.sleep(0.5) 210eb8dc403SDave Cobbley 211eb8dc403SDave Cobbley if not self.is_alive(): 212eb8dc403SDave Cobbley self.logger.error("Qemu pid didn't appear in %s seconds (%s)" % 213eb8dc403SDave Cobbley (self.runqemutime, time.strftime("%D %H:%M:%S"))) 214eb8dc403SDave Cobbley # Dump all processes to help us to figure out what is going on... 215eb8dc403SDave Cobbley ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command '], stdout=subprocess.PIPE).communicate()[0] 216eb8dc403SDave Cobbley processes = ps.decode("utf-8") 217eb8dc403SDave Cobbley self.logger.debug("Running processes:\n%s" % processes) 218eb8dc403SDave Cobbley self._dump_host() 219eb8dc403SDave Cobbley op = self.getOutput(output) 220f86d0556SBrad Bishop self.stop() 221eb8dc403SDave Cobbley if op: 222eb8dc403SDave Cobbley self.logger.error("Output from runqemu:\n%s" % op) 223eb8dc403SDave Cobbley else: 224eb8dc403SDave Cobbley self.logger.error("No output from runqemu.\n") 225eb8dc403SDave Cobbley return False 226eb8dc403SDave Cobbley 227eb8dc403SDave Cobbley # We are alive: qemu is running 228eb8dc403SDave Cobbley out = self.getOutput(output) 229eb8dc403SDave Cobbley netconf = False # network configuration is not required by default 230eb8dc403SDave Cobbley self.logger.debug("qemu started in %s seconds - qemu procces pid is %s (%s)" % 231eb8dc403SDave Cobbley (time.time() - (endtime - self.runqemutime), 232eb8dc403SDave Cobbley self.qemupid, time.strftime("%D %H:%M:%S"))) 233eb8dc403SDave Cobbley if get_ip: 234eb8dc403SDave Cobbley cmdline = '' 235eb8dc403SDave Cobbley with open('/proc/%s/cmdline' % self.qemupid) as p: 236eb8dc403SDave Cobbley cmdline = p.read() 237eb8dc403SDave Cobbley # It is needed to sanitize the data received 238eb8dc403SDave Cobbley # because is possible to have control characters 239eb8dc403SDave Cobbley cmdline = re_control_char.sub(' ', cmdline) 240eb8dc403SDave Cobbley try: 241f86d0556SBrad Bishop ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1]) 242eb8dc403SDave Cobbley self.ip = ips[0] 243eb8dc403SDave Cobbley self.server_ip = ips[1] 244eb8dc403SDave Cobbley self.logger.debug("qemu cmdline used:\n{}".format(cmdline)) 245eb8dc403SDave Cobbley except (IndexError, ValueError): 246eb8dc403SDave Cobbley # Try to get network configuration from runqemu output 247f86d0556SBrad Bishop match = re.match(r'.*Network configuration: ([0-9.]+)::([0-9.]+):([0-9.]+)$.*', 248eb8dc403SDave Cobbley out, re.MULTILINE|re.DOTALL) 249eb8dc403SDave Cobbley if match: 250eb8dc403SDave Cobbley self.ip, self.server_ip, self.netmask = match.groups() 251eb8dc403SDave Cobbley # network configuration is required as we couldn't get it 252eb8dc403SDave Cobbley # from the runqemu command line, so qemu doesn't run kernel 253eb8dc403SDave Cobbley # and guest networking is not configured 254eb8dc403SDave Cobbley netconf = True 255eb8dc403SDave Cobbley else: 256eb8dc403SDave Cobbley self.logger.error("Couldn't get ip from qemu command line and runqemu output! " 257eb8dc403SDave Cobbley "Here is the qemu command line used:\n%s\n" 258eb8dc403SDave Cobbley "and output from runqemu:\n%s" % (cmdline, out)) 259eb8dc403SDave Cobbley self._dump_host() 260eb8dc403SDave Cobbley self.stop() 261eb8dc403SDave Cobbley return False 262eb8dc403SDave Cobbley 263eb8dc403SDave Cobbley self.logger.debug("Target IP: %s" % self.ip) 264eb8dc403SDave Cobbley self.logger.debug("Server IP: %s" % self.server_ip) 265eb8dc403SDave Cobbley 266f86d0556SBrad Bishop self.thread = LoggingThread(self.log, self.threadsock, self.logger) 267eb8dc403SDave Cobbley self.thread.start() 268eb8dc403SDave Cobbley if not self.thread.connection_established.wait(self.boottime): 269eb8dc403SDave Cobbley self.logger.error("Didn't receive a console connection from qemu. " 270eb8dc403SDave Cobbley "Here is the qemu command line used:\n%s\nand " 271eb8dc403SDave Cobbley "output from runqemu:\n%s" % (cmdline, out)) 272eb8dc403SDave Cobbley self.stop_thread() 273eb8dc403SDave Cobbley return False 274eb8dc403SDave Cobbley 275eb8dc403SDave Cobbley self.logger.debug("Output from runqemu:\n%s", out) 276eb8dc403SDave Cobbley self.logger.debug("Waiting at most %d seconds for login banner (%s)" % 277eb8dc403SDave Cobbley (self.boottime, time.strftime("%D %H:%M:%S"))) 278eb8dc403SDave Cobbley endtime = time.time() + self.boottime 279eb8dc403SDave Cobbley socklist = [self.server_socket] 280eb8dc403SDave Cobbley reachedlogin = False 281eb8dc403SDave Cobbley stopread = False 282eb8dc403SDave Cobbley qemusock = None 283eb8dc403SDave Cobbley bootlog = b'' 284eb8dc403SDave Cobbley data = b'' 285eb8dc403SDave Cobbley while time.time() < endtime and not stopread: 286eb8dc403SDave Cobbley try: 287eb8dc403SDave Cobbley sread, swrite, serror = select.select(socklist, [], [], 5) 288eb8dc403SDave Cobbley except InterruptedError: 289eb8dc403SDave Cobbley continue 290eb8dc403SDave Cobbley for sock in sread: 291eb8dc403SDave Cobbley if sock is self.server_socket: 292eb8dc403SDave Cobbley qemusock, addr = self.server_socket.accept() 293eb8dc403SDave Cobbley qemusock.setblocking(0) 294eb8dc403SDave Cobbley socklist.append(qemusock) 295eb8dc403SDave Cobbley socklist.remove(self.server_socket) 296eb8dc403SDave Cobbley self.logger.debug("Connection from %s:%s" % addr) 297eb8dc403SDave Cobbley else: 298eb8dc403SDave Cobbley data = data + sock.recv(1024) 299eb8dc403SDave Cobbley if data: 300eb8dc403SDave Cobbley bootlog += data 301eb8dc403SDave Cobbley data = b'' 302eb8dc403SDave Cobbley if b' login:' in bootlog: 303eb8dc403SDave Cobbley self.server_socket = qemusock 304eb8dc403SDave Cobbley stopread = True 305eb8dc403SDave Cobbley reachedlogin = True 306eb8dc403SDave Cobbley self.logger.debug("Reached login banner in %s seconds (%s)" % 307eb8dc403SDave Cobbley (time.time() - (endtime - self.boottime), 308eb8dc403SDave Cobbley time.strftime("%D %H:%M:%S"))) 309eb8dc403SDave Cobbley else: 310eb8dc403SDave Cobbley # no need to check if reachedlogin unless we support multiple connections 311eb8dc403SDave Cobbley self.logger.debug("QEMU socket disconnected before login banner reached. (%s)" % 312eb8dc403SDave Cobbley time.strftime("%D %H:%M:%S")) 313eb8dc403SDave Cobbley socklist.remove(sock) 314eb8dc403SDave Cobbley sock.close() 315eb8dc403SDave Cobbley stopread = True 316eb8dc403SDave Cobbley 317eb8dc403SDave Cobbley 318eb8dc403SDave Cobbley if not reachedlogin: 319eb8dc403SDave Cobbley if time.time() >= endtime: 320eb8dc403SDave Cobbley self.logger.debug("Target didn't reach login banner in %d seconds (%s)" % 321eb8dc403SDave Cobbley (self.boottime, time.strftime("%D %H:%M:%S"))) 322eb8dc403SDave Cobbley tail = lambda l: "\n".join(l.splitlines()[-25:]) 323eb8dc403SDave Cobbley # in case bootlog is empty, use tail qemu log store at self.msg 324eb8dc403SDave Cobbley lines = tail(bootlog if bootlog else self.msg) 325eb8dc403SDave Cobbley self.logger.debug("Last 25 lines of text:\n%s" % lines) 326eb8dc403SDave Cobbley self.logger.debug("Check full boot log: %s" % self.logfile) 327eb8dc403SDave Cobbley self._dump_host() 328eb8dc403SDave Cobbley self.stop() 329eb8dc403SDave Cobbley return False 330eb8dc403SDave Cobbley 331eb8dc403SDave Cobbley # If we are not able to login the tests can continue 332eb8dc403SDave Cobbley try: 333eb8dc403SDave Cobbley (status, output) = self.run_serial("root\n", raw=True) 334f86d0556SBrad Bishop if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): 335eb8dc403SDave Cobbley self.logged = True 336eb8dc403SDave Cobbley self.logger.debug("Logged as root in serial console") 337eb8dc403SDave Cobbley if netconf: 338eb8dc403SDave Cobbley # configure guest networking 339eb8dc403SDave Cobbley cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) 340eb8dc403SDave Cobbley output = self.run_serial(cmd, raw=True)[1] 341f86d0556SBrad Bishop if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): 342eb8dc403SDave Cobbley self.logger.debug("configured ip address %s", self.ip) 343eb8dc403SDave Cobbley else: 344eb8dc403SDave Cobbley self.logger.debug("Couldn't configure guest networking") 345eb8dc403SDave Cobbley else: 346eb8dc403SDave Cobbley self.logger.debug("Couldn't login into serial console" 347eb8dc403SDave Cobbley " as root using blank password") 348eb8dc403SDave Cobbley except: 349eb8dc403SDave Cobbley self.logger.debug("Serial console failed while trying to login") 350eb8dc403SDave Cobbley return True 351eb8dc403SDave Cobbley 352eb8dc403SDave Cobbley def stop(self): 353eb8dc403SDave Cobbley if hasattr(self, "origchldhandler"): 354eb8dc403SDave Cobbley signal.signal(signal.SIGCHLD, self.origchldhandler) 355*1a4b7ee2SBrad Bishop self.stop_thread() 356*1a4b7ee2SBrad Bishop self.stop_qemu_system() 357eb8dc403SDave Cobbley if self.runqemu: 358eb8dc403SDave Cobbley if hasattr(self, "monitorpid"): 359eb8dc403SDave Cobbley os.kill(self.monitorpid, signal.SIGKILL) 360eb8dc403SDave Cobbley self.logger.debug("Sending SIGTERM to runqemu") 361eb8dc403SDave Cobbley try: 362eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) 363eb8dc403SDave Cobbley except OSError as e: 364eb8dc403SDave Cobbley if e.errno != errno.ESRCH: 365eb8dc403SDave Cobbley raise 366eb8dc403SDave Cobbley endtime = time.time() + self.runqemutime 367eb8dc403SDave Cobbley while self.runqemu.poll() is None and time.time() < endtime: 368eb8dc403SDave Cobbley time.sleep(1) 369eb8dc403SDave Cobbley if self.runqemu.poll() is None: 370eb8dc403SDave Cobbley self.logger.debug("Sending SIGKILL to runqemu") 371eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL) 372f86d0556SBrad Bishop self.runqemu.stdin.close() 373f86d0556SBrad Bishop self.runqemu.stdout.close() 374eb8dc403SDave Cobbley self.runqemu = None 375f86d0556SBrad Bishop 376eb8dc403SDave Cobbley if hasattr(self, 'server_socket') and self.server_socket: 377eb8dc403SDave Cobbley self.server_socket.close() 378eb8dc403SDave Cobbley self.server_socket = None 379f86d0556SBrad Bishop if hasattr(self, 'threadsock') and self.threadsock: 380f86d0556SBrad Bishop self.threadsock.close() 381f86d0556SBrad Bishop self.threadsock = None 382eb8dc403SDave Cobbley self.qemupid = None 383eb8dc403SDave Cobbley self.ip = None 384eb8dc403SDave Cobbley if os.path.exists(self.qemu_pidfile): 385eb8dc403SDave Cobbley os.remove(self.qemu_pidfile) 386f86d0556SBrad Bishop if self.monitorpipe: 387f86d0556SBrad Bishop self.monitorpipe.close() 388eb8dc403SDave Cobbley 389eb8dc403SDave Cobbley def stop_qemu_system(self): 390eb8dc403SDave Cobbley if self.qemupid: 391eb8dc403SDave Cobbley try: 392eb8dc403SDave Cobbley # qemu-system behaves well and a SIGTERM is enough 393eb8dc403SDave Cobbley os.kill(self.qemupid, signal.SIGTERM) 394eb8dc403SDave Cobbley except ProcessLookupError as e: 395*1a4b7ee2SBrad Bishop self.logger.warning('qemu-system ended unexpectedly') 396eb8dc403SDave Cobbley 397eb8dc403SDave Cobbley def stop_thread(self): 398eb8dc403SDave Cobbley if self.thread and self.thread.is_alive(): 399eb8dc403SDave Cobbley self.thread.stop() 400eb8dc403SDave Cobbley self.thread.join() 401eb8dc403SDave Cobbley 402eb8dc403SDave Cobbley def restart(self, qemuparams = None): 403eb8dc403SDave Cobbley self.logger.debug("Restarting qemu process") 404eb8dc403SDave Cobbley if self.runqemu.poll() is None: 405eb8dc403SDave Cobbley self.stop() 406eb8dc403SDave Cobbley if self.start(qemuparams): 407eb8dc403SDave Cobbley return True 408eb8dc403SDave Cobbley return False 409eb8dc403SDave Cobbley 410eb8dc403SDave Cobbley def is_alive(self): 411*1a4b7ee2SBrad Bishop if not self.runqemu or self.runqemu.poll() is not None: 412eb8dc403SDave Cobbley return False 413eb8dc403SDave Cobbley if os.path.isfile(self.qemu_pidfile): 414eb8dc403SDave Cobbley f = open(self.qemu_pidfile, 'r') 415eb8dc403SDave Cobbley qemu_pid = f.read() 416eb8dc403SDave Cobbley f.close() 417eb8dc403SDave Cobbley qemupid = int(qemu_pid) 418eb8dc403SDave Cobbley if os.path.exists("/proc/" + str(qemupid)): 419eb8dc403SDave Cobbley self.qemupid = qemupid 420eb8dc403SDave Cobbley return True 421eb8dc403SDave Cobbley return False 422eb8dc403SDave Cobbley 423eb8dc403SDave Cobbley def run_serial(self, command, raw=False, timeout=5): 424eb8dc403SDave Cobbley # We assume target system have echo to get command status 425eb8dc403SDave Cobbley if not raw: 426eb8dc403SDave Cobbley command = "%s; echo $?\n" % command 427eb8dc403SDave Cobbley 428eb8dc403SDave Cobbley data = '' 429eb8dc403SDave Cobbley status = 0 430eb8dc403SDave Cobbley self.server_socket.sendall(command.encode('utf-8')) 431eb8dc403SDave Cobbley start = time.time() 432eb8dc403SDave Cobbley end = start + timeout 433eb8dc403SDave Cobbley while True: 434eb8dc403SDave Cobbley now = time.time() 435eb8dc403SDave Cobbley if now >= end: 436eb8dc403SDave Cobbley data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout 437eb8dc403SDave Cobbley break 438eb8dc403SDave Cobbley try: 439eb8dc403SDave Cobbley sread, _, _ = select.select([self.server_socket],[],[], end - now) 440eb8dc403SDave Cobbley except InterruptedError: 441eb8dc403SDave Cobbley continue 442eb8dc403SDave Cobbley if sread: 443eb8dc403SDave Cobbley answer = self.server_socket.recv(1024) 444eb8dc403SDave Cobbley if answer: 445eb8dc403SDave Cobbley data += answer.decode('utf-8') 446eb8dc403SDave Cobbley # Search the prompt to stop 447f86d0556SBrad Bishop if re.search(r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#", data): 448eb8dc403SDave Cobbley break 449eb8dc403SDave Cobbley else: 450eb8dc403SDave Cobbley raise Exception("No data on serial console socket") 451eb8dc403SDave Cobbley 452eb8dc403SDave Cobbley if data: 453eb8dc403SDave Cobbley if raw: 454eb8dc403SDave Cobbley status = 1 455eb8dc403SDave Cobbley else: 456eb8dc403SDave Cobbley # Remove first line (command line) and last line (prompt) 457eb8dc403SDave Cobbley data = data[data.find('$?\r\n')+4:data.rfind('\r\n')] 458eb8dc403SDave Cobbley index = data.rfind('\r\n') 459eb8dc403SDave Cobbley if index == -1: 460eb8dc403SDave Cobbley status_cmd = data 461eb8dc403SDave Cobbley data = "" 462eb8dc403SDave Cobbley else: 463eb8dc403SDave Cobbley status_cmd = data[index+2:] 464eb8dc403SDave Cobbley data = data[:index] 465eb8dc403SDave Cobbley if (status_cmd == "0"): 466eb8dc403SDave Cobbley status = 1 467eb8dc403SDave Cobbley return (status, str(data)) 468eb8dc403SDave Cobbley 469eb8dc403SDave Cobbley 470eb8dc403SDave Cobbley def _dump_host(self): 471eb8dc403SDave Cobbley self.host_dumper.create_dir("qemu") 472*1a4b7ee2SBrad Bishop self.logger.warning("Qemu ended unexpectedly, dump data from host" 473eb8dc403SDave Cobbley " is in %s" % self.host_dumper.dump_dir) 474eb8dc403SDave Cobbley self.host_dumper.dump_host() 475eb8dc403SDave Cobbley 476eb8dc403SDave Cobbley# This class is for reading data from a socket and passing it to logfunc 477eb8dc403SDave Cobbley# to be processed. It's completely event driven and has a straightforward 478eb8dc403SDave Cobbley# event loop. The mechanism for stopping the thread is a simple pipe which 479eb8dc403SDave Cobbley# will wake up the poll and allow for tearing everything down. 480eb8dc403SDave Cobbleyclass LoggingThread(threading.Thread): 481eb8dc403SDave Cobbley def __init__(self, logfunc, sock, logger): 482eb8dc403SDave Cobbley self.connection_established = threading.Event() 483eb8dc403SDave Cobbley self.serversock = sock 484eb8dc403SDave Cobbley self.logfunc = logfunc 485eb8dc403SDave Cobbley self.logger = logger 486eb8dc403SDave Cobbley self.readsock = None 487eb8dc403SDave Cobbley self.running = False 488eb8dc403SDave Cobbley 489eb8dc403SDave Cobbley self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL 490eb8dc403SDave Cobbley self.readevents = select.POLLIN | select.POLLPRI 491eb8dc403SDave Cobbley 492eb8dc403SDave Cobbley threading.Thread.__init__(self, target=self.threadtarget) 493eb8dc403SDave Cobbley 494eb8dc403SDave Cobbley def threadtarget(self): 495eb8dc403SDave Cobbley try: 496eb8dc403SDave Cobbley self.eventloop() 497eb8dc403SDave Cobbley finally: 498eb8dc403SDave Cobbley self.teardown() 499eb8dc403SDave Cobbley 500eb8dc403SDave Cobbley def run(self): 501eb8dc403SDave Cobbley self.logger.debug("Starting logging thread") 502eb8dc403SDave Cobbley self.readpipe, self.writepipe = os.pipe() 503eb8dc403SDave Cobbley threading.Thread.run(self) 504eb8dc403SDave Cobbley 505eb8dc403SDave Cobbley def stop(self): 506eb8dc403SDave Cobbley self.logger.debug("Stopping logging thread") 507eb8dc403SDave Cobbley if self.running: 508eb8dc403SDave Cobbley os.write(self.writepipe, bytes("stop", "utf-8")) 509eb8dc403SDave Cobbley 510eb8dc403SDave Cobbley def teardown(self): 511eb8dc403SDave Cobbley self.logger.debug("Tearing down logging thread") 512eb8dc403SDave Cobbley self.close_socket(self.serversock) 513eb8dc403SDave Cobbley 514eb8dc403SDave Cobbley if self.readsock is not None: 515eb8dc403SDave Cobbley self.close_socket(self.readsock) 516eb8dc403SDave Cobbley 517eb8dc403SDave Cobbley self.close_ignore_error(self.readpipe) 518eb8dc403SDave Cobbley self.close_ignore_error(self.writepipe) 519eb8dc403SDave Cobbley self.running = False 520eb8dc403SDave Cobbley 521eb8dc403SDave Cobbley def eventloop(self): 522eb8dc403SDave Cobbley poll = select.poll() 523eb8dc403SDave Cobbley event_read_mask = self.errorevents | self.readevents 524eb8dc403SDave Cobbley poll.register(self.serversock.fileno()) 525eb8dc403SDave Cobbley poll.register(self.readpipe, event_read_mask) 526eb8dc403SDave Cobbley 527eb8dc403SDave Cobbley breakout = False 528eb8dc403SDave Cobbley self.running = True 529eb8dc403SDave Cobbley self.logger.debug("Starting thread event loop") 530eb8dc403SDave Cobbley while not breakout: 531eb8dc403SDave Cobbley events = poll.poll() 532eb8dc403SDave Cobbley for event in events: 533eb8dc403SDave Cobbley # An error occurred, bail out 534eb8dc403SDave Cobbley if event[1] & self.errorevents: 535eb8dc403SDave Cobbley raise Exception(self.stringify_event(event[1])) 536eb8dc403SDave Cobbley 537eb8dc403SDave Cobbley # Event to stop the thread 538eb8dc403SDave Cobbley if self.readpipe == event[0]: 539eb8dc403SDave Cobbley self.logger.debug("Stop event received") 540eb8dc403SDave Cobbley breakout = True 541eb8dc403SDave Cobbley break 542eb8dc403SDave Cobbley 543eb8dc403SDave Cobbley # A connection request was received 544eb8dc403SDave Cobbley elif self.serversock.fileno() == event[0]: 545eb8dc403SDave Cobbley self.logger.debug("Connection request received") 546eb8dc403SDave Cobbley self.readsock, _ = self.serversock.accept() 547eb8dc403SDave Cobbley self.readsock.setblocking(0) 548eb8dc403SDave Cobbley poll.unregister(self.serversock.fileno()) 549eb8dc403SDave Cobbley poll.register(self.readsock.fileno(), event_read_mask) 550eb8dc403SDave Cobbley 551eb8dc403SDave Cobbley self.logger.debug("Setting connection established event") 552eb8dc403SDave Cobbley self.connection_established.set() 553eb8dc403SDave Cobbley 554eb8dc403SDave Cobbley # Actual data to be logged 555eb8dc403SDave Cobbley elif self.readsock.fileno() == event[0]: 556eb8dc403SDave Cobbley data = self.recv(1024) 557eb8dc403SDave Cobbley self.logfunc(data) 558eb8dc403SDave Cobbley 559eb8dc403SDave Cobbley # Since the socket is non-blocking make sure to honor EAGAIN 560eb8dc403SDave Cobbley # and EWOULDBLOCK. 561eb8dc403SDave Cobbley def recv(self, count): 562eb8dc403SDave Cobbley try: 563eb8dc403SDave Cobbley data = self.readsock.recv(count) 564eb8dc403SDave Cobbley except socket.error as e: 565eb8dc403SDave Cobbley if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK: 566eb8dc403SDave Cobbley return '' 567eb8dc403SDave Cobbley else: 568eb8dc403SDave Cobbley raise 569eb8dc403SDave Cobbley 570eb8dc403SDave Cobbley if data is None: 571eb8dc403SDave Cobbley raise Exception("No data on read ready socket") 572eb8dc403SDave Cobbley elif not data: 573eb8dc403SDave Cobbley # This actually means an orderly shutdown 574eb8dc403SDave Cobbley # happened. But for this code it counts as an 575eb8dc403SDave Cobbley # error since the connection shouldn't go away 576eb8dc403SDave Cobbley # until qemu exits. 577eb8dc403SDave Cobbley raise Exception("Console connection closed unexpectedly") 578eb8dc403SDave Cobbley 579eb8dc403SDave Cobbley return data 580eb8dc403SDave Cobbley 581eb8dc403SDave Cobbley def stringify_event(self, event): 582eb8dc403SDave Cobbley val = '' 583eb8dc403SDave Cobbley if select.POLLERR == event: 584eb8dc403SDave Cobbley val = 'POLLER' 585eb8dc403SDave Cobbley elif select.POLLHUP == event: 586eb8dc403SDave Cobbley val = 'POLLHUP' 587eb8dc403SDave Cobbley elif select.POLLNVAL == event: 588eb8dc403SDave Cobbley val = 'POLLNVAL' 589eb8dc403SDave Cobbley return val 590eb8dc403SDave Cobbley 591eb8dc403SDave Cobbley def close_socket(self, sock): 592eb8dc403SDave Cobbley sock.shutdown(socket.SHUT_RDWR) 593eb8dc403SDave Cobbley sock.close() 594eb8dc403SDave Cobbley 595eb8dc403SDave Cobbley def close_ignore_error(self, fd): 596eb8dc403SDave Cobbley try: 597eb8dc403SDave Cobbley os.close(fd) 598eb8dc403SDave Cobbley except OSError: 599eb8dc403SDave Cobbley pass 600