diff options
author | Stefan Stanacar <stefanx.stanacar@intel.com> | 2013-08-13 17:47:12 +0300 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2013-08-13 23:05:59 +0100 |
commit | 0ba78c1162bb125850a0ee504ca6fbe5bf21247f (patch) | |
tree | 7624e31a6b04e6cc154013b787660c6abdaeb0ed /meta/lib/oeqa | |
parent | bc4b98bd4ce8fcf2bedc09d172893aa770f68df0 (diff) | |
download | poky-0ba78c1162bb125850a0ee504ca6fbe5bf21247f.tar.gz |
oeqa/utils/qemurunner: get ip old fashioned way and use tcp serial console
The way we read data from the serial console was unreliable and blocking (AutoBuilder
seems to hit that often), so change the serial console type from unix socket to tcp
and reverse the connection - don't let qemu act as server (wait for a connection).
So now the serial console is used to save the boot log and make sure that we reached
the login prompt. Until a better way is found this should solve some of the AutoBuilder
failures (one being YB#4904).
Also we need to use the same method as the old qemuimagetest to get the ip
(from the qemu process arguments), because that it's more reliable.
The first version used here was to log into the target and use the output of
"ip addr show eth0" but then systemd decides that it should rename interfaces,
so that was changed to get the ip of the interface that has the default gw,
but if there is no default gw we'll get the loopback ip and we end up trying to
ssh into the host machine (some recent AutoBuilder runs showed that).
Changed in V2:
- use -ww for ps, as output might get truncated
(From OE-Core rev: 55e78185110937b7e2b143cf1020426d8df58b72)
Signed-off-by: Stefan Stanacar <stefanx.stanacar@intel.com>
Signed-off-by: Saul Wold <sgw@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib/oeqa')
-rw-r--r-- | meta/lib/oeqa/utils/oeqemuconsole.py | 45 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/qemurunner.py | 123 |
2 files changed, 81 insertions, 87 deletions
diff --git a/meta/lib/oeqa/utils/oeqemuconsole.py b/meta/lib/oeqa/utils/oeqemuconsole.py deleted file mode 100644 index 95a21332de..0000000000 --- a/meta/lib/oeqa/utils/oeqemuconsole.py +++ /dev/null | |||
@@ -1,45 +0,0 @@ | |||
1 | import socket | ||
2 | import time | ||
3 | import re | ||
4 | from telnetlib import Telnet | ||
5 | |||
6 | class oeQemuConsole(Telnet): | ||
7 | |||
8 | """ | ||
9 | Override Telnet class to use unix domain sockets, | ||
10 | Telnet uses AF_INET for socket, we don't want that. | ||
11 | Also, provide a read_all variant with timeout, that | ||
12 | returns whatever output there is. | ||
13 | """ | ||
14 | |||
15 | def __init__(self, stream, logfile): | ||
16 | |||
17 | Telnet.__init__(self, host=None) | ||
18 | self.stream = stream | ||
19 | self.logfile = logfile | ||
20 | self.eof = 0 | ||
21 | self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
22 | self.sock.connect(stream) | ||
23 | |||
24 | def log(self, msg): | ||
25 | if self.logfile: | ||
26 | with open(self.logfile, "a") as f: | ||
27 | f.write("%s\n" % msg) | ||
28 | |||
29 | |||
30 | def read_all_timeout(self, match, timeout=200): | ||
31 | """Read until EOF or until timeout or until match. | ||
32 | """ | ||
33 | ret = False | ||
34 | self.process_rawq() | ||
35 | endtime = time.time() + timeout | ||
36 | while not self.eof and time.time() < endtime: | ||
37 | self.fill_rawq() | ||
38 | self.process_rawq() | ||
39 | if re.search(match, self.cookedq): | ||
40 | ret = True | ||
41 | break | ||
42 | buf = self.cookedq | ||
43 | self.cookedq = '' | ||
44 | self.log(buf) | ||
45 | return (ret, buf) | ||
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py index d086203c04..20bb1e5e8d 100644 --- a/meta/lib/oeqa/utils/qemurunner.py +++ b/meta/lib/oeqa/utils/qemurunner.py | |||
@@ -6,26 +6,23 @@ | |||
6 | # It's used by testimage.bbclass. | 6 | # It's used by testimage.bbclass. |
7 | 7 | ||
8 | import subprocess | 8 | import subprocess |
9 | import optparse | ||
10 | import sys | ||
11 | import os | 9 | import os |
12 | import time | 10 | import time |
13 | import signal | 11 | import signal |
14 | import re | 12 | import re |
13 | import socket | ||
14 | import select | ||
15 | import bb | 15 | import bb |
16 | from oeqa.utils.oeqemuconsole import oeQemuConsole | ||
17 | 16 | ||
18 | class QemuRunner: | 17 | class QemuRunner: |
19 | 18 | ||
20 | def __init__(self, machine, rootfs, display = None, tmpdir = None, logfile = None, boottime = 400): | 19 | def __init__(self, machine, rootfs, display = None, tmpdir = None, logfile = None, boottime = 400, runqemutime = 60): |
21 | # Popen object | 20 | # Popen object |
22 | self.runqemu = None | 21 | self.runqemu = None |
23 | 22 | ||
24 | self.machine = machine | 23 | self.machine = machine |
25 | self.rootfs = rootfs | 24 | self.rootfs = rootfs |
26 | 25 | ||
27 | self.streampath = '/tmp/qemuconnection.%s' % os.getpid() | ||
28 | self.qemuparams = 'bootparams="console=tty1 console=ttyS0,115200n8" qemuparams="-serial unix:%s,server,nowait"' % self.streampath | ||
29 | self.qemupid = None | 26 | self.qemupid = None |
30 | self.ip = None | 27 | self.ip = None |
31 | 28 | ||
@@ -33,11 +30,30 @@ class QemuRunner: | |||
33 | self.tmpdir = tmpdir | 30 | self.tmpdir = tmpdir |
34 | self.logfile = logfile | 31 | self.logfile = logfile |
35 | self.boottime = boottime | 32 | self.boottime = boottime |
33 | self.runqemutime = runqemutime | ||
34 | |||
35 | self.bootlog = '' | ||
36 | self.qemusock = None | ||
37 | |||
38 | try: | ||
39 | self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
40 | self.server_socket.setblocking(0) | ||
41 | self.server_socket.bind(("127.0.0.1",0)) | ||
42 | self.server_socket.listen(2) | ||
43 | self.serverport = self.server_socket.getsockname()[1] | ||
44 | bb.note("Created listening socket for qemu serial console on: 127.0.0.1:%s" % self.serverport) | ||
45 | except socket.error, msg: | ||
46 | self.server_socket.close() | ||
47 | bb.fatal("Failed to create listening socket: %s" %msg[1]) | ||
48 | |||
49 | |||
50 | def log(self, msg): | ||
51 | if self.logfile: | ||
52 | with open(self.logfile, "a") as f: | ||
53 | f.write("%s" % msg) | ||
36 | 54 | ||
37 | def launch(self, qemuparams = None): | 55 | def launch(self, qemuparams = None): |
38 | 56 | ||
39 | if qemuparams: | ||
40 | self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' | ||
41 | 57 | ||
42 | if self.display: | 58 | if self.display: |
43 | os.environ["DISPLAY"] = self.display | 59 | os.environ["DISPLAY"] = self.display |
@@ -53,49 +69,70 @@ class QemuRunner: | |||
53 | else: | 69 | else: |
54 | os.environ["OE_TMPDIR"] = self.tmpdir | 70 | os.environ["OE_TMPDIR"] = self.tmpdir |
55 | 71 | ||
72 | self.qemuparams = 'bootparams="console=tty1 console=ttyS0,115200n8" qemuparams="-serial tcp:127.0.0.1:%s"' % self.serverport | ||
73 | if qemuparams: | ||
74 | self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' | ||
75 | |||
56 | launch_cmd = 'runqemu %s %s %s' % (self.machine, self.rootfs, self.qemuparams) | 76 | launch_cmd = 'runqemu %s %s %s' % (self.machine, self.rootfs, self.qemuparams) |
57 | self.runqemu = subprocess.Popen(launch_cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,preexec_fn=os.setpgrp) | 77 | self.runqemu = subprocess.Popen(launch_cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,preexec_fn=os.setpgrp) |
58 | 78 | ||
59 | bb.note("runqemu started, pid is %s" % self.runqemu.pid) | 79 | bb.note("runqemu started, pid is %s" % self.runqemu.pid) |
60 | bb.note("waiting at most 60 seconds for qemu pid") | 80 | bb.note("waiting at most %s seconds for qemu pid" % self.runqemutime) |
61 | endtime = time.time() + 60 | 81 | endtime = time.time() + self.runqemutime |
62 | while not self.is_alive() and time.time() < endtime: | 82 | while not self.is_alive() and time.time() < endtime: |
63 | time.sleep(1) | 83 | time.sleep(1) |
64 | 84 | ||
65 | if self.is_alive(): | 85 | if self.is_alive(): |
66 | bb.note("qemu started - qemu procces pid is %s" % self.qemupid) | 86 | bb.note("qemu started - qemu procces pid is %s" % self.qemupid) |
67 | 87 | pscmd = 'ps -p %s -fww | grep -o "192\.168\.7\.[0-9]*::" | awk -F":" \'{print $1}\'' % self.qemupid | |
68 | console = oeQemuConsole(self.streampath, self.logfile) | 88 | self.ip = subprocess.Popen(pscmd,shell=True,stdout=subprocess.PIPE).communicate()[0].strip() |
89 | if not re.search("^((?:[0-9]{1,3}\.){3}[0-9]{1,3})$", self.ip): | ||
90 | bb.note("Couldn't get ip from qemu process arguments, I got '%s'" % self.ip) | ||
91 | bb.note("Here is the ps output:\n%s" % \ | ||
92 | subprocess.Popen("ps -p %s -fww" % self.qemupid,shell=True,stdout=subprocess.PIPE).communicate()[0]) | ||
93 | self.kill() | ||
94 | return False | ||
95 | bb.note("IP found: %s" % self.ip) | ||
69 | bb.note("Waiting at most %d seconds for login banner" % self.boottime ) | 96 | bb.note("Waiting at most %d seconds for login banner" % self.boottime ) |
70 | (match, text) = console.read_all_timeout("login:", self.boottime) | 97 | endtime = time.time() + self.boottime |
71 | 98 | socklist = [self.server_socket] | |
72 | if match: | 99 | reachedlogin = False |
73 | bb.note("Reached login banner") | 100 | stopread = False |
74 | console.write("root\n") | 101 | while time.time() < endtime and not stopread: |
75 | (index, match, text) = console.expect([r"(root@[\w-]+:~#)"],10) | 102 | sread, swrite, serror = select.select(socklist, [], [], 0) |
76 | if not match: | 103 | for sock in sread: |
77 | bb.note("Couldn't get prompt, all I got was:\n%s" % text) | 104 | if sock is self.server_socket: |
78 | return False | 105 | self.qemusock, addr = self.server_socket.accept() |
79 | console.write("ip addr show `ip route list | sed -n '1p' | awk '{print $5}'` | sed -n '3p' | awk '{ print $2 }' | cut -f 1 -d \"/\"\n") | 106 | self.qemusock.setblocking(0) |
80 | (index, match, text) = console.expect([r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})"],10) | 107 | socklist.append(self.qemusock) |
81 | console.close() | 108 | socklist.remove(self.server_socket) |
82 | if match: | 109 | bb.note("Connection from %s:%s" % addr) |
83 | self.ip = match.group(0) | 110 | else: |
84 | bb.note("Ip found: %s" % self.ip) | 111 | data = sock.recv(1024) |
85 | else: | 112 | if data: |
86 | bb.note("Couldn't determine ip, all I got was:\n%s" % text) | 113 | self.log(data) |
87 | return False | 114 | self.bootlog += data |
88 | else: | 115 | lastlines = "\n".join(self.bootlog.splitlines()[-2:]) |
89 | console.close() | 116 | if re.search("login:", lastlines): |
117 | stopread = True | ||
118 | reachedlogin = True | ||
119 | bb.note("Reached login banner") | ||
120 | else: | ||
121 | socklist.remove(sock) | ||
122 | sock.close() | ||
123 | stopread = True | ||
124 | |||
125 | |||
126 | if not reachedlogin: | ||
90 | bb.note("Target didn't reached login boot in %d seconds" % self.boottime) | 127 | bb.note("Target didn't reached login boot in %d seconds" % self.boottime) |
91 | lines = "\n".join(text.splitlines()[-5:]) | 128 | lines = "\n".join(self.bootlog.splitlines()[-5:]) |
92 | bb.note("Last 5 lines of text:\n%s" % lines) | 129 | bb.note("Last 5 lines of text:\n%s" % lines) |
93 | bb.note("Check full boot log: %s" % self.logfile) | 130 | bb.note("Check full boot log: %s" % self.logfile) |
131 | self.kill() | ||
94 | return False | 132 | return False |
95 | else: | 133 | else: |
96 | bb.note("Qemu pid didn't appeared in 30 seconds") | 134 | bb.note("Qemu pid didn't appeared in %s seconds" % self.runqemutime) |
97 | self.runqemu.terminate() | 135 | self.kill() |
98 | self.runqemu.kill() | ||
99 | bb.note("Output from runqemu: %s " % self.runqemu.stdout.read()) | 136 | bb.note("Output from runqemu: %s " % self.runqemu.stdout.read()) |
100 | self.runqemu.stdout.close() | 137 | self.runqemu.stdout.close() |
101 | return False | 138 | return False |
@@ -104,12 +141,15 @@ class QemuRunner: | |||
104 | 141 | ||
105 | 142 | ||
106 | def kill(self): | 143 | def kill(self): |
107 | if self.runqemu: | 144 | if self.server_socket: |
145 | self.server_socket.close() | ||
146 | self.server_socket = None | ||
147 | if self.runqemu.pid: | ||
108 | os.kill(-self.runqemu.pid,signal.SIGTERM) | 148 | os.kill(-self.runqemu.pid,signal.SIGTERM) |
149 | os.kill(-self.runqemu.pid,signal.SIGKILL) | ||
150 | self.runqemu.pid = None | ||
109 | self.qemupid = None | 151 | self.qemupid = None |
110 | self.ip = None | 152 | self.ip = None |
111 | if os.path.exists(self.streampath): | ||
112 | os.remove(self.streampath) | ||
113 | 153 | ||
114 | def restart(self, qemuparams = None): | 154 | def restart(self, qemuparams = None): |
115 | if self.is_alive(): | 155 | if self.is_alive(): |
@@ -121,7 +161,7 @@ class QemuRunner: | |||
121 | qemu_child = self.find_child(str(self.runqemu.pid)) | 161 | qemu_child = self.find_child(str(self.runqemu.pid)) |
122 | if qemu_child: | 162 | if qemu_child: |
123 | self.qemupid = qemu_child[0] | 163 | self.qemupid = qemu_child[0] |
124 | if os.path.exists("/proc/" + str(self.qemupid)) and os.path.exists(self.streampath): | 164 | if os.path.exists("/proc/" + str(self.qemupid)): |
125 | return True | 165 | return True |
126 | return False | 166 | return False |
127 | 167 | ||
@@ -145,7 +185,6 @@ class QemuRunner: | |||
145 | commands[data[0]] = data[2] | 185 | commands[data[0]] = data[2] |
146 | 186 | ||
147 | if parent_pid not in pids: | 187 | if parent_pid not in pids: |
148 | sys.stderr.write("No children found matching %s\n" % parent_pid) | ||
149 | return [] | 188 | return [] |
150 | 189 | ||
151 | parents = [] | 190 | parents = [] |
@@ -166,6 +205,6 @@ class QemuRunner: | |||
166 | # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx" | 205 | # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx" |
167 | basecmd = commands[p].split()[0] | 206 | basecmd = commands[p].split()[0] |
168 | basecmd = os.path.basename(basecmd) | 207 | basecmd = os.path.basename(basecmd) |
169 | if "qemu-system" in basecmd and "-serial unix" in commands[p]: | 208 | if "qemu-system" in basecmd and "-serial tcp" in commands[p]: |
170 | return [int(p),commands[p]] | 209 | return [int(p),commands[p]] |
171 | 210 | ||