Remove obsolete QEMU installation
[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 sleep
17 from string import Template
18 import json
19 from re import match
20 # Disable due to pylint bug
21 # pylint: disable=no-name-in-module,import-error
22 from distutils.version import StrictVersion
23
24 from robot.api import logger
25 from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
26 from resources.libraries.python.Constants import Constants
27 from resources.libraries.python.DpdkUtil import DpdkUtil
28 from resources.libraries.python.DUTSetup import DUTSetup
29 from resources.libraries.python.topology import NodeType, Topology
30 from resources.libraries.python.VppConfigGenerator import VppConfigGenerator
31 from resources.libraries.python.VPPUtil import VPPUtil
32
33 __all__ = ["QemuOptions", "QemuUtils"]
34
35
36 class QemuOptions(object):
37     """QEMU option class.
38
39     The class can handle input parameters that acts as QEMU command line
40     parameters. The only variable is a list of dictionaries where dictionaries
41     can be added multiple times. This emulates the QEMU behavior where one
42     command line parameter can be used multiple times (1..N). Example can be
43     device or object (so it is not an issue to have one memory
44     block of 2G and and second memory block of 512M but from other numa).
45
46     Class does support get value or string representation that will return
47     space separated, dash prefixed string of key value pairs used for command
48     line.
49     """
50
51     # Use one instance of class per tests.
52     ROBOT_LIBRARY_SCOPE = 'TEST CASE'
53
54     def __init__(self):
55         self.variables = list()
56
57     def add(self, variable, value):
58         """Add parameter to the list.
59
60         :param variable: QEMU parameter name (without dash).
61         :param value: Paired value.
62         :type variable: str
63         :type value: str or int
64         """
65         self.variables.append({str(variable): value})
66
67     def __str__(self):
68         """Return space separated string of key value pairs.
69
70         The format is suitable to be pasted to qemu command line.
71
72         :returns: Space separated string of key value pairs.
73         :rtype: str
74         """
75         return " ".join(["-{k} {v}".format(k=d.keys()[0], v=d.values()[0])
76                          for d in self.variables])
77
78
79 class QemuUtils(object):
80     """QEMU utilities."""
81
82     # Use one instance of class per tests.
83     ROBOT_LIBRARY_SCOPE = 'TEST CASE'
84
85     def __init__(self, node, qemu_id=1, smp=1, mem=512, vnf=None,
86                  img=Constants.QEMU_VM_IMAGE):
87         """Initialize QemuUtil class.
88
89         :param node: Node to run QEMU on.
90         :param qemu_id: QEMU identifier.
91         :param smp: Number of virtual SMP units (cores).
92         :param mem: Amount of memory.
93         :param vnf: Network function workload.
94         :param img: QEMU disk image or kernel image path.
95         :type node: dict
96         :type qemu_id: int
97         :type smp: int
98         :type mem: int
99         :type vnf: str
100         :type img: str
101         """
102         self._vhost_id = 0
103         self._node = node
104         self._vm_info = {
105             'host': node['host'],
106             'type': NodeType.VM,
107             'port': 10021 + qemu_id,
108             'serial': 4555 + qemu_id,
109             'username': 'cisco',
110             'password': 'cisco',
111             'interfaces': {},
112         }
113         if node['port'] != 22:
114             self._vm_info['host_port'] = node['port']
115             self._vm_info['host_username'] = node['username']
116             self._vm_info['host_password'] = node['password']
117         # Input Options.
118         self._opt = dict()
119         self._opt['qemu_id'] = qemu_id
120         self._opt['mem'] = int(mem)
121         self._opt['smp'] = int(smp)
122         self._opt['img'] = img
123         self._opt['vnf'] = vnf
124         # Temporary files.
125         self._temp = dict()
126         self._temp['pidfile'] = '/var/run/qemu_{id}.pid'.format(id=qemu_id)
127         if '/var/lib/vm/' in img:
128             self._opt['vm_type'] = 'nestedvm'
129             self._temp['qmp'] = '/var/run/qmp_{id}.sock'.format(id=qemu_id)
130             self._temp['qga'] = '/var/run/qga_{id}.sock'.format(id=qemu_id)
131         elif '/opt/boot/vmlinuz' in img:
132             self._opt['vm_type'] = 'kernelvm'
133             self._temp['log'] = '/tmp/serial_{id}.log'.format(id=qemu_id)
134             self._temp['ini'] = '/etc/vm_init_{id}.conf'.format(id=qemu_id)
135         else:
136             raise RuntimeError('QEMU: Unknown VM image option!')
137         # Computed parameters for QEMU command line.
138         self._params = QemuOptions()
139         self.add_params()
140
141     def add_params(self):
142         """Set QEMU command line parameters."""
143         self.add_default_params()
144         if self._opt.get('vm_type', '') == 'nestedvm':
145             self.add_nestedvm_params()
146         elif self._opt.get('vm_type', '') == 'kernelvm':
147             self.add_kernelvm_params()
148         else:
149             raise RuntimeError('QEMU: Unsupported VM type!')
150
151     def add_default_params(self):
152         """Set default QEMU command line parameters."""
153         self._params.add('daemonize', '')
154         self._params.add('nodefaults', '')
155         self._params.add('name', 'vnf{qemu},debug-threads=on'.
156                          format(qemu=self._opt.get('qemu_id')))
157         self._params.add('no-user-config', '')
158         self._params.add('monitor', 'none')
159         self._params.add('display', 'none')
160         self._params.add('vga', 'none')
161         self._params.add('enable-kvm', '')
162         self._params.add('pidfile', '{pidfile}'.
163                          format(pidfile=self._temp.get('pidfile')))
164         self._params.add('cpu', 'host')
165         self._params.add('machine', 'pc,accel=kvm,usb=off,mem-merge=off')
166         self._params.add('smp', '{smp},sockets=1,cores={smp},threads=1'.
167                          format(smp=self._opt.get('smp')))
168         self._params.add('object',
169                          'memory-backend-file,id=mem,size={mem}M,'
170                          'mem-path=/dev/hugepages,share=on'.
171                          format(mem=self._opt.get('mem')))
172         self._params.add('m', '{mem}M'.
173                          format(mem=self._opt.get('mem')))
174         self._params.add('numa', 'node,memdev=mem')
175         self._params.add('balloon', 'none')
176
177     def add_nestedvm_params(self):
178         """Set NestedVM QEMU parameters."""
179         self._params.add('net', 'nic,macaddr=52:54:00:00:{qemu:02x}:ff'.
180                          format(qemu=self._opt.get('qemu_id')))
181         self._params.add('net', 'user,hostfwd=tcp::{info[port]}-:22'.
182                          format(info=self._vm_info))
183         # TODO: Remove try except after fully migrated to Bionic or
184         # qemu_set_node is removed.
185         try:
186             locking = ',file.locking=off'\
187                 if self.qemu_version(version='2.10') else ''
188         except AttributeError:
189             locking = ''
190         self._params.add('drive',
191                          'file={img},format=raw,cache=none,if=virtio{locking}'.
192                          format(img=self._opt.get('img'), locking=locking))
193         self._params.add('qmp', 'unix:{qmp},server,nowait'.
194                          format(qmp=self._temp.get('qmp')))
195         self._params.add('chardev', 'socket,host=127.0.0.1,port={info[serial]},'
196                          'id=gnc0,server,nowait'.format(info=self._vm_info))
197         self._params.add('device', 'isa-serial,chardev=gnc0')
198         self._params.add('chardev',
199                          'socket,path={qga},server,nowait,id=qga0'.
200                          format(qga=self._temp.get('qga')))
201         self._params.add('device', 'isa-serial,chardev=qga0')
202
203     def add_kernelvm_params(self):
204         """Set KernelVM QEMU parameters."""
205         self._params.add('chardev', 'file,id=char0,path={log}'.
206                          format(log=self._temp.get('log')))
207         self._params.add('device', 'isa-serial,chardev=char0')
208         self._params.add('fsdev', 'local,id=root9p,path=/,security_model=none')
209         self._params.add('device',
210                          'virtio-9p-pci,fsdev=root9p,mount_tag=/dev/root')
211         self._params.add('kernel', '$(readlink -m {img}* | tail -1)'.
212                          format(img=self._opt.get('img')))
213         self._params.add('append',
214                          '"ro rootfstype=9p rootflags=trans=virtio '
215                          'console=ttyS0 tsc=reliable hugepages=256 '
216                          'init={init}"'.format(init=self._temp.get('ini')))
217
218     def create_kernelvm_config_vpp(self, **kwargs):
219         """Create QEMU VPP config files.
220
221         :param kwargs: Key-value pairs to replace content of VPP configuration
222             file.
223         :type kwargs: dict
224         """
225         startup = ('/etc/vpp/vm_startup_{id}.conf'.
226                    format(id=self._opt.get('qemu_id')))
227         running = ('/etc/vpp/vm_running_{id}.exec'.
228                    format(id=self._opt.get('qemu_id')))
229
230         self._temp['startup'] = startup
231         self._temp['running'] = running
232         self._opt['vnf_bin'] = ('/usr/bin/vpp -c {startup}'.
233                                 format(startup=startup))
234
235         # Create VPP startup configuration.
236         vpp_config = VppConfigGenerator()
237         vpp_config.set_node(self._node)
238         vpp_config.add_unix_nodaemon()
239         vpp_config.add_unix_cli_listen()
240         vpp_config.add_unix_exec(running)
241         vpp_config.add_cpu_main_core('0')
242         vpp_config.add_cpu_corelist_workers('1-{smp}'.
243                                             format(smp=self._opt.get('smp')-1))
244         vpp_config.add_dpdk_dev('0000:00:06.0', '0000:00:07.0')
245         vpp_config.add_dpdk_log_level('debug')
246         if not kwargs['jumbo_frames']:
247             vpp_config.add_dpdk_no_multi_seg()
248             vpp_config.add_dpdk_no_tx_checksum_offload()
249         vpp_config.add_plugin('disable', 'default')
250         vpp_config.add_plugin('enable', 'dpdk_plugin.so')
251         vpp_config.apply_config(startup, restart_vpp=False)
252
253         # Create VPP running configuration.
254         template = '{res}/{tpl}.exec'.format(res=Constants.RESOURCES_TPL_VM,
255                                              tpl=self._opt.get('vnf'))
256         exec_cmd_no_error(self._node, 'rm -f {running}'.format(running=running),
257                           sudo=True)
258
259         with open(template, 'r') as src_file:
260             src = Template(src_file.read())
261             exec_cmd_no_error(self._node, "echo '{out}' | sudo tee {running}".
262                               format(out=src.safe_substitute(**kwargs),
263                                      running=running))
264
265     def create_kernelvm_config_testpmd_io(self, **kwargs):
266         """Create QEMU testpmd-io command line.
267
268         :param kwargs: Key-value pairs to construct command line parameters.
269         :type kwargs: dict
270         """
271         testpmd_path = ('{path}/{arch}-native-linuxapp-gcc/app'.
272                         format(path=Constants.QEMU_VM_DPDK,
273                                arch=Topology.get_node_arch(self._node)))
274         testpmd_cmd = DpdkUtil.get_testpmd_cmdline(
275             eal_corelist='0-{smp}'.format(smp=self._opt.get('smp') - 1),
276             eal_driver=False,
277             eal_in_memory=True,
278             pmd_num_mbufs=16384,
279             pmd_rxq=kwargs['queues'],
280             pmd_txq=kwargs['queues'],
281             pmd_tx_offloads=False,
282             pmd_disable_hw_vlan=False,
283             pmd_max_pkt_len=9200 if kwargs['jumbo_frames'] else None,
284             pmd_nb_cores=str(self._opt.get('smp') - 1))
285
286         self._opt['vnf_bin'] = ('{testpmd_path}/{testpmd_cmd}'.
287                                 format(testpmd_path=testpmd_path,
288                                        testpmd_cmd=testpmd_cmd))
289
290     def create_kernelvm_config_testpmd_mac(self, **kwargs):
291         """Create QEMU testpmd-mac command line.
292
293         :param kwargs: Key-value pairs to construct command line parameters.
294         :type kwargs: dict
295         """
296         testpmd_path = ('{path}/{arch}-native-linuxapp-gcc/app'.
297                         format(path=Constants.QEMU_VM_DPDK,
298                                arch=Topology.get_node_arch(self._node)))
299         testpmd_cmd = DpdkUtil.get_testpmd_cmdline(
300             eal_corelist='0-{smp}'.format(smp=self._opt.get('smp') - 1),
301             eal_driver=False,
302             eal_in_memory=True,
303             pmd_num_mbufs=16384,
304             pmd_fwd_mode='mac',
305             pmd_eth_peer_0='0,{mac}'.format(mac=kwargs['vif1_mac']),
306             pmd_eth_peer_1='1,{mac}'.format(mac=kwargs['vif2_mac']),
307             pmd_rxq=kwargs['queues'],
308             pmd_txq=kwargs['queues'],
309             pmd_tx_offloads=False,
310             pmd_disable_hw_vlan=False,
311             pmd_max_pkt_len=9200 if kwargs['jumbo_frames'] else None,
312             pmd_nb_cores=str(self._opt.get('smp') - 1))
313
314         self._opt['vnf_bin'] = ('{testpmd_path}/{testpmd_cmd}'.
315                                 format(testpmd_path=testpmd_path,
316                                        testpmd_cmd=testpmd_cmd))
317
318     def create_kernelvm_init(self, **kwargs):
319         """Create QEMU init script.
320
321         :param kwargs: Key-value pairs to replace content of init startup file.
322         :type kwargs: dict
323         """
324         template = '{res}/init.sh'.format(res=Constants.RESOURCES_TPL_VM)
325         init = self._temp.get('ini')
326         exec_cmd_no_error(self._node, 'rm -f {init}'.format(init=init),
327                           sudo=True)
328
329         with open(template, 'r') as src_file:
330             src = Template(src_file.read())
331             exec_cmd_no_error(self._node, "echo '{out}' | sudo tee {init}".
332                               format(out=src.safe_substitute(**kwargs),
333                                      init=init))
334             exec_cmd_no_error(self._node, "chmod +x {init}".
335                               format(init=init), sudo=True)
336
337     def configure_kernelvm_vnf(self, **kwargs):
338         """Create KernelVM VNF configurations.
339
340         :param kwargs: Key-value pairs for templating configs.
341         :type kwargs: dict
342         """
343         if 'vpp' in self._opt.get('vnf'):
344             self.create_kernelvm_config_vpp(**kwargs)
345         elif 'testpmd_io' in self._opt.get('vnf'):
346             self.create_kernelvm_config_testpmd_io(**kwargs)
347         elif 'testpmd_mac' in self._opt.get('vnf'):
348             self.create_kernelvm_config_testpmd_mac(**kwargs)
349         else:
350             raise RuntimeError('QEMU: Unsupported VNF!')
351         self.create_kernelvm_init(vnf_bin=self._opt.get('vnf_bin'))
352
353     def get_qemu_pids(self):
354         """Get QEMU CPU pids.
355
356         :returns: List of QEMU CPU pids.
357         :rtype: list of str
358         """
359         command = ("grep -rwl 'CPU' /proc/$(sudo cat {pidfile})/task/*/comm ".
360                    format(pidfile=self._temp.get('pidfile')))
361         command += (r"| xargs dirname | sed -e 's/\/.*\///g' | uniq")
362
363         stdout, _ = exec_cmd_no_error(self._node, command)
364         return stdout.splitlines()
365
366     def qemu_set_affinity(self, *host_cpus):
367         """Set qemu affinity by getting thread PIDs via QMP and taskset to list
368         of CPU cores. Function tries to execute 3 times to avoid race condition
369         in getting thread PIDs.
370
371         :param host_cpus: List of CPU cores.
372         :type host_cpus: list
373         """
374         for _ in range(3):
375             try:
376                 qemu_cpus = self.get_qemu_pids()
377
378                 if len(qemu_cpus) != len(host_cpus):
379                     sleep(1)
380                     continue
381                 for qemu_cpu, host_cpu in zip(qemu_cpus, host_cpus):
382                     command = ('taskset -pc {host_cpu} {thread}'.
383                                format(host_cpu=host_cpu, thread=qemu_cpu))
384                     message = ('QEMU: Set affinity failed on {host}!'.
385                                format(host=self._node['host']))
386                     exec_cmd_no_error(self._node, command, sudo=True,
387                                       message=message)
388                 break
389             except (RuntimeError, ValueError):
390                 self.qemu_kill_all()
391                 raise
392         else:
393             self.qemu_kill_all()
394             raise RuntimeError('Failed to set Qemu threads affinity!')
395
396     def qemu_set_scheduler_policy(self):
397         """Set scheduler policy to SCHED_RR with priority 1 for all Qemu CPU
398         processes.
399
400         :raises RuntimeError: Set scheduler policy failed.
401         """
402         try:
403             qemu_cpus = self.get_qemu_pids()
404
405             for qemu_cpu in qemu_cpus:
406                 command = ('chrt -r -p 1 {thread}'.
407                            format(thread=qemu_cpu))
408                 message = ('QEMU: Set SCHED_RR failed on {host}'.
409                            format(host=self._node['host']))
410                 exec_cmd_no_error(self._node, command, sudo=True,
411                                   message=message)
412         except (RuntimeError, ValueError):
413             self.qemu_kill_all()
414             raise
415
416     def qemu_add_vhost_user_if(self, socket, server=True, jumbo_frames=False,
417                                queue_size=None, queues=1):
418         """Add Vhost-user interface.
419
420         :param socket: Path of the unix socket.
421         :param server: If True the socket shall be a listening socket.
422         :param jumbo_frames: Set True if jumbo frames are used in the test.
423         :param queue_size: Vring queue size.
424         :param queues: Number of queues.
425         :type socket: str
426         :type server: bool
427         :type jumbo_frames: bool
428         :type queue_size: int
429         :type queues: int
430         """
431         self._vhost_id += 1
432         self._params.add('chardev',
433                          'socket,id=char{vhost},path={socket}{server}'.
434                          format(vhost=self._vhost_id, socket=socket,
435                                 server=',server' if server is True else ''))
436         self._params.add('netdev',
437                          'vhost-user,id=vhost{vhost},'
438                          'chardev=char{vhost},queues={queues}'.
439                          format(vhost=self._vhost_id, queues=queues))
440         mac = ('52:54:00:00:{qemu:02x}:{vhost:02x}'.
441                format(qemu=self._opt.get('qemu_id'), vhost=self._vhost_id))
442         queue_size = ('rx_queue_size={queue_size},tx_queue_size={queue_size}'.
443                       format(queue_size=queue_size)) if queue_size else ''
444         mbuf = 'on,host_mtu=9200'
445         self._params.add('device',
446                          'virtio-net-pci,netdev=vhost{vhost},'
447                          'mac={mac},bus=pci.0,addr={addr}.0,mq=on,'
448                          'vectors={vectors},csum=off,gso=off,'
449                          'guest_tso4=off,guest_tso6=off,guest_ecn=off,'
450                          'mrg_rxbuf={mbuf},{queue_size}'.
451                          format(addr=self._vhost_id+5,
452                                 vhost=self._vhost_id, mac=mac,
453                                 mbuf=mbuf if jumbo_frames else 'off',
454                                 queue_size=queue_size,
455                                 vectors=(2 * queues + 2)))
456
457         # Add interface MAC and socket to the node dict.
458         if_data = {'mac_address': mac, 'socket': socket}
459         if_name = 'vhost{vhost}'.format(vhost=self._vhost_id)
460         self._vm_info['interfaces'][if_name] = if_data
461         # Add socket to temporary file list.
462         self._temp[if_name] = socket
463
464     def _qemu_qmp_exec(self, cmd):
465         """Execute QMP command.
466
467         QMP is JSON based protocol which allows to control QEMU instance.
468
469         :param cmd: QMP command to execute.
470         :type cmd: str
471         :returns: Command output in python representation of JSON format. The
472             { "return": {} } response is QMP's success response. An error
473             response will contain the "error" keyword instead of "return".
474         """
475         # To enter command mode, the qmp_capabilities command must be issued.
476         command = ('echo "{{ \\"execute\\": \\"qmp_capabilities\\" }}'
477                    '{{ \\"execute\\": \\"{cmd}\\" }}" | '
478                    'sudo -S socat - UNIX-CONNECT:{qmp}'.
479                    format(cmd=cmd, qmp=self._temp.get('qmp')))
480         message = ('QMP execute "{cmd}" failed on {host}'.
481                    format(cmd=cmd, host=self._node['host']))
482         stdout, _ = exec_cmd_no_error(self._node, command, sudo=False,
483                                       message=message)
484
485         # Skip capabilities negotiation messages.
486         out_list = stdout.splitlines()
487         if len(out_list) < 3:
488             raise RuntimeError('Invalid QMP output on {host}'.
489                                format(host=self._node['host']))
490         return json.loads(out_list[2])
491
492     def _qemu_qga_flush(self):
493         """Flush the QGA parser state."""
494         command = ('(printf "\xFF"; sleep 1) | '
495                    'sudo -S socat - UNIX-CONNECT:{qga}'.
496                    format(qga=self._temp.get('qga')))
497         message = ('QGA flush failed on {host}'.format(host=self._node['host']))
498         stdout, _ = exec_cmd_no_error(self._node, command, sudo=False,
499                                       message=message)
500
501         return json.loads(stdout.split('\n', 1)[0]) if stdout else dict()
502
503     def _qemu_qga_exec(self, cmd):
504         """Execute QGA command.
505
506         QGA provide access to a system-level agent via standard QMP commands.
507
508         :param cmd: QGA command to execute.
509         :type cmd: str
510         """
511         command = ('(echo "{{ \\"execute\\": \\"{cmd}\\" }}"; sleep 1) | '
512                    'sudo -S socat - UNIX-CONNECT:{qga}'.
513                    format(cmd=cmd, qga=self._temp.get('qga')))
514         message = ('QGA execute "{cmd}" failed on {host}'.
515                    format(cmd=cmd, host=self._node['host']))
516         stdout, _ = exec_cmd_no_error(self._node, command, sudo=False,
517                                       message=message)
518
519         return json.loads(stdout.split('\n', 1)[0]) if stdout else dict()
520
521     def _wait_until_vm_boot(self):
522         """Wait until QEMU with NestedVM is booted."""
523         if self._opt.get('vm_type') == 'nestedvm':
524             self._wait_until_nestedvm_boot()
525             self._update_vm_interfaces()
526         elif self._opt.get('vm_type') == 'kernelvm':
527             self._wait_until_kernelvm_boot()
528         else:
529             raise RuntimeError('QEMU: Unsupported VM type!')
530
531     def _wait_until_nestedvm_boot(self, retries=12):
532         """Wait until QEMU with NestedVM is booted.
533
534         First try to flush qga until there is output.
535         Then ping QEMU guest agent each 5s until VM booted or timeout.
536
537         :param retries: Number of retries with 5s between trials.
538         :type retries: int
539         """
540         for _ in range(retries):
541             out = None
542             try:
543                 out = self._qemu_qga_flush()
544             except ValueError:
545                 logger.trace('QGA qga flush unexpected output {out}'.
546                              format(out=out))
547             # Empty output - VM not booted yet
548             if not out:
549                 sleep(5)
550             else:
551                 break
552         else:
553             raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'.
554                                format(host=self._node['host']))
555         for _ in range(retries):
556             out = None
557             try:
558                 out = self._qemu_qga_exec('guest-ping')
559             except ValueError:
560                 logger.trace('QGA guest-ping unexpected output {out}'.
561                              format(out=out))
562             # Empty output - VM not booted yet.
563             if not out:
564                 sleep(5)
565             # Non-error return - VM booted.
566             elif out.get('return') is not None:
567                 break
568             # Skip error and wait.
569             elif out.get('error') is not None:
570                 sleep(5)
571             else:
572                 # If there is an unexpected output from QGA guest-info, try
573                 # again until timeout.
574                 logger.trace('QGA guest-ping unexpected output {out}'.
575                              format(out=out))
576         else:
577             raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'.
578                                format(host=self._node['host']))
579
580     def _wait_until_kernelvm_boot(self, retries=60):
581         """Wait until QEMU KernelVM is booted.
582
583         :param retries: Number of retries.
584         :type retries: int
585         """
586         vpp_ver = VPPUtil.vpp_show_version(self._node)
587
588         for _ in range(retries):
589             command = ('tail -1 {log}'.format(log=self._temp.get('log')))
590             stdout = None
591             try:
592                 stdout, _ = exec_cmd_no_error(self._node, command, sudo=True)
593                 sleep(1)
594             except RuntimeError:
595                 pass
596             if vpp_ver in stdout or 'Press enter to exit' in stdout:
597                 break
598             if 'reboot: Power down' in stdout:
599                 raise RuntimeError('QEMU: NF failed to run on {host}!'.
600                                    format(host=self._node['host']))
601         else:
602             raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'.
603                                format(host=self._node['host']))
604
605     def _update_vm_interfaces(self):
606         """Update interface names in VM node dict."""
607         # Send guest-network-get-interfaces command via QGA, output example:
608         # {"return": [{"name": "eth0", "hardware-address": "52:54:00:00:04:01"},
609         # {"name": "eth1", "hardware-address": "52:54:00:00:04:02"}]}.
610         out = self._qemu_qga_exec('guest-network-get-interfaces')
611         interfaces = out.get('return')
612         mac_name = {}
613         if not interfaces:
614             raise RuntimeError('Get VM interface list failed on {host}'.
615                                format(host=self._node['host']))
616         # Create MAC-name dict.
617         for interface in interfaces:
618             if 'hardware-address' not in interface:
619                 continue
620             mac_name[interface['hardware-address']] = interface['name']
621         # Match interface by MAC and save interface name.
622         for interface in self._vm_info['interfaces'].values():
623             mac = interface.get('mac_address')
624             if_name = mac_name.get(mac)
625             if if_name is None:
626                 logger.trace('Interface name for MAC {mac} not found'.
627                              format(mac=mac))
628             else:
629                 interface['name'] = if_name
630
631     def qemu_start(self):
632         """Start QEMU and wait until VM boot.
633
634         :returns: VM node info.
635         :rtype: dict
636         """
637         command = ('{bin_path}/qemu-system-{arch} {params}'.
638                    format(bin_path=Constants.QEMU_BIN_PATH,
639                           arch=Topology.get_node_arch(self._node),
640                           params=self._params))
641         message = ('QEMU: Start failed on {host}!'.
642                    format(host=self._node['host']))
643         try:
644             DUTSetup.check_huge_page(self._node, '/dev/hugepages',
645                                      self._opt.get('mem'))
646
647             exec_cmd_no_error(self._node, command, timeout=300, sudo=True,
648                               message=message)
649             self._wait_until_vm_boot()
650         except RuntimeError:
651             self.qemu_kill_all()
652             raise
653         return self._vm_info
654
655     def qemu_kill(self):
656         """Kill qemu process."""
657         exec_cmd(self._node, 'chmod +r {pidfile}'.
658                  format(pidfile=self._temp.get('pidfile')), sudo=True)
659         exec_cmd(self._node, 'kill -SIGKILL $(cat {pidfile})'.
660                  format(pidfile=self._temp.get('pidfile')), sudo=True)
661
662         for value in self._temp.values():
663             exec_cmd(self._node, 'cat {value}'.format(value=value), sudo=True)
664             exec_cmd(self._node, 'rm -f {value}'.format(value=value), sudo=True)
665
666     def qemu_kill_all(self):
667         """Kill all qemu processes on DUT node if specified."""
668         exec_cmd(self._node, 'pkill -SIGKILL qemu', sudo=True)
669
670         for value in self._temp.values():
671             exec_cmd(self._node, 'cat {value}'.format(value=value), sudo=True)
672             exec_cmd(self._node, 'rm -f {value}'.format(value=value), sudo=True)
673
674     def qemu_version(self, version=None):
675         """Return Qemu version or compare if version is higher than parameter.
676
677         :param version: Version to compare.
678         :type version: str
679         :returns: Qemu version or Boolean if version is higher than parameter.
680         :rtype: str or bool
681         """
682         command = ('{bin_path}/qemu-system-{arch} --version'.
683                    format(bin_path=Constants.QEMU_BIN_PATH,
684                           arch=Topology.get_node_arch(self._node)))
685         try:
686             stdout, _ = exec_cmd_no_error(self._node, command, sudo=True)
687             ver = match(r'QEMU emulator version ([\d.]*)', stdout).group(1)
688             return StrictVersion(ver) > StrictVersion(version) \
689                 if version else ver
690         except RuntimeError:
691             self.qemu_kill_all()
692             raise