def __init__(self, node, qemu_id=1, smp=1, mem=512, vnf=None, img=Constants.QEMU_VM_IMAGE): """Initialize QemuUtil class. :param node: Node to run QEMU on. :param qemu_id: QEMU identifier. :param smp: Number of virtual SMP units (cores). :param mem: Amount of memory. :param vnf: Network function workload. :param img: QEMU disk image or kernel image path. :type node: dict :type qemu_id: int :type smp: int :type mem: int :type vnf: str :type img: str """ self._vhost_id = 0 self._node = node self._vm_info = { 'host': node['host'], 'type': NodeType.VM, 'port': 10021 + qemu_id, 'serial': 4555 + qemu_id, 'username': '******', 'password': '******', 'interfaces': {}, } if node['port'] != 22: self._vm_info['host_port'] = node['port'] self._vm_info['host_username'] = node['username'] self._vm_info['host_password'] = node['password'] # Input Options. self._opt = dict() self._opt['qemu_id'] = qemu_id self._opt['mem'] = int(mem) self._opt['smp'] = int(smp) self._opt['img'] = img self._opt['vnf'] = vnf # Temporary files. self._temp = dict() self._temp['pidfile'] = '/var/run/qemu_{id}.pid'.format(id=qemu_id) if '/var/lib/vm/' in img: self._opt['vm_type'] = 'nestedvm' self._temp['qmp'] = '/var/run/qmp_{id}.sock'.format(id=qemu_id) self._temp['qga'] = '/var/run/qga_{id}.sock'.format(id=qemu_id) elif '/opt/boot/vmlinuz' in img: self._opt['vm_type'] = 'kernelvm' self._temp['log'] = '/tmp/serial_{id}.log'.format(id=qemu_id) self._temp['ini'] = '/etc/vm_init_{id}.conf'.format(id=qemu_id) else: raise RuntimeError('QEMU: Unknown VM image option!') # Computed parameters for QEMU command line. self._params = OptionString(prefix='-') self.add_params()
def get_testpmd_cmdline(**kwargs): """Get DPDK testpmd command line arguments with testpmd command. :param kwargs: Key-value testpmd parameters. :type kwargs: dict :returns: Command line string. :rtype: OptionString """ options = OptionString() options.add(u"testpmd") options.extend(DpdkUtil.get_eal_options(**kwargs)) options.add(u"--") options.extend(DpdkUtil.get_testpmd_pmd_options(**kwargs)) return options
def dpdk_testpmd_start(node, **kwargs): """Start DPDK testpmd app on VM node. :param node: VM Node to start testpmd on. :param kwargs: Key-value testpmd parameters. :type node: dict :type kwargs: dict """ cmd_options = OptionString() cmd_options.add(u"/start-testpmd.sh") cmd_options.extend(DpdkUtil.get_eal_options(**kwargs)) cmd_options.add(u"--") cmd_options.extend(DpdkUtil.get_testpmd_pmd_options(**kwargs)) exec_cmd_no_error(node, cmd_options, sudo=True, disconnect=True)
def exec_command_sudo(self, cmd, cmd_input=None, timeout=30, log_stdout_err=True): """Execute SSH command with sudo on a new channel on the connected Node. :param cmd: Command to be executed. :param cmd_input: Input redirected to the command. :param timeout: Timeout. :param log_stdout_err: If True, stdout and stderr are logged. :type cmd: str :type cmd_input: str :type timeout: int :type log_stdout_err: bool :returns: return_code, stdout, stderr :rtype: tuple(int, str, str) :Example: >>> from ssh import SSH >>> ssh = SSH() >>> ssh.connect(node) >>> # Execute command without input (sudo -S cmd) >>> ssh.exec_command_sudo("ifconfig eth0 down") >>> # Execute command with input (sudo -S cmd <<< "input") >>> ssh.exec_command_sudo("vpp_api_test", "dump_interface_table") """ if isinstance(cmd, (list, tuple)): cmd = OptionString(cmd) if cmd_input is None: command = 'sudo -S {c}'.format(c=cmd) else: command = 'sudo -S {c} <<< "{i}"'.format(c=cmd, i=cmd_input) return self.exec_command(command, timeout, log_stdout_err=log_stdout_err)
def get_l3fwd_args(**kwargs): """Get DPDK l3fwd command line arguments. :param kwargs: Key-value l3fwd parameters. :type kwargs: dict :returns: Command line string. :rtype: OptionString """ options = OptionString() options.extend(DpdkUtil.get_eal_options(**kwargs)) options.add(u"--") options.extend(DpdkUtil.get_l3fwd_pmd_options(**kwargs)) return options
def get_l3fwd_pmd_options(**kwargs): """Create PMD parameters options for l3fwd (without --). :param kwargs: List of l3fwd parameters. :type kwargs: dict :returns: PMD parameters. :rtype: OptionString """ options = OptionString(prefix=u"--") # Set to use software to analyze packet type. options.add_if_from_dict(u"parse-ptype", u"pmd_parse_ptype", kwargs, True) # Set the MAC address XX:XX:XX:XX:XX:XX of the peer port N. options.add_equals_from_dict(u"eth-dest", u"pmd_eth_dest_0", kwargs) options.add_equals_from_dict(u"eth-dest", u"pmd_eth_dest_1", kwargs) # Determines which queues from which ports are mapped to which cores. options.add_equals_from_dict(u"config", u"pmd_config", kwargs) # Set the max packet length. options.add_with_value_if_from_dict(u"max-pkt-len", u"9200", u"pmd_max_pkt_len", kwargs, False) return options
def exec_command_sudo(self, cmd, cmd_input=None, timeout=30, log_stdout_err=True, export=True): """Execute SSH command with sudo on a new channel on the connected Node. :param cmd: Command to be executed. :param cmd_input: Input redirected to the command. :param timeout: Timeout. :param log_stdout_err: If True, stdout and stderr are logged. :param export: If false, do not attempt JSON export. Needed for calls outside Robot (e.g. from reservation script). :type cmd: str :type cmd_input: str :type timeout: int :type log_stdout_err: bool :type export: bool :returns: return_code, stdout, stderr :rtype: tuple(int, str, str) :Example: >>> from ssh import SSH >>> ssh = SSH() >>> ssh.connect(node) >>> # Execute command without input (sudo -S cmd) >>> ssh.exec_command_sudo(u"ifconfig eth0 down") >>> # Execute command with input (sudo -S cmd <<< 'input') >>> ssh.exec_command_sudo(u"vpp_api_test", u"dump_interface_table") """ if isinstance(cmd, (list, tuple)): cmd = OptionString(cmd) if cmd_input is None: command = f"sudo -E -S {cmd}" else: command = f"sudo -E -S {cmd} <<< \"{cmd_input}\"" return self.exec_command(command, timeout, log_stdout_err=log_stdout_err, export=export)
def qemu_start(self): """Start QEMU and wait until VM boot. :returns: VM node info. :rtype: dict """ cmd_opts = OptionString() cmd_opts.add(f"{Constants.QEMU_BIN_PATH}/qemu-system-{self._arch}") cmd_opts.extend(self._params) message = f"QEMU: Start failed on {self._node[u'host']}!" try: DUTSetup.check_huge_page( self._node, u"/dev/hugepages", int(self._opt.get(u"mem"))) exec_cmd_no_error( self._node, cmd_opts, timeout=300, sudo=True, message=message ) self._wait_until_vm_boot() except RuntimeError: self.qemu_kill_all() raise return self._vm_info
def qemu_start(self): """Start QEMU and wait until VM boot. :returns: VM node info. :rtype: dict """ cmd_opts = OptionString() cmd_opts.add('{bin_path}/qemu-system-{arch}'.format( bin_path=Constants.QEMU_BIN_PATH, arch=Topology.get_node_arch(self._node))) cmd_opts.extend(self._params) message = ('QEMU: Start failed on {host}!'. format(host=self._node['host'])) try: DUTSetup.check_huge_page( self._node, '/dev/hugepages', self._opt.get('mem')) exec_cmd_no_error( self._node, cmd_opts, timeout=300, sudo=True, message=message) self._wait_until_vm_boot() except RuntimeError: self.qemu_kill_all() raise return self._vm_info
def iperf3_cmdline(**kwargs): """Get iperf_client driver command line. :param kwargs: List of iperf_client driver parameters. :type kwargs: dict :returns: iperf_client driver command line. :rtype: OptionString """ cmd = OptionString() cmd.add(u"python3") dirname = f"{Constants.REMOTE_FW_DIR}/resources/tools/iperf" cmd.add(f"'{dirname}/iperf_client.py'") cmd_options = OptionString(prefix=u"--") # Namespace to execute iPerf3 client on. cmd_options.add_with_value_from_dict(u"namespace", u"namespace", kwargs) # Client connecting to an iPerf3 server running on host. cmd_options.add_with_value_from_dict(u"host", u"host", kwargs) # Client bind IP address. cmd_options.add_with_value_from_dict(u"bind", u"bind", kwargs) # Use UDP rather than TCP. cmd_options.add_if_from_dict(u"udp", u"udp", kwargs, False) # Set the CPU affinity, if possible. cmd_options.add_with_value_from_dict(u"affinity", u"affinity", kwargs) # Time expressed in seconds for how long to send traffic. cmd_options.add_with_value_from_dict(u"duration", u"duration", kwargs) # Send bi- (2) or uni- (1) directional traffic. cmd_options.add_with_value_from_dict(u"traffic_directions", u"traffic_directions", kwargs, 1) # Traffic warm-up time in seconds, (0=disable). cmd_options.add_with_value_from_dict(u"warmup_time", u"warmup_time", kwargs, 5.0) # L2 frame size to send (without padding and IPG). cmd_options.add_with_value_from_dict(u"frame_size", u"frame_size", kwargs) # Traffic rate expressed with units. cmd_options.add_with_value_from_dict(u"rate", u"rate", kwargs) # If enabled then don't wait for all incoming traffic. cmd_options.add_if_from_dict(u"async_start", u"async_call", kwargs, False) # Number of iPerf3 client parallel instances. cmd_options.add_with_value_from_dict(u"instances", u"instances", kwargs, 1) # Number of iPerf3 client parallel flows. cmd_options.add_with_value_from_dict(u"parallel", u"parallel", kwargs, 8) return cmd.extend(cmd_options)
def get_cmd_options(**kwargs): """Create parameters options. :param kwargs: Dict of cmd parameters. :type kwargs: dict :returns: Cmd parameters. :rtype: OptionString """ cmd = OptionString() cmd.add(u"python3") dirname = f"{Constants.REMOTE_FW_DIR}/resources/tools/ab" cmd.add(f"{dirname}/ABFork.py") cmd_options = OptionString(prefix=u"-") # Number of requests to perform. cmd_options.add_with_value_from_dict(u"r", u"requests", kwargs) # Server port number to use. cmd_options.add_with_value_from_dict(u"p", u"port", kwargs) # Number of clients being processed at the same time. cmd_options.add_with_value_from_dict(u"c", u"clients", kwargs) # Filename to be requested from the servers. cmd_options.add_with_value_from_dict(u"f", u"files", kwargs) # Server ip address. cmd_options.add_with_value_from_dict(u"i", u"ip", kwargs) # tg ip address. cmd_options.add_with_value_from_dict(u"g", u"tip", kwargs) # Specify SSL/TLS cipher suite. cmd_options.add_with_value_from_dict(u"z", u"cipher", kwargs, default=0) # Specify SSL/TLS protocol. cmd_options.add_with_value_from_dict(u"t", u"protocol", kwargs, default=0) # Mode: RPS or CPS. cmd_options.add_with_value_from_dict(u"m", u"mode", kwargs) return cmd.extend(cmd_options)
def __init__(self, node, qemu_id=1, smp=1, mem=512, vnf=None, img=Constants.QEMU_VM_IMAGE): """Initialize QemuUtil class. :param node: Node to run QEMU on. :param qemu_id: QEMU identifier. :param smp: Number of virtual SMP units (cores). :param mem: Amount of memory. :param vnf: Network function workload. :param img: QEMU disk image or kernel image path. :type node: dict :type qemu_id: int :type smp: int :type mem: int :type vnf: str :type img: str """ self._nic_id = 0 self._node = node self._arch = Topology.get_node_arch(self._node) self._opt = dict() # Architecture specific options if self._arch == u"aarch64": dpdk_target = u"arm64-armv8a" self._opt[u"machine_args"] = \ u"virt,accel=kvm,usb=off,mem-merge=off,gic-version=3" self._opt[u"console"] = u"ttyAMA0" else: dpdk_target = u"x86_64-native" self._opt[u"machine_args"] = u"pc,accel=kvm,usb=off,mem-merge=off" self._opt[u"console"] = u"ttyS0" self._testpmd_path = f"{Constants.QEMU_VM_DPDK}/" \ f"{dpdk_target}-linux-gcc/app" self._vm_info = { u"host": node[u"host"], u"type": NodeType.VM, u"port": 10021 + qemu_id, u"serial": 4555 + qemu_id, u"username": '******', u"password": '******', u"interfaces": {}, } if node[u"port"] != 22: self._vm_info[u"host_port"] = node[u"port"] self._vm_info[u"host_username"] = node[u"username"] self._vm_info[u"host_password"] = node[u"password"] # Input Options. self._opt[u"qemu_id"] = qemu_id self._opt[u"mem"] = int(mem) self._opt[u"smp"] = int(smp) self._opt[u"img"] = img self._opt[u"vnf"] = vnf # Temporary files. self._temp = dict() self._temp[u"log"] = f"/tmp/serial_{qemu_id}.log" self._temp[u"pidfile"] = f"/run/qemu_{qemu_id}.pid" if img == Constants.QEMU_VM_IMAGE: self._temp[u"qmp"] = f"/run/qmp_{qemu_id}.sock" self._temp[u"qga"] = f"/run/qga_{qemu_id}.sock" elif img == Constants.QEMU_VM_KERNEL: self._opt[u"img"], _ = exec_cmd_no_error( node, f"ls -1 {Constants.QEMU_VM_KERNEL}* | tail -1", message=u"Qemu Kernel VM image not found!") self._temp[u"ini"] = f"/etc/vm_init_{qemu_id}.conf" self._opt[u"initrd"], _ = exec_cmd_no_error( node, f"ls -1 {Constants.QEMU_VM_KERNEL_INITRD}* | tail -1", message=u"Qemu Kernel initrd image not found!") else: raise RuntimeError(f"QEMU: Unknown VM image option: {img}") # Computed parameters for QEMU command line. self._params = OptionString(prefix=u"-")
class QemuUtils: """QEMU utilities.""" # Use one instance of class per tests. ROBOT_LIBRARY_SCOPE = u"TEST CASE" def __init__(self, node, qemu_id=1, smp=1, mem=512, vnf=None, img=Constants.QEMU_VM_IMAGE): """Initialize QemuUtil class. :param node: Node to run QEMU on. :param qemu_id: QEMU identifier. :param smp: Number of virtual SMP units (cores). :param mem: Amount of memory. :param vnf: Network function workload. :param img: QEMU disk image or kernel image path. :type node: dict :type qemu_id: int :type smp: int :type mem: int :type vnf: str :type img: str """ self._nic_id = 0 self._node = node self._arch = Topology.get_node_arch(self._node) self._opt = dict() # Architecture specific options if self._arch == u"aarch64": dpdk_target = u"arm64-armv8a" self._opt[u"machine_args"] = \ u"virt,accel=kvm,usb=off,mem-merge=off,gic-version=3" self._opt[u"console"] = u"ttyAMA0" else: dpdk_target = u"x86_64-native" self._opt[u"machine_args"] = u"pc,accel=kvm,usb=off,mem-merge=off" self._opt[u"console"] = u"ttyS0" self._testpmd_path = f"{Constants.QEMU_VM_DPDK}/" \ f"{dpdk_target}-linux-gcc/app" self._vm_info = { u"host": node[u"host"], u"type": NodeType.VM, u"port": 10021 + qemu_id, u"serial": 4555 + qemu_id, u"username": '******', u"password": '******', u"interfaces": {}, } if node[u"port"] != 22: self._vm_info[u"host_port"] = node[u"port"] self._vm_info[u"host_username"] = node[u"username"] self._vm_info[u"host_password"] = node[u"password"] # Input Options. self._opt[u"qemu_id"] = qemu_id self._opt[u"mem"] = int(mem) self._opt[u"smp"] = int(smp) self._opt[u"img"] = img self._opt[u"vnf"] = vnf # Temporary files. self._temp = dict() self._temp[u"log"] = f"/tmp/serial_{qemu_id}.log" self._temp[u"pidfile"] = f"/run/qemu_{qemu_id}.pid" if img == Constants.QEMU_VM_IMAGE: self._temp[u"qmp"] = f"/run/qmp_{qemu_id}.sock" self._temp[u"qga"] = f"/run/qga_{qemu_id}.sock" elif img == Constants.QEMU_VM_KERNEL: self._opt[u"img"], _ = exec_cmd_no_error( node, f"ls -1 {Constants.QEMU_VM_KERNEL}* | tail -1", message=u"Qemu Kernel VM image not found!") self._temp[u"ini"] = f"/etc/vm_init_{qemu_id}.conf" self._opt[u"initrd"], _ = exec_cmd_no_error( node, f"ls -1 {Constants.QEMU_VM_KERNEL_INITRD}* | tail -1", message=u"Qemu Kernel initrd image not found!") else: raise RuntimeError(f"QEMU: Unknown VM image option: {img}") # Computed parameters for QEMU command line. self._params = OptionString(prefix=u"-") def add_default_params(self): """Set default QEMU command line parameters.""" self._params.add(u"daemonize") self._params.add(u"nodefaults") self._params.add_with_value( u"name", f"vnf{self._opt.get(u'qemu_id')},debug-threads=on") self._params.add(u"no-user-config") self._params.add(u"nographic") self._params.add(u"enable-kvm") self._params.add_with_value(u"pidfile", self._temp.get(u"pidfile")) self._params.add_with_value(u"cpu", u"host") self._params.add_with_value(u"machine", self._opt.get(u"machine_args")) self._params.add_with_value( u"smp", f"{self._opt.get(u'smp')},sockets=1," f"cores={self._opt.get(u'smp')},threads=1") self._params.add_with_value( u"object", f"memory-backend-file,id=mem," f"size={self._opt.get(u'mem')}M,mem-path=/dev/hugepages,share=on") self._params.add_with_value(u"m", f"{self._opt.get(u'mem')}M") self._params.add_with_value(u"numa", u"node,memdev=mem") self._params.add_with_value(u"balloon", u"none") def add_net_user(self): """Set managment port forwarding.""" self._params.add_with_value( u"netdev", f"user,id=mgmt,net=172.16.255.0/24," f"hostfwd=tcp::{self._vm_info[u'port']}-:22") self._params.add_with_value(u"device", f"virtio-net,netdev=mgmt") def add_qmp_qga(self): """Set QMP, QGA management.""" self._params.add_with_value( u"chardev", f"socket,path={self._temp.get(u'qga')}," f"server,nowait,id=qga0") self._params.add_with_value(u"device", u"isa-serial,chardev=qga0") self._params.add_with_value( u"qmp", f"unix:{self._temp.get(u'qmp')},server,nowait") def add_serial(self): """Set serial to file redirect.""" self._params.add_with_value( u"chardev", f"socket,host=127.0.0.1," f"port={self._vm_info[u'serial']},id=gnc0,server,nowait") self._params.add_with_value(u"device", u"isa-serial,chardev=gnc0") self._params.add_with_value(u"serial", f"file:{self._temp.get(u'log')}") def add_drive_cdrom(self, drive_file, index=None): """Set CD-ROM drive. :param drive_file: Path to drive image. :param index: Drive index. :type drive_file: str :type index: int """ index = f"index={index}," if index else u"" self._params.add_with_value(u"drive", f"file={drive_file},{index}media=cdrom") def add_drive(self, drive_file, drive_format): """Set drive with custom format. :param drive_file: Path to drive image. :param drive_format: Drive image format. :type drive_file: str :type drive_format: str """ self._params.add_with_value( u"drive", f"file={drive_file},format={drive_format}," u"cache=none,if=virtio,file.locking=off") def add_kernelvm_params(self): """Set KernelVM QEMU parameters.""" self._params.add_with_value(u"serial", f"file:{self._temp.get(u'log')}") self._params.add_with_value( u"fsdev", u"local,id=root9p,path=/,security_model=none") self._params.add_with_value( u"device", u"virtio-9p-pci,fsdev=root9p,mount_tag=virtioroot") self._params.add_with_value(u"kernel", f"{self._opt.get(u'img')}") self._params.add_with_value(u"initrd", f"{self._opt.get(u'initrd')}") self._params.add_with_value( u"append", f"'ro rootfstype=9p rootflags=trans=virtio " f"root=virtioroot console={self._opt.get(u'console')} " f"tsc=reliable hugepages=512 " f"init={self._temp.get(u'ini')} fastboot'") def add_vhost_user_if(self, socket, server=True, jumbo_frames=False, queue_size=None, queues=1, csum=False, gso=False): """Add Vhost-user interface. :param socket: Path of the unix socket. :param server: If True the socket shall be a listening socket. :param jumbo_frames: Set True if jumbo frames are used in the test. :param queue_size: Vring queue size. :param queues: Number of queues. :param csum: Checksum offloading. :param gso: Generic segmentation offloading. :type socket: str :type server: bool :type jumbo_frames: bool :type queue_size: int :type queues: int :type csum: bool :type gso: bool """ self._nic_id += 1 self._params.add_with_value( u"chardev", f"socket,id=char{self._nic_id}," f"path={socket}{u',server' if server is True else u''}") self._params.add_with_value( u"netdev", f"vhost-user,id=vhost{self._nic_id}," f"chardev=char{self._nic_id},queues={queues}") mac = f"52:54:00:00:{self._opt.get(u'qemu_id'):02x}:" \ f"{self._nic_id:02x}" queue_size = f"rx_queue_size={queue_size},tx_queue_size={queue_size}" \ if queue_size else u"" self._params.add_with_value( u"device", f"virtio-net-pci,netdev=vhost{self._nic_id},mac={mac}," f"addr={self._nic_id+5}.0,mq=on,vectors={2 * queues + 2}," f"csum={u'on' if csum else u'off'},gso={u'on' if gso else u'off'}," f"guest_tso4=off,guest_tso6=off,guest_ecn=off," f"{queue_size}") # Add interface MAC and socket to the node dict. if_data = {u"mac_address": mac, u"socket": socket} if_name = f"vhost{self._nic_id}" self._vm_info[u"interfaces"][if_name] = if_data # Add socket to temporary file list. self._temp[if_name] = socket def add_vfio_pci_if(self, pci): """Add VFIO PCI interface. :param pci: PCI address of interface. :type pci: str """ self._nic_id += 1 self._params.add_with_value( u"device", f"vfio-pci,host={pci},addr={self._nic_id+5}.0") def create_kernelvm_config_vpp(self, **kwargs): """Create QEMU VPP config files. :param kwargs: Key-value pairs to replace content of VPP configuration file. :type kwargs: dict """ startup = f"/etc/vpp/vm_startup_{self._opt.get(u'qemu_id')}.conf" running = f"/etc/vpp/vm_running_{self._opt.get(u'qemu_id')}.exec" self._temp[u"startup"] = startup self._temp[u"running"] = running self._opt[u"vnf_bin"] = f"/usr/bin/vpp -c {startup}" # Create VPP startup configuration. vpp_config = VppConfigGenerator() vpp_config.set_node(self._node) vpp_config.add_unix_nodaemon() vpp_config.add_unix_cli_listen() vpp_config.add_unix_exec(running) vpp_config.add_socksvr() vpp_config.add_main_heap_size(u"512M") vpp_config.add_main_heap_page_size(u"2M") vpp_config.add_statseg_size(u"512M") vpp_config.add_statseg_page_size(u"2M") vpp_config.add_statseg_per_node_counters(u"on") vpp_config.add_buffers_per_numa(107520) vpp_config.add_cpu_main_core(u"0") if self._opt.get(u"smp") > 1: vpp_config.add_cpu_corelist_workers(f"1-{self._opt.get(u'smp')-1}") vpp_config.add_plugin(u"disable", u"default") vpp_config.add_plugin(u"enable", u"ping_plugin.so") if "2vfpt" in self._opt.get(u'vnf'): vpp_config.add_plugin(u"enable", u"avf_plugin.so") if "vhost" in self._opt.get(u'vnf'): vpp_config.add_plugin(u"enable", u"dpdk_plugin.so") vpp_config.add_dpdk_dev(u"0000:00:06.0", u"0000:00:07.0") vpp_config.add_dpdk_dev_default_rxq(kwargs[u"queues"]) vpp_config.add_dpdk_log_level(u"debug") if not kwargs[u"jumbo_frames"]: vpp_config.add_dpdk_no_multi_seg() vpp_config.add_dpdk_no_tx_checksum_offload() if "ipsec" in self._opt.get(u'vnf'): vpp_config.add_plugin(u"enable", u"crypto_native_plugin.so") vpp_config.add_plugin(u"enable", u"crypto_ipsecmb_plugin.so") vpp_config.add_plugin(u"enable", u"crypto_openssl_plugin.so") if "nat" in self._opt.get(u'vnf'): vpp_config.add_nat(value=u"endpoint-dependent") vpp_config.add_plugin(u"enable", u"nat_plugin.so") vpp_config.write_config(startup) # Create VPP running configuration. template = f"{Constants.RESOURCES_TPL}/vm/{self._opt.get(u'vnf')}.exec" exec_cmd_no_error(self._node, f"rm -f {running}", sudo=True) with open(template, u"rt") as src_file: src = Template(src_file.read()) exec_cmd_no_error( self._node, f"echo '{src.safe_substitute(**kwargs)}' | " f"sudo tee {running}") def create_kernelvm_config_testpmd_io(self, **kwargs): """Create QEMU testpmd-io command line. :param kwargs: Key-value pairs to construct command line parameters. :type kwargs: dict """ pmd_max_pkt_len = u"9200" if kwargs[u"jumbo_frames"] else u"1518" testpmd_cmd = DpdkUtil.get_testpmd_cmdline( eal_corelist=f"0-{self._opt.get(u'smp') - 1}", eal_driver=False, eal_pci_whitelist0=u"0000:00:06.0", eal_pci_whitelist1=u"0000:00:07.0", eal_in_memory=True, pmd_num_mbufs=16384, pmd_fwd_mode=u"io", pmd_nb_ports=u"2", pmd_portmask=u"0x3", pmd_max_pkt_len=pmd_max_pkt_len, pmd_mbuf_size=u"16384", pmd_rxq=kwargs[u"queues"], pmd_txq=kwargs[u"queues"], pmd_tx_offloads='0x0', pmd_nb_cores=str(self._opt.get(u"smp") - 1)) self._opt[u"vnf_bin"] = f"{self._testpmd_path}/{testpmd_cmd}" def create_kernelvm_config_testpmd_mac(self, **kwargs): """Create QEMU testpmd-mac command line. :param kwargs: Key-value pairs to construct command line parameters. :type kwargs: dict """ pmd_max_pkt_len = u"9200" if kwargs[u"jumbo_frames"] else u"1518" testpmd_cmd = DpdkUtil.get_testpmd_cmdline( eal_corelist=f"0-{self._opt.get(u'smp') - 1}", eal_driver=False, eal_pci_whitelist0=u"0000:00:06.0", eal_pci_whitelist1=u"0000:00:07.0", eal_in_memory=True, pmd_num_mbufs=16384, pmd_fwd_mode=u"mac", pmd_nb_ports=u"2", pmd_portmask=u"0x3", pmd_max_pkt_len=pmd_max_pkt_len, pmd_mbuf_size=u"16384", pmd_eth_peer_0=f"0,{kwargs[u'vif1_mac']}", pmd_eth_peer_1=f"1,{kwargs[u'vif2_mac']}", pmd_rxq=kwargs[u"queues"], pmd_txq=kwargs[u"queues"], pmd_tx_offloads=u"0x0", pmd_nb_cores=str(self._opt.get(u"smp") - 1)) self._opt[u"vnf_bin"] = f"{self._testpmd_path}/{testpmd_cmd}" def create_kernelvm_init(self, **kwargs): """Create QEMU init script. :param kwargs: Key-value pairs to replace content of init startup file. :type kwargs: dict """ template = f"{Constants.RESOURCES_TPL}/vm/init.sh" init = self._temp.get(u"ini") exec_cmd_no_error(self._node, f"rm -f {init}", sudo=True) with open(template, u"rt") as src_file: src = Template(src_file.read()) exec_cmd_no_error( self._node, f"echo '{src.safe_substitute(**kwargs)}' | " f"sudo tee {init}") exec_cmd_no_error(self._node, f"chmod +x {init}", sudo=True) def configure_kernelvm_vnf(self, **kwargs): """Create KernelVM VNF configurations. :param kwargs: Key-value pairs for templating configs. :type kwargs: dict """ if u"vpp" in self._opt.get(u"vnf"): self.create_kernelvm_config_vpp(**kwargs) self.create_kernelvm_init(vnf_bin=self._opt.get(u"vnf_bin")) elif u"testpmd_io" in self._opt.get(u"vnf"): self.create_kernelvm_config_testpmd_io(**kwargs) self.create_kernelvm_init(vnf_bin=self._opt.get(u"vnf_bin")) elif u"testpmd_mac" in self._opt.get(u"vnf"): self.create_kernelvm_config_testpmd_mac(**kwargs) self.create_kernelvm_init(vnf_bin=self._opt.get(u"vnf_bin")) else: raise RuntimeError(u"QEMU: Unsupported VNF!") def get_qemu_pids(self): """Get QEMU CPU pids. :returns: List of QEMU CPU pids. :rtype: list of str """ command = f"grep -rwl 'CPU' /proc/$(sudo cat " \ f"{self._temp.get(u'pidfile')})/task/*/comm " command += r"| xargs dirname | sed -e 's/\/.*\///g' | uniq" stdout, _ = exec_cmd_no_error(self._node, command) return stdout.splitlines() def qemu_set_affinity(self, *host_cpus): """Set qemu affinity by getting thread PIDs via QMP and taskset to list of CPU cores. Function tries to execute 3 times to avoid race condition in getting thread PIDs. :param host_cpus: List of CPU cores. :type host_cpus: list """ for _ in range(3): try: qemu_cpus = self.get_qemu_pids() if len(qemu_cpus) != len(host_cpus): sleep(1) continue for qemu_cpu, host_cpu in zip(qemu_cpus, host_cpus): command = f"taskset -pc {host_cpu} {qemu_cpu}" message = f"QEMU: Set affinity failed " \ f"on {self._node[u'host']}!" exec_cmd_no_error(self._node, command, sudo=True, message=message) break except (RuntimeError, ValueError): self.qemu_kill_all() raise else: self.qemu_kill_all() raise RuntimeError(u"Failed to set Qemu threads affinity!") def qemu_set_scheduler_policy(self): """Set scheduler policy to SCHED_RR with priority 1 for all Qemu CPU processes. :raises RuntimeError: Set scheduler policy failed. """ try: qemu_cpus = self.get_qemu_pids() for qemu_cpu in qemu_cpus: command = f"chrt -r -p 1 {qemu_cpu}" message = f"QEMU: Set SCHED_RR failed on {self._node[u'host']}" exec_cmd_no_error(self._node, command, sudo=True, message=message) except (RuntimeError, ValueError): self.qemu_kill_all() raise def _qemu_qmp_exec(self, cmd): """Execute QMP command. QMP is JSON based protocol which allows to control QEMU instance. :param cmd: QMP command to execute. :type cmd: str :returns: Command output in python representation of JSON format. The { "return": {} } response is QMP's success response. An error response will contain the "error" keyword instead of "return". """ # To enter command mode, the qmp_capabilities command must be issued. command = f"echo \"{{{{ \\\"execute\\\": " \ f"\\\"qmp_capabilities\\\" }}}}" \ f"{{{{ \\\"execute\\\": \\\"{cmd}\\\" }}}}\" | " \ f"sudo -S socat - UNIX-CONNECT:{self._temp.get(u'qmp')}" message = f"QMP execute '{cmd}' failed on {self._node[u'host']}" stdout, _ = exec_cmd_no_error(self._node, command, sudo=False, message=message) # Skip capabilities negotiation messages. out_list = stdout.splitlines() if len(out_list) < 3: raise RuntimeError(f"Invalid QMP output on {self._node[u'host']}") return json.loads(out_list[2]) def _qemu_qga_flush(self): """Flush the QGA parser state.""" command = f"(printf \"\xFF\"; sleep 1) | sudo -S socat " \ f"- UNIX-CONNECT:{self._temp.get(u'qga')}" message = f"QGA flush failed on {self._node[u'host']}" stdout, _ = exec_cmd_no_error(self._node, command, sudo=False, message=message) return json.loads(stdout.split(u"\n", 1)[0]) if stdout else dict() def _qemu_qga_exec(self, cmd): """Execute QGA command. QGA provide access to a system-level agent via standard QMP commands. :param cmd: QGA command to execute. :type cmd: str """ command = f"(echo \"{{{{ \\\"execute\\\": " \ f"\\\"{cmd}\\\" }}}}\"; sleep 1) | " \ f"sudo -S socat - UNIX-CONNECT:{self._temp.get(u'qga')}" message = f"QGA execute '{cmd}' failed on {self._node[u'host']}" stdout, _ = exec_cmd_no_error(self._node, command, sudo=False, message=message) return json.loads(stdout.split(u"\n", 1)[0]) if stdout else dict() def _wait_until_vm_boot(self): """Wait until QEMU VM is booted.""" try: getattr(self, f'_wait_{self._opt["vnf"]}')() except AttributeError: self._wait_default() def _wait_default(self, retries=60): """Wait until QEMU with VPP is booted. :param retries: Number of retries. :type retries: int """ for _ in range(retries): command = f"tail -1 {self._temp.get(u'log')}" stdout = None try: stdout, _ = exec_cmd_no_error(self._node, command, sudo=True) sleep(1) except RuntimeError: pass if "vpp " in stdout and "built by" in stdout: break if u"Press enter to exit" in stdout: break if u"reboot: Power down" in stdout: raise RuntimeError( f"QEMU: NF failed to run on {self._node[u'host']}!") else: raise RuntimeError( f"QEMU: Timeout, VM not booted on {self._node[u'host']}!") def _wait_nestedvm(self, retries=12): """Wait until QEMU with NestedVM is booted. First try to flush qga until there is output. Then ping QEMU guest agent each 5s until VM booted or timeout. :param retries: Number of retries with 5s between trials. :type retries: int """ for _ in range(retries): out = None try: out = self._qemu_qga_flush() except ValueError: logger.trace(f"QGA qga flush unexpected output {out}") # Empty output - VM not booted yet if not out: sleep(5) else: break else: raise RuntimeError( f"QEMU: Timeout, VM not booted on {self._node[u'host']}!") for _ in range(retries): out = None try: out = self._qemu_qga_exec(u"guest-ping") except ValueError: logger.trace(f"QGA guest-ping unexpected output {out}") # Empty output - VM not booted yet. if not out: sleep(5) # Non-error return - VM booted. elif out.get(u"return") is not None: break # Skip error and wait. elif out.get(u"error") is not None: sleep(5) else: # If there is an unexpected output from QGA guest-info, try # again until timeout. logger.trace(f"QGA guest-ping unexpected output {out}") else: raise RuntimeError( f"QEMU: Timeout, VM not booted on {self._node[u'host']}!") def _update_vm_interfaces(self): """Update interface names in VM node dict.""" # Send guest-network-get-interfaces command via QGA, output example: # {"return": [{"name": "eth0", "hardware-address": "52:54:00:00:04:01"}, # {"name": "eth1", "hardware-address": "52:54:00:00:04:02"}]}. out = self._qemu_qga_exec(u"guest-network-get-interfaces") interfaces = out.get(u"return") mac_name = {} if not interfaces: raise RuntimeError( f"Get VM interface list failed on {self._node[u'host']}") # Create MAC-name dict. for interface in interfaces: if u"hardware-address" not in interface: continue mac_name[interface[u"hardware-address"]] = interface[u"name"] # Match interface by MAC and save interface name. for interface in self._vm_info[u"interfaces"].values(): mac = interface.get(u"mac_address") if_name = mac_name.get(mac) if if_name is None: logger.trace(f"Interface name for MAC {mac} not found") else: interface[u"name"] = if_name def qemu_start(self): """Start QEMU and wait until VM boot. :returns: VM node info. :rtype: dict """ cmd_opts = OptionString() cmd_opts.add(f"{Constants.QEMU_BIN_PATH}/qemu-system-{self._arch}") cmd_opts.extend(self._params) message = f"QEMU: Start failed on {self._node[u'host']}!" try: DUTSetup.check_huge_page(self._node, u"/dev/hugepages", int(self._opt.get(u"mem"))) exec_cmd_no_error(self._node, cmd_opts, timeout=300, sudo=True, message=message) self._wait_until_vm_boot() except RuntimeError: self.qemu_kill_all() raise return self._vm_info def qemu_kill(self): """Kill qemu process.""" exec_cmd(self._node, f"chmod +r {self._temp.get(u'pidfile')}", sudo=True) exec_cmd(self._node, f"kill -SIGKILL $(cat {self._temp.get(u'pidfile')})", sudo=True) for value in self._temp.values(): exec_cmd(self._node, f"cat {value}", sudo=True) exec_cmd(self._node, f"rm -f {value}", sudo=True) def qemu_kill_all(self): """Kill all qemu processes on DUT node if specified.""" exec_cmd(self._node, u"pkill -SIGKILL qemu", sudo=True) for value in self._temp.values(): exec_cmd(self._node, f"cat {value}", sudo=True) exec_cmd(self._node, f"rm -f {value}", sudo=True) def qemu_version(self): """Return Qemu version. :returns: Qemu version. :rtype: str """ command = f"{Constants.QEMU_BIN_PATH}/qemu-system-{self._arch} " \ f"--version" try: stdout, _ = exec_cmd_no_error(self._node, command, sudo=True) return match(r"QEMU emulator version ([\d.]*)", stdout).group(1) except RuntimeError: self.qemu_kill_all() raise
def perf_stat(node, cpu_list=None, duration=1): """Get perf stat read for duration. :param node: Node in the topology. :param cpu_list: CPU List as a string separated by comma. :param duration: Measure time in seconds. :type node: dict :type cpu_list: str :type duration: int """ if cpu_list: cpu_list = list(dict.fromkeys(cpu_list.split(u","))) cpu_list = ",".join(str(cpu) for cpu in cpu_list) cmd_opts = OptionString(prefix=u"--") cmd_opts.add(u"no-aggr") cmd_opts.add_with_value_if(u"cpu", cpu_list, cpu_list) cmd_opts.add_if(u"all-cpus", not (cpu_list)) cmd_opts.add_with_value_if(u"event", f"'{{{Constants.PERF_STAT_EVENTS}}}'", Constants.PERF_STAT_EVENTS) cmd_opts.add_with_value(u"interval-print", 1000) cmd_opts.add_with_value(u"field-separator", u"';'") cmd_base = OptionString() cmd_base.add(f"perf stat") cmd_base.extend(cmd_opts) cmd_base.add(u"--") cmd_base.add_with_value(u"sleep", int(duration)) exec_cmd(node, cmd_base, sudo=True)
def exec_command(self, cmd, timeout=10, log_stdout_err=True): """Execute SSH command on a new channel on the connected Node. :param cmd: Command to run on the Node. :param timeout: Maximal time in seconds to wait until the command is done. If set to None then wait forever. :param log_stdout_err: If True, stdout and stderr are logged. stdout and stderr are logged also if the return code is not zero independently of the value of log_stdout_err. :type cmd: str or OptionString :type timeout: int :type log_stdout_err: bool :returns: return_code, stdout, stderr :rtype: tuple(int, str, str) :raises SSHTimeout: If command is not finished in timeout time. """ if isinstance(cmd, (list, tuple)): cmd = OptionString(cmd) cmd = str(cmd) stdout = StringIO.StringIO() stderr = StringIO.StringIO() try: chan = self._ssh.get_transport().open_session(timeout=5) peer = self._ssh.get_transport().getpeername() except (AttributeError, SSHException): self._reconnect() chan = self._ssh.get_transport().open_session(timeout=5) peer = self._ssh.get_transport().getpeername() chan.settimeout(timeout) logger.trace( 'exec_command on {peer} with timeout {timeout}: {cmd}'.format( peer=peer, timeout=timeout, cmd=cmd)) start = time() chan.exec_command(cmd) while not chan.exit_status_ready() and timeout is not None: if chan.recv_ready(): stdout.write(chan.recv(self.__MAX_RECV_BUF)) if chan.recv_stderr_ready(): stderr.write(chan.recv_stderr(self.__MAX_RECV_BUF)) if time() - start > timeout: raise SSHTimeout( 'Timeout exception during execution of command: {cmd}\n' 'Current contents of stdout buffer: {stdout}\n' 'Current contents of stderr buffer: {stderr}\n'.format( cmd=cmd, stdout=stdout.getvalue(), stderr=stderr.getvalue())) sleep(0.1) return_code = chan.recv_exit_status() while chan.recv_ready(): stdout.write(chan.recv(self.__MAX_RECV_BUF)) while chan.recv_stderr_ready(): stderr.write(chan.recv_stderr(self.__MAX_RECV_BUF)) end = time() logger.trace('exec_command on {peer} took {total} seconds'.format( peer=peer, total=end - start)) logger.trace('return RC {rc}'.format(rc=return_code)) if log_stdout_err or int(return_code): logger.trace( 'return STDOUT {stdout}'.format(stdout=stdout.getvalue())) logger.trace( 'return STDERR {stderr}'.format(stderr=stderr.getvalue())) return return_code, stdout.getvalue(), stderr.getvalue()
def get_pmd_options(**kwargs): """Create PMD parameters options (without --). :param kwargs: List of testpmd parameters. :type kwargs: dict :returns: PMD parameters. :rtype: OptionString """ options = OptionString(prefix='--') # Set the forwarding mode: io, mac, mac_retry, mac_swap, flowgen, # rxonly, txonly, csum, icmpecho, ieee1588 options.add_equals_from_dict('forward-mode', 'pmd_fwd_mode', kwargs, 'io') # Set the number of packets per burst to N. options.add_equals('burst', 64) # Set the number of descriptors in the TX rings to N. options.add_equals_from_dict('txd', 'pmd_txd', kwargs, 1024) # Set the number of descriptors in the RX rings to N. options.add_equals_from_dict('rxd', 'pmd_rxd', kwargs, 1024) # Set the number of queues in the TX to N. options.add_equals_from_dict('txq', 'pmd_txq', kwargs, 1) # Set the number of queues in the RX to N. options.add_equals_from_dict('rxq', 'pmd_rxq', kwargs, 1) # Set the hexadecimal bitmask of offloads. options.add_equals_if_from_dict('txqflags', '0xf00', 'pmd_tx_offloads', kwargs, True) # Set the number of mbufs to be allocated in the mbuf pools. options.add_equals_from_dict('total-num-mbufs', 'pmd_num_mbufs', kwargs) # Disable hardware VLAN. options.add_if_from_dict('disable-hw-vlan', 'pmd_disable_hw_vlan', kwargs, True) # Set the MAC address XX:XX:XX:XX:XX:XX of the peer port N options.add_equals_from_dict('eth-peer', 'pmd_eth_peer_0', kwargs) options.add_equals_from_dict('eth-peer', 'pmd_eth_peer_1', kwargs) # Set the max packet length. options.add_equals_from_dict('max-pkt-len', 'pmd_max_pkt_len', kwargs) # Set the number of forwarding cores based on coremask. options.add_equals_from_dict('nb-cores', 'pmd_nb_cores', kwargs) return options
def iperf3_cmdline(**kwargs): """Get iPerf3 server command line. :param kwargs: List of iPerf3 server parameters. :type kwargs: dict :returns: iPerf3 server command line. :rtype: OptionString """ cmd = OptionString() if kwargs['namespace']: cmd.add(f"ip netns exec {kwargs['namespace']}") cmd.add(f"iperf3") cmd_options = OptionString(prefix=u"--") # Run iPerf in server mode. (This will only allow one iperf connection # at a time) cmd_options.add(u"server") # Run the server in background as a daemon. cmd_options.add_if_from_dict(u"daemon", u"daemon", kwargs, True) # Write a file with the process ID, most useful when running as a # daemon. cmd_options.add_with_value_from_dict(u"pidfile", u"pidfile", kwargs, f"/tmp/iperf3_server.pid") # Send output to a log file. cmd_options.add_with_value_from_dict(u"logfile", u"logfile", kwargs, f"/tmp/iperf3.log") # The server port for the server to listen on and the client to # connect to. This should be the same in both client and server. # Default is 5201. cmd_options.add_with_value_from_dict(u"port", u"port", kwargs, 5201) # Set the CPU affinity, if possible (Linux and FreeBSD only). cmd_options.add_with_value_from_dict(u"affinity", u"affinity", kwargs) # Output in JSON format. cmd_options.add_if_from_dict(u"json", u"json", kwargs, True) # Give more detailed output. cmd_options.add_if_from_dict(u"verbose", u"verbose", kwargs, True) return cmd.extend(cmd_options)
def get_eal_options(**kwargs): """Create EAL parameters options (including -v). :param kwargs: Dict of testpmd parameters. :type kwargs: dict :returns: EAL parameters. :rtype: OptionString """ options = OptionString(prefix=u"-") options.add(u"v") # Set the hexadecimal bitmask of the cores to run on. options.add_with_value_from_dict(u"l", u"eal_corelist", kwargs) # Add a PCI device in white list. options.add_with_value_from_dict(u"w", u"eal_pci_whitelist0", kwargs) options.add_with_value_from_dict(u"w", u"eal_pci_whitelist1", kwargs) # Set master core. options.add_with_value(u"-master-lcore", u"0") # Load an external driver. Multiple -d options are allowed. options.add_with_value_if_from_dict(u"d", u"/usr/lib/librte_pmd_virtio.so", u"eal_driver", kwargs, True) options.add_if_from_dict(u"-in-memory", u"eal_in_memory", kwargs, False) return options
def exec_command(self, cmd, timeout=10, log_stdout_err=True, export=True): """Execute SSH command on a new channel on the connected Node. :param cmd: Command to run on the Node. :param timeout: Maximal time in seconds to wait until the command is done. If set to None then wait forever. :param log_stdout_err: If True, stdout and stderr are logged. stdout and stderr are logged also if the return code is not zero independently of the value of log_stdout_err. :param export: If false, do not attempt JSON export. Needed for calls outside Robot (e.g. from reservation script). :type cmd: str or OptionString :type timeout: int :type log_stdout_err: bool :type export: bool :returns: return_code, stdout, stderr :rtype: tuple(int, str, str) :raises SSHTimeout: If command is not finished in timeout time. """ if isinstance(cmd, (list, tuple)): cmd = OptionString(cmd) cmd = str(cmd) stdout = u"" stderr = u"" try: chan = self._ssh.get_transport().open_session(timeout=5) peer = self._ssh.get_transport().getpeername() except (AttributeError, SSHException): self._reconnect() chan = self._ssh.get_transport().open_session(timeout=5) peer = self._ssh.get_transport().getpeername() chan.settimeout(timeout) logger.trace(f"exec_command on {peer} with timeout {timeout}: {cmd}") if export: export_ssh_command(self._node[u"host"], self._node[u"port"], cmd) start = monotonic() chan.exec_command(cmd) while not chan.exit_status_ready() and timeout is not None: if chan.recv_ready(): s_out = chan.recv(self.__MAX_RECV_BUF) stdout += s_out.decode(encoding=u'utf-8', errors=u'ignore') \ if isinstance(s_out, bytes) else s_out if chan.recv_stderr_ready(): s_err = chan.recv_stderr(self.__MAX_RECV_BUF) stderr += s_err.decode(encoding=u'utf-8', errors=u'ignore') \ if isinstance(s_err, bytes) else s_err duration = monotonic() - start if duration > timeout: if export: export_ssh_timeout( host=self._node[u"host"], port=self._node[u"port"], stdout=stdout, stderr=stderr, duration=duration, ) raise SSHTimeout( f"Timeout exception during execution of command: {cmd}\n" f"Current contents of stdout buffer: " f"{stdout}\n" f"Current contents of stderr buffer: " f"{stderr}\n") sleep(0.1) return_code = chan.recv_exit_status() while chan.recv_ready(): s_out = chan.recv(self.__MAX_RECV_BUF) stdout += s_out.decode(encoding=u'utf-8', errors=u'ignore') \ if isinstance(s_out, bytes) else s_out while chan.recv_stderr_ready(): s_err = chan.recv_stderr(self.__MAX_RECV_BUF) stderr += s_err.decode(encoding=u'utf-8', errors=u'ignore') \ if isinstance(s_err, bytes) else s_err duration = monotonic() - start logger.trace(f"exec_command on {peer} took {duration} seconds") logger.trace(f"return RC {return_code}") if log_stdout_err or int(return_code): logger.trace(f"return STDOUT {stdout}") logger.trace(f"return STDERR {stderr}") if export: export_ssh_result( host=self._node[u"host"], port=self._node[u"port"], code=return_code, stdout=stdout, stderr=stderr, duration=duration, ) return return_code, stdout, stderr
class QemuUtils(object): """QEMU utilities.""" # Use one instance of class per tests. ROBOT_LIBRARY_SCOPE = 'TEST CASE' def __init__(self, node, qemu_id=1, smp=1, mem=512, vnf=None, img=Constants.QEMU_VM_IMAGE): """Initialize QemuUtil class. :param node: Node to run QEMU on. :param qemu_id: QEMU identifier. :param smp: Number of virtual SMP units (cores). :param mem: Amount of memory. :param vnf: Network function workload. :param img: QEMU disk image or kernel image path. :type node: dict :type qemu_id: int :type smp: int :type mem: int :type vnf: str :type img: str """ self._vhost_id = 0 self._node = node self._vm_info = { 'host': node['host'], 'type': NodeType.VM, 'port': 10021 + qemu_id, 'serial': 4555 + qemu_id, 'username': '******', 'password': '******', 'interfaces': {}, } if node['port'] != 22: self._vm_info['host_port'] = node['port'] self._vm_info['host_username'] = node['username'] self._vm_info['host_password'] = node['password'] # Input Options. self._opt = dict() self._opt['qemu_id'] = qemu_id self._opt['mem'] = int(mem) self._opt['smp'] = int(smp) self._opt['img'] = img self._opt['vnf'] = vnf # Temporary files. self._temp = dict() self._temp['pidfile'] = '/var/run/qemu_{id}.pid'.format(id=qemu_id) if '/var/lib/vm/' in img: self._opt['vm_type'] = 'nestedvm' self._temp['qmp'] = '/var/run/qmp_{id}.sock'.format(id=qemu_id) self._temp['qga'] = '/var/run/qga_{id}.sock'.format(id=qemu_id) elif '/opt/boot/vmlinuz' in img: self._opt['vm_type'] = 'kernelvm' self._temp['log'] = '/tmp/serial_{id}.log'.format(id=qemu_id) self._temp['ini'] = '/etc/vm_init_{id}.conf'.format(id=qemu_id) else: raise RuntimeError('QEMU: Unknown VM image option!') # Computed parameters for QEMU command line. self._params = OptionString(prefix='-') self.add_params() def add_params(self): """Set QEMU command line parameters.""" self.add_default_params() if self._opt.get('vm_type', '') == 'nestedvm': self.add_nestedvm_params() elif self._opt.get('vm_type', '') == 'kernelvm': self.add_kernelvm_params() else: raise RuntimeError('QEMU: Unsupported VM type!') def add_default_params(self): """Set default QEMU command line parameters.""" self._params.add('daemonize') self._params.add('nodefaults') self._params.add_with_value('name', 'vnf{qemu},debug-threads=on'.format( qemu=self._opt.get('qemu_id'))) self._params.add('no-user-config') self._params.add_with_value('monitor', 'none') self._params.add_with_value('display', 'none') self._params.add_with_value('vga', 'none') self._params.add('enable-kvm') self._params.add_with_value('pidfile', self._temp.get('pidfile')) self._params.add_with_value('cpu', 'host') self._params.add_with_value( 'machine', 'pc,accel=kvm,usb=off,mem-merge=off') self._params.add_with_value( 'smp', '{smp},sockets=1,cores={smp},threads=1'.format( smp=self._opt.get('smp'))) self._params.add_with_value( 'object', 'memory-backend-file,id=mem,size={mem}M,' 'mem-path=/dev/hugepages,share=on'.format(mem=self._opt.get('mem'))) self._params.add_with_value( 'm', '{mem}M'.format(mem=self._opt.get('mem'))) self._params.add_with_value('numa', 'node,memdev=mem') self._params.add_with_value('balloon', 'none') def add_nestedvm_params(self): """Set NestedVM QEMU parameters.""" self._params.add_with_value( 'net', 'nic,macaddr=52:54:00:00:{qemu:02x}:ff'.format( qemu=self._opt.get('qemu_id'))) self._params.add_with_value( 'net', 'user,hostfwd=tcp::{info[port]}-:22'.format( info=self._vm_info)) # TODO: Remove try except after fully migrated to Bionic or # qemu_set_node is removed. try: locking = ',file.locking=off'\ if self.qemu_version(version='2.10') else '' except AttributeError: locking = '' self._params.add_with_value( 'drive', 'file={img},format=raw,cache=none,if=virtio{locking}'. format(img=self._opt.get('img'), locking=locking)) self._params.add_with_value( 'qmp', 'unix:{qmp},server,nowait'.format(qmp=self._temp.get('qmp'))) self._params.add_with_value( 'chardev', 'socket,host=127.0.0.1,port={info[serial]},' 'id=gnc0,server,nowait'.format(info=self._vm_info)) self._params.add_with_value('device', 'isa-serial,chardev=gnc0') self._params.add_with_value( 'chardev', 'socket,path={qga},server,nowait,id=qga0'.format( qga=self._temp.get('qga'))) self._params.add_with_value('device', 'isa-serial,chardev=qga0') def add_kernelvm_params(self): """Set KernelVM QEMU parameters.""" self._params.add_with_value( 'chardev', 'file,id=char0,path={log}'.format( log=self._temp.get('log'))) self._params.add_with_value('device', 'isa-serial,chardev=char0') self._params.add_with_value( 'fsdev', 'local,id=root9p,path=/,security_model=none') self._params.add_with_value( 'device', 'virtio-9p-pci,fsdev=root9p,mount_tag=/dev/root') self._params.add_with_value( 'kernel', '$(readlink -m {img}* | tail -1)'.format( img=self._opt.get('img'))) self._params.add_with_value( 'append', '"ro rootfstype=9p rootflags=trans=virtio console=ttyS0' ' tsc=reliable hugepages=256 init={init}"'.format( init=self._temp.get('ini'))) def create_kernelvm_config_vpp(self, **kwargs): """Create QEMU VPP config files. :param kwargs: Key-value pairs to replace content of VPP configuration file. :type kwargs: dict """ startup = ('/etc/vpp/vm_startup_{id}.conf'. format(id=self._opt.get('qemu_id'))) running = ('/etc/vpp/vm_running_{id}.exec'. format(id=self._opt.get('qemu_id'))) self._temp['startup'] = startup self._temp['running'] = running self._opt['vnf_bin'] = ('/usr/bin/vpp -c {startup}'. format(startup=startup)) # Create VPP startup configuration. vpp_config = VppConfigGenerator() vpp_config.set_node(self._node) vpp_config.add_unix_nodaemon() vpp_config.add_unix_cli_listen() vpp_config.add_unix_exec(running) vpp_config.add_cpu_main_core('0') vpp_config.add_cpu_corelist_workers('1-{smp}'. format(smp=self._opt.get('smp')-1)) vpp_config.add_dpdk_dev('0000:00:06.0', '0000:00:07.0') vpp_config.add_dpdk_dev_default_rxq(kwargs['queues']) vpp_config.add_dpdk_log_level('debug') if not kwargs['jumbo_frames']: vpp_config.add_dpdk_no_multi_seg() vpp_config.add_dpdk_no_tx_checksum_offload() vpp_config.add_plugin('disable', 'default') vpp_config.add_plugin('enable', 'dpdk_plugin.so') vpp_config.write_config(startup) # Create VPP running configuration. template = '{res}/{tpl}.exec'.format(res=Constants.RESOURCES_TPL_VM, tpl=self._opt.get('vnf')) exec_cmd_no_error(self._node, 'rm -f {running}'.format(running=running), sudo=True) with open(template, 'r') as src_file: src = Template(src_file.read()) exec_cmd_no_error( self._node, "echo '{out}' | sudo tee {running}".format( out=src.safe_substitute(**kwargs), running=running)) def create_kernelvm_config_testpmd_io(self, **kwargs): """Create QEMU testpmd-io command line. :param kwargs: Key-value pairs to construct command line parameters. :type kwargs: dict """ testpmd_path = ('{path}/{arch}-native-linuxapp-gcc/app'. format(path=Constants.QEMU_VM_DPDK, arch=Topology.get_node_arch(self._node))) testpmd_cmd = DpdkUtil.get_testpmd_cmdline( eal_corelist='0-{smp}'.format(smp=self._opt.get('smp') - 1), eal_driver=False, eal_in_memory=True, pmd_num_mbufs=16384, pmd_rxq=kwargs['queues'], pmd_txq=kwargs['queues'], pmd_tx_offloads=False, pmd_disable_hw_vlan=False, pmd_max_pkt_len=9200 if kwargs['jumbo_frames'] else None, pmd_nb_cores=str(self._opt.get('smp') - 1)) self._opt['vnf_bin'] = ('{testpmd_path}/{testpmd_cmd}'. format(testpmd_path=testpmd_path, testpmd_cmd=testpmd_cmd)) def create_kernelvm_config_testpmd_mac(self, **kwargs): """Create QEMU testpmd-mac command line. :param kwargs: Key-value pairs to construct command line parameters. :type kwargs: dict """ testpmd_path = ('{path}/{arch}-native-linuxapp-gcc/app'. format(path=Constants.QEMU_VM_DPDK, arch=Topology.get_node_arch(self._node))) testpmd_cmd = DpdkUtil.get_testpmd_cmdline( eal_corelist='0-{smp}'.format(smp=self._opt.get('smp') - 1), eal_driver=False, eal_in_memory=True, pmd_num_mbufs=16384, pmd_fwd_mode='mac', pmd_eth_peer_0='0,{mac}'.format(mac=kwargs['vif1_mac']), pmd_eth_peer_1='1,{mac}'.format(mac=kwargs['vif2_mac']), pmd_rxq=kwargs['queues'], pmd_txq=kwargs['queues'], pmd_tx_offloads=False, pmd_disable_hw_vlan=False, pmd_max_pkt_len=9200 if kwargs['jumbo_frames'] else None, pmd_nb_cores=str(self._opt.get('smp') - 1)) self._opt['vnf_bin'] = ('{testpmd_path}/{testpmd_cmd}'. format(testpmd_path=testpmd_path, testpmd_cmd=testpmd_cmd)) def create_kernelvm_init(self, **kwargs): """Create QEMU init script. :param kwargs: Key-value pairs to replace content of init startup file. :type kwargs: dict """ template = '{res}/init.sh'.format(res=Constants.RESOURCES_TPL_VM) init = self._temp.get('ini') exec_cmd_no_error( self._node, 'rm -f {init}'.format(init=init), sudo=True) with open(template, 'r') as src_file: src = Template(src_file.read()) exec_cmd_no_error( self._node, "echo '{out}' | sudo tee {init}".format( out=src.safe_substitute(**kwargs), init=init)) exec_cmd_no_error( self._node, "chmod +x {init}".format(init=init), sudo=True) def configure_kernelvm_vnf(self, **kwargs): """Create KernelVM VNF configurations. :param kwargs: Key-value pairs for templating configs. :type kwargs: dict """ if 'vpp' in self._opt.get('vnf'): self.create_kernelvm_config_vpp(**kwargs) elif 'testpmd_io' in self._opt.get('vnf'): self.create_kernelvm_config_testpmd_io(**kwargs) elif 'testpmd_mac' in self._opt.get('vnf'): self.create_kernelvm_config_testpmd_mac(**kwargs) else: raise RuntimeError('QEMU: Unsupported VNF!') self.create_kernelvm_init(vnf_bin=self._opt['vnf_bin']) def get_qemu_pids(self): """Get QEMU CPU pids. :returns: List of QEMU CPU pids. :rtype: list of str """ command = ("grep -rwl 'CPU' /proc/$(sudo cat {pidfile})/task/*/comm ". format(pidfile=self._temp.get('pidfile'))) command += (r"| xargs dirname | sed -e 's/\/.*\///g' | uniq") stdout, _ = exec_cmd_no_error(self._node, command) return stdout.splitlines() def qemu_set_affinity(self, *host_cpus): """Set qemu affinity by getting thread PIDs via QMP and taskset to list of CPU cores. Function tries to execute 3 times to avoid race condition in getting thread PIDs. :param host_cpus: List of CPU cores. :type host_cpus: list """ for _ in range(3): try: qemu_cpus = self.get_qemu_pids() if len(qemu_cpus) != len(host_cpus): sleep(1) continue for qemu_cpu, host_cpu in zip(qemu_cpus, host_cpus): command = ('taskset -pc {host_cpu} {thread}'. format(host_cpu=host_cpu, thread=qemu_cpu)) message = ('QEMU: Set affinity failed on {host}!'. format(host=self._node['host'])) exec_cmd_no_error(self._node, command, sudo=True, message=message) break except (RuntimeError, ValueError): self.qemu_kill_all() raise else: self.qemu_kill_all() raise RuntimeError('Failed to set Qemu threads affinity!') def qemu_set_scheduler_policy(self): """Set scheduler policy to SCHED_RR with priority 1 for all Qemu CPU processes. :raises RuntimeError: Set scheduler policy failed. """ try: qemu_cpus = self.get_qemu_pids() for qemu_cpu in qemu_cpus: command = ('chrt -r -p 1 {thread}'. format(thread=qemu_cpu)) message = ('QEMU: Set SCHED_RR failed on {host}'. format(host=self._node['host'])) exec_cmd_no_error(self._node, command, sudo=True, message=message) except (RuntimeError, ValueError): self.qemu_kill_all() raise def qemu_add_vhost_user_if(self, socket, server=True, jumbo_frames=False, queue_size=None, queues=1): """Add Vhost-user interface. :param socket: Path of the unix socket. :param server: If True the socket shall be a listening socket. :param jumbo_frames: Set True if jumbo frames are used in the test. :param queue_size: Vring queue size. :param queues: Number of queues. :type socket: str :type server: bool :type jumbo_frames: bool :type queue_size: int :type queues: int """ self._vhost_id += 1 self._params.add_with_value( 'chardev', 'socket,id=char{vhost},path={socket}{server}'.format( vhost=self._vhost_id, socket=socket, server=',server' if server is True else '')) self._params.add_with_value( 'netdev', 'vhost-user,id=vhost{vhost},chardev=char{vhost},' 'queues={queues}'.format(vhost=self._vhost_id, queues=queues)) mac = ('52:54:00:00:{qemu:02x}:{vhost:02x}'. format(qemu=self._opt.get('qemu_id'), vhost=self._vhost_id)) queue_size = ('rx_queue_size={queue_size},tx_queue_size={queue_size}'. format(queue_size=queue_size)) if queue_size else '' mbuf = 'on,host_mtu=9200' self._params.add_with_value( 'device', 'virtio-net-pci,netdev=vhost{vhost},mac={mac},bus=pci.0,' 'addr={addr}.0,mq=on,vectors={vectors},csum=off,gso=off,' 'guest_tso4=off,guest_tso6=off,guest_ecn=off,mrg_rxbuf={mbuf},' '{queue_size}'.format( addr=self._vhost_id+5, vhost=self._vhost_id, mac=mac, mbuf=mbuf if jumbo_frames else 'off', queue_size=queue_size, vectors=(2 * queues + 2))) # Add interface MAC and socket to the node dict. if_data = {'mac_address': mac, 'socket': socket} if_name = 'vhost{vhost}'.format(vhost=self._vhost_id) self._vm_info['interfaces'][if_name] = if_data # Add socket to temporary file list. self._temp[if_name] = socket def _qemu_qmp_exec(self, cmd): """Execute QMP command. QMP is JSON based protocol which allows to control QEMU instance. :param cmd: QMP command to execute. :type cmd: str :returns: Command output in python representation of JSON format. The { "return": {} } response is QMP's success response. An error response will contain the "error" keyword instead of "return". """ # To enter command mode, the qmp_capabilities command must be issued. command = ('echo "{{ \\"execute\\": \\"qmp_capabilities\\" }}' '{{ \\"execute\\": \\"{cmd}\\" }}" | ' 'sudo -S socat - UNIX-CONNECT:{qmp}'. format(cmd=cmd, qmp=self._temp.get('qmp'))) message = ('QMP execute "{cmd}" failed on {host}'. format(cmd=cmd, host=self._node['host'])) stdout, _ = exec_cmd_no_error( self._node, command, sudo=False, message=message) # Skip capabilities negotiation messages. out_list = stdout.splitlines() if len(out_list) < 3: raise RuntimeError( 'Invalid QMP output on {host}'.format(host=self._node['host'])) return json.loads(out_list[2]) def _qemu_qga_flush(self): """Flush the QGA parser state.""" command = ('(printf "\xFF"; sleep 1) | ' 'sudo -S socat - UNIX-CONNECT:{qga}'. format(qga=self._temp.get('qga'))) message = ('QGA flush failed on {host}'.format(host=self._node['host'])) stdout, _ = exec_cmd_no_error( self._node, command, sudo=False, message=message) return json.loads(stdout.split('\n', 1)[0]) if stdout else dict() def _qemu_qga_exec(self, cmd): """Execute QGA command. QGA provide access to a system-level agent via standard QMP commands. :param cmd: QGA command to execute. :type cmd: str """ command = ('(echo "{{ \\"execute\\": \\"{cmd}\\" }}"; sleep 1) | ' 'sudo -S socat - UNIX-CONNECT:{qga}'. format(cmd=cmd, qga=self._temp.get('qga'))) message = ('QGA execute "{cmd}" failed on {host}'. format(cmd=cmd, host=self._node['host'])) stdout, _ = exec_cmd_no_error( self._node, command, sudo=False, message=message) return json.loads(stdout.split('\n', 1)[0]) if stdout else dict() def _wait_until_vm_boot(self): """Wait until QEMU with NestedVM is booted.""" if self._opt.get('vm_type') == 'nestedvm': self._wait_until_nestedvm_boot() self._update_vm_interfaces() elif self._opt.get('vm_type') == 'kernelvm': self._wait_until_kernelvm_boot() else: raise RuntimeError('QEMU: Unsupported VM type!') def _wait_until_nestedvm_boot(self, retries=12): """Wait until QEMU with NestedVM is booted. First try to flush qga until there is output. Then ping QEMU guest agent each 5s until VM booted or timeout. :param retries: Number of retries with 5s between trials. :type retries: int """ for _ in range(retries): out = None try: out = self._qemu_qga_flush() except ValueError: logger.trace('QGA qga flush unexpected output {out}'. format(out=out)) # Empty output - VM not booted yet if not out: sleep(5) else: break else: raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'. format(host=self._node['host'])) for _ in range(retries): out = None try: out = self._qemu_qga_exec('guest-ping') except ValueError: logger.trace('QGA guest-ping unexpected output {out}'. format(out=out)) # Empty output - VM not booted yet. if not out: sleep(5) # Non-error return - VM booted. elif out.get('return') is not None: break # Skip error and wait. elif out.get('error') is not None: sleep(5) else: # If there is an unexpected output from QGA guest-info, try # again until timeout. logger.trace('QGA guest-ping unexpected output {out}'. format(out=out)) else: raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'. format(host=self._node['host'])) def _wait_until_kernelvm_boot(self, retries=60): """Wait until QEMU KernelVM is booted. :param retries: Number of retries. :type retries: int """ vpp_ver = VPPUtil.vpp_show_version(self._node) for _ in range(retries): command = ('tail -1 {log}'.format(log=self._temp.get('log'))) stdout = None try: stdout, _ = exec_cmd_no_error(self._node, command, sudo=True) sleep(1) except RuntimeError: pass if vpp_ver in stdout or 'Press enter to exit' in stdout: break if 'reboot: Power down' in stdout: raise RuntimeError('QEMU: NF failed to run on {host}!'. format(host=self._node['host'])) else: raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'. format(host=self._node['host'])) def _update_vm_interfaces(self): """Update interface names in VM node dict.""" # Send guest-network-get-interfaces command via QGA, output example: # {"return": [{"name": "eth0", "hardware-address": "52:54:00:00:04:01"}, # {"name": "eth1", "hardware-address": "52:54:00:00:04:02"}]}. out = self._qemu_qga_exec('guest-network-get-interfaces') interfaces = out.get('return') mac_name = {} if not interfaces: raise RuntimeError('Get VM interface list failed on {host}'. format(host=self._node['host'])) # Create MAC-name dict. for interface in interfaces: if 'hardware-address' not in interface: continue mac_name[interface['hardware-address']] = interface['name'] # Match interface by MAC and save interface name. for interface in self._vm_info['interfaces'].values(): mac = interface.get('mac_address') if_name = mac_name.get(mac) if if_name is None: logger.trace( 'Interface name for MAC {mac} not found'.format(mac=mac)) else: interface['name'] = if_name def qemu_start(self): """Start QEMU and wait until VM boot. :returns: VM node info. :rtype: dict """ cmd_opts = OptionString() cmd_opts.add('{bin_path}/qemu-system-{arch}'.format( bin_path=Constants.QEMU_BIN_PATH, arch=Topology.get_node_arch(self._node))) cmd_opts.extend(self._params) message = ('QEMU: Start failed on {host}!'. format(host=self._node['host'])) try: DUTSetup.check_huge_page( self._node, '/dev/hugepages', self._opt.get('mem')) exec_cmd_no_error( self._node, cmd_opts, timeout=300, sudo=True, message=message) self._wait_until_vm_boot() except RuntimeError: self.qemu_kill_all() raise return self._vm_info def qemu_kill(self): """Kill qemu process.""" exec_cmd(self._node, 'chmod +r {pidfile}'. format(pidfile=self._temp.get('pidfile')), sudo=True) exec_cmd(self._node, 'kill -SIGKILL $(cat {pidfile})'. format(pidfile=self._temp.get('pidfile')), sudo=True) for value in self._temp.values(): exec_cmd(self._node, 'cat {value}'.format(value=value), sudo=True) exec_cmd(self._node, 'rm -f {value}'.format(value=value), sudo=True) def qemu_kill_all(self): """Kill all qemu processes on DUT node if specified.""" exec_cmd(self._node, 'pkill -SIGKILL qemu', sudo=True) for value in self._temp.values(): exec_cmd(self._node, 'cat {value}'.format(value=value), sudo=True) exec_cmd(self._node, 'rm -f {value}'.format(value=value), sudo=True) def qemu_version(self, version=None): """Return Qemu version or compare if version is higher than parameter. :param version: Version to compare. :type version: str :returns: Qemu version or Boolean if version is higher than parameter. :rtype: str or bool """ command = ('{bin_path}/qemu-system-{arch} --version'.format( bin_path=Constants.QEMU_BIN_PATH, arch=Topology.get_node_arch(self._node))) try: stdout, _ = exec_cmd_no_error(self._node, command, sudo=True) ver = match(r'QEMU emulator version ([\d.]*)', stdout).group(1) return StrictVersion(ver) > StrictVersion(version) \ if version else ver except RuntimeError: self.qemu_kill_all() raise
def get_testpmd_pmd_options(**kwargs): """Create PMD parameters options for testpmd (without --). :param kwargs: List of testpmd parameters. :type kwargs: dict :returns: PMD parameters. :rtype: OptionString """ options = OptionString(prefix=u"--") # Set the forwarding mode: io, mac, mac_retry, mac_swap, flowgen, # rxonly, txonly, csum, icmpecho, ieee1588 options.add_equals_from_dict(u"forward-mode", u"pmd_fwd_mode", kwargs, u"io") # Set the number of packets per burst to N. options.add_equals(u"burst", 64) # Set the number of descriptors in the TX rings to N. options.add_equals_from_dict(u"txd", u"pmd_txd", kwargs, 1024) # Set the number of descriptors in the RX rings to N. options.add_equals_from_dict(u"rxd", u"pmd_rxd", kwargs, 1024) # Set the number of queues in the TX to N. options.add_equals_from_dict(u"txq", u"pmd_txq", kwargs, 1) # Set the number of queues in the RX to N. options.add_equals_from_dict(u"rxq", u"pmd_rxq", kwargs, 1) # Set the hexadecimal bitmask of offloads. options.add_equals_from_dict(u"tx-offloads", u"pmd_tx_offloads", kwargs, u"0x0") # Enables numa aware allocation of mbufs. options.add_if_from_dict(u"numa", u"pmd_numa", kwargs, True) # Run by default. options.add_if_from_dict(u"auto-start", u"pmd_auto_start", kwargs, True) # Set the number of mbufs to be allocated in the mbuf pools. options.add_equals_from_dict(u"total-num-mbufs", u"pmd_num_mbufs", kwargs) # Set the number of forwarding ports. options.add_equals_from_dict(u"nb-ports", u"pmd_nb_ports", kwargs) # Set the hexadecimal bitmask of the ports used by the packet # forwarding test. options.add_equals_from_dict(u"portmask", u"pmd_portmask", kwargs) # Disable link status check. options.add_if_from_dict(u"disable-link-check", u"pmd_disable_link_check", kwargs, True) # Set the MAC address XX:XX:XX:XX:XX:XX of the peer port N options.add_equals_from_dict(u"eth-peer", u"pmd_eth_peer_0", kwargs) options.add_equals_from_dict(u"eth-peer", u"pmd_eth_peer_1", kwargs) # Set the max packet length. options.add_equals_from_dict(u"max-pkt-len", u"pmd_max_pkt_len", kwargs) # Set the max packet length. options.add_equals_from_dict(u"mbuf-size", u"pmd_mbuf_size", kwargs) # Set the number of forwarding cores based on coremask. options.add_equals_from_dict(u"nb-cores", u"pmd_nb_cores", kwargs) return options
def get_eal_options(**kwargs): """Create EAL parameters options (including -v). :param kwargs: Dict of testpmd parameters. :type kwargs: dict :returns: EAL parameters. :rtype: OptionString """ options = OptionString(prefix='-') options.add('v') # Set the hexadecimal bitmask of the cores to run on. options.add_with_value_from_dict('l', 'eal_corelist', kwargs) # Set master core. options.add_with_value('-master-lcore', '0') # Load an external driver. Multiple -d options are allowed. options.add_with_value_if_from_dict('d', '/usr/lib/librte_pmd_virtio.so', 'eal_driver', kwargs, True) options.add_if_from_dict('-in-memory', 'eal_in_memory', kwargs, False) return options