Fix jumbo frames
[csit.git] / resources / libraries / python / QemuUtils.py
1 # Copyright (c) 2018 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """QEMU utilities library."""
15
16 from time import time, sleep
17 import json
18 import re
19 # Disable due to pylint bug
20 # pylint: disable=no-name-in-module,import-error
21 from distutils.version import StrictVersion
22
23 from robot.api import logger
24
25 from resources.libraries.python.ssh import SSH, SSHTimeout
26 from resources.libraries.python.constants import Constants
27 from resources.libraries.python.DUTSetup import DUTSetup
28 from resources.libraries.python.topology import NodeType, Topology
29
30
31 class QemuUtils(object):
32     """QEMU utilities."""
33
34     def __init__(self, qemu_id=1):
35         self._qemu_id = qemu_id
36         self._vhost_id = 0
37         self._ssh = None
38         self._node = None
39         # Qemu Options
40         self._qemu_opt = {}
41         # Path to QEMU binary. Use x86_64 by default
42         self._qemu_opt['qemu_path'] = '/usr/bin/'
43         self._qemu_opt['qemu_bin'] = 'qemu-system-x86_64'
44         # QEMU Machine Protocol socket
45         self._qemu_opt['qmp_sock'] = '/tmp/qmp{0}.sock'.format(self._qemu_id)
46         # QEMU Guest Agent socket
47         self._qemu_opt['qga_sock'] = '/tmp/qga{0}.sock'.format(self._qemu_id)
48         # QEMU PID file
49         self._qemu_opt['pid_file'] = '/tmp/qemu{0}.pid'.format(self._qemu_id)
50         # Default 1 CPU.
51         self._qemu_opt['smp'] = '-smp 1,sockets=1,cores=1,threads=1'
52         # Daemonize the QEMU process after initialization. Default one
53         # management interface.
54         self._qemu_opt['options'] = '-cpu host -daemonize -enable-kvm ' \
55             '-machine pc,accel=kvm,usb=off,mem-merge=off ' \
56             '-net nic,macaddr=52:54:00:00:{0:02x}:ff -balloon none'\
57             .format(self._qemu_id)
58         self._qemu_opt['ssh_fwd_port'] = 10021 + qemu_id
59         # Default serial console port
60         self._qemu_opt['serial_port'] = 4555 + qemu_id
61         # Default 512MB virtual RAM
62         self._qemu_opt['mem_size'] = 512
63         # Default huge page mount point, required for Vhost-user interfaces.
64         self._qemu_opt['huge_mnt'] = '/mnt/huge'
65         # Default do not allocate huge pages.
66         self._qemu_opt['huge_allocate'] = False
67         # Default image for CSIT virl setup
68         self._qemu_opt['disk_image'] = '/var/lib/vm/vhost-nested.img'
69         # Virtio queue count
70         self._qemu_opt['queue_count'] = 1
71         # Virtio queue size
72         self._qemu_opt['queue_size'] = None
73         # VM node info dict
74         self._vm_info = {
75             'type': NodeType.VM,
76             'port': self._qemu_opt['ssh_fwd_port'],
77             'username': 'cisco',
78             'password': 'cisco',
79             'interfaces': {},
80         }
81         # Qemu Sockets
82         self._socks = [self._qemu_opt.get('qmp_sock'),
83                        self._qemu_opt.get('qga_sock')]
84
85     def qemu_set_path(self, path):
86         """Set binary path for QEMU.
87
88         :param path: Absolute path in filesystem.
89         :type path: str
90         """
91         self._qemu_opt['qemu_path'] = path
92
93     def qemu_set_queue_count(self, count):
94         """Set number of virtio queues.
95
96         :param count: Number of virtio queues.
97         :type count: int
98         """
99         self._qemu_opt['queue_count'] = int(count)
100
101     def qemu_set_queue_size(self, size):
102         """Set RX/TX size of virtio queues.
103
104         :param size: Size of virtio queues.
105         :type size: int
106         """
107         self._qemu_opt['queue_size'] = int(size)
108
109     def qemu_set_smp(self, smp, cores, threads, sockets):
110         """Set SMP option for QEMU.
111
112         :param smp: Number of CPUs.
113         :param cores: Number of CPU cores on one socket.
114         :param threads: Number of threads on one CPU core.
115         :param sockets: Number of discrete sockets in the system.
116         :type smp: int
117         :type cores: int
118         :type threads: int
119         :type sockets: int
120         """
121         self._qemu_opt['smp'] = \
122             ('-smp {smp},cores={cores},threads={threads},sockets={sockets}'.
123              format(smp=smp, cores=cores, threads=threads, sockets=sockets))
124
125     def qemu_set_ssh_fwd_port(self, fwd_port):
126         """Set host port for guest SSH forwarding.
127
128         :param fwd_port: Port number on host for guest SSH forwarding.
129         :type fwd_port: int
130         """
131         self._qemu_opt['ssh_fwd_port'] = fwd_port
132         self._vm_info['port'] = fwd_port
133
134     def qemu_set_serial_port(self, port):
135         """Set serial console port.
136
137         :param port: Serial console port.
138         :type port: int
139         """
140         self._qemu_opt['serial_port'] = port
141
142     def qemu_set_mem_size(self, mem_size):
143         """Set virtual RAM size.
144
145         :param mem_size: RAM size in Mega Bytes.
146         :type mem_size: int
147         """
148         self._qemu_opt['mem_size'] = int(mem_size)
149
150     def qemu_set_huge_mnt(self, huge_mnt):
151         """Set hugefile mount point.
152
153         :param huge_mnt: System hugefile mount point.
154         :type huge_mnt: int
155         """
156         self._qemu_opt['huge_mnt'] = huge_mnt
157
158     def qemu_set_huge_allocate(self):
159         """Set flag to allocate more huge pages if needed."""
160         self._qemu_opt['huge_allocate'] = True
161
162     def qemu_set_disk_image(self, disk_image):
163         """Set disk image.
164
165         :param disk_image: Path of the disk image.
166         :type disk_image: str
167         """
168         self._qemu_opt['disk_image'] = disk_image
169
170     def qemu_set_affinity(self, *host_cpus):
171         """Set qemu affinity by getting thread PIDs via QMP and taskset to list
172         of CPU cores.
173
174         :param host_cpus: List of CPU cores.
175         :type host_cpus: list
176         """
177         qemu_cpus = self._qemu_qmp_exec('query-cpus')['return']
178
179         if len(qemu_cpus) != len(host_cpus):
180             raise ValueError('Host CPU count must match Qemu Thread count')
181
182         for qemu_cpu, host_cpu in zip(qemu_cpus, host_cpus):
183             ret_code, _, _ = self._ssh.exec_command_sudo(
184                 'taskset -pc {host_cpu} {thread_id}'.
185                 format(host_cpu=host_cpu, thread_id=qemu_cpu['thread_id']))
186             if int(ret_code) != 0:
187                 raise RuntimeError('Set affinity failed on {host}'.
188                                    format(host=self._node['host']))
189
190     def qemu_set_scheduler_policy(self):
191         """Set scheduler policy to SCHED_RR with priority 1 for all Qemu CPU
192         processes.
193
194         :raises RuntimeError: Set scheduler policy failed.
195         """
196         qemu_cpus = self._qemu_qmp_exec('query-cpus')['return']
197
198         for qemu_cpu in qemu_cpus:
199             ret_code, _, _ = self._ssh.exec_command_sudo(
200                 'chrt -r -p 1 {thread_id}'.
201                 format(thread_id=qemu_cpu['thread_id']))
202             if int(ret_code) != 0:
203                 raise RuntimeError('Set SCHED_RR failed on {host}'.
204                                    format(host=self._node['host']))
205
206     def qemu_set_node(self, node):
207         """Set node to run QEMU on.
208
209         :param node: Node to run QEMU on.
210         :type node: dict
211         """
212         self._node = node
213         self._ssh = SSH()
214         self._ssh.connect(node)
215         self._vm_info['host'] = node['host']
216
217         arch = Topology.get_node_arch(node)
218         self._qemu_opt['qemu_bin'] = 'qemu-system-{arch}'.format(arch=arch)
219
220     def qemu_add_vhost_user_if(self, socket, server=True, mac=None,
221                                jumbo_frames=False):
222         """Add Vhost-user interface.
223
224         :param socket: Path of the unix socket.
225         :param server: If True the socket shall be a listening socket.
226         :param mac: Vhost-user interface MAC address (optional, otherwise is
227             used auto-generated MAC 52:54:00:00:xx:yy).
228         :param jumbo_frames: Set True if jumbo frames are used in the test.
229         :type socket: str
230         :type server: bool
231         :type mac: str
232         :type jumbo_frames: bool
233         """
234         self._vhost_id += 1
235         # Create unix socket character device.
236         chardev = (' -chardev socket,id=char{vhost_id},path={socket}{server}'.
237                    format(vhost_id=self._vhost_id,
238                           socket=socket,
239                           server=',server' if server is True else ''))
240         self._qemu_opt['options'] += chardev
241         # Create Vhost-user network backend.
242         netdev = (' -netdev vhost-user,id=vhost{vhost_id},'
243                   'chardev=char{vhost_id},queues={queue_count}'.
244                   format(vhost_id=self._vhost_id,
245                          queue_count=self._qemu_opt.get('queue_count')))
246         self._qemu_opt['options'] += netdev
247         # If MAC is not specified use auto-generated MAC address based on
248         # template 52:54:00:00:<qemu_id>:<vhost_id>, e.g. vhost1 MAC of QEMU
249         #  with ID 1 is 52:54:00:00:01:01
250         mac = ('52:54:00:00:{qemu_id:02x}:{vhost_id:02x}'.
251                format(qemu_id=self._qemu_id, vhost_id=self._vhost_id))\
252             if mac is None else mac
253
254         queue_size = (',rx_queue_size={queue_size},tx_queue_size={queue_size}'.
255                       format(queue_size=self._qemu_opt.get('queue_size')))\
256             if self._qemu_opt.get('queue_size') else ''
257
258         # Create Virtio network device.
259         device = (' -device virtio-net-pci,netdev=vhost{vhost_id},mac={mac},'
260                   'mq=on,csum=off,gso=off,guest_tso4=off,guest_tso6=off,'
261                   'guest_ecn=off,mrg_rxbuf={mbuf}{queue_size}'.
262                   format(vhost_id=self._vhost_id, mac=mac,
263                          mbuf='on,host_mtu=9200' if jumbo_frames else 'off',
264                          queue_size=queue_size))
265         self._qemu_opt['options'] += device
266         # Add interface MAC and socket to the node dict
267         if_data = {'mac_address': mac, 'socket': socket}
268         if_name = 'vhost{vhost_id}'.format(vhost_id=self._vhost_id)
269         self._vm_info['interfaces'][if_name] = if_data
270         # Add socket to the socket list
271         self._socks.append(socket)
272
273     def _qemu_qmp_exec(self, cmd):
274         """Execute QMP command.
275
276         QMP is JSON based protocol which allows to control QEMU instance.
277
278         :param cmd: QMP command to execute.
279         :type cmd: str
280         :returns: Command output in python representation of JSON format. The
281             { "return": {} } response is QMP's success response. An error
282             response will contain the "error" keyword instead of "return".
283         """
284         # To enter command mode, the qmp_capabilities command must be issued.
285         ret_code, stdout, _ = self._ssh.exec_command(
286             'echo "{{ \\"execute\\": \\"qmp_capabilities\\" }}'
287             '{{ \\"execute\\": \\"{cmd}\\" }}" | '
288             'sudo -S socat - UNIX-CONNECT:{qmp_sock}'.
289             format(cmd=cmd, qmp_sock=self._qemu_opt.get('qmp_sock')))
290         if int(ret_code) != 0:
291             raise RuntimeError('QMP execute "{cmd}" failed on {host}'.
292                                format(cmd=cmd, host=self._node['host']))
293         # Skip capabilities negotiation messages.
294         out_list = stdout.splitlines()
295         if len(out_list) < 3:
296             raise RuntimeError('Invalid QMP output on {host}'.
297                                format(host=self._node['host']))
298         return json.loads(out_list[2])
299
300     def _qemu_qga_flush(self):
301         """Flush the QGA parser state."""
302         ret_code, stdout, _ = self._ssh.exec_command(
303             '(printf "\xFF"; sleep 1) | '
304             'sudo -S socat - UNIX-CONNECT:{qga_sock}'.
305             format(qga_sock=self._qemu_opt.get('qga_sock')))
306         if int(ret_code) != 0:
307             raise RuntimeError('QGA flush failed on {host}'.
308                                format(host=self._node['host']))
309         if not stdout:
310             return {}
311         return json.loads(stdout.split('\n', 1)[0])
312
313     def _qemu_qga_exec(self, cmd):
314         """Execute QGA command.
315
316         QGA provide access to a system-level agent via standard QMP commands.
317
318         :param cmd: QGA command to execute.
319         :type cmd: str
320         """
321         ret_code, stdout, _ = self._ssh.exec_command(
322             '(echo "{{ \\"execute\\": \\"{cmd}\\" }}"; sleep 1) | '
323             'sudo -S socat - UNIX-CONNECT:{qga_sock}'.
324             format(cmd=cmd, qga_sock=self._qemu_opt.get('qga_sock')))
325         if int(ret_code) != 0:
326             raise RuntimeError('QGA execute "{cmd}" failed on {host}'.
327                                format(cmd=cmd, host=self._node['host']))
328         if not stdout:
329             return {}
330         return json.loads(stdout.split('\n', 1)[0])
331
332     def _wait_until_vm_boot(self, timeout=60):
333         """Wait until QEMU VM is booted.
334
335         First try to flush qga until there is output.
336         Then ping QEMU guest agent each 5s until VM booted or timeout.
337
338         :param timeout: Waiting timeout in seconds (optional, default 60s).
339         :type timeout: int
340         """
341         start = time()
342         while True:
343             if time() - start > timeout:
344                 raise RuntimeError('timeout, VM {disk} not booted on {host}'.
345                                    format(disk=self._qemu_opt['disk_image'],
346                                           host=self._node['host']))
347             out = None
348             try:
349                 out = self._qemu_qga_flush()
350             except ValueError:
351                 logger.trace('QGA qga flush unexpected output {out}'.
352                              format(out=out))
353             # Empty output - VM not booted yet
354             if not out:
355                 sleep(5)
356             else:
357                 break
358         while True:
359             if time() - start > timeout:
360                 raise RuntimeError('timeout, VM with {disk} not booted '
361                                    'on {host}'.
362                                    format(disk=self._qemu_opt['disk_image'],
363                                           host=self._node['host']))
364             out = None
365             try:
366                 out = self._qemu_qga_exec('guest-ping')
367             except ValueError:
368                 logger.trace('QGA guest-ping unexpected output {out}'.
369                              format(out=out))
370             # Empty output - VM not booted yet
371             if not out:
372                 sleep(5)
373             # Non-error return - VM booted
374             elif out.get('return') is not None:
375                 break
376             # Skip error and wait
377             elif out.get('error') is not None:
378                 sleep(5)
379             else:
380                 # If there is an unexpected output from QGA guest-info, try
381                 # again until timeout.
382                 logger.trace('QGA guest-ping unexpected output {out}'.
383                              format(out=out))
384
385         logger.trace('VM with {disk_image} booted on {host}'.
386                      format(disk_image=self._qemu_opt['disk_image'],
387                             host=self._node['host']))
388
389     def _update_vm_interfaces(self):
390         """Update interface names in VM node dict."""
391         # Send guest-network-get-interfaces command via QGA, output example:
392         # {"return": [{"name": "eth0", "hardware-address": "52:54:00:00:04:01"},
393         # {"name": "eth1", "hardware-address": "52:54:00:00:04:02"}]}
394         out = self._qemu_qga_exec('guest-network-get-interfaces')
395         interfaces = out.get('return')
396         mac_name = {}
397         if not interfaces:
398             raise RuntimeError('Get VM {disk_image} interface list failed '
399                                'on {host}'.
400                                format(disk_image=self._qemu_opt['disk_image'],
401                                       host=self._node['host']))
402         # Create MAC-name dict
403         for interface in interfaces:
404             if 'hardware-address' not in interface:
405                 continue
406             mac_name[interface['hardware-address']] = interface['name']
407         # Match interface by MAC and save interface name
408         for interface in self._vm_info['interfaces'].values():
409             mac = interface.get('mac_address')
410             if_name = mac_name.get(mac)
411             if if_name is None:
412                 logger.trace('Interface name for MAC {mac} not found'.
413                              format(mac=mac))
414             else:
415                 interface['name'] = if_name
416
417     def qemu_start(self):
418         """Start QEMU and wait until VM boot.
419
420         .. note:: First set at least node to run QEMU on.
421
422         :returns: VM node info.
423         :rtype: dict
424         """
425         # Qemu binary path
426         bin_path = ('{qemu_path}{qemu_bin}'.
427                     format(qemu_path=self._qemu_opt.get('qemu_path'),
428                            qemu_bin=self._qemu_opt.get('qemu_bin')))
429
430         # Memory and huge pages
431         mem = ('-object memory-backend-file,id=mem,size={mem_size}M,'
432                'mem-path={path},share=on -m {mem_size} -numa node,memdev=mem'.
433                format(mem_size=self._qemu_opt.get('mem_size'),
434                       path=self._qemu_opt.get('huge_mnt')))
435
436         # Drive option
437         drive = ('-drive file={disk_image},format=raw,cache=none,if=virtio'
438                  '{locking}'.
439                  format(disk_image=self._qemu_opt.get('disk_image'),
440                         locking=',file.locking=off'\
441                             if self._qemu_version_is_greater('2.10') else ''))
442
443         # SSH forwarding
444         ssh = ('-net user,hostfwd=tcp::{ssh_fwd_port}-:22'.
445                format(ssh_fwd_port=self._qemu_opt.get('ssh_fwd_port')))
446         # Setup QMP via unix socket
447         qmp = ('-qmp unix:{qmp_sock},server,nowait'.
448                format(qmp_sock=self._qemu_opt.get('qmp_sock')))
449         # Setup QGA via chardev (unix socket) and isa-serial channel
450         qga = ('-chardev socket,path={qga_sock},server,nowait,id=qga0 '
451                '-device isa-serial,chardev=qga0'.
452                format(qga_sock=self._qemu_opt.get('qga_sock')))
453         # Setup serial console
454         serial = ('-chardev socket,host=127.0.0.1,port={serial_port},id=gnc0,'
455                   'server,nowait -device isa-serial,chardev=gnc0'.
456                   format(serial_port=self._qemu_opt.get('serial_port')))
457
458         # Graphic setup
459         graphic = '-monitor none -display none -vga none'
460
461         # PID file
462         pid = ('-pidfile {pid_file}'.
463                format(pid_file=self._qemu_opt.get('pid_file')))
464
465         # By default check only if hugepages are available.
466         # If 'huge_allocate' is set to true try to allocate as well.
467         DUTSetup.check_huge_page(self._node, self._qemu_opt.get('huge_mnt'),
468                                  self._qemu_opt.get('mem_size'),
469                                  allocate=self._qemu_opt.get('huge_allocate'))
470
471         # Run QEMU
472         cmd = ('{bin_path} {smp} {mem} {ssh} {options} {drive} {qmp} {serial} '
473                '{qga} {graphic} {pid}'.
474                format(bin_path=bin_path, smp=self._qemu_opt.get('smp'),
475                       mem=mem, ssh=ssh, options=self._qemu_opt.get('options'),
476                       drive=drive, qmp=qmp, serial=serial, qga=qga,
477                       graphic=graphic, pid=pid))
478         try:
479             ret_code, _, _ = self._ssh.exec_command_sudo(cmd, timeout=300)
480             if int(ret_code) != 0:
481                 raise RuntimeError('QEMU start failed on {host}'.
482                                    format(host=self._node['host']))
483             # Wait until VM boot
484             self._wait_until_vm_boot()
485         except (RuntimeError, SSHTimeout):
486             self.qemu_kill_all()
487             self.qemu_clear_socks()
488             raise
489         logger.trace('QEMU started successfully.')
490         # Update interface names in VM node dict
491         self._update_vm_interfaces()
492         # Return VM node dict
493         return self._vm_info
494
495     def qemu_quit(self):
496         """Quit the QEMU emulator."""
497         out = self._qemu_qmp_exec('quit')
498         err = out.get('error')
499         if err is not None:
500             raise RuntimeError('QEMU quit failed on {host}: {error}'.
501                                format(host=self._node['host'],
502                                       error=json.dumps(err)))
503
504     def qemu_system_powerdown(self):
505         """Power down the system (if supported)."""
506         out = self._qemu_qmp_exec('system_powerdown')
507         err = out.get('error')
508         if err is not None:
509             raise RuntimeError(
510                 'QEMU system powerdown failed on {host}: {error}'.
511                 format(host=self._node['host'], error=json.dumps(err)))
512
513     def qemu_system_reset(self):
514         """Reset the system."""
515         out = self._qemu_qmp_exec('system_reset')
516         err = out.get('error')
517         if err is not None:
518             raise RuntimeError(
519                 'QEMU system reset failed on {host}: {error}'.
520                 format(host=self._node['host'], error=json.dumps(err)))
521
522     def qemu_kill(self):
523         """Kill qemu process."""
524         # Note: in QEMU start phase there are 3 QEMU processes because we
525         # daemonize QEMU
526         self._ssh.exec_command_sudo('chmod +r {pid}'.
527                                     format(pid=self._qemu_opt.get('pid_file')))
528         self._ssh.exec_command_sudo('kill -SIGKILL $(cat {pid})'.
529                                     format(pid=self._qemu_opt.get('pid_file')))
530         # Delete PID file
531         self._ssh.exec_command_sudo('rm -f {pid}'.
532                                     format(pid=self._qemu_opt.get('pid_file')))
533
534     def qemu_kill_all(self, node=None):
535         """Kill all qemu processes on DUT node if specified.
536
537         :param node: Node to kill all QEMU processes on.
538         :type node: dict
539         """
540         if node:
541             self.qemu_set_node(node)
542         self._ssh.exec_command_sudo('pkill -SIGKILL qemu')
543
544     def qemu_clear_socks(self):
545         """Remove all sockets created by QEMU."""
546         # If serial console port still open kill process
547         self._ssh.exec_command_sudo('fuser -k {serial_port}/tcp'.
548                                     format(serial_port=\
549                                            self._qemu_opt.get('serial_port')))
550         # Delete all created sockets
551         for socket in self._socks:
552             self._ssh.exec_command_sudo('rm -f {socket}'.
553                                         format(socket=socket))
554
555     def qemu_system_status(self):
556         """Return current VM status.
557
558         VM should be in following status:
559
560             - debug: QEMU running on a debugger
561             - finish-migrate: paused to finish the migration process
562             - inmigrate: waiting for an incoming migration
563             - internal-error: internal error has occurred
564             - io-error: the last IOP has failed
565             - paused: paused
566             - postmigrate: paused following a successful migrate
567             - prelaunch: QEMU was started with -S and guest has not started
568             - restore-vm: paused to restore VM state
569             - running: actively running
570             - save-vm: paused to save the VM state
571             - shutdown: shut down (and -no-shutdown is in use)
572             - suspended: suspended (ACPI S3)
573             - watchdog: watchdog action has been triggered
574             - guest-panicked: panicked as a result of guest OS panic
575
576         :returns: VM status.
577         :rtype: str
578         """
579         out = self._qemu_qmp_exec('query-status')
580         ret = out.get('return')
581         if ret is not None:
582             return ret.get('status')
583         else:
584             err = out.get('error')
585             raise RuntimeError('QEMU query-status failed on {host}: {error}'.
586                                format(host=self._node['host'],
587                                       error=json.dumps(err)))
588
589     def qemu_version(self):
590         """Return Qemu version.
591
592         :returns: Qemu version.
593         :rtype: str
594         """
595         # Qemu binary path
596         bin_path = ('{qemu_path}{qemu_bin}'.
597                     format(qemu_path=self._qemu_opt.get('qemu_path'),
598                            qemu_bin=self._qemu_opt.get('qemu_bin')))
599
600         try:
601             ret_code, stdout, _ = self._ssh.exec_command_sudo(
602                 '{bin_path} --version'.
603                 format(bin_path=bin_path))
604             if int(ret_code) != 0:
605                 raise RuntimeError('Failed to get QEMU version on {host}'.
606                                    format(host=self._node['host']))
607
608             return re.match(r'QEMU emulator version ([\d.]*)', stdout).group(1)
609         except (RuntimeError, SSHTimeout):
610             self.qemu_kill_all()
611             self.qemu_clear_socks()
612             raise
613
614     def _qemu_version_is_greater(self, version):
615         """Compare Qemu versions.
616
617         :returns: True if installed Qemu version is greater.
618         :rtype: bool
619         """
620         return StrictVersion(self.qemu_version()) > StrictVersion(version)
621
622     @staticmethod
623     def build_qemu(node, force_install=False, apply_patch=False):
624         """Build QEMU from sources.
625
626         :param node: Node to build QEMU on.
627         :param force_install: If True, then remove previous build.
628         :param apply_patch: If True, then apply patches from qemu_patches dir.
629         :type node: dict
630         :type force_install: bool
631         :type apply_patch: bool
632         :raises RuntimeError: If building QEMU failed.
633         """
634         ssh = SSH()
635         ssh.connect(node)
636
637         directory = (' --directory={install_dir}{patch}'.
638                      format(install_dir=Constants.QEMU_INSTALL_DIR,
639                             patch='-patch' if apply_patch else '-base'))
640         version = (' --version={install_version}'.
641                    format(install_version=Constants.QEMU_INSTALL_VERSION))
642         force = ' --force' if force_install else ''
643         patch = ' --patch' if apply_patch else ''
644         arch = Topology.get_node_arch(node)
645         target_list = (' --target-list={arch}-softmmu'.
646                        format(arch=arch))
647
648         ret_code, _, _ = ssh.exec_command(
649             "sudo -E sh -c '{fw_dir}/{lib_sh}/qemu_build.sh{version}{directory}"
650             "{force}{patch}{target_list}'".
651             format(fw_dir=Constants.REMOTE_FW_DIR,
652                    lib_sh=Constants.RESOURCES_LIB_SH,
653                    version=version, directory=directory, force=force,
654                    patch=patch, target_list=target_list), 1000)
655
656         if int(ret_code) != 0:
657             raise RuntimeError('QEMU build failed on {host}'.
658                                format(host=node['host']))