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