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