Add test suites for crypto sw scheduler engine
[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.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"
1034         args = dict(
1035             mac_address=L2Util.mac_to_bin(mac) if mac else 0
1036         )
1037         err_msg = f"Failed to create loopback interface on host {node[u'host']}"
1038         with PapiSocketExecutor(node) as papi_exec:
1039             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1040
1041         if_key = Topology.add_new_port(node, u"loopback")
1042         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
1043         ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
1044         Topology.update_interface_name(node, if_key, ifc_name)
1045         if mac:
1046             mac = InterfaceUtil.vpp_get_interface_mac(node, ifc_name)
1047             Topology.update_interface_mac_address(node, if_key, mac)
1048
1049         return sw_if_index
1050
1051     @staticmethod
1052     def vpp_create_bond_interface(
1053             node, mode, load_balance=None, mac=None, gso=False):
1054         """Create bond interface on VPP node.
1055
1056         :param node: DUT node from topology.
1057         :param mode: Link bonding mode.
1058         :param load_balance: Load balance (optional, valid for xor and lacp
1059             modes, otherwise ignored). Default: None.
1060         :param mac: MAC address to assign to the bond interface (optional).
1061             Default: None.
1062         :param gso: Enable GSO support (optional). Default: False.
1063         :type node: dict
1064         :type mode: str
1065         :type load_balance: str
1066         :type mac: str
1067         :type gso: bool
1068         :returns: Interface key (name) in topology.
1069         :rtype: str
1070         :raises RuntimeError: If it is not possible to create bond interface on
1071             the node.
1072         """
1073         cmd = u"bond_create2"
1074         args = dict(
1075             id=int(Constants.BITWISE_NON_ZERO),
1076             use_custom_mac=bool(mac is not None),
1077             mac_address=L2Util.mac_to_bin(mac) if mac else None,
1078             mode=getattr(
1079                 LinkBondMode,
1080                 f"BOND_API_MODE_{mode.replace(u'-', u'_').upper()}"
1081             ).value,
1082             lb=0 if load_balance is None else getattr(
1083                 LinkBondLoadBalanceAlgo,
1084                 f"BOND_API_LB_ALGO_{load_balance.upper()}"
1085             ).value,
1086             numa_only=False,
1087             enable_gso=gso
1088         )
1089         err_msg = f"Failed to create bond interface on host {node[u'host']}"
1090         with PapiSocketExecutor(node) as papi_exec:
1091             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1092
1093         InterfaceUtil.add_eth_interface(
1094             node, sw_if_index=sw_if_index, ifc_pfx=u"eth_bond"
1095         )
1096         if_key = Topology.get_interface_by_sw_index(node, sw_if_index)
1097
1098         return if_key
1099
1100     @staticmethod
1101     def add_eth_interface(
1102             node, ifc_name=None, sw_if_index=None, ifc_pfx=None,
1103             host_if_key=None):
1104         """Add ethernet interface to current topology.
1105
1106         :param node: DUT node from topology.
1107         :param ifc_name: Name of the interface.
1108         :param sw_if_index: SW interface index.
1109         :param ifc_pfx: Interface key prefix.
1110         :param host_if_key: Host interface key from topology file.
1111         :type node: dict
1112         :type ifc_name: str
1113         :type sw_if_index: int
1114         :type ifc_pfx: str
1115         :type host_if_key: str
1116         """
1117         if_key = Topology.add_new_port(node, ifc_pfx)
1118
1119         if ifc_name and sw_if_index is None:
1120             sw_if_index = InterfaceUtil.vpp_get_interface_sw_index(
1121                 node, ifc_name)
1122         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
1123         if sw_if_index and ifc_name is None:
1124             ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
1125         Topology.update_interface_name(node, if_key, ifc_name)
1126         ifc_mac = InterfaceUtil.vpp_get_interface_mac(node, sw_if_index)
1127         Topology.update_interface_mac_address(node, if_key, ifc_mac)
1128         if host_if_key is not None:
1129             Topology.set_interface_numa_node(
1130                 node, if_key, Topology.get_interface_numa_node(
1131                     node, host_if_key
1132                 )
1133             )
1134             Topology.update_interface_pci_address(
1135                 node, if_key, Topology.get_interface_pci_addr(node, host_if_key)
1136             )
1137
1138     @staticmethod
1139     def vpp_create_avf_interface(
1140             node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0):
1141         """Create AVF interface on VPP node.
1142
1143         :param node: DUT node from topology.
1144         :param if_key: Interface key from topology file of interface
1145             to be bound to i40evf driver.
1146         :param num_rx_queues: Number of RX queues.
1147         :param rxq_size: Size of RXQ (0 = Default API; 512 = Default VPP).
1148         :param txq_size: Size of TXQ (0 = Default API; 512 = Default VPP).
1149         :type node: dict
1150         :type if_key: str
1151         :type num_rx_queues: int
1152         :type rxq_size: int
1153         :type txq_size: int
1154         :returns: AVF interface key (name) in topology.
1155         :rtype: str
1156         :raises RuntimeError: If it is not possible to create AVF interface on
1157             the node.
1158         """
1159         PapiSocketExecutor.run_cli_cmd(
1160             node, u"set logging class avf level debug"
1161         )
1162
1163         cmd = u"avf_create"
1164         vf_pci_addr = Topology.get_interface_pci_addr(node, if_key)
1165         args = dict(
1166             pci_addr=InterfaceUtil.pci_to_int(vf_pci_addr),
1167             enable_elog=0,
1168             rxq_num=int(num_rx_queues) if num_rx_queues else 0,
1169             rxq_size=rxq_size,
1170             txq_size=txq_size
1171         )
1172         err_msg = f"Failed to create AVF interface on host {node[u'host']}"
1173         with PapiSocketExecutor(node) as papi_exec:
1174             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1175
1176         InterfaceUtil.add_eth_interface(
1177             node, sw_if_index=sw_if_index, ifc_pfx=u"eth_avf",
1178             host_if_key=if_key
1179         )
1180
1181         return Topology.get_interface_by_sw_index(node, sw_if_index)
1182
1183     @staticmethod
1184     def vpp_create_rdma_interface(
1185             node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0,
1186             mode=u"auto"):
1187         """Create RDMA interface on VPP node.
1188
1189         :param node: DUT node from topology.
1190         :param if_key: Physical interface key from topology file of interface
1191             to be bound to rdma-core driver.
1192         :param num_rx_queues: Number of RX queues.
1193         :param rxq_size: Size of RXQ (0 = Default API; 512 = Default VPP).
1194         :param txq_size: Size of TXQ (0 = Default API; 512 = Default VPP).
1195         :param mode: RDMA interface mode - auto/ibv/dv.
1196         :type node: dict
1197         :type if_key: str
1198         :type num_rx_queues: int
1199         :type rxq_size: int
1200         :type txq_size: int
1201         :type mode: str
1202         :returns: Interface key (name) in topology file.
1203         :rtype: str
1204         :raises RuntimeError: If it is not possible to create RDMA interface on
1205             the node.
1206         """
1207         PapiSocketExecutor.run_cli_cmd(
1208             node, u"set logging class rdma level debug"
1209         )
1210
1211         cmd = u"rdma_create"
1212         pci_addr = Topology.get_interface_pci_addr(node, if_key)
1213         args = dict(
1214             name=InterfaceUtil.pci_to_eth(node, pci_addr),
1215             host_if=InterfaceUtil.pci_to_eth(node, pci_addr),
1216             rxq_num=int(num_rx_queues) if num_rx_queues else 0,
1217             rxq_size=rxq_size,
1218             txq_size=txq_size,
1219             mode=getattr(RdmaMode, f"RDMA_API_MODE_{mode.upper()}").value,
1220         )
1221         err_msg = f"Failed to create RDMA interface on host {node[u'host']}"
1222         with PapiSocketExecutor(node) as papi_exec:
1223             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
1224
1225         InterfaceUtil.vpp_set_interface_mac(
1226             node, sw_if_index, Topology.get_interface_mac(node, if_key)
1227         )
1228         InterfaceUtil.add_eth_interface(
1229             node, sw_if_index=sw_if_index, ifc_pfx=u"eth_rdma",
1230             host_if_key=if_key
1231         )
1232
1233         return Topology.get_interface_by_sw_index(node, sw_if_index)
1234
1235     @staticmethod
1236     def vpp_add_bond_member(node, interface, bond_if):
1237         """Add member interface to bond interface on VPP node.
1238
1239         :param node: DUT node from topology.
1240         :param interface: Physical interface key from topology file.
1241         :param bond_if: Load balance
1242         :type node: dict
1243         :type interface: str
1244         :type bond_if: str
1245         :raises RuntimeError: If it is not possible to add member to bond
1246             interface on the node.
1247         """
1248         cmd = u"bond_add_member"
1249         args = dict(
1250             sw_if_index=Topology.get_interface_sw_index(node, interface),
1251             bond_sw_if_index=Topology.get_interface_sw_index(node, bond_if),
1252             is_passive=False,
1253             is_long_timeout=False
1254         )
1255         err_msg = f"Failed to add member {interface} to bond interface " \
1256             f"{bond_if} on host {node[u'host']}"
1257         with PapiSocketExecutor(node) as papi_exec:
1258             papi_exec.add(cmd, **args).get_reply(err_msg)
1259
1260     @staticmethod
1261     def vpp_show_bond_data_on_node(node, verbose=False):
1262         """Show (detailed) bond information on VPP node.
1263
1264         :param node: DUT node from topology.
1265         :param verbose: If detailed information is required or not.
1266         :type node: dict
1267         :type verbose: bool
1268         """
1269         cmd = u"sw_bond_interface_dump"
1270         err_msg = f"Failed to get bond interface dump on host {node[u'host']}"
1271
1272         data = f"Bond data on node {node[u'host']}:\n"
1273         with PapiSocketExecutor(node) as papi_exec:
1274             details = papi_exec.add(cmd).get_details(err_msg)
1275
1276         for bond in details:
1277             data += f"{bond[u'interface_name']}\n"
1278             data += u"  mode: {m}\n".format(
1279                 m=bond[u"mode"].name.replace(u"BOND_API_MODE_", u"").lower()
1280             )
1281             data += u"  load balance: {lb}\n".format(
1282                 lb=bond[u"lb"].name.replace(u"BOND_API_LB_ALGO_", u"").lower()
1283             )
1284             data += f"  number of active members: {bond[u'active_members']}\n"
1285             if verbose:
1286                 member_data = InterfaceUtil.vpp_bond_member_dump(
1287                     node, Topology.get_interface_by_sw_index(
1288                         node, bond[u"sw_if_index"]
1289                     )
1290                 )
1291                 for member in member_data:
1292                     if not member[u"is_passive"]:
1293                         data += f"    {member[u'interface_name']}\n"
1294             data += f"  number of members: {bond[u'members']}\n"
1295             if verbose:
1296                 for member in member_data:
1297                     data += f"    {member[u'interface_name']}\n"
1298             data += f"  interface id: {bond[u'id']}\n"
1299             data += f"  sw_if_index: {bond[u'sw_if_index']}\n"
1300         logger.info(data)
1301
1302     @staticmethod
1303     def vpp_bond_member_dump(node, interface):
1304         """Get bond interface slave(s) data on VPP node.
1305
1306         :param node: DUT node from topology.
1307         :param interface: Physical interface key from topology file.
1308         :type node: dict
1309         :type interface: str
1310         :returns: Bond slave interface data.
1311         :rtype: dict
1312         """
1313         cmd = u"sw_member_interface_dump"
1314         args = dict(
1315             sw_if_index=Topology.get_interface_sw_index(node, interface)
1316         )
1317         err_msg = f"Failed to get slave dump on host {node[u'host']}"
1318
1319         with PapiSocketExecutor(node) as papi_exec:
1320             details = papi_exec.add(cmd, **args).get_details(err_msg)
1321
1322         logger.debug(f"Member data:\n{details}")
1323         return details
1324
1325     @staticmethod
1326     def vpp_show_bond_data_on_all_nodes(nodes, verbose=False):
1327         """Show (detailed) bond information on all VPP nodes in DICT__nodes.
1328
1329         :param nodes: Nodes in the topology.
1330         :param verbose: If detailed information is required or not.
1331         :type nodes: dict
1332         :type verbose: bool
1333         """
1334         for node_data in nodes.values():
1335             if node_data[u"type"] == NodeType.DUT:
1336                 InterfaceUtil.vpp_show_bond_data_on_node(node_data, verbose)
1337
1338     @staticmethod
1339     def vpp_enable_input_acl_interface(
1340             node, interface, ip_version, table_index):
1341         """Enable input acl on interface.
1342
1343         :param node: VPP node to setup interface for input acl.
1344         :param interface: Interface to setup input acl.
1345         :param ip_version: Version of IP protocol.
1346         :param table_index: Classify table index.
1347         :type node: dict
1348         :type interface: str or int
1349         :type ip_version: str
1350         :type table_index: int
1351         """
1352         cmd = u"input_acl_set_interface"
1353         args = dict(
1354             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
1355             ip4_table_index=table_index if ip_version == u"ip4"
1356             else Constants.BITWISE_NON_ZERO,
1357             ip6_table_index=table_index if ip_version == u"ip6"
1358             else Constants.BITWISE_NON_ZERO,
1359             l2_table_index=table_index if ip_version == u"l2"
1360             else Constants.BITWISE_NON_ZERO,
1361             is_add=1)
1362         err_msg = f"Failed to enable input acl on interface {interface}"
1363         with PapiSocketExecutor(node) as papi_exec:
1364             papi_exec.add(cmd, **args).get_reply(err_msg)
1365
1366     @staticmethod
1367     def get_interface_classify_table(node, interface):
1368         """Get name of classify table for the given interface.
1369
1370         TODO: Move to Classify.py.
1371
1372         :param node: VPP node to get data from.
1373         :param interface: Name or sw_if_index of a specific interface.
1374         :type node: dict
1375         :type interface: str or int
1376         :returns: Classify table name.
1377         :rtype: str
1378         """
1379         if isinstance(interface, str):
1380             sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
1381         else:
1382             sw_if_index = interface
1383
1384         cmd = u"classify_table_by_interface"
1385         args = dict(
1386             sw_if_index=sw_if_index
1387         )
1388         err_msg = f"Failed to get classify table name by interface {interface}"
1389         with PapiSocketExecutor(node) as papi_exec:
1390             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
1391
1392         return reply
1393
1394     @staticmethod
1395     def get_sw_if_index(node, interface_name):
1396         """Get sw_if_index for the given interface from actual interface dump.
1397
1398         FIXME: Delete and redirect callers to vpp_get_interface_sw_index.
1399
1400         :param node: VPP node to get interface data from.
1401         :param interface_name: Name of the specific interface.
1402         :type node: dict
1403         :type interface_name: str
1404         :returns: sw_if_index of the given interface.
1405         :rtype: str
1406         """
1407         interface_data = InterfaceUtil.vpp_get_interface_data(
1408             node, interface=interface_name
1409         )
1410         return interface_data.get(u"sw_if_index")
1411
1412     @staticmethod
1413     def vxlan_gpe_dump(node, interface_name=None):
1414         """Get VxLAN GPE data for the given interface.
1415
1416         :param node: VPP node to get interface data from.
1417         :param interface_name: Name of the specific interface. If None,
1418             information about all VxLAN GPE interfaces is returned.
1419         :type node: dict
1420         :type interface_name: str
1421         :returns: Dictionary containing data for the given VxLAN GPE interface
1422             or if interface=None, the list of dictionaries with all VxLAN GPE
1423             interfaces.
1424         :rtype: dict or list
1425         """
1426         def process_vxlan_gpe_dump(vxlan_dump):
1427             """Process vxlan_gpe dump.
1428
1429             :param vxlan_dump: Vxlan_gpe nterface dump.
1430             :type vxlan_dump: dict
1431             :returns: Processed vxlan_gpe interface dump.
1432             :rtype: dict
1433             """
1434             if vxlan_dump[u"is_ipv6"]:
1435                 vxlan_dump[u"local"] = ip_address(vxlan_dump[u"local"])
1436                 vxlan_dump[u"remote"] = ip_address(vxlan_dump[u"remote"])
1437             else:
1438                 vxlan_dump[u"local"] = ip_address(vxlan_dump[u"local"][0:4])
1439                 vxlan_dump[u"remote"] = ip_address(vxlan_dump[u"remote"][0:4])
1440             return vxlan_dump
1441
1442         if interface_name is not None:
1443             sw_if_index = InterfaceUtil.get_interface_index(
1444                 node, interface_name
1445             )
1446         else:
1447             sw_if_index = int(Constants.BITWISE_NON_ZERO)
1448
1449         cmd = u"vxlan_gpe_tunnel_dump"
1450         args = dict(
1451             sw_if_index=sw_if_index
1452         )
1453         err_msg = f"Failed to get VXLAN-GPE dump on host {node[u'host']}"
1454         with PapiSocketExecutor(node) as papi_exec:
1455             details = papi_exec.add(cmd, **args).get_details(err_msg)
1456
1457         data = list() if interface_name is None else dict()
1458         for dump in details:
1459             if interface_name is None:
1460                 data.append(process_vxlan_gpe_dump(dump))
1461             elif dump[u"sw_if_index"] == sw_if_index:
1462                 data = process_vxlan_gpe_dump(dump)
1463                 break
1464
1465         logger.debug(f"VXLAN-GPE data:\n{data}")
1466         return data
1467
1468     @staticmethod
1469     def assign_interface_to_fib_table(node, interface, table_id, ipv6=False):
1470         """Assign VPP interface to specific VRF/FIB table.
1471
1472         :param node: VPP node where the FIB and interface are located.
1473         :param interface: Interface to be assigned to FIB.
1474         :param table_id: VRF table ID.
1475         :param ipv6: Assign to IPv6 table. Default False.
1476         :type node: dict
1477         :type interface: str or int
1478         :type table_id: int
1479         :type ipv6: bool
1480         """
1481         cmd = u"sw_interface_set_table"
1482         args = dict(
1483             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
1484             is_ipv6=ipv6,
1485             vrf_id=int(table_id)
1486         )
1487         err_msg = f"Failed to assign interface {interface} to FIB table"
1488         with PapiSocketExecutor(node) as papi_exec:
1489             papi_exec.add(cmd, **args).get_reply(err_msg)
1490
1491     @staticmethod
1492     def set_linux_interface_mac(
1493             node, interface, mac, namespace=None, vf_id=None):
1494         """Set MAC address for interface in linux.
1495
1496         :param node: Node where to execute command.
1497         :param interface: Interface in namespace.
1498         :param mac: MAC to be assigned to interface.
1499         :param namespace: Execute command in namespace. Optional
1500         :param vf_id: Virtual Function id. Optional
1501         :type node: dict
1502         :type interface: str
1503         :type mac: str
1504         :type namespace: str
1505         :type vf_id: int
1506         """
1507         mac_str = f"vf {vf_id} mac {mac}" if vf_id is not None \
1508             else f"address {mac}"
1509         ns_str = f"ip netns exec {namespace}" if namespace else u""
1510
1511         cmd = f"{ns_str} ip link set {interface} {mac_str}"
1512         exec_cmd_no_error(node, cmd, sudo=True)
1513
1514     @staticmethod
1515     def set_linux_interface_trust_on(
1516             node, interface, namespace=None, vf_id=None):
1517         """Set trust on (promisc) for interface in linux.
1518
1519         :param node: Node where to execute command.
1520         :param interface: Interface in namespace.
1521         :param namespace: Execute command in namespace. Optional
1522         :param vf_id: Virtual Function id. Optional
1523         :type node: dict
1524         :type interface: str
1525         :type namespace: str
1526         :type vf_id: int
1527         """
1528         trust_str = f"vf {vf_id} trust on" if vf_id is not None else u"trust on"
1529         ns_str = f"ip netns exec {namespace}" if namespace else u""
1530
1531         cmd = f"{ns_str} ip link set dev {interface} {trust_str}"
1532         exec_cmd_no_error(node, cmd, sudo=True)
1533
1534     @staticmethod
1535     def set_linux_interface_spoof_off(
1536             node, interface, namespace=None, vf_id=None):
1537         """Set spoof off for interface in linux.
1538
1539         :param node: Node where to execute command.
1540         :param interface: Interface in namespace.
1541         :param namespace: Execute command in namespace. Optional
1542         :param vf_id: Virtual Function id. Optional
1543         :type node: dict
1544         :type interface: str
1545         :type namespace: str
1546         :type vf_id: int
1547         """
1548         spoof_str = f"vf {vf_id} spoof off" if vf_id is not None \
1549             else u"spoof off"
1550         ns_str = f"ip netns exec {namespace}" if namespace else u""
1551
1552         cmd = f"{ns_str} ip link set dev {interface} {spoof_str}"
1553         exec_cmd_no_error(node, cmd, sudo=True)
1554
1555     @staticmethod
1556     def set_linux_interface_state(
1557             node, interface, namespace=None, state=u"up"):
1558         """Set operational state for interface in linux.
1559
1560         :param node: Node where to execute command.
1561         :param interface: Interface in namespace.
1562         :param namespace: Execute command in namespace. Optional
1563         :param state: Up/Down.
1564         :type node: dict
1565         :type interface: str
1566         :type namespace: str
1567         :type state: str
1568         """
1569         ns_str = f"ip netns exec {namespace}" if namespace else u""
1570
1571         cmd = f"{ns_str} ip link set dev {interface} {state}"
1572         exec_cmd_no_error(node, cmd, sudo=True)
1573
1574     @staticmethod
1575     def init_avf_interface(node, ifc_key, numvfs=1, osi_layer=u"L2"):
1576         """Init PCI device by creating VIFs and bind them to vfio-pci for AVF
1577         driver testing on DUT.
1578
1579         :param node: DUT node.
1580         :param ifc_key: Interface key from topology file.
1581         :param numvfs: Number of VIFs to initialize, 0 - disable the VIFs.
1582         :param osi_layer: OSI Layer type to initialize TG with.
1583             Default value "L2" sets linux interface spoof off.
1584         :type node: dict
1585         :type ifc_key: str
1586         :type numvfs: int
1587         :type osi_layer: str
1588         :returns: Virtual Function topology interface keys.
1589         :rtype: list
1590         :raises RuntimeError: If a reason preventing initialization is found.
1591         """
1592         # Read PCI address and driver.
1593         pf_pci_addr = Topology.get_interface_pci_addr(node, ifc_key)
1594         pf_mac_addr = Topology.get_interface_mac(node, ifc_key).split(":")
1595         uio_driver = Topology.get_uio_driver(node)
1596         kernel_driver = Topology.get_interface_driver(node, ifc_key)
1597         if kernel_driver not in (u"ice", u"iavf", u"i40e", u"i40evf"):
1598             raise RuntimeError(
1599                 f"AVF needs ice or i40e compatible driver, not {kernel_driver}"
1600                 f"at node {node[u'host']} ifc {ifc_key}"
1601             )
1602         current_driver = DUTSetup.get_pci_dev_driver(
1603             node, pf_pci_addr.replace(u":", r"\:"))
1604
1605         VPPUtil.stop_vpp_service(node)
1606         if current_driver != kernel_driver:
1607             # PCI device must be re-bound to kernel driver before creating VFs.
1608             DUTSetup.verify_kernel_module(node, kernel_driver, force_load=True)
1609             # Stop VPP to prevent deadlock.
1610             # Unbind from current driver.
1611             DUTSetup.pci_driver_unbind(node, pf_pci_addr)
1612             # Bind to kernel driver.
1613             DUTSetup.pci_driver_bind(node, pf_pci_addr, kernel_driver)
1614
1615         # Initialize PCI VFs.
1616         DUTSetup.set_sriov_numvfs(node, pf_pci_addr, numvfs)
1617
1618         vf_ifc_keys = []
1619         # Set MAC address and bind each virtual function to uio driver.
1620         for vf_id in range(numvfs):
1621             vf_mac_addr = u":".join(
1622                 [pf_mac_addr[0], pf_mac_addr[2], pf_mac_addr[3], pf_mac_addr[4],
1623                  pf_mac_addr[5], f"{vf_id:02x}"
1624                  ]
1625             )
1626
1627             pf_dev = f"`basename /sys/bus/pci/devices/{pf_pci_addr}/net/*`"
1628             InterfaceUtil.set_linux_interface_trust_on(
1629                 node, pf_dev, vf_id=vf_id
1630             )
1631             if osi_layer == u"L2":
1632                 InterfaceUtil.set_linux_interface_spoof_off(
1633                     node, pf_dev, vf_id=vf_id
1634                 )
1635             InterfaceUtil.set_linux_interface_mac(
1636                 node, pf_dev, vf_mac_addr, vf_id=vf_id
1637             )
1638             InterfaceUtil.set_linux_interface_state(
1639                 node, pf_dev, state=u"up"
1640             )
1641
1642             DUTSetup.pci_vf_driver_unbind(node, pf_pci_addr, vf_id)
1643             DUTSetup.pci_vf_driver_bind(node, pf_pci_addr, vf_id, uio_driver)
1644
1645             # Add newly created ports into topology file
1646             vf_ifc_name = f"{ifc_key}_vif"
1647             vf_pci_addr = DUTSetup.get_virtfn_pci_addr(node, pf_pci_addr, vf_id)
1648             vf_ifc_key = Topology.add_new_port(node, vf_ifc_name)
1649             Topology.update_interface_name(
1650                 node, vf_ifc_key, vf_ifc_name+str(vf_id+1)
1651             )
1652             Topology.update_interface_mac_address(node, vf_ifc_key, vf_mac_addr)
1653             Topology.update_interface_pci_address(node, vf_ifc_key, vf_pci_addr)
1654             Topology.set_interface_numa_node(
1655                 node, vf_ifc_key, Topology.get_interface_numa_node(
1656                     node, ifc_key
1657                 )
1658             )
1659             vf_ifc_keys.append(vf_ifc_key)
1660
1661         return vf_ifc_keys
1662
1663     @staticmethod
1664     def vpp_sw_interface_rx_placement_dump(node):
1665         """Dump VPP interface RX placement on node.
1666
1667         :param node: Node to run command on.
1668         :type node: dict
1669         :returns: Thread mapping information as a list of dictionaries.
1670         :rtype: list
1671         """
1672         cmd = u"sw_interface_rx_placement_dump"
1673         err_msg = f"Failed to run '{cmd}' PAPI command on host {node[u'host']}!"
1674         with PapiSocketExecutor(node) as papi_exec:
1675             for ifc in node[u"interfaces"].values():
1676                 if ifc[u"vpp_sw_index"] is not None:
1677                     papi_exec.add(cmd, sw_if_index=ifc[u"vpp_sw_index"])
1678             details = papi_exec.get_details(err_msg)
1679         return sorted(details, key=lambda k: k[u"sw_if_index"])
1680
1681     @staticmethod
1682     def vpp_sw_interface_set_rx_placement(
1683             node, sw_if_index, queue_id, worker_id):
1684         """Set interface RX placement to worker on node.
1685
1686         :param node: Node to run command on.
1687         :param sw_if_index: VPP SW interface index.
1688         :param queue_id: VPP interface queue ID.
1689         :param worker_id: VPP worker ID (indexing from 0).
1690         :type node: dict
1691         :type sw_if_index: int
1692         :type queue_id: int
1693         :type worker_id: int
1694         :raises RuntimeError: If failed to run command on host or if no API
1695             reply received.
1696         """
1697         cmd = u"sw_interface_set_rx_placement"
1698         err_msg = f"Failed to set interface RX placement to worker " \
1699             f"on host {node[u'host']}!"
1700         args = dict(
1701             sw_if_index=sw_if_index,
1702             queue_id=queue_id,
1703             worker_id=worker_id,
1704             is_main=False
1705         )
1706         with PapiSocketExecutor(node) as papi_exec:
1707             papi_exec.add(cmd, **args).get_reply(err_msg)
1708
1709     @staticmethod
1710     def vpp_round_robin_rx_placement(
1711         node, prefix, dp_worker_limit=None
1712     ):
1713         """Set Round Robin interface RX placement on all worker threads
1714         on node.
1715
1716         If specified, dp_core_limit limits the number of physical cores used
1717         for data plane I/O work. Other cores are presumed to do something else,
1718         e.g. asynchronous crypto processing.
1719         None means all workers are used for data plane work.
1720         Note this keyword specifies workers, not cores.
1721
1722         :param node: Topology nodes.
1723         :param prefix: Interface name prefix.
1724         :param dp_worker_limit: How many cores for data plane work.
1725         :type node: dict
1726         :type prefix: str
1727         :type dp_worker_limit: Optional[int]
1728         """
1729         worker_id = 0
1730         worker_cnt = len(VPPUtil.vpp_show_threads(node)) - 1
1731         if dp_worker_limit is not None:
1732             worker_cnt = min(worker_cnt, dp_worker_limit)
1733         if not worker_cnt:
1734             return
1735         for placement in InterfaceUtil.vpp_sw_interface_rx_placement_dump(node):
1736             for interface in node[u"interfaces"].values():
1737                 if placement[u"sw_if_index"] == interface[u"vpp_sw_index"] \
1738                     and prefix in interface[u"name"]:
1739                     InterfaceUtil.vpp_sw_interface_set_rx_placement(
1740                         node, placement[u"sw_if_index"], placement[u"queue_id"],
1741                         worker_id % worker_cnt
1742                     )
1743                     worker_id += 1
1744
1745     @staticmethod
1746     def vpp_round_robin_rx_placement_on_all_duts(
1747         nodes, prefix, dp_core_limit=None
1748     ):
1749         """Set Round Robin interface RX placement on all worker threads
1750         on all DUTs.
1751
1752         If specified, dp_core_limit limits the number of physical cores used
1753         for data plane I/O work. Other cores are presumed to do something else,
1754         e.g. asynchronous crypto processing.
1755         None means all cores are used for data plane work.
1756         Note this keyword specifies cores, not workers.
1757
1758         :param nodes: Topology nodes.
1759         :param prefix: Interface name prefix.
1760         :param dp_worker_limit: How many cores for data plane work.
1761         :type nodes: dict
1762         :type prefix: str
1763         :type dp_worker_limit: Optional[int]
1764         """
1765         for node in nodes.values():
1766             if node[u"type"] == NodeType.DUT:
1767                 dp_worker_limit = CpuUtils.worker_count_from_cores_and_smt(
1768                     phy_cores=dp_core_limit,
1769                     smt_used=CpuUtils.is_smt_enabled(node[u"cpuinfo"]),
1770                 )
1771                 InterfaceUtil.vpp_round_robin_rx_placement(
1772                     node, prefix, dp_worker_limit
1773                 )