perf: add TCP Nginx+LDPRELOAD suites
[csit.git] / resources / libraries / python / InterfaceUtil.py
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:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Interface util library."""
15
16 from time import sleep
17 from enum import IntEnum
18
19 from ipaddress import ip_address
20 from robot.api import logger
21
22 from resources.libraries.python.Constants import Constants
23 from resources.libraries.python.CpuUtils import CpuUtils
24 from resources.libraries.python.DUTSetup import DUTSetup
25 from resources.libraries.python.IPAddress import IPAddress
26 from resources.libraries.python.L2Util import L2Util
27 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
28 from resources.libraries.python.parsers.JsonParser import JsonParser
29 from resources.libraries.python.ssh import SSH, exec_cmd, exec_cmd_no_error
30 from resources.libraries.python.topology import NodeType, Topology
31 from resources.libraries.python.VPPUtil import VPPUtil
32
33
34 class InterfaceStatusFlags(IntEnum):
35     """Interface status flags."""
36     IF_STATUS_API_FLAG_ADMIN_UP = 1
37     IF_STATUS_API_FLAG_LINK_UP = 2
38
39
40 class MtuProto(IntEnum):
41     """MTU protocol."""
42     MTU_PROTO_API_L3 = 0
43     MTU_PROTO_API_IP4 = 1
44     MTU_PROTO_API_IP6 = 2
45     MTU_PROTO_API_MPLS = 3
46     MTU_PROTO_API_N = 4
47
48
49 class LinkDuplex(IntEnum):
50     """Link duplex"""
51     LINK_DUPLEX_API_UNKNOWN = 0
52     LINK_DUPLEX_API_HALF = 1
53     LINK_DUPLEX_API_FULL = 2
54
55
56 class SubInterfaceFlags(IntEnum):
57     """Sub-interface flags."""
58     SUB_IF_API_FLAG_NO_TAGS = 1
59     SUB_IF_API_FLAG_ONE_TAG = 2
60     SUB_IF_API_FLAG_TWO_TAGS = 4
61     SUB_IF_API_FLAG_DOT1AD = 8
62     SUB_IF_API_FLAG_EXACT_MATCH = 16
63     SUB_IF_API_FLAG_DEFAULT = 32
64     SUB_IF_API_FLAG_OUTER_VLAN_ID_ANY = 64
65     SUB_IF_API_FLAG_INNER_VLAN_ID_ANY = 128
66     SUB_IF_API_FLAG_DOT1AH = 256
67
68
69 class RxMode(IntEnum):
70     """RX mode"""
71     RX_MODE_API_UNKNOWN = 0
72     RX_MODE_API_POLLING = 1
73     RX_MODE_API_INTERRUPT = 2
74     RX_MODE_API_ADAPTIVE = 3
75     RX_MODE_API_DEFAULT = 4
76
77
78 class IfType(IntEnum):
79     """Interface type"""
80     # A hw interface
81     IF_API_TYPE_HARDWARE = 0
82     # A sub-interface
83     IF_API_TYPE_SUB = 1
84     IF_API_TYPE_P2P = 2
85     IF_API_TYPE_PIPE = 3
86
87
88 class LinkBondLoadBalanceAlgo(IntEnum):
89     """Link bonding load balance algorithm."""
90     BOND_API_LB_ALGO_L2 = 0
91     BOND_API_LB_ALGO_L34 = 1
92     BOND_API_LB_ALGO_L23 = 2
93     BOND_API_LB_ALGO_RR = 3
94     BOND_API_LB_ALGO_BC = 4
95     BOND_API_LB_ALGO_AB = 5
96
97
98 class LinkBondMode(IntEnum):
99     """Link bonding mode."""
100     BOND_API_MODE_ROUND_ROBIN = 1
101     BOND_API_MODE_ACTIVE_BACKUP = 2
102     BOND_API_MODE_XOR = 3
103     BOND_API_MODE_BROADCAST = 4
104     BOND_API_MODE_LACP = 5
105
106
107 class RdmaMode(IntEnum):
108     """RDMA interface mode."""
109     RDMA_API_MODE_AUTO = 0
110     RDMA_API_MODE_IBV = 1
111     RDMA_API_MODE_DV = 2
112
113
114 class InterfaceUtil:
115     """General utilities for managing interfaces"""
116
117     @staticmethod
118     def pci_to_int(pci_str):
119         """Convert PCI address from string format (0000:18:0a.0) to
120         integer representation (169345024).
121
122         :param pci_str: PCI address in string representation.
123         :type pci_str: str
124         :returns: Integer representation of PCI address.
125         :rtype: int
126         """
127         pci = list(pci_str.split(u":")[0:2])
128         pci.extend(pci_str.split(u":")[2].split(u"."))
129
130         return (int(pci[0], 16) | int(pci[1], 16) << 16 |
131                 int(pci[2], 16) << 24 | int(pci[3], 16) << 29)
132
133     @staticmethod
134     def pci_to_eth(node, pci_str):
135         """Convert PCI address on DUT to Linux ethernet name.
136
137         :param node: DUT node
138         :param pci_str: PCI address.
139         :type node: dict
140         :type pci_str: str
141         :returns: Ethernet name.
142         :rtype: str
143         """
144         cmd = f"basename /sys/bus/pci/devices/{pci_str}/net/*"
145         try:
146             stdout, _ = exec_cmd_no_error(node, cmd)
147         except RuntimeError:
148             raise RuntimeError(f"Cannot convert {pci_str} to ethernet name!")
149
150         return stdout.strip()
151
152     @staticmethod
153     def get_interface_index(node, interface):
154         """Get interface sw_if_index from topology file.
155
156         :param node: Node where the interface is.
157         :param interface: Numeric index or name string of a specific interface.
158         :type node: dict
159         :type interface: str or int
160         :returns: SW interface index.
161         :rtype: int
162         """
163         try:
164             sw_if_index = int(interface)
165         except ValueError:
166             sw_if_index = Topology.get_interface_sw_index(node, interface)
167             if sw_if_index is None:
168                 sw_if_index = \
169                     Topology.get_interface_sw_index_by_name(node, interface)
170         except TypeError as err:
171             raise TypeError(f"Wrong interface format {interface}") from err
172
173         return sw_if_index
174
175     @staticmethod
176     def set_interface_state(node, interface, state, if_type=u"key"):
177         """Set interface state on a node.
178
179         Function can be used for DUTs as well as for TGs.
180
181         :param node: Node where the interface is.
182         :param interface: Interface key or sw_if_index or name.
183         :param state: One of 'up' or 'down'.
184         :param if_type: Interface type
185         :type node: dict
186         :type interface: str or int
187         :type state: str
188         :type if_type: str
189         :returns: Nothing.
190         :raises ValueError: If the interface type is unknown.
191         :raises ValueError: If the state of interface is unexpected.
192         :raises ValueError: If the node has an unknown node type.
193         """
194         if if_type == u"key":
195             if isinstance(interface, str):
196                 sw_if_index = Topology.get_interface_sw_index(node, interface)
197                 iface_name = Topology.get_interface_name(node, interface)
198             else:
199                 sw_if_index = interface
200         elif if_type == u"name":
201             iface_key = Topology.get_interface_by_name(node, interface)
202             if iface_key is not None:
203                 sw_if_index = Topology.get_interface_sw_index(node, iface_key)
204             iface_name = interface
205         else:
206             raise ValueError(f"Unknown if_type: {if_type}")
207
208         if node[u"type"] == NodeType.DUT:
209             if state == u"up":
210                 flags = InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value
211             elif state == u"down":
212                 flags = 0
213             else:
214                 raise ValueError(f"Unexpected interface state: {state}")
215             cmd = u"sw_interface_set_flags"
216             err_msg = f"Failed to set interface state on host {node[u'host']}"
217             args = dict(
218                 sw_if_index=int(sw_if_index),
219                 flags=flags
220             )
221             with PapiSocketExecutor(node) as papi_exec:
222                 papi_exec.add(cmd, **args).get_reply(err_msg)
223         elif node[u"type"] == NodeType.TG or node[u"type"] == NodeType.VM:
224             cmd = f"ip link set {iface_name} {state}"
225             exec_cmd_no_error(node, cmd, sudo=True)
226         else:
227             raise ValueError(
228                 f"Node {node[u'host']} has unknown NodeType: {node[u'type']}"
229             )
230
231     @staticmethod
232     def set_interface_mtu(node, pf_pcis, mtu=9200):
233         """Set Ethernet MTU for specified interfaces.
234
235         :param node: Topology node.
236         :param pf_pcis: List of node's interfaces PCI addresses.
237         :param mtu: MTU to set. Default: 9200.
238         :type nodes: dict
239         :type pf_pcis: list
240         :type mtu: int
241         :raises RuntimeError: If failed to set MTU on interface.
242         """
243         for pf_pci in pf_pcis:
244             pf_eth = InterfaceUtil.pci_to_eth(node, pf_pci)
245             cmd = f"ip link set {pf_eth} mtu {mtu}"
246             exec_cmd_no_error(node, cmd, sudo=True)
247
248     @staticmethod
249     def set_interface_flow_control(node, pf_pcis, rx=u"off", tx=u"off"):
250         """Set Ethernet flow control for specified interfaces.
251
252         :param node: Topology node.
253         :param pf_pcis: List of node's interfaces PCI addresses.
254         :param rx: RX flow. Default: off.
255         :param tx: TX flow. Default: off.
256         :type nodes: dict
257         :type pf_pcis: list
258         :type rx: str
259         :type tx: str
260         """
261         for pf_pci in pf_pcis:
262             pf_eth = InterfaceUtil.pci_to_eth(node, pf_pci)
263             cmd = f"ethtool -A {pf_eth} rx off tx off"
264             ret_code, _, _ = exec_cmd(node, cmd, sudo=True)
265             if int(ret_code) not in (0, 78):
266                 raise RuntimeError("Failed to set MTU on {pf_eth}!")
267
268
269     @staticmethod
270     def set_pci_parameter(node, pf_pcis, key, value):
271         """Set PCI parameter for specified interfaces.
272
273         :param node: Topology node.
274         :param pf_pcis: List of node's interfaces PCI addresses.
275         :param key: Key to set.
276         :param value: Value to set.
277         :type nodes: dict
278         :type pf_pcis: list
279         :type key: str
280         :type value: str
281         """
282         for pf_pci in pf_pcis:
283             cmd = f"setpci -s {pf_pci} {key}={value}"
284             exec_cmd_no_error(node, cmd, sudo=True)
285
286     @staticmethod
287     def vpp_set_interface_mtu(node, interface, mtu=9200):
288         """Set Ethernet MTU on interface.
289
290         :param node: VPP node.
291         :param interface: Interface to setup MTU. Default: 9200.
292         :param mtu: Ethernet MTU size in Bytes.
293         :type node: dict
294         :type interface: str or int
295         :type mtu: int
296         """
297         if isinstance(interface, str):
298             sw_if_index = Topology.get_interface_sw_index(node, interface)
299         else:
300             sw_if_index = interface
301
302         cmd = u"hw_interface_set_mtu"
303         err_msg = f"Failed to set interface MTU on host {node[u'host']}"
304         args = dict(
305             sw_if_index=sw_if_index,
306             mtu=int(mtu)
307         )
308         try:
309             with PapiSocketExecutor(node) as papi_exec:
310                 papi_exec.add(cmd, **args).get_reply(err_msg)
311         except AssertionError as err:
312             # TODO: Make failure tolerance optional.
313             logger.debug(f"Setting MTU failed. Expected?\n{err}")
314
315     @staticmethod
316     def vpp_set_interfaces_mtu_on_node(node, mtu=9200):
317         """Set Ethernet MTU on all interfaces.
318
319         :param node: VPP node.
320         :param mtu: Ethernet MTU size in Bytes. Default: 9200.
321         :type node: dict
322         :type mtu: int
323         """
324         for interface in node[u"interfaces"]:
325             InterfaceUtil.vpp_set_interface_mtu(node, interface, mtu)
326
327     @staticmethod
328     def vpp_set_interfaces_mtu_on_all_duts(nodes, mtu=9200):
329         """Set Ethernet MTU on all interfaces on all DUTs.
330
331         :param nodes: VPP nodes.
332         :param mtu: Ethernet MTU size in Bytes. Default: 9200.
333         :type nodes: dict
334         :type mtu: int
335         """
336         for node in nodes.values():
337             if node[u"type"] == NodeType.DUT:
338                 InterfaceUtil.vpp_set_interfaces_mtu_on_node(node, mtu)
339
340     @staticmethod
341     def vpp_node_interfaces_ready_wait(node, retries=15):
342         """Wait until all interfaces with admin-up are in link-up state.
343
344         :param node: Node to wait on.
345         :param retries: Number of retries to check interface status (optional,
346             default 15).
347         :type node: dict
348         :type retries: int
349         :returns: Nothing.
350         :raises RuntimeError: If any interface is not in link-up state after
351             defined number of retries.
352         """
353         for _ in range(0, retries):
354             not_ready = list()
355             out = InterfaceUtil.vpp_get_interface_data(node)
356             for interface in out:
357                 if interface.get(u"flags") == 1:
358                     not_ready.append(interface.get(u"interface_name"))
359             if not_ready:
360                 logger.debug(
361                     f"Interfaces still not in link-up state:\n{not_ready}"
362                 )
363                 sleep(1)
364             else:
365                 break
366         else:
367             err = f"Timeout, interfaces not up:\n{not_ready}" \
368                 if u"not_ready" in locals() else u"No check executed!"
369             raise RuntimeError(err)
370
371     @staticmethod
372     def all_vpp_interfaces_ready_wait(nodes, retries=15):
373         """Wait until all interfaces with admin-up are in link-up state for all
374         nodes in the topology.
375
376         :param nodes: Nodes in the topology.
377         :param retries: Number of retries to check interface status (optional,
378             default 15).
379         :type nodes: dict
380         :type retries: int
381         :returns: Nothing.
382         """
383         for node in nodes.values():
384             if node[u"type"] == NodeType.DUT:
385                 InterfaceUtil.vpp_node_interfaces_ready_wait(node, retries)
386
387     @staticmethod
388     def vpp_get_interface_data(node, interface=None):
389         """Get all interface data from a VPP node. If a name or
390         sw_interface_index is provided, return only data for the matching
391         interface(s).
392
393         :param node: VPP node to get interface data from.
394         :param interface: Numeric index or name string of a specific interface.
395         :type node: dict
396         :type interface: int or str
397         :returns: List of dictionaries containing data for each interface, or a
398             single dictionary for the specified interface.
399         :rtype: list or dict
400         :raises TypeError: if the data type of interface is neither basestring
401             nor int.
402         """
403         def process_if_dump(if_dump):
404             """Process interface dump.
405
406             :param if_dump: Interface dump.
407             :type if_dump: dict
408             :returns: Processed interface dump.
409             :rtype: dict
410             """
411             if_dump[u"l2_address"] = str(if_dump[u"l2_address"])
412             if_dump[u"b_dmac"] = str(if_dump[u"b_dmac"])
413             if_dump[u"b_smac"] = str(if_dump[u"b_smac"])
414             if_dump[u"flags"] = if_dump[u"flags"].value
415             if_dump[u"type"] = if_dump[u"type"].value
416             if_dump[u"link_duplex"] = if_dump[u"link_duplex"].value
417             if_dump[u"sub_if_flags"] = if_dump[u"sub_if_flags"].value \
418                 if hasattr(if_dump[u"sub_if_flags"], u"value") \
419                 else int(if_dump[u"sub_if_flags"])
420
421             return if_dump
422
423         if interface is not None:
424             if isinstance(interface, str):
425                 param = u"interface_name"
426             elif isinstance(interface, int):
427                 param = u"sw_if_index"
428             else:
429                 raise TypeError(f"Wrong interface format {interface}")
430         else:
431             param = u""
432
433         cmd = u"sw_interface_dump"
434         args = dict(
435             name_filter_valid=False,
436             name_filter=u""
437         )
438         err_msg = f"Failed to get interface dump on host {node[u'host']}"
439
440         with PapiSocketExecutor(node) as papi_exec:
441             details = papi_exec.add(cmd, **args).get_details(err_msg)
442         logger.debug(f"Received data:\n{details!r}")
443
444         data = list() if interface is None else dict()
445         for dump in details:
446             if interface is None:
447                 data.append(process_if_dump(dump))
448             elif str(dump.get(param)).rstrip(u"\x00") == str(interface):
449                 data = process_if_dump(dump)
450                 break
451
452         logger.debug(f"Interface data:\n{data}")
453         return data
454
455     @staticmethod
456     def vpp_get_interface_name(node, sw_if_index):
457         """Get interface name for the given SW interface index from actual
458         interface dump.
459
460         :param node: VPP node to get interface data from.
461         :param sw_if_index: SW interface index of the specific interface.
462         :type node: dict
463         :type sw_if_index: int
464         :returns: Name of the given interface.
465         :rtype: str
466         """
467         if_data = InterfaceUtil.vpp_get_interface_data(node, sw_if_index)
468         if if_data[u"sup_sw_if_index"] != if_data[u"sw_if_index"]:
469             if_data = InterfaceUtil.vpp_get_interface_data(
470                 node, if_data[u"sup_sw_if_index"]
471             )
472
473         return if_data.get(u"interface_name")
474
475     @staticmethod
476     def vpp_get_interface_sw_index(node, interface_name):
477         """Get interface name for the given SW interface index from actual
478         interface dump.
479
480         :param node: VPP node to get interface data from.
481         :param interface_name: Interface name.
482         :type node: dict
483         :type interface_name: str
484         :returns: Name of the given interface.
485         :rtype: str
486         """
487         if_data = InterfaceUtil.vpp_get_interface_data(node, interface_name)
488
489         return if_data.get(u"sw_if_index")
490
491     @staticmethod
492     def vpp_get_interface_mac(node, interface):
493         """Get MAC address for the given interface from actual interface dump.
494
495         :param node: VPP node to get interface data from.
496         :param interface: Numeric index or name string of a specific interface.
497         :type node: dict
498         :type interface: int or str
499         :returns: MAC address.
500         :rtype: str
501         """
502         if_data = InterfaceUtil.vpp_get_interface_data(node, interface)
503         if if_data[u"sup_sw_if_index"] != if_data[u"sw_if_index"]:
504             if_data = InterfaceUtil.vpp_get_interface_data(
505                 node, if_data[u"sup_sw_if_index"])
506
507         return if_data.get(u"l2_address")
508
509     @staticmethod
510     def vpp_set_interface_mac(node, interface, mac):
511         """Set MAC address for the given interface.
512
513         :param node: VPP node to set interface MAC.
514         :param interface: Numeric index or name string of a specific interface.
515         :param mac: Required MAC address.
516         :type node: dict
517         :type interface: int or str
518         :type mac: str
519         """
520         cmd = u"sw_interface_set_mac_address"
521         args = dict(
522             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
523             mac_address=L2Util.mac_to_bin(mac)
524         )
525         err_msg = f"Failed to set MAC address of interface {interface}" \
526             f"on host {node[u'host']}"
527         with PapiSocketExecutor(node) as papi_exec:
528             papi_exec.add(cmd, **args).get_reply(err_msg)
529
530     @staticmethod
531     def tg_set_interface_driver(node, pci_addr, driver):
532         """Set interface driver on the TG node.
533
534         :param node: Node to set interface driver on (must be TG node).
535         :param pci_addr: PCI address of the interface.
536         :param driver: Driver name.
537         :type node: dict
538         :type pci_addr: str
539         :type driver: str
540         :raises RuntimeError: If unbinding from the current driver fails.
541         :raises RuntimeError: If binding to the new driver fails.
542         """
543         old_driver = InterfaceUtil.tg_get_interface_driver(node, pci_addr)
544         if old_driver == driver:
545             return
546
547         ssh = SSH()
548         ssh.connect(node)
549
550         # Unbind from current driver
551         if old_driver is not None:
552             cmd = f"sh -c \"echo {pci_addr} > " \
553                 f"/sys/bus/pci/drivers/{old_driver}/unbind\""
554             ret_code, _, _ = ssh.exec_command_sudo(cmd)
555             if int(ret_code) != 0:
556                 raise RuntimeError(f"'{cmd}' failed on '{node[u'host']}'")
557
558         # Bind to the new driver
559         cmd = f"sh -c \"echo {pci_addr} > /sys/bus/pci/drivers/{driver}/bind\""
560         ret_code, _, _ = ssh.exec_command_sudo(cmd)
561         if int(ret_code) != 0:
562             raise RuntimeError(f"'{cmd}' failed on '{node[u'host']}'")
563
564     @staticmethod
565     def tg_get_interface_driver(node, pci_addr):
566         """Get interface driver from the TG node.
567
568         :param node: Node to get interface driver on (must be TG node).
569         :param pci_addr: PCI address of the interface.
570         :type node: dict
571         :type pci_addr: str
572         :returns: Interface driver or None if not found.
573         :rtype: str
574         :raises RuntimeError: If PCI rescan or lspci command execution failed.
575         """
576         return DUTSetup.get_pci_dev_driver(node, pci_addr)
577
578     @staticmethod
579     def tg_set_interfaces_default_driver(node):
580         """Set interfaces default driver specified in topology yaml file.
581
582         :param node: Node to setup interfaces driver on (must be TG node).
583         :type node: dict
584         """
585         for interface in node[u"interfaces"].values():
586             InterfaceUtil.tg_set_interface_driver(
587                 node, interface[u"pci_address"], interface[u"driver"]
588             )
589
590     @staticmethod
591     def update_vpp_interface_data_on_node(node):
592         """Update vpp generated interface data for a given node in DICT__nodes.
593
594         Updates interface names, software if index numbers and any other details
595         generated specifically by vpp that are unknown before testcase run.
596         It does this by dumping interface list from all devices using python
597         api, and pairing known information from topology (mac address) to state
598         from VPP.
599
600         :param node: Node selected from DICT__nodes.
601         :type node: dict
602         """
603         interface_list = InterfaceUtil.vpp_get_interface_data(node)
604         interface_dict = dict()
605         for ifc in interface_list:
606             interface_dict[ifc[u"l2_address"]] = ifc
607
608         for if_name, if_data in node[u"interfaces"].items():
609             ifc_dict = interface_dict.get(if_data[u"mac_address"])
610             if ifc_dict is not None:
611                 if_data[u"name"] = ifc_dict[u"interface_name"]
612                 if_data[u"vpp_sw_index"] = ifc_dict[u"sw_if_index"]
613                 if_data[u"mtu"] = ifc_dict[u"mtu"][0]
614                 logger.trace(
615                     f"Interface {if_name} found by MAC "
616                     f"{if_data[u'mac_address']}"
617                 )
618             else:
619                 logger.trace(
620                     f"Interface {if_name} not found by MAC "
621                     f"{if_data[u'mac_address']}"
622                 )
623                 if_data[u"vpp_sw_index"] = None
624
625     @staticmethod
626     def update_nic_interface_names(node):
627         """Update interface names based on nic type and PCI address.
628
629         This method updates interface names in the same format as VPP does.
630
631         :param node: Node dictionary.
632         :type node: dict
633         """
634         for ifc in node[u"interfaces"].values():
635             if_pci = ifc[u"pci_address"].replace(u".", u":").split(u":")
636             loc = f"{int(if_pci[1], 16):x}/{int(if_pci[2], 16):x}/" \
637                 f"{int(if_pci[3], 16):x}"
638             if ifc[u"model"] == u"Intel-XL710":
639                 ifc[u"name"] = f"FortyGigabitEthernet{loc}"
640             elif ifc[u"model"] == u"Intel-X710":
641                 ifc[u"name"] = f"TenGigabitEthernet{loc}"
642             elif ifc[u"model"] == u"Intel-X520-DA2":
643                 ifc[u"name"] = f"TenGigabitEthernet{loc}"
644             elif ifc[u"model"] == u"Cisco-VIC-1385":
645                 ifc[u"name"] = f"FortyGigabitEthernet{loc}"
646             elif ifc[u"model"] == u"Cisco-VIC-1227":
647                 ifc[u"name"] = f"TenGigabitEthernet{loc}"
648             else:
649                 ifc[u"name"] = f"UnknownEthernet{loc}"
650
651     @staticmethod
652     def update_nic_interface_names_on_all_duts(nodes):
653         """Update interface names based on nic type and PCI address on all DUTs.
654
655         This method updates interface names in the same format as VPP does.
656
657         :param nodes: Topology nodes.
658         :type nodes: dict
659         """
660         for node in nodes.values():
661             if node[u"type"] == NodeType.DUT:
662                 InterfaceUtil.update_nic_interface_names(node)
663
664     @staticmethod
665     def update_tg_interface_data_on_node(node):
666         """Update interface name for TG/linux node in DICT__nodes.
667
668         .. note::
669             # for dev in `ls /sys/class/net/`;
670             > do echo "\"`cat /sys/class/net/$dev/address`\": \"$dev\""; done
671             "52:54:00:9f:82:63": "eth0"
672             "52:54:00:77:ae:a9": "eth1"
673             "52:54:00:e1:8a:0f": "eth2"
674             "00:00:00:00:00:00": "lo"
675
676         :param node: Node selected from DICT__nodes.
677         :type node: dict
678         :raises RuntimeError: If getting of interface name and MAC fails.
679         """
680         # First setup interface driver specified in yaml file
681         InterfaceUtil.tg_set_interfaces_default_driver(node)
682
683         # Get interface names
684         ssh = SSH()
685         ssh.connect(node)
686
687         cmd = u'for dev in `ls /sys/class/net/`; do echo "\\"`cat ' \
688               u'/sys/class/net/$dev/address`\\": \\"$dev\\""; done;'
689
690         ret_code, stdout, _ = ssh.exec_command(cmd)
691         if int(ret_code) != 0:
692             raise RuntimeError(u"Get interface name and MAC failed")
693         tmp = u"{" + stdout.rstrip().replace(u"\n", u",") + u"}"
694
695         interfaces = JsonParser().parse_data(tmp)
696         for interface in node[u"interfaces"].values():
697             name = interfaces.get(interface[u"mac_address"])
698             if name is None:
699                 continue
700             interface[u"name"] = name
701
702     @staticmethod
703     def iface_update_numa_node(node):
704         """For all interfaces from topology file update numa node based on
705            information from the node.
706
707         :param node: Node from topology.
708         :type node: dict
709         :returns: Nothing.
710         :raises ValueError: If numa node ia less than 0.
711         :raises RuntimeError: If update of numa node failed.
712         """
713         ssh = SSH()
714         for if_key in Topology.get_node_interfaces(node):
715             if_pci = Topology.get_interface_pci_addr(node, if_key)
716             ssh.connect(node)
717             cmd = f"cat /sys/bus/pci/devices/{if_pci}/numa_node"
718             for _ in range(3):
719                 ret, out, _ = ssh.exec_command(cmd)
720                 if ret == 0:
721                     try:
722                         numa_node = 0 if int(out) < 0 else int(out)
723                     except ValueError:
724                         logger.trace(
725                             f"Reading numa location failed for: {if_pci}"
726                         )
727                     else:
728                         Topology.set_interface_numa_node(
729                             node, if_key, numa_node
730                         )
731                         break
732             else:
733                 raise RuntimeError(f"Update numa node failed for: {if_pci}")
734
735     @staticmethod
736     def update_all_interface_data_on_all_nodes(
737             nodes, skip_tg=False, skip_vpp=False):
738         """Update interface names on all nodes in DICT__nodes.
739
740         This method updates the topology dictionary by querying interface lists
741         of all nodes mentioned in the topology dictionary.
742
743         :param nodes: Nodes in the topology.
744         :param skip_tg: Skip TG node.
745         :param skip_vpp: Skip VPP node.
746         :type nodes: dict
747         :type skip_tg: bool
748         :type skip_vpp: bool
749         """
750         for node in nodes.values():
751             if node[u"type"] == NodeType.DUT and not skip_vpp:
752                 InterfaceUtil.update_vpp_interface_data_on_node(node)
753             elif node[u"type"] == NodeType.TG and not skip_tg:
754                 InterfaceUtil.update_tg_interface_data_on_node(node)
755             InterfaceUtil.iface_update_numa_node(node)
756
757     @staticmethod
758     def create_vlan_subinterface(node, interface, vlan):
759         """Create VLAN sub-interface on node.
760
761         :param node: Node to add VLAN subinterface on.
762         :param interface: Interface name or index on which create VLAN
763             subinterface.
764         :param vlan: VLAN ID of the subinterface to be created.
765         :type node: dict
766         :type interface: str on int
767         :type vlan: int
768         :returns: Name and index of created subinterface.
769         :rtype: tuple
770         :raises RuntimeError: if it is unable to create VLAN subinterface on the
771             node or interface cannot be converted.
772         """
773         sw_if_index = InterfaceUtil.get_interface_index(node, interface)
774
775         cmd = u"create_vlan_subif"
776         args = dict(
777             sw_if_index=sw_if_index,
778             vlan_id=int(vlan)
779         )
780         err_msg = f"Failed to create VLAN sub-interface on host {node[u'host']}"
781
782         with PapiSocketExecutor(node) as papi_exec:
783             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
784
785         if_key = Topology.add_new_port(node, u"vlan_subif")
786         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
787         ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
788         Topology.update_interface_name(node, if_key, ifc_name)
789
790         return f"{interface}.{vlan}", sw_if_index
791
792     @staticmethod
793     def create_vxlan_interface(node, vni, source_ip, destination_ip):
794         """Create VXLAN interface and return sw if index of created interface.
795
796         :param node: Node where to create VXLAN interface.
797         :param vni: VXLAN Network Identifier.
798         :param source_ip: Source IP of a VXLAN Tunnel End Point.
799         :param destination_ip: Destination IP of a VXLAN Tunnel End Point.
800         :type node: dict
801         :type vni: int
802         :type source_ip: str
803         :type destination_ip: str
804         :returns: SW IF INDEX of created interface.
805         :rtype: int
806         :raises RuntimeError: if it is unable to create VxLAN interface on the
807             node.
808         """
809         cmd = u"vxlan_add_del_tunnel"
810         args = dict(
811             is_add=True,
812             instance=Constants.BITWISE_NON_ZERO,
813             src_address=IPAddress.create_ip_address_object(
814                 ip_address(source_ip)
815             ),
816             dst_address=IPAddress.create_ip_address_object(
817                 ip_address(destination_ip)
818             ),
819             mcast_sw_if_index=Constants.BITWISE_NON_ZERO,
820             encap_vrf_id=0,
821             decap_next_index=Constants.BITWISE_NON_ZERO,
822             vni=int(vni)
823         )
824         err_msg = f"Failed to create VXLAN tunnel interface " \
825             f"on host {node[u'host']}"
826         with PapiSocketExecutor(node) as papi_exec:
827             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
828
829         if_key = Topology.add_new_port(node, u"vxlan_tunnel")
830         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
831         ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
832         Topology.update_interface_name(node, if_key, ifc_name)
833
834         return sw_if_index
835
836     @staticmethod
837     def set_vxlan_bypass(node, interface=None):
838         """Add the 'ip4-vxlan-bypass' graph node for a given interface.
839
840         By adding the IPv4 vxlan-bypass graph node to an interface, the node
841         checks for and validate input vxlan packet and bypass ip4-lookup,
842         ip4-local, ip4-udp-lookup nodes to speedup vxlan packet forwarding.
843         This node will cause extra overhead to for non-vxlan packets which is
844         kept at a minimum.
845
846         :param node: Node where to set VXLAN bypass.
847         :param interface: Numeric index or name string of a specific interface.
848         :type node: dict
849         :type interface: int or str
850         :raises RuntimeError: if it failed to set VXLAN bypass on interface.
851         """
852         sw_if_index = InterfaceUtil.get_interface_index(node, interface)
853
854         cmd = u"sw_interface_set_vxlan_bypass"
855         args = dict(
856             is_ipv6=False,
857             sw_if_index=sw_if_index,
858             enable=True
859         )
860         err_msg = f"Failed to set VXLAN bypass on interface " \
861             f"on host {node[u'host']}"
862         with PapiSocketExecutor(node) as papi_exec:
863             papi_exec.add(cmd, **args).get_replies(err_msg)
864
865     @staticmethod
866     def vxlan_dump(node, interface=None):
867         """Get VxLAN data for the given interface.
868
869         :param node: VPP node to get interface data from.
870         :param interface: Numeric index or name string of a specific interface.
871             If None, information about all VxLAN interfaces is returned.
872         :type node: dict
873         :type interface: int or str
874         :returns: Dictionary containing data for the given VxLAN interface or if
875             interface=None, the list of dictionaries with all VxLAN interfaces.
876         :rtype: dict or list
877         :raises TypeError: if the data type of interface is neither basestring
878             nor int.
879         """
880         def process_vxlan_dump(vxlan_dump):
881             """Process vxlan dump.
882
883             :param vxlan_dump: Vxlan interface dump.
884             :type vxlan_dump: dict
885             :returns: Processed vxlan interface dump.
886             :rtype: dict
887             """
888             vxlan_dump[u"src_address"] = str(vxlan_dump[u"src_address"])
889             vxlan_dump[u"dst_address"] = str(vxlan_dump[u"dst_address"])
890             return vxlan_dump
891
892         if interface is not None:
893             sw_if_index = InterfaceUtil.get_interface_index(node, interface)
894         else:
895             sw_if_index = int(Constants.BITWISE_NON_ZERO)
896
897         cmd = u"vxlan_tunnel_dump"
898         args = dict(
899             sw_if_index=sw_if_index
900         )
901         err_msg = f"Failed to get VXLAN dump on host {node[u'host']}"
902
903         with PapiSocketExecutor(node) as papi_exec:
904             details = papi_exec.add(cmd, **args).get_details(err_msg)
905
906         data = list() if interface is None else dict()
907         for dump in details:
908             if interface is None:
909                 data.append(process_vxlan_dump(dump))
910             elif dump[u"sw_if_index"] == sw_if_index:
911                 data = process_vxlan_dump(dump)
912                 break
913
914         logger.debug(f"VXLAN data:\n{data}")
915         return data
916
917     @staticmethod
918     def create_subinterface(
919             node, interface, sub_id, outer_vlan_id=None, inner_vlan_id=None,
920             type_subif=None):
921         """Create sub-interface on node. It is possible to set required
922         sub-interface type and VLAN tag(s).
923
924         :param node: Node to add sub-interface.
925         :param interface: Interface name on which create sub-interface.
926         :param sub_id: ID of the sub-interface to be created.
927         :param outer_vlan_id: Optional outer VLAN ID.
928         :param inner_vlan_id: Optional inner VLAN ID.
929         :param type_subif: Optional type of sub-interface. Values supported by
930             VPP: [no_tags] [one_tag] [two_tags] [dot1ad] [exact_match]
931             [default_sub]
932         :type node: dict
933         :type interface: str or int
934         :type sub_id: int
935         :type outer_vlan_id: int
936         :type inner_vlan_id: int
937         :type type_subif: str
938         :returns: Name and index of created sub-interface.
939         :rtype: tuple
940         :raises RuntimeError: If it is not possible to create sub-interface.
941         """
942         subif_types = type_subif.split()
943
944         flags = 0
945         if u"no_tags" in subif_types:
946             flags = flags | SubInterfaceFlags.SUB_IF_API_FLAG_NO_TAGS
947         if u"one_tag" in subif_types:
948             flags = flags | SubInterfaceFlags.SUB_IF_API_FLAG_ONE_TAG
949         if u"two_tags" in subif_types:
950             flags = flags | SubInterfaceFlags.SUB_IF_API_FLAG_TWO_TAGS
951         if u"dot1ad" in subif_types:
952             flags = flags | SubInterfaceFlags.SUB_IF_API_FLAG_DOT1AD
953         if u"exact_match" in subif_types:
954             flags = flags | SubInterfaceFlags.SUB_IF_API_FLAG_EXACT_MATCH
955         if u"default_sub" in subif_types:
956             flags = flags | SubInterfaceFlags.SUB_IF_API_FLAG_DEFAULT
957         if type_subif == u"default_sub":
958             flags = flags | SubInterfaceFlags.SUB_IF_API_FLAG_INNER_VLAN_ID_ANY\
959                     | SubInterfaceFlags.SUB_IF_API_FLAG_OUTER_VLAN_ID_ANY
960
961         cmd = u"create_subif"
962         args = dict(
963             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
964             sub_id=int(sub_id),
965             sub_if_flags=flags.value if hasattr(flags, u"value")
966             else int(flags),
967             outer_vlan_id=int(outer_vlan_id) if outer_vlan_id else 0,
968             inner_vlan_id=int(inner_vlan_id) if inner_vlan_id else 0
969         )
970         err_msg = f"Failed to create sub-interface on host {node[u'host']}"
971         with PapiSocketExecutor(node) as papi_exec:
972             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
973
974         if_key = Topology.add_new_port(node, u"subinterface")
975         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
976         ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
977         Topology.update_interface_name(node, if_key, ifc_name)
978
979         return f"{interface}.{sub_id}", sw_if_index
980
981     @staticmethod
982     def create_gre_tunnel_interface(node, source_ip, destination_ip):
983         """Create GRE tunnel interface on node.
984
985         :param node: VPP node to add tunnel interface.
986         :param source_ip: Source of the GRE tunnel.
987         :param destination_ip: Destination of the GRE tunnel.
988         :type node: dict
989         :type source_ip: str
990         :type destination_ip: str
991         :returns: Name and index of created GRE tunnel interface.
992         :rtype: tuple
993         :raises RuntimeError: If unable to create GRE tunnel interface.
994         """
995         cmd = u"gre_tunnel_add_del"
996         tunnel = dict(
997             type=0,
998             instance=Constants.BITWISE_NON_ZERO,
999             src=str(source_ip),
1000             dst=str(destination_ip),
1001             outer_fib_id=0,
1002             session_id=0
1003         )
1004         args = dict(
1005             is_add=1,
1006             tunnel=tunnel
1007         )
1008         err_msg = f"Failed to create GRE tunnel interface " \
1009             f"on host {node[u'host']}"
1010         with PapiSocketExecutor(node) as papi_exec:
1011             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1012
1013         if_key = Topology.add_new_port(node, u"gre_tunnel")
1014         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
1015         ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
1016         Topology.update_interface_name(node, if_key, ifc_name)
1017
1018         return ifc_name, sw_if_index
1019
1020     @staticmethod
1021     def vpp_create_loopback(node, mac=None):
1022         """Create loopback interface on VPP node.
1023
1024         :param node: Node to create loopback interface on.
1025         :param mac: Optional MAC address for loopback interface.
1026         :type node: dict
1027         :type mac: str
1028         :returns: SW interface index.
1029         :rtype: int
1030         :raises RuntimeError: If it is not possible to create loopback on the
1031             node.
1032         """
1033         cmd = u"create_loopback_instance"
1034         args = dict(
1035             mac_address=L2Util.mac_to_bin(mac) if mac else 0,
1036             is_specified=False,
1037             user_instance=0,
1038         )
1039         err_msg = f"Failed to create loopback interface on host {node[u'host']}"
1040         with PapiSocketExecutor(node) as papi_exec:
1041             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1042
1043         if_key = Topology.add_new_port(node, u"loopback")
1044         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
1045         ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
1046         Topology.update_interface_name(node, if_key, ifc_name)
1047         if mac:
1048             mac = InterfaceUtil.vpp_get_interface_mac(node, ifc_name)
1049             Topology.update_interface_mac_address(node, if_key, mac)
1050
1051         return sw_if_index
1052
1053     @staticmethod
1054     def vpp_create_bond_interface(
1055             node, mode, load_balance=None, mac=None, gso=False):
1056         """Create bond interface on VPP node.
1057
1058         :param node: DUT node from topology.
1059         :param mode: Link bonding mode.
1060         :param load_balance: Load balance (optional, valid for xor and lacp
1061             modes, otherwise ignored). Default: None.
1062         :param mac: MAC address to assign to the bond interface (optional).
1063             Default: None.
1064         :param gso: Enable GSO support (optional). Default: False.
1065         :type node: dict
1066         :type mode: str
1067         :type load_balance: str
1068         :type mac: str
1069         :type gso: bool
1070         :returns: Interface key (name) in topology.
1071         :rtype: str
1072         :raises RuntimeError: If it is not possible to create bond interface on
1073             the node.
1074         """
1075         cmd = u"bond_create2"
1076         args = dict(
1077             id=int(Constants.BITWISE_NON_ZERO),
1078             use_custom_mac=bool(mac is not None),
1079             mac_address=L2Util.mac_to_bin(mac) if mac else None,
1080             mode=getattr(
1081                 LinkBondMode,
1082                 f"BOND_API_MODE_{mode.replace(u'-', u'_').upper()}"
1083             ).value,
1084             lb=0 if load_balance is None else getattr(
1085                 LinkBondLoadBalanceAlgo,
1086                 f"BOND_API_LB_ALGO_{load_balance.upper()}"
1087             ).value,
1088             numa_only=False,
1089             enable_gso=gso
1090         )
1091         err_msg = f"Failed to create bond interface on host {node[u'host']}"
1092         with PapiSocketExecutor(node) as papi_exec:
1093             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1094
1095         InterfaceUtil.add_eth_interface(
1096             node, sw_if_index=sw_if_index, ifc_pfx=u"eth_bond"
1097         )
1098         if_key = Topology.get_interface_by_sw_index(node, sw_if_index)
1099
1100         return if_key
1101
1102     @staticmethod
1103     def add_eth_interface(
1104             node, ifc_name=None, sw_if_index=None, ifc_pfx=None,
1105             host_if_key=None):
1106         """Add ethernet interface to current topology.
1107
1108         :param node: DUT node from topology.
1109         :param ifc_name: Name of the interface.
1110         :param sw_if_index: SW interface index.
1111         :param ifc_pfx: Interface key prefix.
1112         :param host_if_key: Host interface key from topology file.
1113         :type node: dict
1114         :type ifc_name: str
1115         :type sw_if_index: int
1116         :type ifc_pfx: str
1117         :type host_if_key: str
1118         """
1119         if_key = Topology.add_new_port(node, ifc_pfx)
1120
1121         if ifc_name and sw_if_index is None:
1122             sw_if_index = InterfaceUtil.vpp_get_interface_sw_index(
1123                 node, ifc_name)
1124         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
1125         if sw_if_index and ifc_name is None:
1126             ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
1127         Topology.update_interface_name(node, if_key, ifc_name)
1128         ifc_mac = InterfaceUtil.vpp_get_interface_mac(node, sw_if_index)
1129         Topology.update_interface_mac_address(node, if_key, ifc_mac)
1130         if host_if_key is not None:
1131             Topology.set_interface_numa_node(
1132                 node, if_key, Topology.get_interface_numa_node(
1133                     node, host_if_key
1134                 )
1135             )
1136             Topology.update_interface_pci_address(
1137                 node, if_key, Topology.get_interface_pci_addr(node, host_if_key)
1138             )
1139
1140     @staticmethod
1141     def vpp_create_avf_interface(
1142             node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0):
1143         """Create AVF interface on VPP node.
1144
1145         :param node: DUT node from topology.
1146         :param if_key: Interface key from topology file of interface
1147             to be bound to i40evf driver.
1148         :param num_rx_queues: Number of RX queues.
1149         :param rxq_size: Size of RXQ (0 = Default API; 512 = Default VPP).
1150         :param txq_size: Size of TXQ (0 = Default API; 512 = Default VPP).
1151         :type node: dict
1152         :type if_key: str
1153         :type num_rx_queues: int
1154         :type rxq_size: int
1155         :type txq_size: int
1156         :returns: AVF interface key (name) in topology.
1157         :rtype: str
1158         :raises RuntimeError: If it is not possible to create AVF interface on
1159             the node.
1160         """
1161         PapiSocketExecutor.run_cli_cmd(
1162             node, u"set logging class avf level debug"
1163         )
1164
1165         cmd = u"avf_create"
1166         vf_pci_addr = Topology.get_interface_pci_addr(node, if_key)
1167         args = dict(
1168             pci_addr=InterfaceUtil.pci_to_int(vf_pci_addr),
1169             enable_elog=0,
1170             rxq_num=int(num_rx_queues) if num_rx_queues else 0,
1171             rxq_size=rxq_size,
1172             txq_size=txq_size
1173         )
1174         err_msg = f"Failed to create AVF interface on host {node[u'host']}"
1175         with PapiSocketExecutor(node) as papi_exec:
1176             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1177
1178         InterfaceUtil.add_eth_interface(
1179             node, sw_if_index=sw_if_index, ifc_pfx=u"eth_avf",
1180             host_if_key=if_key
1181         )
1182
1183         return Topology.get_interface_by_sw_index(node, sw_if_index)
1184
1185     @staticmethod
1186     def vpp_create_rdma_interface(
1187             node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0,
1188             mode=u"auto"):
1189         """Create RDMA interface on VPP node.
1190
1191         :param node: DUT node from topology.
1192         :param if_key: Physical interface key from topology file of interface
1193             to be bound to rdma-core driver.
1194         :param num_rx_queues: Number of RX queues.
1195         :param rxq_size: Size of RXQ (0 = Default API; 512 = Default VPP).
1196         :param txq_size: Size of TXQ (0 = Default API; 512 = Default VPP).
1197         :param mode: RDMA interface mode - auto/ibv/dv.
1198         :type node: dict
1199         :type if_key: str
1200         :type num_rx_queues: int
1201         :type rxq_size: int
1202         :type txq_size: int
1203         :type mode: str
1204         :returns: Interface key (name) in topology file.
1205         :rtype: str
1206         :raises RuntimeError: If it is not possible to create RDMA interface on
1207             the node.
1208         """
1209         PapiSocketExecutor.run_cli_cmd(
1210             node, u"set logging class rdma level debug"
1211         )
1212
1213         cmd = u"rdma_create_v2"
1214         pci_addr = Topology.get_interface_pci_addr(node, if_key)
1215         args = dict(
1216             name=InterfaceUtil.pci_to_eth(node, pci_addr),
1217             host_if=InterfaceUtil.pci_to_eth(node, pci_addr),
1218             rxq_num=int(num_rx_queues) if num_rx_queues else 0,
1219             rxq_size=rxq_size,
1220             txq_size=txq_size,
1221             mode=getattr(RdmaMode, f"RDMA_API_MODE_{mode.upper()}").value,
1222             # TODO: Set True for non-jumbo packets.
1223             no_multi_seg=False,
1224             max_pktlen=0,
1225         )
1226         err_msg = f"Failed to create RDMA interface on host {node[u'host']}"
1227         with PapiSocketExecutor(node) as papi_exec:
1228             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1229
1230         InterfaceUtil.vpp_set_interface_mac(
1231             node, sw_if_index, Topology.get_interface_mac(node, if_key)
1232         )
1233         InterfaceUtil.add_eth_interface(
1234             node, sw_if_index=sw_if_index, ifc_pfx=u"eth_rdma",
1235             host_if_key=if_key
1236         )
1237
1238         return Topology.get_interface_by_sw_index(node, sw_if_index)
1239
1240     @staticmethod
1241     def vpp_add_bond_member(node, interface, bond_if):
1242         """Add member interface to bond interface on VPP node.
1243
1244         :param node: DUT node from topology.
1245         :param interface: Physical interface key from topology file.
1246         :param bond_if: Load balance
1247         :type node: dict
1248         :type interface: str
1249         :type bond_if: str
1250         :raises RuntimeError: If it is not possible to add member to bond
1251             interface on the node.
1252         """
1253         cmd = u"bond_add_member"
1254         args = dict(
1255             sw_if_index=Topology.get_interface_sw_index(node, interface),
1256             bond_sw_if_index=Topology.get_interface_sw_index(node, bond_if),
1257             is_passive=False,
1258             is_long_timeout=False
1259         )
1260         err_msg = f"Failed to add member {interface} to bond interface " \
1261             f"{bond_if} on host {node[u'host']}"
1262         with PapiSocketExecutor(node) as papi_exec:
1263             papi_exec.add(cmd, **args).get_reply(err_msg)
1264
1265     @staticmethod
1266     def vpp_show_bond_data_on_node(node, verbose=False):
1267         """Show (detailed) bond information on VPP node.
1268
1269         :param node: DUT node from topology.
1270         :param verbose: If detailed information is required or not.
1271         :type node: dict
1272         :type verbose: bool
1273         """
1274         cmd = u"sw_bond_interface_dump"
1275         err_msg = f"Failed to get bond interface dump on host {node[u'host']}"
1276
1277         data = f"Bond data on node {node[u'host']}:\n"
1278         with PapiSocketExecutor(node) as papi_exec:
1279             details = papi_exec.add(cmd).get_details(err_msg)
1280
1281         for bond in details:
1282             data += f"{bond[u'interface_name']}\n"
1283             data += u"  mode: {m}\n".format(
1284                 m=bond[u"mode"].name.replace(u"BOND_API_MODE_", u"").lower()
1285             )
1286             data += u"  load balance: {lb}\n".format(
1287                 lb=bond[u"lb"].name.replace(u"BOND_API_LB_ALGO_", u"").lower()
1288             )
1289             data += f"  number of active members: {bond[u'active_members']}\n"
1290             if verbose:
1291                 member_data = InterfaceUtil.vpp_bond_member_dump(
1292                     node, Topology.get_interface_by_sw_index(
1293                         node, bond[u"sw_if_index"]
1294                     )
1295                 )
1296                 for member in member_data:
1297                     if not member[u"is_passive"]:
1298                         data += f"    {member[u'interface_name']}\n"
1299             data += f"  number of members: {bond[u'members']}\n"
1300             if verbose:
1301                 for member in member_data:
1302                     data += f"    {member[u'interface_name']}\n"
1303             data += f"  interface id: {bond[u'id']}\n"
1304             data += f"  sw_if_index: {bond[u'sw_if_index']}\n"
1305         logger.info(data)
1306
1307     @staticmethod
1308     def vpp_bond_member_dump(node, interface):
1309         """Get bond interface slave(s) data on VPP node.
1310
1311         :param node: DUT node from topology.
1312         :param interface: Physical interface key from topology file.
1313         :type node: dict
1314         :type interface: str
1315         :returns: Bond slave interface data.
1316         :rtype: dict
1317         """
1318         cmd = u"sw_member_interface_dump"
1319         args = dict(
1320             sw_if_index=Topology.get_interface_sw_index(node, interface)
1321         )
1322         err_msg = f"Failed to get slave dump on host {node[u'host']}"
1323
1324         with PapiSocketExecutor(node) as papi_exec:
1325             details = papi_exec.add(cmd, **args).get_details(err_msg)
1326
1327         logger.debug(f"Member data:\n{details}")
1328         return details
1329
1330     @staticmethod
1331     def vpp_show_bond_data_on_all_nodes(nodes, verbose=False):
1332         """Show (detailed) bond information on all VPP nodes in DICT__nodes.
1333
1334         :param nodes: Nodes in the topology.
1335         :param verbose: If detailed information is required or not.
1336         :type nodes: dict
1337         :type verbose: bool
1338         """
1339         for node_data in nodes.values():
1340             if node_data[u"type"] == NodeType.DUT:
1341                 InterfaceUtil.vpp_show_bond_data_on_node(node_data, verbose)
1342
1343     @staticmethod
1344     def vpp_enable_input_acl_interface(
1345             node, interface, ip_version, table_index):
1346         """Enable input acl on interface.
1347
1348         :param node: VPP node to setup interface for input acl.
1349         :param interface: Interface to setup input acl.
1350         :param ip_version: Version of IP protocol.
1351         :param table_index: Classify table index.
1352         :type node: dict
1353         :type interface: str or int
1354         :type ip_version: str
1355         :type table_index: int
1356         """
1357         cmd = u"input_acl_set_interface"
1358         args = dict(
1359             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
1360             ip4_table_index=table_index if ip_version == u"ip4"
1361             else Constants.BITWISE_NON_ZERO,
1362             ip6_table_index=table_index if ip_version == u"ip6"
1363             else Constants.BITWISE_NON_ZERO,
1364             l2_table_index=table_index if ip_version == u"l2"
1365             else Constants.BITWISE_NON_ZERO,
1366             is_add=1)
1367         err_msg = f"Failed to enable input acl on interface {interface}"
1368         with PapiSocketExecutor(node) as papi_exec:
1369             papi_exec.add(cmd, **args).get_reply(err_msg)
1370
1371     @staticmethod
1372     def get_interface_classify_table(node, interface):
1373         """Get name of classify table for the given interface.
1374
1375         TODO: Move to Classify.py.
1376
1377         :param node: VPP node to get data from.
1378         :param interface: Name or sw_if_index of a specific interface.
1379         :type node: dict
1380         :type interface: str or int
1381         :returns: Classify table name.
1382         :rtype: str
1383         """
1384         if isinstance(interface, str):
1385             sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
1386         else:
1387             sw_if_index = interface
1388
1389         cmd = u"classify_table_by_interface"
1390         args = dict(
1391             sw_if_index=sw_if_index
1392         )
1393         err_msg = f"Failed to get classify table name by interface {interface}"
1394         with PapiSocketExecutor(node) as papi_exec:
1395             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
1396
1397         return reply
1398
1399     @staticmethod
1400     def get_sw_if_index(node, interface_name):
1401         """Get sw_if_index for the given interface from actual interface dump.
1402
1403         FIXME: Delete and redirect callers to vpp_get_interface_sw_index.
1404
1405         :param node: VPP node to get interface data from.
1406         :param interface_name: Name of the specific interface.
1407         :type node: dict
1408         :type interface_name: str
1409         :returns: sw_if_index of the given interface.
1410         :rtype: str
1411         """
1412         interface_data = InterfaceUtil.vpp_get_interface_data(
1413             node, interface=interface_name
1414         )
1415         return interface_data.get(u"sw_if_index")
1416
1417     @staticmethod
1418     def vxlan_gpe_dump(node, interface_name=None):
1419         """Get VxLAN GPE data for the given interface.
1420
1421         :param node: VPP node to get interface data from.
1422         :param interface_name: Name of the specific interface. If None,
1423             information about all VxLAN GPE interfaces is returned.
1424         :type node: dict
1425         :type interface_name: str
1426         :returns: Dictionary containing data for the given VxLAN GPE interface
1427             or if interface=None, the list of dictionaries with all VxLAN GPE
1428             interfaces.
1429         :rtype: dict or list
1430         """
1431         def process_vxlan_gpe_dump(vxlan_dump):
1432             """Process vxlan_gpe dump.
1433
1434             :param vxlan_dump: Vxlan_gpe nterface dump.
1435             :type vxlan_dump: dict
1436             :returns: Processed vxlan_gpe interface dump.
1437             :rtype: dict
1438             """
1439             if vxlan_dump[u"is_ipv6"]:
1440                 vxlan_dump[u"local"] = ip_address(vxlan_dump[u"local"])
1441                 vxlan_dump[u"remote"] = ip_address(vxlan_dump[u"remote"])
1442             else:
1443                 vxlan_dump[u"local"] = ip_address(vxlan_dump[u"local"][0:4])
1444                 vxlan_dump[u"remote"] = ip_address(vxlan_dump[u"remote"][0:4])
1445             return vxlan_dump
1446
1447         if interface_name is not None:
1448             sw_if_index = InterfaceUtil.get_interface_index(
1449                 node, interface_name
1450             )
1451         else:
1452             sw_if_index = int(Constants.BITWISE_NON_ZERO)
1453
1454         cmd = u"vxlan_gpe_tunnel_dump"
1455         args = dict(
1456             sw_if_index=sw_if_index
1457         )
1458         err_msg = f"Failed to get VXLAN-GPE dump on host {node[u'host']}"
1459         with PapiSocketExecutor(node) as papi_exec:
1460             details = papi_exec.add(cmd, **args).get_details(err_msg)
1461
1462         data = list() if interface_name is None else dict()
1463         for dump in details:
1464             if interface_name is None:
1465                 data.append(process_vxlan_gpe_dump(dump))
1466             elif dump[u"sw_if_index"] == sw_if_index:
1467                 data = process_vxlan_gpe_dump(dump)
1468                 break
1469
1470         logger.debug(f"VXLAN-GPE data:\n{data}")
1471         return data
1472
1473     @staticmethod
1474     def assign_interface_to_fib_table(node, interface, table_id, ipv6=False):
1475         """Assign VPP interface to specific VRF/FIB table.
1476
1477         :param node: VPP node where the FIB and interface are located.
1478         :param interface: Interface to be assigned to FIB.
1479         :param table_id: VRF table ID.
1480         :param ipv6: Assign to IPv6 table. Default False.
1481         :type node: dict
1482         :type interface: str or int
1483         :type table_id: int
1484         :type ipv6: bool
1485         """
1486         cmd = u"sw_interface_set_table"
1487         args = dict(
1488             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
1489             is_ipv6=ipv6,
1490             vrf_id=int(table_id)
1491         )
1492         err_msg = f"Failed to assign interface {interface} to FIB table"
1493         with PapiSocketExecutor(node) as papi_exec:
1494             papi_exec.add(cmd, **args).get_reply(err_msg)
1495
1496     @staticmethod
1497     def set_linux_interface_mac(
1498             node, interface, mac, namespace=None, vf_id=None):
1499         """Set MAC address for interface in linux.
1500
1501         :param node: Node where to execute command.
1502         :param interface: Interface in namespace.
1503         :param mac: MAC to be assigned to interface.
1504         :param namespace: Execute command in namespace. Optional
1505         :param vf_id: Virtual Function id. Optional
1506         :type node: dict
1507         :type interface: str
1508         :type mac: str
1509         :type namespace: str
1510         :type vf_id: int
1511         """
1512         mac_str = f"vf {vf_id} mac {mac}" if vf_id is not None \
1513             else f"address {mac}"
1514         ns_str = f"ip netns exec {namespace}" if namespace else u""
1515
1516         cmd = f"{ns_str} ip link set {interface} {mac_str}"
1517         exec_cmd_no_error(node, cmd, sudo=True)
1518
1519     @staticmethod
1520     def set_linux_interface_trust_on(
1521             node, interface, namespace=None, vf_id=None):
1522         """Set trust on (promisc) for interface in linux.
1523
1524         :param node: Node where to execute command.
1525         :param interface: Interface in namespace.
1526         :param namespace: Execute command in namespace. Optional
1527         :param vf_id: Virtual Function id. Optional
1528         :type node: dict
1529         :type interface: str
1530         :type namespace: str
1531         :type vf_id: int
1532         """
1533         trust_str = f"vf {vf_id} trust on" if vf_id is not None else u"trust on"
1534         ns_str = f"ip netns exec {namespace}" if namespace else u""
1535
1536         cmd = f"{ns_str} ip link set dev {interface} {trust_str}"
1537         exec_cmd_no_error(node, cmd, sudo=True)
1538
1539     @staticmethod
1540     def set_linux_interface_spoof_off(
1541             node, interface, namespace=None, vf_id=None):
1542         """Set spoof off for interface in linux.
1543
1544         :param node: Node where to execute command.
1545         :param interface: Interface in namespace.
1546         :param namespace: Execute command in namespace. Optional
1547         :param vf_id: Virtual Function id. Optional
1548         :type node: dict
1549         :type interface: str
1550         :type namespace: str
1551         :type vf_id: int
1552         """
1553         spoof_str = f"vf {vf_id} spoof off" if vf_id is not None \
1554             else u"spoof off"
1555         ns_str = f"ip netns exec {namespace}" if namespace else u""
1556
1557         cmd = f"{ns_str} ip link set dev {interface} {spoof_str}"
1558         exec_cmd_no_error(node, cmd, sudo=True)
1559
1560     @staticmethod
1561     def set_linux_interface_state(
1562             node, interface, namespace=None, state=u"up"):
1563         """Set operational state for interface in linux.
1564
1565         :param node: Node where to execute command.
1566         :param interface: Interface in namespace.
1567         :param namespace: Execute command in namespace. Optional
1568         :param state: Up/Down.
1569         :type node: dict
1570         :type interface: str
1571         :type namespace: str
1572         :type state: str
1573         """
1574         ns_str = f"ip netns exec {namespace}" if namespace else u""
1575
1576         cmd = f"{ns_str} ip link set dev {interface} {state}"
1577         exec_cmd_no_error(node, cmd, sudo=True)
1578
1579     @staticmethod
1580     def init_avf_interface(node, ifc_key, numvfs=1, osi_layer=u"L2"):
1581         """Init PCI device by creating VIFs and bind them to vfio-pci for AVF
1582         driver testing on DUT.
1583
1584         :param node: DUT node.
1585         :param ifc_key: Interface key from topology file.
1586         :param numvfs: Number of VIFs to initialize, 0 - disable the VIFs.
1587         :param osi_layer: OSI Layer type to initialize TG with.
1588             Default value "L2" sets linux interface spoof off.
1589         :type node: dict
1590         :type ifc_key: str
1591         :type numvfs: int
1592         :type osi_layer: str
1593         :returns: Virtual Function topology interface keys.
1594         :rtype: list
1595         :raises RuntimeError: If a reason preventing initialization is found.
1596         """
1597         # Read PCI address and driver.
1598         pf_pci_addr = Topology.get_interface_pci_addr(node, ifc_key)
1599         pf_mac_addr = Topology.get_interface_mac(node, ifc_key).split(":")
1600         uio_driver = Topology.get_uio_driver(node)
1601         kernel_driver = Topology.get_interface_driver(node, ifc_key)
1602         if kernel_driver not in (u"ice", u"iavf", u"i40e", u"i40evf"):
1603             raise RuntimeError(
1604                 f"AVF needs ice or i40e compatible driver, not {kernel_driver}"
1605                 f"at node {node[u'host']} ifc {ifc_key}"
1606             )
1607         current_driver = DUTSetup.get_pci_dev_driver(
1608             node, pf_pci_addr.replace(u":", r"\:"))
1609
1610         VPPUtil.stop_vpp_service(node)
1611         if current_driver != kernel_driver:
1612             # PCI device must be re-bound to kernel driver before creating VFs.
1613             DUTSetup.verify_kernel_module(node, kernel_driver, force_load=True)
1614             # Stop VPP to prevent deadlock.
1615             # Unbind from current driver.
1616             DUTSetup.pci_driver_unbind(node, pf_pci_addr)
1617             # Bind to kernel driver.
1618             DUTSetup.pci_driver_bind(node, pf_pci_addr, kernel_driver)
1619
1620         # Initialize PCI VFs.
1621         DUTSetup.set_sriov_numvfs(node, pf_pci_addr, numvfs)
1622
1623         vf_ifc_keys = []
1624         # Set MAC address and bind each virtual function to uio driver.
1625         for vf_id in range(numvfs):
1626             vf_mac_addr = u":".join(
1627                 [pf_mac_addr[0], pf_mac_addr[2], pf_mac_addr[3], pf_mac_addr[4],
1628                  pf_mac_addr[5], f"{vf_id:02x}"
1629                  ]
1630             )
1631
1632             pf_dev = f"`basename /sys/bus/pci/devices/{pf_pci_addr}/net/*`"
1633             InterfaceUtil.set_linux_interface_trust_on(
1634                 node, pf_dev, vf_id=vf_id
1635             )
1636             if osi_layer == u"L2":
1637                 InterfaceUtil.set_linux_interface_spoof_off(
1638                     node, pf_dev, vf_id=vf_id
1639                 )
1640             InterfaceUtil.set_linux_interface_mac(
1641                 node, pf_dev, vf_mac_addr, vf_id=vf_id
1642             )
1643             InterfaceUtil.set_linux_interface_state(
1644                 node, pf_dev, state=u"up"
1645             )
1646
1647             DUTSetup.pci_vf_driver_unbind(node, pf_pci_addr, vf_id)
1648             DUTSetup.pci_vf_driver_bind(node, pf_pci_addr, vf_id, uio_driver)
1649
1650             # Add newly created ports into topology file
1651             vf_ifc_name = f"{ifc_key}_vif"
1652             vf_pci_addr = DUTSetup.get_virtfn_pci_addr(node, pf_pci_addr, vf_id)
1653             vf_ifc_key = Topology.add_new_port(node, vf_ifc_name)
1654             Topology.update_interface_name(
1655                 node, vf_ifc_key, vf_ifc_name+str(vf_id+1)
1656             )
1657             Topology.update_interface_mac_address(node, vf_ifc_key, vf_mac_addr)
1658             Topology.update_interface_pci_address(node, vf_ifc_key, vf_pci_addr)
1659             Topology.set_interface_numa_node(
1660                 node, vf_ifc_key, Topology.get_interface_numa_node(
1661                     node, ifc_key
1662                 )
1663             )
1664             vf_ifc_keys.append(vf_ifc_key)
1665
1666         return vf_ifc_keys
1667
1668     @staticmethod
1669     def vpp_sw_interface_rx_placement_dump(node):
1670         """Dump VPP interface RX placement on node.
1671
1672         :param node: Node to run command on.
1673         :type node: dict
1674         :returns: Thread mapping information as a list of dictionaries.
1675         :rtype: list
1676         """
1677         cmd = u"sw_interface_rx_placement_dump"
1678         err_msg = f"Failed to run '{cmd}' PAPI command on host {node[u'host']}!"
1679         with PapiSocketExecutor(node) as papi_exec:
1680             for ifc in node[u"interfaces"].values():
1681                 if ifc[u"vpp_sw_index"] is not None:
1682                     papi_exec.add(cmd, sw_if_index=ifc[u"vpp_sw_index"])
1683             details = papi_exec.get_details(err_msg)
1684         return sorted(details, key=lambda k: k[u"sw_if_index"])
1685
1686     @staticmethod
1687     def vpp_sw_interface_set_rx_placement(
1688             node, sw_if_index, queue_id, worker_id):
1689         """Set interface RX placement to worker on node.
1690
1691         :param node: Node to run command on.
1692         :param sw_if_index: VPP SW interface index.
1693         :param queue_id: VPP interface queue ID.
1694         :param worker_id: VPP worker ID (indexing from 0).
1695         :type node: dict
1696         :type sw_if_index: int
1697         :type queue_id: int
1698         :type worker_id: int
1699         :raises RuntimeError: If failed to run command on host or if no API
1700             reply received.
1701         """
1702         cmd = u"sw_interface_set_rx_placement"
1703         err_msg = f"Failed to set interface RX placement to worker " \
1704             f"on host {node[u'host']}!"
1705         args = dict(
1706             sw_if_index=sw_if_index,
1707             queue_id=queue_id,
1708             worker_id=worker_id,
1709             is_main=False
1710         )
1711         with PapiSocketExecutor(node) as papi_exec:
1712             papi_exec.add(cmd, **args).get_reply(err_msg)
1713
1714     @staticmethod
1715     def vpp_round_robin_rx_placement(
1716         node, prefix, dp_worker_limit=None
1717     ):
1718         """Set Round Robin interface RX placement on all worker threads
1719         on node.
1720
1721         If specified, dp_core_limit limits the number of physical cores used
1722         for data plane I/O work. Other cores are presumed to do something else,
1723         e.g. asynchronous crypto processing.
1724         None means all workers are used for data plane work.
1725         Note this keyword specifies workers, not cores.
1726
1727         :param node: Topology nodes.
1728         :param prefix: Interface name prefix.
1729         :param dp_worker_limit: How many cores for data plane work.
1730         :type node: dict
1731         :type prefix: str
1732         :type dp_worker_limit: Optional[int]
1733         """
1734         worker_id = 0
1735         worker_cnt = len(VPPUtil.vpp_show_threads(node)) - 1
1736         if dp_worker_limit is not None:
1737             worker_cnt = min(worker_cnt, dp_worker_limit)
1738         if not worker_cnt:
1739             return
1740         for placement in InterfaceUtil.vpp_sw_interface_rx_placement_dump(node):
1741             for interface in node[u"interfaces"].values():
1742                 if placement[u"sw_if_index"] == interface[u"vpp_sw_index"] \
1743                     and prefix in interface[u"name"]:
1744                     InterfaceUtil.vpp_sw_interface_set_rx_placement(
1745                         node, placement[u"sw_if_index"], placement[u"queue_id"],
1746                         worker_id % worker_cnt
1747                     )
1748                     worker_id += 1
1749
1750     @staticmethod
1751     def vpp_round_robin_rx_placement_on_all_duts(
1752         nodes, prefix, dp_core_limit=None
1753     ):
1754         """Set Round Robin interface RX placement on all worker threads
1755         on all DUTs.
1756
1757         If specified, dp_core_limit limits the number of physical cores used
1758         for data plane I/O work. Other cores are presumed to do something else,
1759         e.g. asynchronous crypto processing.
1760         None means all cores are used for data plane work.
1761         Note this keyword specifies cores, not workers.
1762
1763         :param nodes: Topology nodes.
1764         :param prefix: Interface name prefix.
1765         :param dp_worker_limit: How many cores for data plane work.
1766         :type nodes: dict
1767         :type prefix: str
1768         :type dp_worker_limit: Optional[int]
1769         """
1770         for node in nodes.values():
1771             if node[u"type"] == NodeType.DUT:
1772                 dp_worker_limit = CpuUtils.worker_count_from_cores_and_smt(
1773                     phy_cores=dp_core_limit,
1774                     smt_used=CpuUtils.is_smt_enabled(node[u"cpuinfo"]),
1775                 )
1776                 InterfaceUtil.vpp_round_robin_rx_placement(
1777                     node, prefix, dp_worker_limit
1778                 )