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