+ cmd = u"l2_patch_add_del"
+ args1 = dict(
+ rx_sw_if_index=sw_iface1,
+ tx_sw_if_index=sw_iface2,
+ is_add=True
+ )
+ args2 = dict(
+ rx_sw_if_index=sw_iface2,
+ tx_sw_if_index=sw_iface1,
+ is_add=True
+ )
+ err_msg = f"Failed to add L2 patch between two interfaces " \
+ f"on host {node['host']}"
+
+ with PapiSocketExecutor(node) as papi_exec:
+ papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg)
+
+ @staticmethod
+ def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
+ """Bridge two interfaces on linux node.
+
+ :param node: Node to add bridge on.
+ :param br_name: Bridge name.
+ :param if_1: First interface to be added to the bridge.
+ :param if_2: Second interface to be added to the bridge.
+ :param set_up: Change bridge interface state to up after create bridge.
+ Optional. Default: True.
+ :type node: dict
+ :type br_name: str
+ :type if_1: str
+ :type if_2: str
+ :type set_up: bool
+ """
+ cmd = f"brctl addbr {br_name}"
+ exec_cmd_no_error(node, cmd, sudo=True)
+
+ cmd = f"brctl addif {br_name} {if_1}"
+ exec_cmd_no_error(node, cmd, sudo=True)
+
+ cmd = f"brctl addif {br_name} {if_2}"
+ exec_cmd_no_error(node, cmd, sudo=True)
+
+ if set_up:
+ cmd = f"ip link set dev {br_name} up"
+ exec_cmd_no_error(node, cmd, sudo=True)
+
+ @staticmethod
+ def linux_del_bridge(node, br_name, set_down=True):
+ """Delete bridge from linux node.
+
+ ..note:: The network interface corresponding to the bridge must be
+ down before it can be deleted!
+
+ :param node: Node to delete bridge from.
+ :param br_name: Bridge name.
+ :param set_down: Change bridge interface state to down before delbr
+ command. Optional. Default: True.
+ :type node: dict
+ :type br_name: str
+ :type set_down: bool
+ """
+ if set_down:
+ cmd = f"ip link set dev {br_name} down"
+ exec_cmd_no_error(node, cmd, sudo=True)
+
+ cmd = f"brctl delbr {br_name}"
+ exec_cmd_no_error(node, cmd, sudo=True)
+
+ @staticmethod
+ def vpp_get_bridge_domain_data(node, bd_id=Constants.BITWISE_NON_ZERO):
+ """Get all bridge domain data from a VPP node. If a domain ID number is
+ provided, return only data for the matching bridge domain.
+
+ :param node: VPP node to get bridge domain data from.
+ :param bd_id: Numeric ID of a specific bridge domain.
+ :type node: dict
+ :type bd_id: int
+ :returns: List of dictionaries containing data for each bridge domain,
+ or a single dictionary for the specified bridge domain.
+ :rtype: list or dict
+ """
+ cmd = u"bridge_domain_dump"
+ args = dict(
+ bd_id=int(bd_id)
+ )
+ err_msg = f"Failed to get L2FIB dump on host {node[u'host']}"
+
+ with PapiSocketExecutor(node) as papi_exec:
+ details = papi_exec.add(cmd, **args).get_details(err_msg)
+
+ retval = details if bd_id == Constants.BITWISE_NON_ZERO else None
+
+ for bridge_domain in details:
+ if bridge_domain[u"bd_id"] == bd_id:
+ retval = bridge_domain
+
+ return retval
+
+ @staticmethod
+ def l2_vlan_tag_rewrite(
+ node, interface, tag_rewrite_method, push_dot1q=True, tag1_id=None,
+ tag2_id=None):
+ """Rewrite tags in ethernet frame.
+
+ :param node: Node to rewrite tags.
+ :param interface: Interface on which rewrite tags.
+ :param tag_rewrite_method: Method of tag rewrite.
+ :param push_dot1q: Optional parameter to disable to push dot1q tag
+ instead of dot1ad.
+ :param tag1_id: Optional tag1 ID for VLAN.
+ :param tag2_id: Optional tag2 ID for VLAN.
+ :type node: dict
+ :type interface: str or int
+ :type tag_rewrite_method: str
+ :type push_dot1q: bool
+ :type tag1_id: int
+ :type tag2_id: int
+ """
+ tag1_id = int(tag1_id) if tag1_id else 0
+ tag2_id = int(tag2_id) if tag2_id else 0
+
+ vtr_oper = getattr(
+ L2VtrOp, f"L2_VTR_{tag_rewrite_method.replace(u'-', u'_').upper()}"
+ )
+
+ if isinstance(interface, str):
+ iface_key = Topology.get_interface_by_name(node, interface)
+ sw_if_index = Topology.get_interface_sw_index(node, iface_key)
+ else:
+ sw_if_index = interface
+
+ cmd = u"l2_interface_vlan_tag_rewrite"
+ args = dict(
+ sw_if_index=sw_if_index,
+ vtr_op=int(vtr_oper),
+ push_dot1q=int(push_dot1q),
+ tag1=tag1_id,
+ tag2=tag2_id
+ )
+ err_msg = f"Failed to set VLAN TAG rewrite on host {node['host']}"
+
+ with PapiSocketExecutor(node) as papi_exec:
+ papi_exec.add(cmd, **args).get_reply(err_msg)
+
+ @staticmethod
+ def get_l2_fib_table(node, bd_id):
+ """Retrieves the L2 FIB table.
+
+ :param node: VPP node.
+ :param bd_id: Index of the bridge domain.
+ :type node: dict
+ :type bd_id: int
+ :returns: L2 FIB table.
+ :rtype: list
+ """
+ cmd = u"l2_fib_table_dump"
+ args = dict(
+ bd_id=int(bd_id)
+ )
+ err_msg = f"Failed to get L2FIB dump on host {node['host']}"
+
+ with PapiSocketExecutor(node) as papi_exec:
+ details = papi_exec.add(cmd, **args).get_details(err_msg)
+
+ for fib_item in details:
+ fib_item[u"mac"] = L2Util.bin_to_mac(fib_item[u"mac"])
+
+ return details
+
+ @staticmethod
+ def get_l2_fib_entry_by_mac(node, bd_index, mac):
+ """Retrieves the L2 FIB entry specified by MAC address using PAPI.
+
+ :param node: VPP node.
+ :param bd_index: Index of the bridge domain.
+ :param mac: MAC address used as the key in L2 FIB data structure.
+ :type node: dict
+ :type bd_index: int
+ :type mac: str
+ :returns: L2 FIB entry
+ :rtype: dict
+ """
+ bd_data = L2Util.vpp_get_bridge_domain_data(node)
+ bd_id = bd_data[bd_index-1][u"bd_id"]
+
+ table = L2Util.get_l2_fib_table(node, bd_id)
+
+ for entry in table:
+ if entry[u"mac"] == mac:
+ return entry
+ return {}