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