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