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