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