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