1 # Copyright (c) 2021 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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """QEMU utilities library."""
19 from string import Template
20 from time import sleep
22 from robot.api import logger
24 from resources.libraries.python.Constants import Constants
25 from resources.libraries.python.DpdkUtil import DpdkUtil
26 from resources.libraries.python.DUTSetup import DUTSetup
27 from resources.libraries.python.OptionString import OptionString
28 from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
29 from resources.libraries.python.topology import NodeType, Topology
30 from resources.libraries.python.VhostUser import VirtioFeaturesFlags
31 from resources.libraries.python.VhostUser import VirtioFeatureMask
32 from resources.libraries.python.VppConfigGenerator import VppConfigGenerator
34 __all__ = [u"QemuUtils"]
40 # Use one instance of class per tests.
41 ROBOT_LIBRARY_SCOPE = u"TEST CASE"
44 self, node, qemu_id=1, smp=1, mem=512, vnf=None,
45 img=Constants.QEMU_VM_IMAGE):
46 """Initialize QemuUtil class.
48 :param node: Node to run QEMU on.
49 :param qemu_id: QEMU identifier.
50 :param smp: Number of virtual SMP units (cores).
51 :param mem: Amount of memory.
52 :param vnf: Network function workload.
53 :param img: QEMU disk image or kernel image path.
63 self._arch = Topology.get_node_arch(self._node)
66 # Architecture specific options
67 if self._arch == u"aarch64":
68 dpdk_target = u"arm64-armv8a"
69 self._opt[u"machine_args"] = \
70 u"virt,accel=kvm,usb=off,mem-merge=off,gic-version=3"
71 self._opt[u"console"] = u"ttyAMA0"
73 dpdk_target = u"x86_64-native"
74 self._opt[u"machine_args"] = u"pc,accel=kvm,usb=off,mem-merge=off"
75 self._opt[u"console"] = u"ttyS0"
76 self._testpmd_path = f"{Constants.QEMU_VM_DPDK}/" \
77 f"{dpdk_target}-linux-gcc/app"
79 u"host": node[u"host"],
81 u"port": 10021 + qemu_id,
82 u"serial": 4555 + qemu_id,
83 u"username": 'testuser',
84 u"password": 'Csit1234',
87 if node[u"port"] != 22:
88 self._vm_info[u"host_port"] = node[u"port"]
89 self._vm_info[u"host_username"] = node[u"username"]
90 self._vm_info[u"host_password"] = node[u"password"]
92 self._opt[u"qemu_id"] = qemu_id
93 self._opt[u"mem"] = int(mem)
94 self._opt[u"smp"] = int(smp)
95 self._opt[u"img"] = img
96 self._opt[u"vnf"] = vnf
99 self._temp[u"log"] = f"/tmp/serial_{qemu_id}.log"
100 self._temp[u"pidfile"] = f"/run/qemu_{qemu_id}.pid"
101 if img == Constants.QEMU_VM_IMAGE:
102 self._temp[u"qmp"] = f"/run/qmp_{qemu_id}.sock"
103 self._temp[u"qga"] = f"/run/qga_{qemu_id}.sock"
104 elif img == Constants.QEMU_VM_KERNEL:
105 self._opt[u"img"], _ = exec_cmd_no_error(
106 node, f"ls -1 {Constants.QEMU_VM_KERNEL}* | tail -1",
107 message=u"Qemu Kernel VM image not found!"
109 self._temp[u"ini"] = f"/etc/vm_init_{qemu_id}.conf"
110 self._opt[u"initrd"], _ = exec_cmd_no_error(
111 node, f"ls -1 {Constants.QEMU_VM_KERNEL_INITRD}* | tail -1",
112 message=u"Qemu Kernel initrd image not found!"
115 raise RuntimeError(f"QEMU: Unknown VM image option: {img}")
116 # Computed parameters for QEMU command line.
117 self._params = OptionString(prefix=u"-")
119 def add_default_params(self):
120 """Set default QEMU command line parameters."""
121 self._params.add(u"daemonize")
122 self._params.add(u"nodefaults")
123 self._params.add_with_value(
124 u"name", f"vnf{self._opt.get(u'qemu_id')},debug-threads=on"
126 self._params.add(u"no-user-config")
127 self._params.add(u"nographic")
128 self._params.add(u"enable-kvm")
129 self._params.add_with_value(u"pidfile", self._temp.get(u"pidfile"))
130 self._params.add_with_value(u"cpu", u"host")
132 self._params.add_with_value(u"machine", self._opt.get(u"machine_args"))
133 self._params.add_with_value(
134 u"smp", f"{self._opt.get(u'smp')},sockets=1,"
135 f"cores={self._opt.get(u'smp')},threads=1"
137 self._params.add_with_value(
138 u"object", f"memory-backend-file,id=mem,"
139 f"size={self._opt.get(u'mem')}M,mem-path=/dev/hugepages,share=on"
141 self._params.add_with_value(u"m", f"{self._opt.get(u'mem')}M")
142 self._params.add_with_value(u"numa", u"node,memdev=mem")
143 self._params.add_with_value(u"balloon", u"none")
145 def add_net_user(self, net="10.0.2.0/24"):
146 """Set managment port forwarding."""
147 self._params.add_with_value(
148 u"netdev", f"user,id=mgmt,net={net},"
149 f"hostfwd=tcp::{self._vm_info[u'port']}-:22"
151 self._params.add_with_value(
152 u"device", f"virtio-net,netdev=mgmt"
155 def add_qmp_qga(self):
156 """Set QMP, QGA management."""
157 self._params.add_with_value(
158 u"chardev", f"socket,path={self._temp.get(u'qga')},"
159 f"server,nowait,id=qga0"
161 self._params.add_with_value(
162 u"device", u"isa-serial,chardev=qga0"
164 self._params.add_with_value(
165 u"qmp", f"unix:{self._temp.get(u'qmp')},server,nowait"
168 def add_serial(self):
169 """Set serial to file redirect."""
170 self._params.add_with_value(
171 u"chardev", f"socket,host=127.0.0.1,"
172 f"port={self._vm_info[u'serial']},id=gnc0,server,nowait"
174 self._params.add_with_value(
175 u"device", u"isa-serial,chardev=gnc0"
177 self._params.add_with_value(
178 u"serial", f"file:{self._temp.get(u'log')}"
181 def add_drive_cdrom(self, drive_file, index=None):
184 :param drive_file: Path to drive image.
185 :param index: Drive index.
186 :type drive_file: str
189 index = f"index={index}," if index else u""
190 self._params.add_with_value(
191 u"drive", f"file={drive_file},{index}media=cdrom"
194 def add_drive(self, drive_file, drive_format):
195 """Set drive with custom format.
197 :param drive_file: Path to drive image.
198 :param drive_format: Drive image format.
199 :type drive_file: str
200 :type drive_format: str
202 self._params.add_with_value(
203 u"drive", f"file={drive_file},format={drive_format},"
204 u"cache=none,if=virtio,file.locking=off"
207 def add_kernelvm_params(self):
208 """Set KernelVM QEMU parameters."""
209 self._params.add_with_value(
210 u"serial", f"file:{self._temp.get(u'log')}"
212 self._params.add_with_value(
213 u"fsdev", u"local,id=root9p,path=/,security_model=none"
215 self._params.add_with_value(
216 u"device", u"virtio-9p-pci,fsdev=root9p,mount_tag=virtioroot"
218 self._params.add_with_value(
219 u"kernel", f"{self._opt.get(u'img')}"
221 self._params.add_with_value(
222 u"initrd", f"{self._opt.get(u'initrd')}"
224 self._params.add_with_value(
225 u"append", f"'ro rootfstype=9p rootflags=trans=virtio "
226 f"root=virtioroot console={self._opt.get(u'console')} "
227 f"tsc=reliable hugepages=512 "
228 f"init={self._temp.get(u'ini')} fastboot'"
231 def add_vhost_user_if(
232 self, socket, server=True, jumbo_frames=False, queue_size=None,
233 queues=1, virtio_feature_mask=None):
234 """Add Vhost-user interface.
236 :param socket: Path of the unix socket.
237 :param server: If True the socket shall be a listening socket.
238 :param jumbo_frames: Set True if jumbo frames are used in the test.
239 :param queue_size: Vring queue size.
240 :param queues: Number of queues.
241 :param virtio_feature_mask: Mask of virtio features to be enabled.
244 :type jumbo_frames: bool
245 :type queue_size: int
247 :type virtio_feature_mask: int
250 self._params.add_with_value(
251 u"chardev", f"socket,id=char{self._nic_id},"
252 f"path={socket}{u',server' if server is True else u''}"
254 self._params.add_with_value(
255 u"netdev", f"vhost-user,id=vhost{self._nic_id},"
256 f"chardev=char{self._nic_id},queues={queues}"
258 mac = f"52:54:00:00:{self._opt.get(u'qemu_id'):02x}:" \
259 f"{self._nic_id:02x}"
260 queue_size = f"rx_queue_size={queue_size},tx_queue_size={queue_size}" \
261 if queue_size else u""
262 gso = VirtioFeatureMask.is_feature_enabled(
263 virtio_feature_mask, VirtioFeaturesFlags.VIRTIO_NET_F_API_GSO)
264 csum = VirtioFeatureMask.is_feature_enabled(
265 virtio_feature_mask, VirtioFeaturesFlags.VIRTIO_NET_F_API_CSUM)
267 self._params.add_with_value(
268 u"device", f"virtio-net-pci,netdev=vhost{self._nic_id},mac={mac},"
269 f"addr={self._nic_id+5}.0,mq=on,vectors={2 * queues + 2},"
270 f"csum={u'on' if csum else u'off'},"
271 f"gso={u'on' if gso else u'off'},"
272 f"guest_tso4={u'on' if gso else u'off'},"
273 f"guest_tso6={u'on' if gso else u'off'},"
274 f"guest_ecn={u'on' if gso else u'off'},"
278 # Add interface MAC and socket to the node dict.
279 if_data = {u"mac_address": mac, u"socket": socket}
280 if_name = f"vhost{self._nic_id}"
281 self._vm_info[u"interfaces"][if_name] = if_data
282 # Add socket to temporary file list.
283 self._temp[if_name] = socket
285 def add_vfio_pci_if(self, pci):
286 """Add VFIO PCI interface.
288 :param pci: PCI address of interface.
292 self._params.add_with_value(
293 u"device", f"vfio-pci,host={pci},addr={self._nic_id+5}.0"
296 def create_kernelvm_config_vpp(self, **kwargs):
297 """Create QEMU VPP config files.
299 :param kwargs: Key-value pairs to replace content of VPP configuration
303 startup = f"/etc/vpp/vm_startup_{self._opt.get(u'qemu_id')}.conf"
304 running = f"/etc/vpp/vm_running_{self._opt.get(u'qemu_id')}.exec"
306 self._temp[u"startup"] = startup
307 self._temp[u"running"] = running
308 self._opt[u"vnf_bin"] = f"/usr/bin/vpp -c {startup}"
310 # Create VPP startup configuration.
311 vpp_config = VppConfigGenerator()
312 vpp_config.set_node(self._node)
313 vpp_config.add_unix_nodaemon()
314 vpp_config.add_unix_cli_listen()
315 vpp_config.add_unix_exec(running)
316 vpp_config.add_socksvr()
317 vpp_config.add_main_heap_size(u"512M")
318 vpp_config.add_main_heap_page_size(u"2M")
319 vpp_config.add_statseg_size(u"512M")
320 vpp_config.add_statseg_page_size(u"2M")
321 vpp_config.add_statseg_per_node_counters(u"on")
322 vpp_config.add_buffers_per_numa(107520)
323 vpp_config.add_cpu_main_core(u"0")
324 if self._opt.get(u"smp") > 1:
325 vpp_config.add_cpu_corelist_workers(f"1-{self._opt.get(u'smp')-1}")
326 vpp_config.add_plugin(u"disable", u"default")
327 vpp_config.add_plugin(u"enable", u"ping_plugin.so")
328 if "2vfpt" in self._opt.get(u'vnf'):
329 vpp_config.add_plugin(u"enable", u"avf_plugin.so")
330 if "vhost" in self._opt.get(u'vnf'):
331 vpp_config.add_plugin(u"enable", u"dpdk_plugin.so")
332 vpp_config.add_dpdk_dev(u"0000:00:06.0", u"0000:00:07.0")
333 vpp_config.add_dpdk_dev_default_rxq(kwargs[u"queues"])
334 vpp_config.add_dpdk_log_level(u"debug")
335 if not kwargs[u"jumbo_frames"]:
336 vpp_config.add_dpdk_no_multi_seg()
337 vpp_config.add_dpdk_no_tx_checksum_offload()
338 if "ipsec" in self._opt.get(u'vnf'):
339 vpp_config.add_plugin(u"enable", u"crypto_native_plugin.so")
340 vpp_config.add_plugin(u"enable", u"crypto_ipsecmb_plugin.so")
341 vpp_config.add_plugin(u"enable", u"crypto_openssl_plugin.so")
342 if "nat" in self._opt.get(u'vnf'):
343 vpp_config.add_nat(value=u"endpoint-dependent")
344 vpp_config.add_plugin(u"enable", u"nat_plugin.so")
345 vpp_config.write_config(startup)
347 # Create VPP running configuration.
348 template = f"{Constants.RESOURCES_TPL}/vm/{self._opt.get(u'vnf')}.exec"
349 exec_cmd_no_error(self._node, f"rm -f {running}", sudo=True)
351 with open(template, u"rt") as src_file:
352 src = Template(src_file.read())
354 self._node, f"echo '{src.safe_substitute(**kwargs)}' | "
355 f"sudo tee {running}"
358 def create_kernelvm_config_testpmd_io(self, **kwargs):
359 """Create QEMU testpmd-io command line.
361 :param kwargs: Key-value pairs to construct command line parameters.
364 pmd_max_pkt_len = u"9200" if kwargs[u"jumbo_frames"] else u"1518"
365 testpmd_cmd = DpdkUtil.get_testpmd_cmdline(
366 eal_corelist=f"0-{self._opt.get(u'smp') - 1}",
368 eal_pci_whitelist0=u"0000:00:06.0",
369 eal_pci_whitelist1=u"0000:00:07.0",
375 pmd_max_pkt_len=pmd_max_pkt_len,
376 pmd_mbuf_size=u"16384",
377 pmd_rxq=kwargs[u"queues"],
378 pmd_txq=kwargs[u"queues"],
379 pmd_tx_offloads='0x0',
380 pmd_nb_cores=str(self._opt.get(u"smp") - 1)
383 self._opt[u"vnf_bin"] = f"{self._testpmd_path}/{testpmd_cmd}"
385 def create_kernelvm_config_testpmd_mac(self, **kwargs):
386 """Create QEMU testpmd-mac command line.
388 :param kwargs: Key-value pairs to construct command line parameters.
391 pmd_max_pkt_len = u"9200" if kwargs[u"jumbo_frames"] else u"1518"
392 testpmd_cmd = DpdkUtil.get_testpmd_cmdline(
393 eal_corelist=f"0-{self._opt.get(u'smp') - 1}",
395 eal_pci_whitelist0=u"0000:00:06.0",
396 eal_pci_whitelist1=u"0000:00:07.0",
402 pmd_max_pkt_len=pmd_max_pkt_len,
403 pmd_mbuf_size=u"16384",
404 pmd_eth_peer_0=f"0,{kwargs[u'vif1_mac']}",
405 pmd_eth_peer_1=f"1,{kwargs[u'vif2_mac']}",
406 pmd_rxq=kwargs[u"queues"],
407 pmd_txq=kwargs[u"queues"],
408 pmd_tx_offloads=u"0x0",
409 pmd_nb_cores=str(self._opt.get(u"smp") - 1)
412 self._opt[u"vnf_bin"] = f"{self._testpmd_path}/{testpmd_cmd}"
414 def create_kernelvm_config_iperf3(self):
415 """Create QEMU iperf3 command line."""
416 self._opt[u"vnf_bin"] = f"mkdir /run/sshd; /usr/sbin/sshd -D -d"
418 def create_kernelvm_init(self, **kwargs):
419 """Create QEMU init script.
421 :param kwargs: Key-value pairs to replace content of init startup file.
424 init = self._temp.get(u"ini")
425 exec_cmd_no_error(self._node, f"rm -f {init}", sudo=True)
427 with open(kwargs[u"template"], u"rt") as src_file:
428 src = Template(src_file.read())
430 self._node, f"echo '{src.safe_substitute(**kwargs)}' | "
433 exec_cmd_no_error(self._node, f"chmod +x {init}", sudo=True)
435 def configure_kernelvm_vnf(self, **kwargs):
436 """Create KernelVM VNF configurations.
438 :param kwargs: Key-value pairs for templating configs.
441 if u"vpp" in self._opt.get(u"vnf"):
442 self.create_kernelvm_config_vpp(**kwargs)
443 self.create_kernelvm_init(
444 template=f"{Constants.RESOURCES_TPL}/vm/init.sh",
445 vnf_bin=self._opt.get(u"vnf_bin")
447 elif u"testpmd_io" in self._opt.get(u"vnf"):
448 self.create_kernelvm_config_testpmd_io(**kwargs)
449 self.create_kernelvm_init(
450 template=f"{Constants.RESOURCES_TPL}/vm/init.sh",
451 vnf_bin=self._opt.get(u"vnf_bin")
453 elif u"testpmd_mac" in self._opt.get(u"vnf"):
454 self.create_kernelvm_config_testpmd_mac(**kwargs)
455 self.create_kernelvm_init(
456 template=f"{Constants.RESOURCES_TPL}/vm/init.sh",
457 vnf_bin=self._opt.get(u"vnf_bin")
459 elif u"iperf3" in self._opt.get(u"vnf"):
460 qemu_id = self._opt.get(u'qemu_id') % 2
461 self.create_kernelvm_config_iperf3()
462 self.create_kernelvm_init(
463 template=f"{Constants.RESOURCES_TPL}/vm/init_iperf3.sh",
464 vnf_bin=self._opt.get(u"vnf_bin"),
465 ip_address_l=u"2.2.2.2/30" if qemu_id else u"1.1.1.1/30",
466 ip_address_r=u"2.2.2.1" if qemu_id else u"1.1.1.2",
467 ip_route_r=u"1.1.1.0/30" if qemu_id else u"2.2.2.0/30"
470 raise RuntimeError(u"QEMU: Unsupported VNF!")
472 def get_qemu_pids(self):
473 """Get QEMU CPU pids.
475 :returns: List of QEMU CPU pids.
478 command = f"grep -rwl 'CPU' /proc/$(sudo cat " \
479 f"{self._temp.get(u'pidfile')})/task/*/comm "
480 command += r"| xargs dirname | sed -e 's/\/.*\///g' | uniq"
482 stdout, _ = exec_cmd_no_error(self._node, command)
483 return stdout.splitlines()
485 def qemu_set_affinity(self, *host_cpus):
486 """Set qemu affinity by getting thread PIDs via QMP and taskset to list
487 of CPU cores. Function tries to execute 3 times to avoid race condition
488 in getting thread PIDs.
490 :param host_cpus: List of CPU cores.
491 :type host_cpus: list
495 qemu_cpus = self.get_qemu_pids()
497 if len(qemu_cpus) != len(host_cpus):
500 for qemu_cpu, host_cpu in zip(qemu_cpus, host_cpus):
501 command = f"taskset -pc {host_cpu} {qemu_cpu}"
502 message = f"QEMU: Set affinity failed " \
503 f"on {self._node[u'host']}!"
505 self._node, command, sudo=True, message=message
508 except (RuntimeError, ValueError):
513 raise RuntimeError(u"Failed to set Qemu threads affinity!")
515 def qemu_set_scheduler_policy(self):
516 """Set scheduler policy to SCHED_RR with priority 1 for all Qemu CPU
519 :raises RuntimeError: Set scheduler policy failed.
522 qemu_cpus = self.get_qemu_pids()
524 for qemu_cpu in qemu_cpus:
525 command = f"chrt -r -p 1 {qemu_cpu}"
526 message = f"QEMU: Set SCHED_RR failed on {self._node[u'host']}"
528 self._node, command, sudo=True, message=message
530 except (RuntimeError, ValueError):
534 def _qemu_qmp_exec(self, cmd):
535 """Execute QMP command.
537 QMP is JSON based protocol which allows to control QEMU instance.
539 :param cmd: QMP command to execute.
541 :returns: Command output in python representation of JSON format. The
542 { "return": {} } response is QMP's success response. An error
543 response will contain the "error" keyword instead of "return".
545 # To enter command mode, the qmp_capabilities command must be issued.
546 command = f"echo \"{{{{ \\\"execute\\\": " \
547 f"\\\"qmp_capabilities\\\" }}}}" \
548 f"{{{{ \\\"execute\\\": \\\"{cmd}\\\" }}}}\" | " \
549 f"sudo -S socat - UNIX-CONNECT:{self._temp.get(u'qmp')}"
550 message = f"QMP execute '{cmd}' failed on {self._node[u'host']}"
552 stdout, _ = exec_cmd_no_error(
553 self._node, command, sudo=False, message=message
556 # Skip capabilities negotiation messages.
557 out_list = stdout.splitlines()
558 if len(out_list) < 3:
559 raise RuntimeError(f"Invalid QMP output on {self._node[u'host']}")
560 return json.loads(out_list[2])
562 def _qemu_qga_flush(self):
563 """Flush the QGA parser state."""
564 command = f"(printf \"\xFF\"; sleep 1) | sudo -S socat " \
565 f"- UNIX-CONNECT:{self._temp.get(u'qga')}"
566 message = f"QGA flush failed on {self._node[u'host']}"
567 stdout, _ = exec_cmd_no_error(
568 self._node, command, sudo=False, message=message
571 return json.loads(stdout.split(u"\n", 1)[0]) if stdout else dict()
573 def _qemu_qga_exec(self, cmd):
574 """Execute QGA command.
576 QGA provide access to a system-level agent via standard QMP commands.
578 :param cmd: QGA command to execute.
581 command = f"(echo \"{{{{ \\\"execute\\\": " \
582 f"\\\"{cmd}\\\" }}}}\"; sleep 1) | " \
583 f"sudo -S socat - UNIX-CONNECT:{self._temp.get(u'qga')}"
584 message = f"QGA execute '{cmd}' failed on {self._node[u'host']}"
585 stdout, _ = exec_cmd_no_error(
586 self._node, command, sudo=False, message=message
589 return json.loads(stdout.split(u"\n", 1)[0]) if stdout else dict()
591 def _wait_until_vm_boot(self):
592 """Wait until QEMU VM is booted."""
594 getattr(self, f'_wait_{self._opt["vnf"]}')()
595 except AttributeError:
598 def _wait_default(self, retries=60):
599 """Wait until QEMU with VPP is booted.
601 :param retries: Number of retries.
604 for _ in range(retries):
605 command = f"tail -1 {self._temp.get(u'log')}"
608 stdout, _ = exec_cmd_no_error(self._node, command, sudo=True)
612 if "vpp " in stdout and "built by" in stdout:
614 if u"Press enter to exit" in stdout:
616 if u"reboot: Power down" in stdout:
618 f"QEMU: NF failed to run on {self._node[u'host']}!"
622 f"QEMU: Timeout, VM not booted on {self._node[u'host']}!"
625 def _wait_nestedvm(self, retries=12):
626 """Wait until QEMU with NestedVM is booted.
628 First try to flush qga until there is output.
629 Then ping QEMU guest agent each 5s until VM booted or timeout.
631 :param retries: Number of retries with 5s between trials.
634 for _ in range(retries):
637 out = self._qemu_qga_flush()
639 logger.trace(f"QGA qga flush unexpected output {out}")
640 # Empty output - VM not booted yet
647 f"QEMU: Timeout, VM not booted on {self._node[u'host']}!"
649 for _ in range(retries):
652 out = self._qemu_qga_exec(u"guest-ping")
654 logger.trace(f"QGA guest-ping unexpected output {out}")
655 # Empty output - VM not booted yet.
658 # Non-error return - VM booted.
659 elif out.get(u"return") is not None:
661 # Skip error and wait.
662 elif out.get(u"error") is not None:
665 # If there is an unexpected output from QGA guest-info, try
666 # again until timeout.
667 logger.trace(f"QGA guest-ping unexpected output {out}")
670 f"QEMU: Timeout, VM not booted on {self._node[u'host']}!"
673 def _wait_iperf3(self, retries=60):
674 """Wait until QEMU with iPerf3 is booted.
676 :param retries: Number of retries.
679 grep = u"Server listening on 0.0.0.0 port 22."
680 cmd = f"fgrep '{grep}' {self._temp.get(u'log')}"
681 message = f"QEMU: Timeout, VM not booted on {self._node[u'host']}!"
683 self._node, cmd=cmd, sudo=True, message=message, retries=retries,
687 def _update_vm_interfaces(self):
688 """Update interface names in VM node dict."""
689 # Send guest-network-get-interfaces command via QGA, output example:
690 # {"return": [{"name": "eth0", "hardware-address": "52:54:00:00:04:01"},
691 # {"name": "eth1", "hardware-address": "52:54:00:00:04:02"}]}.
692 out = self._qemu_qga_exec(u"guest-network-get-interfaces")
693 interfaces = out.get(u"return")
697 f"Get VM interface list failed on {self._node[u'host']}"
699 # Create MAC-name dict.
700 for interface in interfaces:
701 if u"hardware-address" not in interface:
703 mac_name[interface[u"hardware-address"]] = interface[u"name"]
704 # Match interface by MAC and save interface name.
705 for interface in self._vm_info[u"interfaces"].values():
706 mac = interface.get(u"mac_address")
707 if_name = mac_name.get(mac)
709 logger.trace(f"Interface name for MAC {mac} not found")
711 interface[u"name"] = if_name
713 def qemu_start(self):
714 """Start QEMU and wait until VM boot.
716 :returns: VM node info.
719 cmd_opts = OptionString()
720 cmd_opts.add(f"{Constants.QEMU_BIN_PATH}/qemu-system-{self._arch}")
721 cmd_opts.extend(self._params)
722 message = f"QEMU: Start failed on {self._node[u'host']}!"
724 DUTSetup.check_huge_page(
725 self._node, u"/dev/hugepages", int(self._opt.get(u"mem")))
728 self._node, cmd_opts, timeout=300, sudo=True, message=message
730 self._wait_until_vm_boot()
737 """Kill qemu process."""
739 self._node, f"chmod +r {self._temp.get(u'pidfile')}", sudo=True
742 self._node, f"kill -SIGKILL $(cat {self._temp.get(u'pidfile')})",
746 for value in self._temp.values():
747 exec_cmd(self._node, f"cat {value}", sudo=True)
748 exec_cmd(self._node, f"rm -f {value}", sudo=True)
750 def qemu_kill_all(self):
751 """Kill all qemu processes on DUT node if specified."""
752 exec_cmd(self._node, u"pkill -SIGKILL qemu", sudo=True)
754 for value in self._temp.values():
755 exec_cmd(self._node, f"cat {value}", sudo=True)
756 exec_cmd(self._node, f"rm -f {value}", sudo=True)
758 def qemu_version(self):
759 """Return Qemu version.
761 :returns: Qemu version.
764 command = f"{Constants.QEMU_BIN_PATH}/qemu-system-{self._arch} " \
767 stdout, _ = exec_cmd_no_error(self._node, command, sudo=True)
768 return match(r"QEMU emulator version ([\d.]*)", stdout).group(1)