ff3a00f730479c5cbe41ec0bf5de18f6748066da
[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         vector_size = ('vectors={vectors}'.
258                        format(vectors=2*self._qemu_opt.get('queue_count')+2))\
259             if self._qemu_opt.get('queue_count') else ''
260
261         # Create Virtio network device.
262         device = (' -device virtio-net-pci,netdev=vhost{vhost_id},mac={mac},'
263                   'mq=on,{vector_size},csum=off,gso=off,guest_tso4=off,'
264                   'guest_tso6=off,guest_ecn=off,mrg_rxbuf={mbuf},{queue_size}'.
265                   format(vhost_id=self._vhost_id, mac=mac,
266                          mbuf='on,host_mtu=9200' if jumbo_frames else 'off',
267                          queue_size=queue_size, vector_size=vector_size))
268         self._qemu_opt['options'] += device
269         # Add interface MAC and socket to the node dict
270         if_data = {'mac_address': mac, 'socket': socket}
271         if_name = 'vhost{vhost_id}'.format(vhost_id=self._vhost_id)
272         self._vm_info['interfaces'][if_name] = if_data
273         # Add socket to the socket list
274         self._socks.append(socket)
275
276     def _qemu_qmp_exec(self, cmd):
277         """Execute QMP command.
278
279         QMP is JSON based protocol which allows to control QEMU instance.
280
281         :param cmd: QMP command to execute.
282         :type cmd: str
283         :returns: Command output in python representation of JSON format. The
284             { "return": {} } response is QMP's success response. An error
285             response will contain the "error" keyword instead of "return".
286         """
287         # To enter command mode, the qmp_capabilities command must be issued.
288         ret_code, stdout, _ = self._ssh.exec_command(
289             'echo "{{ \\"execute\\": \\"qmp_capabilities\\" }}'
290             '{{ \\"execute\\": \\"{cmd}\\" }}" | '
291             'sudo -S socat - UNIX-CONNECT:{qmp_sock}'.
292             format(cmd=cmd, qmp_sock=self._qemu_opt.get('qmp_sock')))
293         if int(ret_code) != 0:
294             raise RuntimeError('QMP execute "{cmd}" failed on {host}'.
295                                format(cmd=cmd, host=self._node['host']))
296         # Skip capabilities negotiation messages.
297         out_list = stdout.splitlines()
298         if len(out_list) < 3:
299             raise RuntimeError('Invalid QMP output on {host}'.
300                                format(host=self._node['host']))
301         return json.loads(out_list[2])
302
303     def _qemu_qga_flush(self):
304         """Flush the QGA parser state."""
305         ret_code, stdout, _ = self._ssh.exec_command(
306             '(printf "\xFF"; sleep 1) | '
307             'sudo -S socat - UNIX-CONNECT:{qga_sock}'.
308             format(qga_sock=self._qemu_opt.get('qga_sock')))
309         if int(ret_code) != 0:
310             raise RuntimeError('QGA flush failed on {host}'.
311                                format(host=self._node['host']))
312         if not stdout:
313             return {}
314         return json.loads(stdout.split('\n', 1)[0])
315
316     def _qemu_qga_exec(self, cmd):
317         """Execute QGA command.
318
319         QGA provide access to a system-level agent via standard QMP commands.
320
321         :param cmd: QGA command to execute.
322         :type cmd: str
323         """
324         ret_code, stdout, _ = self._ssh.exec_command(
325             '(echo "{{ \\"execute\\": \\"{cmd}\\" }}"; sleep 1) | '
326             'sudo -S socat - UNIX-CONNECT:{qga_sock}'.
327             format(cmd=cmd, qga_sock=self._qemu_opt.get('qga_sock')))
328         if int(ret_code) != 0:
329             raise RuntimeError('QGA execute "{cmd}" failed on {host}'.
330                                format(cmd=cmd, host=self._node['host']))
331         if not stdout:
332             return {}
333         return json.loads(stdout.split('\n', 1)[0])
334
335     def _wait_until_vm_boot(self, timeout=60):
336         """Wait until QEMU VM is booted.
337
338         First try to flush qga until there is output.
339         Then ping QEMU guest agent each 5s until VM booted or timeout.
340
341         :param timeout: Waiting timeout in seconds (optional, default 60s).
342         :type timeout: int
343         """
344         start = time()
345         while True:
346             if time() - start > timeout:
347                 raise RuntimeError('timeout, VM {disk} not booted on {host}'.
348                                    format(disk=self._qemu_opt['disk_image'],
349                                           host=self._node['host']))
350             out = None
351             try:
352                 out = self._qemu_qga_flush()
353             except ValueError:
354                 logger.trace('QGA qga flush unexpected output {out}'.
355                              format(out=out))
356             # Empty output - VM not booted yet
357             if not out:
358                 sleep(5)
359             else:
360                 break
361         while True:
362             if time() - start > timeout:
363                 raise RuntimeError('timeout, VM with {disk} not booted '
364                                    'on {host}'.
365                                    format(disk=self._qemu_opt['disk_image'],
366                                           host=self._node['host']))
367             out = None
368             try:
369                 out = self._qemu_qga_exec('guest-ping')
370             except ValueError:
371                 logger.trace('QGA guest-ping unexpected output {out}'.
372                              format(out=out))
373             # Empty output - VM not booted yet
374             if not out:
375                 sleep(5)
376             # Non-error return - VM booted
377             elif out.get('return') is not None:
378                 break
379             # Skip error and wait
380             elif out.get('error') is not None:
381                 sleep(5)
382             else:
383                 # If there is an unexpected output from QGA guest-info, try
384                 # again until timeout.
385                 logger.trace('QGA guest-ping unexpected output {out}'.
386                              format(out=out))
387
388         logger.trace('VM with {disk_image} booted on {host}'.
389                      format(disk_image=self._qemu_opt['disk_image'],
390                             host=self._node['host']))
391
392     def _update_vm_interfaces(self):
393         """Update interface names in VM node dict."""
394         # Send guest-network-get-interfaces command via QGA, output example:
395         # {"return": [{"name": "eth0", "hardware-address": "52:54:00:00:04:01"},
396         # {"name": "eth1", "hardware-address": "52:54:00:00:04:02"}]}
397         out = self._qemu_qga_exec('guest-network-get-interfaces')
398         interfaces = out.get('return')
399         mac_name = {}
400         if not interfaces:
401             raise RuntimeError('Get VM {disk_image} interface list failed '
402                                'on {host}'.
403                                format(disk_image=self._qemu_opt['disk_image'],
404                                       host=self._node['host']))
405         # Create MAC-name dict
406         for interface in interfaces:
407             if 'hardware-address' not in interface:
408                 continue
409             mac_name[interface['hardware-address']] = interface['name']
410         # Match interface by MAC and save interface name
411         for interface in self._vm_info['interfaces'].values():
412             mac = interface.get('mac_address')
413             if_name = mac_name.get(mac)
414             if if_name is None:
415                 logger.trace('Interface name for MAC {mac} not found'.
416                              format(mac=mac))
417             else:
418                 interface['name'] = if_name
419
420     def qemu_start(self):
421         """Start QEMU and wait until VM boot.
422
423         .. note:: First set at least node to run QEMU on.
424
425         :returns: VM node info.
426         :rtype: dict
427         """
428         # Qemu binary path
429         bin_path = ('{qemu_path}{qemu_bin}'.
430                     format(qemu_path=self._qemu_opt.get('qemu_path'),
431                            qemu_bin=self._qemu_opt.get('qemu_bin')))
432
433         # Memory and huge pages
434         mem = ('-object memory-backend-file,id=mem,size={mem_size}M,'
435                'mem-path={path},share=on -m {mem_size} -numa node,memdev=mem'.
436                format(mem_size=self._qemu_opt.get('mem_size'),
437                       path=self._qemu_opt.get('huge_mnt')))
438
439         # Drive option
440         drive = ('-drive file={disk_image},format=raw,cache=none,if=virtio'
441                  '{locking}'.
442                  format(disk_image=self._qemu_opt.get('disk_image'),
443                         locking=',file.locking=off'\
444                             if self._qemu_version_is_greater('2.10') else ''))
445
446         # SSH forwarding
447         ssh = ('-net user,hostfwd=tcp::{ssh_fwd_port}-:22'.
448                format(ssh_fwd_port=self._qemu_opt.get('ssh_fwd_port')))
449         # Setup QMP via unix socket
450         qmp = ('-qmp unix:{qmp_sock},server,nowait'.
451                format(qmp_sock=self._qemu_opt.get('qmp_sock')))
452         # Setup QGA via chardev (unix socket) and isa-serial channel
453         qga = ('-chardev socket,path={qga_sock},server,nowait,id=qga0 '
454                '-device isa-serial,chardev=qga0'.
455                format(qga_sock=self._qemu_opt.get('qga_sock')))
456         # Setup serial console
457         serial = ('-chardev socket,host=127.0.0.1,port={serial_port},id=gnc0,'
458                   'server,nowait -device isa-serial,chardev=gnc0'.
459                   format(serial_port=self._qemu_opt.get('serial_port')))
460
461         # Graphic setup
462         graphic = '-monitor none -display none -vga none'
463
464         # PID file
465         pid = ('-pidfile {pid_file}'.
466                format(pid_file=self._qemu_opt.get('pid_file')))
467
468         # By default check only if hugepages are available.
469         # If 'huge_allocate' is set to true try to allocate as well.
470         DUTSetup.check_huge_page(self._node, self._qemu_opt.get('huge_mnt'),
471                                  self._qemu_opt.get('mem_size'),
472                                  allocate=self._qemu_opt.get('huge_allocate'))
473
474         # Run QEMU
475         cmd = ('{bin_path} {smp} {mem} {ssh} {options} {drive} {qmp} {serial} '
476                '{qga} {graphic} {pid}'.
477                format(bin_path=bin_path, smp=self._qemu_opt.get('smp'),
478                       mem=mem, ssh=ssh, options=self._qemu_opt.get('options'),
479                       drive=drive, qmp=qmp, serial=serial, qga=qga,
480                       graphic=graphic, pid=pid))
481         try:
482             ret_code, _, _ = self._ssh.exec_command_sudo(cmd, timeout=300)
483             if int(ret_code) != 0:
484                 raise RuntimeError('QEMU start failed on {host}'.
485                                    format(host=self._node['host']))
486             # Wait until VM boot
487             self._wait_until_vm_boot()
488         except (RuntimeError, SSHTimeout):
489             self.qemu_kill_all()
490             self.qemu_clear_socks()
491             raise
492         logger.trace('QEMU started successfully.')
493         # Update interface names in VM node dict
494         self._update_vm_interfaces()
495         # Return VM node dict
496         return self._vm_info
497
498     def qemu_quit(self):
499         """Quit the QEMU emulator."""
500         out = self._qemu_qmp_exec('quit')
501         err = out.get('error')
502         if err is not None:
503             raise RuntimeError('QEMU quit failed on {host}: {error}'.
504                                format(host=self._node['host'],
505                                       error=json.dumps(err)))
506
507     def qemu_system_powerdown(self):
508         """Power down the system (if supported)."""
509         out = self._qemu_qmp_exec('system_powerdown')
510         err = out.get('error')
511         if err is not None:
512             raise RuntimeError(
513                 'QEMU system powerdown failed on {host}: {error}'.
514                 format(host=self._node['host'], error=json.dumps(err)))
515
516     def qemu_system_reset(self):
517         """Reset the system."""
518         out = self._qemu_qmp_exec('system_reset')
519         err = out.get('error')
520         if err is not None:
521             raise RuntimeError(
522                 'QEMU system reset failed on {host}: {error}'.
523                 format(host=self._node['host'], error=json.dumps(err)))
524
525     def qemu_kill(self):
526         """Kill qemu process."""
527         # Note: in QEMU start phase there are 3 QEMU processes because we
528         # daemonize QEMU
529         self._ssh.exec_command_sudo('chmod +r {pid}'.
530                                     format(pid=self._qemu_opt.get('pid_file')))
531         self._ssh.exec_command_sudo('kill -SIGKILL $(cat {pid})'.
532                                     format(pid=self._qemu_opt.get('pid_file')))
533         # Delete PID file
534         self._ssh.exec_command_sudo('rm -f {pid}'.
535                                     format(pid=self._qemu_opt.get('pid_file')))
536
537     def qemu_kill_all(self, node=None):
538         """Kill all qemu processes on DUT node if specified.
539
540         :param node: Node to kill all QEMU processes on.
541         :type node: dict
542         """
543         if node:
544             self.qemu_set_node(node)
545         self._ssh.exec_command_sudo('pkill -SIGKILL qemu')
546
547     def qemu_clear_socks(self):
548         """Remove all sockets created by QEMU."""
549         # If serial console port still open kill process
550         self._ssh.exec_command_sudo('fuser -k {serial_port}/tcp'.
551                                     format(serial_port=\
552                                            self._qemu_opt.get('serial_port')))
553         # Delete all created sockets
554         for socket in self._socks:
555             self._ssh.exec_command_sudo('rm -f {socket}'.
556                                         format(socket=socket))
557
558     def qemu_system_status(self):
559         """Return current VM status.
560
561         VM should be in following status:
562
563             - debug: QEMU running on a debugger
564             - finish-migrate: paused to finish the migration process
565             - inmigrate: waiting for an incoming migration
566             - internal-error: internal error has occurred
567             - io-error: the last IOP has failed
568             - paused: paused
569             - postmigrate: paused following a successful migrate
570             - prelaunch: QEMU was started with -S and guest has not started
571             - restore-vm: paused to restore VM state
572             - running: actively running
573             - save-vm: paused to save the VM state
574             - shutdown: shut down (and -no-shutdown is in use)
575             - suspended: suspended (ACPI S3)
576             - watchdog: watchdog action has been triggered
577             - guest-panicked: panicked as a result of guest OS panic
578
579         :returns: VM status.
580         :rtype: str
581         """
582         out = self._qemu_qmp_exec('query-status')
583         ret = out.get('return')
584         if ret is not None:
585             return ret.get('status')
586         else:
587             err = out.get('error')
588             raise RuntimeError('QEMU query-status failed on {host}: {error}'.
589                                format(host=self._node['host'],
590                                       error=json.dumps(err)))
591
592     def qemu_version(self):
593         """Return Qemu version.
594
595         :returns: Qemu version.
596         :rtype: str
597         """
598         # Qemu binary path
599         bin_path = ('{qemu_path}{qemu_bin}'.
600                     format(qemu_path=self._qemu_opt.get('qemu_path'),
601                            qemu_bin=self._qemu_opt.get('qemu_bin')))
602
603         try:
604             ret_code, stdout, _ = self._ssh.exec_command_sudo(
605                 '{bin_path} --version'.
606                 format(bin_path=bin_path))
607             if int(ret_code) != 0:
608                 raise RuntimeError('Failed to get QEMU version on {host}'.
609                                    format(host=self._node['host']))
610
611             return re.match(r'QEMU emulator version ([\d.]*)', stdout).group(1)
612         except (RuntimeError, SSHTimeout):
613             self.qemu_kill_all()
614             self.qemu_clear_socks()
615             raise
616
617     def _qemu_version_is_greater(self, version):
618         """Compare Qemu versions.
619
620         :returns: True if installed Qemu version is greater.
621         :rtype: bool
622         """
623         return StrictVersion(self.qemu_version()) > StrictVersion(version)
624
625     @staticmethod
626     def build_qemu(node, force_install=False, apply_patch=False):
627         """Build QEMU from sources.
628
629         :param node: Node to build QEMU on.
630         :param force_install: If True, then remove previous build.
631         :param apply_patch: If True, then apply patches from qemu_patches dir.
632         :type node: dict
633         :type force_install: bool
634         :type apply_patch: bool
635         :raises RuntimeError: If building QEMU failed.
636         """
637         ssh = SSH()
638         ssh.connect(node)
639
640         directory = (' --directory={install_dir}{patch}'.
641                      format(install_dir=Constants.QEMU_INSTALL_DIR,
642                             patch='-patch' if apply_patch else '-base'))
643         version = (' --version={install_version}'.
644                    format(install_version=Constants.QEMU_INSTALL_VERSION))
645         force = ' --force' if force_install else ''
646         patch = ' --patch' if apply_patch else ''
647         arch = Topology.get_node_arch(node)
648         target_list = (' --target-list={arch}-softmmu'.
649                        format(arch=arch))
650
651         ret_code, _, _ = ssh.exec_command(
652             "sudo -E sh -c '{fw_dir}/{lib_sh}/qemu_build.sh{version}{directory}"
653             "{force}{patch}{target_list}'".
654             format(fw_dir=Constants.REMOTE_FW_DIR,
655                    lib_sh=Constants.RESOURCES_LIB_SH,
656                    version=version, directory=directory, force=force,
657                    patch=patch, target_list=target_list), 1000)
658
659         if int(ret_code) != 0:
660             raise RuntimeError('QEMU build failed on {host}'.
661                                format(host=node['host']))