+ :raises RuntimeError: If it is not possible to create loopback on the
+ node.
+ """
+ cmd = u"create_loopback_instance"
+ args = dict(
+ mac_address=L2Util.mac_to_bin(mac) if mac else 0,
+ is_specified=False,
+ user_instance=0,
+ )
+ err_msg = f"Failed to create loopback interface on host {node[u'host']}"
+ with PapiSocketExecutor(node) as papi_exec:
+ sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
+
+ if_key = Topology.add_new_port(node, u"loopback")
+ Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
+ ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
+ Topology.update_interface_name(node, if_key, ifc_name)
+ if mac:
+ mac = InterfaceUtil.vpp_get_interface_mac(node, ifc_name)
+ Topology.update_interface_mac_address(node, if_key, mac)
+
+ return sw_if_index
+
+ @staticmethod
+ def vpp_create_bond_interface(
+ node, mode, load_balance=None, mac=None, gso=False):
+ """Create bond interface on VPP node.
+
+ :param node: DUT node from topology.
+ :param mode: Link bonding mode.
+ :param load_balance: Load balance (optional, valid for xor and lacp
+ modes, otherwise ignored). Default: None.
+ :param mac: MAC address to assign to the bond interface (optional).
+ Default: None.
+ :param gso: Enable GSO support (optional). Default: False.
+ :type node: dict
+ :type mode: str
+ :type load_balance: str
+ :type mac: str
+ :type gso: bool
+ :returns: Interface key (name) in topology.
+ :rtype: str
+ :raises RuntimeError: If it is not possible to create bond interface on
+ the node.
+ """
+ cmd = u"bond_create2"
+ args = dict(
+ id=int(Constants.BITWISE_NON_ZERO),
+ use_custom_mac=bool(mac is not None),
+ mac_address=L2Util.mac_to_bin(mac) if mac else None,
+ mode=getattr(
+ LinkBondMode,
+ f"BOND_API_MODE_{mode.replace(u'-', u'_').upper()}"
+ ).value,
+ lb=0 if load_balance is None else getattr(
+ LinkBondLoadBalanceAlgo,
+ f"BOND_API_LB_ALGO_{load_balance.upper()}"
+ ).value,
+ numa_only=False,
+ enable_gso=gso
+ )
+ err_msg = f"Failed to create bond interface on host {node[u'host']}"
+ with PapiSocketExecutor(node) as papi_exec:
+ sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
+
+ InterfaceUtil.add_eth_interface(
+ node, sw_if_index=sw_if_index, ifc_pfx=u"eth_bond"
+ )
+ if_key = Topology.get_interface_by_sw_index(node, sw_if_index)
+
+ return if_key
+
+ @staticmethod
+ def add_eth_interface(
+ node, ifc_name=None, sw_if_index=None, ifc_pfx=None,
+ host_if_key=None):
+ """Add ethernet interface to current topology.
+
+ :param node: DUT node from topology.
+ :param ifc_name: Name of the interface.
+ :param sw_if_index: SW interface index.
+ :param ifc_pfx: Interface key prefix.
+ :param host_if_key: Host interface key from topology file.
+ :type node: dict
+ :type ifc_name: str
+ :type sw_if_index: int
+ :type ifc_pfx: str
+ :type host_if_key: str
+ """
+ if_key = Topology.add_new_port(node, ifc_pfx)
+
+ if ifc_name and sw_if_index is None:
+ sw_if_index = InterfaceUtil.vpp_get_interface_sw_index(
+ node, ifc_name)
+ Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
+ if sw_if_index and ifc_name is None:
+ ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
+ Topology.update_interface_name(node, if_key, ifc_name)
+ ifc_mac = InterfaceUtil.vpp_get_interface_mac(node, sw_if_index)
+ Topology.update_interface_mac_address(node, if_key, ifc_mac)
+ if host_if_key is not None:
+ Topology.set_interface_numa_node(
+ node, if_key, Topology.get_interface_numa_node(
+ node, host_if_key
+ )
+ )
+ Topology.update_interface_pci_address(
+ node, if_key, Topology.get_interface_pci_addr(node, host_if_key)
+ )
+
+ @staticmethod
+ def vpp_create_avf_interface(
+ node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0):
+ """Create AVF interface on VPP node.
+
+ :param node: DUT node from topology.
+ :param if_key: Interface key from topology file of interface
+ to be bound to i40evf driver.
+ :param num_rx_queues: Number of RX queues.
+ :param rxq_size: Size of RXQ (0 = Default API; 512 = Default VPP).
+ :param txq_size: Size of TXQ (0 = Default API; 512 = Default VPP).
+ :type node: dict
+ :type if_key: str
+ :type num_rx_queues: int
+ :type rxq_size: int
+ :type txq_size: int
+ :returns: AVF interface key (name) in topology.
+ :rtype: str
+ :raises RuntimeError: If it is not possible to create AVF interface on
+ the node.
+ """
+ PapiSocketExecutor.run_cli_cmd(
+ node, u"set logging class avf level debug"
+ )
+
+ cmd = u"avf_create"
+ vf_pci_addr = Topology.get_interface_pci_addr(node, if_key)
+ args = dict(
+ pci_addr=InterfaceUtil.pci_to_int(vf_pci_addr),
+ enable_elog=0,
+ rxq_num=int(num_rx_queues) if num_rx_queues else 0,
+ rxq_size=rxq_size,
+ txq_size=txq_size
+ )
+ err_msg = f"Failed to create AVF interface on host {node[u'host']}"
+
+ # FIXME: Remove once the fw/driver is upgraded.
+ for _ in range(10):
+ with PapiSocketExecutor(node) as papi_exec:
+ try:
+ sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(
+ err_msg
+ )
+ break
+ except AssertionError:
+ logger.error(err_msg)
+ else:
+ raise AssertionError(err_msg)
+
+ InterfaceUtil.add_eth_interface(
+ node, sw_if_index=sw_if_index, ifc_pfx=u"eth_avf",
+ host_if_key=if_key
+ )
+
+ return Topology.get_interface_by_sw_index(node, sw_if_index)
+
+ @staticmethod
+ def vpp_create_af_xdp_interface(
+ node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0,
+ mode=u"auto"):
+ """Create AF_XDP interface on VPP node.
+
+ :param node: DUT node from topology.
+ :param if_key: Physical interface key from topology file of interface
+ to be bound to compatible driver.
+ :param num_rx_queues: Number of RX queues. (Optional, Default: none)
+ :param rxq_size: Size of RXQ (0 = Default API; 512 = Default VPP).
+ :param txq_size: Size of TXQ (0 = Default API; 512 = Default VPP).
+ :param mode: AF_XDP interface mode. (Optional, Default: auto).
+ :type node: dict
+ :type if_key: str
+ :type num_rx_queues: int
+ :type rxq_size: int
+ :type txq_size: int
+ :type mode: str
+ :returns: Interface key (name) in topology file.
+ :rtype: str
+ :raises RuntimeError: If it is not possible to create AF_XDP interface
+ on the node.
+ """
+ PapiSocketExecutor.run_cli_cmd(
+ node, u"set logging class af_xdp level debug"
+ )
+
+ cmd = u"af_xdp_create"
+ pci_addr = Topology.get_interface_pci_addr(node, if_key)
+ args = dict(
+ name=InterfaceUtil.pci_to_eth(node, pci_addr),
+ host_if=InterfaceUtil.pci_to_eth(node, pci_addr),
+ rxq_num=int(num_rx_queues) if num_rx_queues else 0,
+ rxq_size=rxq_size,
+ txq_size=txq_size,
+ mode=getattr(AfXdpMode, f"AF_XDP_API_MODE_{mode.upper()}").value
+ )
+ err_msg = f"Failed to create AF_XDP interface on host {node[u'host']}"
+ with PapiSocketExecutor(node) as papi_exec:
+ sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
+
+ InterfaceUtil.vpp_set_interface_mac(
+ node, sw_if_index, Topology.get_interface_mac(node, if_key)
+ )
+ InterfaceUtil.add_eth_interface(
+ node, sw_if_index=sw_if_index, ifc_pfx=u"eth_af_xdp",
+ host_if_key=if_key
+ )
+
+ return Topology.get_interface_by_sw_index(node, sw_if_index)
+
+ @staticmethod
+ def vpp_create_rdma_interface(
+ node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0,
+ mode=u"auto"):
+ """Create RDMA interface on VPP node.
+
+ :param node: DUT node from topology.
+ :param if_key: Physical interface key from topology file of interface
+ to be bound to rdma-core driver.
+ :param num_rx_queues: Number of RX queues.
+ :param rxq_size: Size of RXQ (0 = Default API; 512 = Default VPP).
+ :param txq_size: Size of TXQ (0 = Default API; 512 = Default VPP).
+ :param mode: RDMA interface mode - auto/ibv/dv.
+ :type node: dict
+ :type if_key: str
+ :type num_rx_queues: int
+ :type rxq_size: int
+ :type txq_size: int
+ :type mode: str
+ :returns: Interface key (name) in topology file.
+ :rtype: str
+ :raises RuntimeError: If it is not possible to create RDMA interface on
+ the node.
+ """
+ PapiSocketExecutor.run_cli_cmd(
+ node, u"set logging class rdma level debug"
+ )
+
+ cmd = u"rdma_create_v3"
+ pci_addr = Topology.get_interface_pci_addr(node, if_key)
+ args = dict(
+ name=InterfaceUtil.pci_to_eth(node, pci_addr),
+ host_if=InterfaceUtil.pci_to_eth(node, pci_addr),
+ rxq_num=int(num_rx_queues) if num_rx_queues else 0,
+ rxq_size=rxq_size,
+ txq_size=txq_size,
+ mode=getattr(RdmaMode, f"RDMA_API_MODE_{mode.upper()}").value,
+ # Note: Set True for non-jumbo packets.
+ no_multi_seg=False,
+ max_pktlen=0,
+ # TODO: Apply desired RSS flags.
+ )
+ err_msg = f"Failed to create RDMA interface on host {node[u'host']}"
+ with PapiSocketExecutor(node) as papi_exec:
+ sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
+
+ InterfaceUtil.vpp_set_interface_mac(
+ node, sw_if_index, Topology.get_interface_mac(node, if_key)
+ )
+ InterfaceUtil.add_eth_interface(
+ node, sw_if_index=sw_if_index, ifc_pfx=u"eth_rdma",
+ host_if_key=if_key
+ )
+
+ return Topology.get_interface_by_sw_index(node, sw_if_index)
+
+ @staticmethod
+ def vpp_add_bond_member(node, interface, bond_if):
+ """Add member interface to bond interface on VPP node.
+
+ :param node: DUT node from topology.
+ :param interface: Physical interface key from topology file.
+ :param bond_if: Load balance
+ :type node: dict
+ :type interface: str
+ :type bond_if: str
+ :raises RuntimeError: If it is not possible to add member to bond
+ interface on the node.
+ """
+ cmd = u"bond_add_member"
+ args = dict(
+ sw_if_index=Topology.get_interface_sw_index(node, interface),
+ bond_sw_if_index=Topology.get_interface_sw_index(node, bond_if),
+ is_passive=False,
+ is_long_timeout=False
+ )
+ err_msg = f"Failed to add member {interface} to bond interface " \
+ f"{bond_if} on host {node[u'host']}"
+ with PapiSocketExecutor(node) as papi_exec:
+ papi_exec.add(cmd, **args).get_reply(err_msg)
+
+ @staticmethod
+ def vpp_show_bond_data_on_node(node, verbose=False):
+ """Show (detailed) bond information on VPP node.
+
+ :param node: DUT node from topology.
+ :param verbose: If detailed information is required or not.
+ :type node: dict
+ :type verbose: bool
+ """
+ cmd = u"sw_bond_interface_dump"
+ err_msg = f"Failed to get bond interface dump on host {node[u'host']}"
+
+ data = f"Bond data on node {node[u'host']}:\n"
+ with PapiSocketExecutor(node) as papi_exec:
+ details = papi_exec.add(cmd).get_details(err_msg)
+
+ for bond in details:
+ data += f"{bond[u'interface_name']}\n"
+ data += u" mode: {m}\n".format(
+ m=bond[u"mode"].name.replace(u"BOND_API_MODE_", u"").lower()
+ )
+ data += u" load balance: {lb}\n".format(
+ lb=bond[u"lb"].name.replace(u"BOND_API_LB_ALGO_", u"").lower()
+ )
+ data += f" number of active members: {bond[u'active_members']}\n"
+ if verbose:
+ member_data = InterfaceUtil.vpp_bond_member_dump(
+ node, Topology.get_interface_by_sw_index(
+ node, bond[u"sw_if_index"]
+ )
+ )
+ for member in member_data:
+ if not member[u"is_passive"]:
+ data += f" {member[u'interface_name']}\n"
+ data += f" number of members: {bond[u'members']}\n"
+ if verbose:
+ for member in member_data:
+ data += f" {member[u'interface_name']}\n"
+ data += f" interface id: {bond[u'id']}\n"
+ data += f" sw_if_index: {bond[u'sw_if_index']}\n"
+ logger.info(data)
+
+ @staticmethod
+ def vpp_bond_member_dump(node, interface):
+ """Get bond interface slave(s) data on VPP node.
+
+ :param node: DUT node from topology.
+ :param interface: Physical interface key from topology file.
+ :type node: dict
+ :type interface: str
+ :returns: Bond slave interface data.
+ :rtype: dict
+ """
+ cmd = u"sw_member_interface_dump"
+ args = dict(
+ sw_if_index=Topology.get_interface_sw_index(node, interface)
+ )
+ err_msg = f"Failed to get slave dump on host {node[u'host']}"
+
+ with PapiSocketExecutor(node) as papi_exec:
+ details = papi_exec.add(cmd, **args).get_details(err_msg)
+
+ logger.debug(f"Member data:\n{details}")
+ return details
+
+ @staticmethod
+ def vpp_show_bond_data_on_all_nodes(nodes, verbose=False):
+ """Show (detailed) bond information on all VPP nodes in DICT__nodes.
+
+ :param nodes: Nodes in the topology.
+ :param verbose: If detailed information is required or not.
+ :type nodes: dict
+ :type verbose: bool
+ """
+ for node_data in nodes.values():
+ if node_data[u"type"] == NodeType.DUT:
+ InterfaceUtil.vpp_show_bond_data_on_node(node_data, verbose)
+
+ @staticmethod
+ def vpp_enable_input_acl_interface(
+ node, interface, ip_version, table_index):
+ """Enable input acl on interface.
+
+ :param node: VPP node to setup interface for input acl.
+ :param interface: Interface to setup input acl.
+ :param ip_version: Version of IP protocol.
+ :param table_index: Classify table index.
+ :type node: dict
+ :type interface: str or int
+ :type ip_version: str
+ :type table_index: int
+ """
+ cmd = u"input_acl_set_interface"
+ args = dict(
+ sw_if_index=InterfaceUtil.get_interface_index(node, interface),
+ ip4_table_index=table_index if ip_version == u"ip4"
+ else Constants.BITWISE_NON_ZERO,
+ ip6_table_index=table_index if ip_version == u"ip6"
+ else Constants.BITWISE_NON_ZERO,
+ l2_table_index=table_index if ip_version == u"l2"
+ else Constants.BITWISE_NON_ZERO,
+ is_add=1)
+ err_msg = f"Failed to enable input acl on interface {interface}"
+ with PapiSocketExecutor(node) as papi_exec:
+ papi_exec.add(cmd, **args).get_reply(err_msg)
+
+ @staticmethod
+ def get_interface_classify_table(node, interface):
+ """Get name of classify table for the given interface.
+
+ TODO: Move to Classify.py.
+
+ :param node: VPP node to get data from.
+ :param interface: Name or sw_if_index of a specific interface.
+ :type node: dict
+ :type interface: str or int
+ :returns: Classify table name.
+ :rtype: str
+ """
+ if isinstance(interface, str):
+ sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
+ else:
+ sw_if_index = interface
+
+ cmd = u"classify_table_by_interface"
+ args = dict(
+ sw_if_index=sw_if_index
+ )
+ err_msg = f"Failed to get classify table name by interface {interface}"
+ with PapiSocketExecutor(node) as papi_exec:
+ reply = papi_exec.add(cmd, **args).get_reply(err_msg)
+
+ return reply
+
+ @staticmethod
+ def get_sw_if_index(node, interface_name):
+ """Get sw_if_index for the given interface from actual interface dump.
+
+ FIXME: Delete and redirect callers to vpp_get_interface_sw_index.
+
+ :param node: VPP node to get interface data from.
+ :param interface_name: Name of the specific interface.
+ :type node: dict
+ :type interface_name: str
+ :returns: sw_if_index of the given interface.
+ :rtype: str
+ """
+ interface_data = InterfaceUtil.vpp_get_interface_data(
+ node, interface=interface_name
+ )
+ return interface_data.get(u"sw_if_index")
+
+ @staticmethod
+ def vxlan_gpe_dump(node, interface_name=None):
+ """Get VxLAN GPE data for the given interface.
+
+ :param node: VPP node to get interface data from.
+ :param interface_name: Name of the specific interface. If None,
+ information about all VxLAN GPE interfaces is returned.
+ :type node: dict
+ :type interface_name: str
+ :returns: Dictionary containing data for the given VxLAN GPE interface
+ or if interface=None, the list of dictionaries with all VxLAN GPE
+ interfaces.
+ :rtype: dict or list
+ """
+ def process_vxlan_gpe_dump(vxlan_dump):
+ """Process vxlan_gpe dump.
+
+ :param vxlan_dump: Vxlan_gpe nterface dump.
+ :type vxlan_dump: dict
+ :returns: Processed vxlan_gpe interface dump.
+ :rtype: dict
+ """
+ if vxlan_dump[u"is_ipv6"]:
+ vxlan_dump[u"local"] = ip_address(vxlan_dump[u"local"])
+ vxlan_dump[u"remote"] = ip_address(vxlan_dump[u"remote"])
+ else:
+ vxlan_dump[u"local"] = ip_address(vxlan_dump[u"local"][0:4])
+ vxlan_dump[u"remote"] = ip_address(vxlan_dump[u"remote"][0:4])
+ return vxlan_dump
+
+ if interface_name is not None:
+ sw_if_index = InterfaceUtil.get_interface_index(
+ node, interface_name
+ )
+ else:
+ sw_if_index = int(Constants.BITWISE_NON_ZERO)
+
+ cmd = u"vxlan_gpe_tunnel_dump"
+ args = dict(
+ sw_if_index=sw_if_index
+ )
+ err_msg = f"Failed to get VXLAN-GPE dump on host {node[u'host']}"
+ with PapiSocketExecutor(node) as papi_exec:
+ details = papi_exec.add(cmd, **args).get_details(err_msg)
+
+ data = list() if interface_name is None else dict()
+ for dump in details:
+ if interface_name is None:
+ data.append(process_vxlan_gpe_dump(dump))
+ elif dump[u"sw_if_index"] == sw_if_index:
+ data = process_vxlan_gpe_dump(dump)
+ break
+
+ logger.debug(f"VXLAN-GPE data:\n{data}")
+ return data
+
+ @staticmethod
+ def assign_interface_to_fib_table(node, interface, table_id, ipv6=False):
+ """Assign VPP interface to specific VRF/FIB table.
+
+ :param node: VPP node where the FIB and interface are located.
+ :param interface: Interface to be assigned to FIB.
+ :param table_id: VRF table ID.
+ :param ipv6: Assign to IPv6 table. Default False.
+ :type node: dict
+ :type interface: str or int
+ :type table_id: int
+ :type ipv6: bool
+ """
+ cmd = u"sw_interface_set_table"
+ args = dict(
+ sw_if_index=InterfaceUtil.get_interface_index(node, interface),
+ is_ipv6=ipv6,
+ vrf_id=int(table_id)
+ )
+ err_msg = f"Failed to assign interface {interface} to FIB table"
+ with PapiSocketExecutor(node) as papi_exec:
+ papi_exec.add(cmd, **args).get_reply(err_msg)
+
+ @staticmethod
+ def set_linux_interface_mac(
+ node, interface, mac, namespace=None, vf_id=None):
+ """Set MAC address for interface in linux.
+
+ :param node: Node where to execute command.
+ :param interface: Interface in namespace.
+ :param mac: MAC to be assigned to interface.
+ :param namespace: Execute command in namespace. Optional
+ :param vf_id: Virtual Function id. Optional
+ :type node: dict
+ :type interface: str
+ :type mac: str
+ :type namespace: str
+ :type vf_id: int
+ """
+ mac_str = f"vf {vf_id} mac {mac}" if vf_id is not None \
+ else f"address {mac}"
+ ns_str = f"ip netns exec {namespace}" if namespace else u""
+
+ cmd = f"{ns_str} ip link set {interface} {mac_str}"
+ exec_cmd_no_error(node, cmd, sudo=True)
+
+ @staticmethod
+ def set_linux_interface_promisc(
+ node, interface, namespace=None, vf_id=None, state=u"on"):
+ """Set promisc state for interface in linux.
+
+ :param node: Node where to execute command.
+ :param interface: Interface in namespace.
+ :param namespace: Exec command in namespace. (Optional, Default: None)
+ :param vf_id: Virtual Function id. (Optional, Default: None)
+ :param state: State of feature. (Optional, Default: on)
+ :type node: dict
+ :type interface: str
+ :type namespace: str
+ :type vf_id: int
+ :type state: str
+ """
+ promisc_str = f"vf {vf_id} promisc {state}" if vf_id is not None \
+ else f"promisc {state}"
+ ns_str = f"ip netns exec {namespace}" if namespace else u""
+
+ cmd = f"{ns_str} ip link set dev {interface} {promisc_str}"
+ exec_cmd_no_error(node, cmd, sudo=True)
+
+ @staticmethod
+ def set_linux_interface_trust_on(
+ node, interface, namespace=None, vf_id=None):
+ """Set trust on (promisc) for interface in linux.
+
+ :param node: Node where to execute command.
+ :param interface: Interface in namespace.
+ :param namespace: Execute command in namespace. Optional
+ :param vf_id: Virtual Function id. Optional
+ :type node: dict
+ :type interface: str
+ :type namespace: str
+ :type vf_id: int