Call method in super class too
[ci.git] / htest / vm / qemu.py
blob435636a7e674752e7f02c30825479dcf51e9f5e3
1 #!/usr/bin/env python3
4 # Copyright (c) 2018 Vojtech Horky
5 # All rights reserved.
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
11 # - Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # - Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 # - The name of the author may not be used to endorse or promote products
17 # derived from this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 import subprocess
32 import socket
33 import logging
35 from htest.utils import retries, format_command, format_command_pipe
36 from htest.vm.controller import VMController
38 class QemuVMController(VMController):
39 """
40 QEMU VM controller.
41 """
43 def __init__(self, arch, name, boot_image):
44 VMController.__init__(self, 'QEMU-' + arch)
45 self.arch = arch
46 self.booted = False
47 self.name = name
48 self.boot_image = boot_image
49 self.logger = logging.getLogger('qemu-{}'.format(name))
51 def _check_is_up(self):
52 if not self.booted:
53 raise Exception("Machine not launched")
55 def _send_command(self, command):
56 self._check_is_up()
57 self.logger.debug("Sending command '{}'".format(command))
58 command = command + '\n'
59 self.monitor.sendall(command.encode('utf-8'))
61 def _run_command(self, command):
62 proc = subprocess.Popen(command)
63 proc.wait()
64 if proc.returncode != 0:
65 raise Exception("Command {} failed.".format(command))
67 def _run_pipe(self, commands):
68 self.logger.debug("Running pipe {}".format(format_command_pipe(commands)))
69 procs = []
70 for command in commands:
71 inp = None
72 if len(procs) > 0:
73 inp = procs[-1].stdout
74 proc = subprocess.Popen(command, stdout=subprocess.PIPE, stdin=inp)
75 procs.append(proc)
76 procs[-1].communicate()
79 def boot(self, **kwargs):
80 self.monitor_file = self.get_temp('monitor')
81 cmd = [ 'qemu-system-x86_64' , '-usb', '-m', '256' ]
82 cmd.append('-enable-kvm')
83 cmd.append('-cdrom')
84 cmd.append(self.boot_image)
85 #cmd.append('-daemonize')
86 cmd.append('-monitor')
87 cmd.append('unix:{},server,nowait'.format(self.monitor_file))
88 self.logger.debug("Starting QEMU: {}".format(format_command(cmd)))
90 self.proc = subprocess.Popen(cmd)
91 self.monitor = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
92 for xxx in retries(timeout=30, interval=2, name="ctl-socket", message="Failed to connect to QEMU control socket."):
93 try:
94 self.monitor.connect(self.monitor_file)
95 break
96 except FileNotFoundError:
97 pass
98 except ConnectionRefusedError:
99 pass
100 if self.proc.poll():
101 raise Exception("QEMU not started, aborting.")
103 self.booted = True
104 self.logger.info("Machine started.")
106 # Skip past GRUB
107 self.type('\n')
109 uspace_booted = False
110 for xxx in retries(timeout=3*60, interval=5, name="vterm", message="Failed to boot into userspace"):
111 lines = self.capture_vterm()
112 for l in lines:
113 if l.find('to see a few survival tips') != -1:
114 uspace_booted = True
115 break
116 if uspace_booted:
117 break
119 assert uspace_booted
121 self.logger.info("Machine booted into userspace.")
123 return
125 def capture_vterm_impl(self):
126 screenshot_full = self.get_temp('screen-full.ppm')
127 screenshot_term = self.get_temp('screen-term.png')
128 screenshot_text = self.get_temp('screen-term.txt')
130 self._send_command('screendump ' + screenshot_full)
132 for xxx in retries(timeout=5, interval=1, name="scrdump", message="Failed to capture screen"):
133 try:
134 self._run_command([
135 'convert',
136 screenshot_full,
137 '-crop', '640x480+4+24',
138 '+repage',
139 '-colors', '2',
140 '-monochrome',
141 screenshot_term
143 break
144 except:
145 pass
147 self._run_pipe([
149 'convert',
150 screenshot_term,
151 '-crop', '640x480',
152 '+repage',
153 '-crop', '8x16',
154 '+repage',
155 '+adjoin',
156 'txt:-',
159 'sed',
160 '-e', 's|[0-9]*,[0-9]*: ([^)]*)[ ]*#\\([0-9A-Fa-f]\\{6\\}\\).*|\\1|',
161 '-e', 's:^#.*:@:',
162 '-e', 's#000000#0#g',
163 '-e', 's#FFFFFF#F#',
165 [ 'tee', self.get_temp('1.txt') ],
167 'sed',
168 '-e', ':a',
169 '-e', 'N;s#\\n##;s#^@##;/@$/{s#@$##p;d}',
170 '-e', 't a',
172 [ 'tee', self.get_temp('2.txt') ],
174 'sed',
175 '-f', 'ocr.sed',
178 'sed',
179 '/../s#.*#?#',
181 [ 'tee', self.get_temp('3.txt') ],
183 'paste',
184 '-sd', '',
187 'fold',
188 '-w', '80',
190 [ 'tee', self.get_temp('4.txt') ],
192 'head',
193 '-n', '30',
196 'tee',
197 screenshot_text,
200 with open(screenshot_text, 'r') as f:
201 lines = [ l.strip('\n') for l in f.readlines() ]
202 self.logger.debug("Captured text:")
203 for l in lines:
204 self.logger.debug("| " + l)
205 return lines
207 def terminate(self):
208 if not self.booted:
209 return
210 self._send_command('quit')
211 VMController.terminate(self)
213 def type(self, what):
214 translations = {
215 ' ': 'spc',
216 '.': 'dot',
217 '-': 'minus',
218 '/': 'slash',
219 '\n': 'ret',
220 '_': 'shift-minus',
222 for letter in what:
223 if letter in translations:
224 letter = translations[letter]
225 self._send_command('sendkey ' + letter)
226 pass