CSIT-1043: Execute pci rescan only if needed
[csit.git] / resources / libraries / python / InterfaceUtil.py
1 # Copyright (c) 2018 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 time, sleep
17
18 from robot.api import logger
19
20 from resources.libraries.python.ssh import SSH
21 from resources.libraries.python.IPUtil import convert_ipv4_netmask_prefix
22 from resources.libraries.python.DUTSetup import DUTSetup
23 from resources.libraries.python.ssh import exec_cmd_no_error
24 from resources.libraries.python.topology import NodeType, Topology
25 from resources.libraries.python.VatExecutor import VatExecutor, VatTerminal
26 from resources.libraries.python.VatJsonUtil import VatJsonUtil
27 from resources.libraries.python.parsers.JsonParser import JsonParser
28
29
30 class InterfaceUtil(object):
31     """General utilities for managing interfaces"""
32
33     __UDEV_IF_RULES_FILE = '/etc/udev/rules.d/10-network.rules'
34
35     @staticmethod
36     def set_interface_state(node, interface, state, if_type="key"):
37         """Set interface state on a node.
38
39         Function can be used for DUTs as well as for TGs.
40
41         :param node: Node where the interface is.
42         :param interface: Interface key or sw_if_index or name.
43         :param state: One of 'up' or 'down'.
44         :param if_type: Interface type
45         :type node: dict
46         :type interface: str or int
47         :type state: str
48         :type if_type: str
49         :returns: Nothing.
50         :raises ValueError: If the interface type is unknown.
51         :raises ValueError: If the state of interface is unexpected.
52         :raises ValueError: If the node has an unknown node type.
53         """
54
55         if if_type == "key":
56             if isinstance(interface, basestring):
57                 sw_if_index = Topology.get_interface_sw_index(node, interface)
58                 iface_name = Topology.get_interface_name(node, interface)
59             else:
60                 sw_if_index = interface
61         elif if_type == "name":
62             iface_key = Topology.get_interface_by_name(node, interface)
63             if iface_key is not None:
64                 sw_if_index = Topology.get_interface_sw_index(node, iface_key)
65             iface_name = interface
66         else:
67             raise ValueError("if_type unknown: {}".format(if_type))
68
69         if node['type'] == NodeType.DUT:
70             if state == 'up':
71                 state = 'admin-up'
72             elif state == 'down':
73                 state = 'admin-down'
74             else:
75                 raise ValueError('Unexpected interface state: {}'.format(state))
76             VatExecutor.cmd_from_template(node, 'set_if_state.vat',
77                                           sw_if_index=sw_if_index, state=state)
78         elif node['type'] == NodeType.TG or node['type'] == NodeType.VM:
79             cmd = 'ip link set {} {}'.format(iface_name, state)
80             exec_cmd_no_error(node, cmd, sudo=True)
81         else:
82             raise ValueError('Node {} has unknown NodeType: "{}"'
83                              .format(node['host'], node['type']))
84
85     @staticmethod
86     def set_interface_ethernet_mtu(node, iface_key, mtu):
87         """Set Ethernet MTU for specified interface.
88
89         Function can be used only for TGs.
90
91         :param node: Node where the interface is.
92         :param iface_key: Interface key from topology file.
93         :param mtu: MTU to set.
94         :type node: dict
95         :type iface_key: str
96         :type mtu: int
97         :returns: Nothing.
98         :raises ValueError: If the node type is "DUT".
99         :raises ValueError: If the node has an unknown node type.
100         """
101         if node['type'] == NodeType.DUT:
102             raise ValueError('Node {}: Setting Ethernet MTU for interface '
103                              'on DUT nodes not supported', node['host'])
104         elif node['type'] == NodeType.TG:
105             iface_name = Topology.get_interface_name(node, iface_key)
106             cmd = 'ip link set {} mtu {}'.format(iface_name, mtu)
107             exec_cmd_no_error(node, cmd, sudo=True)
108         else:
109             raise ValueError('Node {} has unknown NodeType: "{}"'
110                              .format(node['host'], node['type']))
111
112     @staticmethod
113     def set_default_ethernet_mtu_on_all_interfaces_on_node(node):
114         """Set default Ethernet MTU on all interfaces on node.
115
116         Function can be used only for TGs.
117
118         :param node: Node where to set default MTU.
119         :type node: dict
120         :returns: Nothing.
121         """
122         for ifc in node['interfaces']:
123             InterfaceUtil.set_interface_ethernet_mtu(node, ifc, 1500)
124
125     @staticmethod
126     def vpp_node_interfaces_ready_wait(node, timeout=10):
127         """Wait until all interfaces with admin-up are in link-up state.
128
129         :param node: Node to wait on.
130         :param timeout: Waiting timeout in seconds (optional, default 10s).
131         :type node: dict
132         :type timeout: int
133         :returns: Nothing.
134         :raises RuntimeError: If the timeout period value has elapsed.
135         """
136         if_ready = False
137         not_ready = []
138         start = time()
139         while not if_ready:
140             out = InterfaceUtil.vpp_get_interface_data(node)
141             if time() - start > timeout:
142                 for interface in out:
143                     if interface.get('admin_up_down') == 1:
144                         if interface.get('link_up_down') != 1:
145                             logger.debug('{0} link-down'.format(
146                                 interface.get('interface_name')))
147                 raise RuntimeError('timeout, not up {0}'.format(not_ready))
148             not_ready = []
149             for interface in out:
150                 if interface.get('admin_up_down') == 1:
151                     if interface.get('link_up_down') != 1:
152                         not_ready.append(interface.get('interface_name'))
153             if not not_ready:
154                 if_ready = True
155             else:
156                 logger.debug('Interfaces still in link-down state: {0}, '
157                              'waiting...'.format(not_ready))
158                 sleep(1)
159
160     @staticmethod
161     def vpp_nodes_interfaces_ready_wait(nodes, timeout=10):
162         """Wait until all interfaces with admin-up are in link-up state for
163         listed nodes.
164
165         :param nodes: List of nodes to wait on.
166         :param timeout: Seconds to wait per node for all interfaces to come up.
167         :type nodes: list
168         :type timeout: int
169         :returns: Nothing.
170         """
171         for node in nodes:
172             InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout)
173
174     @staticmethod
175     def all_vpp_interfaces_ready_wait(nodes, timeout=10):
176         """Wait until all interfaces with admin-up are in link-up state for all
177         nodes in the topology.
178
179         :param nodes: Nodes in the topology.
180         :param timeout: Seconds to wait per node for all interfaces to come up.
181         :type nodes: dict
182         :type timeout: int
183         :returns: Nothing.
184         """
185         for node in nodes.values():
186             if node['type'] == NodeType.DUT:
187                 InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout)
188
189     @staticmethod
190     def vpp_get_interface_data(node, interface=None):
191         """Get all interface data from a VPP node. If a name or
192         sw_interface_index is provided, return only data for the matching
193         interface.
194
195         :param node: VPP node to get interface data from.
196         :param interface: Numeric index or name string of a specific interface.
197         :type node: dict
198         :type interface: int or str
199         :returns: List of dictionaries containing data for each interface, or a
200             single dictionary for the specified interface.
201         :rtype: list or dict
202         :raises TypeError: if the data type of interface is neither basestring
203             nor int.
204         """
205         with VatTerminal(node) as vat:
206             response = vat.vat_terminal_exec_cmd_from_template(
207                 "interface_dump.vat")
208
209         data = response[0]
210
211         if interface is not None:
212             if isinstance(interface, basestring):
213                 param = "interface_name"
214             elif isinstance(interface, int):
215                 param = "sw_if_index"
216             else:
217                 raise TypeError
218             for data_if in data:
219                 if data_if[param] == interface:
220                     return data_if
221             return dict()
222         return data
223
224     @staticmethod
225     def vpp_get_interface_name(node, sw_if_index):
226         """Get interface name for the given SW interface index from actual
227         interface dump.
228
229         :param node: VPP node to get interface data from.
230         :param sw_if_index: SW interface index of the specific interface.
231         :type node: dict
232         :type sw_if_index: int
233         :returns: Name of the given interface.
234         :rtype: str
235         """
236
237         if_data = InterfaceUtil.vpp_get_interface_data(node, sw_if_index)
238         if if_data['sup_sw_if_index'] != if_data['sw_if_index']:
239             if_data = InterfaceUtil.vpp_get_interface_data(
240                 node, if_data['sup_sw_if_index'])
241         try:
242             if_name = if_data["interface_name"]
243         except KeyError:
244             if_name = None
245         return if_name
246
247     @staticmethod
248     def vpp_get_interface_mac(node, interface=None):
249         """Get MAC address for the given interface from actual interface dump.
250
251         :param node: VPP node to get interface data from.
252         :param interface: Numeric index or name string of a specific interface.
253         :type node: dict
254         :type interface: int or str
255         :returns: MAC address.
256         :rtype: str
257         """
258
259         if_data = InterfaceUtil.vpp_get_interface_data(node, interface)
260         if if_data['sup_sw_if_index'] != if_data['sw_if_index']:
261             if_data = InterfaceUtil.vpp_get_interface_data(
262                 node, if_data['sup_sw_if_index'])
263         mac_data = [str(hex(item))[2:] for item in if_data['l2_address'][:6]]
264         mac_data_nice = []
265         for item in mac_data:
266             if len(item) == 1:
267                 item = '0' + item
268             mac_data_nice.append(item)
269         mac = ":".join(mac_data_nice)
270         return mac
271
272     @staticmethod
273     def vpp_get_interface_ip_addresses(node, interface, ip_version):
274         """Get list of IP addresses from an interface on a VPP node.
275
276          :param node: VPP node to get data from.
277          :param interface: Name of an interface on the VPP node.
278          :param ip_version: IP protocol version (ipv4 or ipv6).
279          :type node: dict
280          :type interface: str
281          :type ip_version: str
282          :returns: List of dictionaries, each containing IP address, subnet
283             prefix length and also the subnet mask for ipv4 addresses.
284             Note: A single interface may have multiple IP addresses assigned.
285          :rtype: list
286         """
287
288         try:
289             sw_if_index = Topology.convert_interface_reference(
290                 node, interface, "sw_if_index")
291         except RuntimeError:
292             if isinstance(interface, basestring):
293                 sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
294             else:
295                 raise
296
297         with VatTerminal(node) as vat:
298             response = vat.vat_terminal_exec_cmd_from_template(
299                 "ip_address_dump.vat", ip_version=ip_version,
300                 sw_if_index=sw_if_index)
301
302         data = response[0]
303
304         if ip_version == "ipv4":
305             for item in data:
306                 item["netmask"] = convert_ipv4_netmask_prefix(
307                     item["prefix_length"])
308         return data
309
310     @staticmethod
311     def tg_set_interface_driver(node, pci_addr, driver):
312         """Set interface driver on the TG node.
313
314         :param node: Node to set interface driver on (must be TG node).
315         :param pci_addr: PCI address of the interface.
316         :param driver: Driver name.
317         :type node: dict
318         :type pci_addr: str
319         :type driver: str
320         :raises RuntimeError: If unbinding from the current driver fails.
321         :raises RuntimeError: If binding to the new driver fails.
322         """
323         old_driver = InterfaceUtil.tg_get_interface_driver(node, pci_addr)
324         if old_driver == driver:
325             return
326
327         ssh = SSH()
328         ssh.connect(node)
329
330         # Unbind from current driver
331         if old_driver is not None:
332             cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/unbind"'\
333                 .format(pci_addr, old_driver)
334             (ret_code, _, _) = ssh.exec_command_sudo(cmd)
335             if int(ret_code) != 0:
336                 raise RuntimeError("'{0}' failed on '{1}'"
337                                    .format(cmd, node['host']))
338
339         # Bind to the new driver
340         cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/bind"'\
341             .format(pci_addr, driver)
342         (ret_code, _, _) = ssh.exec_command_sudo(cmd)
343         if int(ret_code) != 0:
344             raise RuntimeError("'{0}' failed on '{1}'"
345                                .format(cmd, node['host']))
346
347     @staticmethod
348     def tg_get_interface_driver(node, pci_addr):
349         """Get interface driver from the TG node.
350
351         :param node: Node to get interface driver on (must be TG node).
352         :param pci_addr: PCI address of the interface.
353         :type node: dict
354         :type pci_addr: str
355         :returns: Interface driver or None if not found.
356         :rtype: str
357         :raises RuntimeError: If PCI rescan or lspci command execution failed.
358         """
359         return DUTSetup.get_pci_dev_driver(node, pci_addr)
360
361     @staticmethod
362     def tg_set_interfaces_udev_rules(node):
363         """Set udev rules for interfaces.
364
365         Create udev rules file in /etc/udev/rules.d where are rules for each
366         interface used by TG node, based on MAC interface has specific name.
367         So after unbind and bind again to kernel driver interface has same
368         name as before. This must be called after TG has set name for each
369         port in topology dictionary.
370         udev rule example
371         SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="52:54:00:e1:8a:0f",
372         NAME="eth1"
373
374         :param node: Node to set udev rules on (must be TG node).
375         :type node: dict
376         :raises RuntimeError: If setting of udev rules fails.
377         """
378         ssh = SSH()
379         ssh.connect(node)
380
381         cmd = 'rm -f {0}'.format(InterfaceUtil.__UDEV_IF_RULES_FILE)
382         (ret_code, _, _) = ssh.exec_command_sudo(cmd)
383         if int(ret_code) != 0:
384             raise RuntimeError("'{0}' failed on '{1}'"
385                                .format(cmd, node['host']))
386
387         for interface in node['interfaces'].values():
388             rule = 'SUBSYSTEM==\\"net\\", ACTION==\\"add\\", ATTR{address}' + \
389                    '==\\"' + interface['mac_address'] + '\\", NAME=\\"' + \
390                    interface['name'] + '\\"'
391             cmd = 'sh -c "echo \'{0}\' >> {1}"'.format(
392                 rule, InterfaceUtil.__UDEV_IF_RULES_FILE)
393             (ret_code, _, _) = ssh.exec_command_sudo(cmd)
394             if int(ret_code) != 0:
395                 raise RuntimeError("'{0}' failed on '{1}'"
396                                    .format(cmd, node['host']))
397
398         cmd = '/etc/init.d/udev restart'
399         ssh.exec_command_sudo(cmd)
400
401     @staticmethod
402     def tg_set_interfaces_default_driver(node):
403         """Set interfaces default driver specified in topology yaml file.
404
405         :param node: Node to setup interfaces driver on (must be TG node).
406         :type node: dict
407         """
408         for interface in node['interfaces'].values():
409             InterfaceUtil.tg_set_interface_driver(node,
410                                                   interface['pci_address'],
411                                                   interface['driver'])
412
413     @staticmethod
414     def update_vpp_interface_data_on_node(node):
415         """Update vpp generated interface data for a given node in DICT__nodes.
416
417         Updates interface names, software if index numbers and any other details
418         generated specifically by vpp that are unknown before testcase run.
419         It does this by dumping interface list to JSON output from all
420         devices using vpp_api_test, and pairing known information from topology
421         (mac address/pci address of interface) to state from VPP.
422
423         :param node: Node selected from DICT__nodes.
424         :type node: dict
425         """
426         vat_executor = VatExecutor()
427         vat_executor.execute_script_json_out("dump_interfaces.vat", node)
428         interface_dump_json = vat_executor.get_script_stdout()
429         VatJsonUtil.update_vpp_interface_data_from_json(node,
430                                                         interface_dump_json)
431
432     @staticmethod
433     def update_nic_interface_names(node):
434         """Update interface names based on nic type and PCI address.
435
436         This method updates interface names in the same format as VPP does.
437
438         :param node: Node dictionary.
439         :type node: dict
440         """
441         for ifc in node['interfaces'].values():
442             if_pci = ifc['pci_address'].replace('.', ':').split(':')
443             bus = '{:x}'.format(int(if_pci[1], 16))
444             dev = '{:x}'.format(int(if_pci[2], 16))
445             fun = '{:x}'.format(int(if_pci[3], 16))
446             loc = '{bus}/{dev}/{fun}'.format(bus=bus, dev=dev, fun=fun)
447             if ifc['model'] == 'Intel-XL710':
448                 ifc['name'] = 'FortyGigabitEthernet{loc}'.format(loc=loc)
449             elif ifc['model'] == 'Intel-X710':
450                 ifc['name'] = 'TenGigabitEthernet{loc}'.format(loc=loc)
451             elif ifc['model'] == 'Intel-X520-DA2':
452                 ifc['name'] = 'TenGigabitEthernet{loc}'.format(loc=loc)
453             elif ifc['model'] == 'Cisco-VIC-1385':
454                 ifc['name'] = 'FortyGigabitEthernet{loc}'.format(loc=loc)
455             elif ifc['model'] == 'Cisco-VIC-1227':
456                 ifc['name'] = 'TenGigabitEthernet{loc}'.format(loc=loc)
457             else:
458                 ifc['name'] = 'UnknownEthernet{loc}'.format(loc=loc)
459
460     @staticmethod
461     def update_nic_interface_names_on_all_duts(nodes):
462         """Update interface names based on nic type and PCI address on all DUTs.
463
464         This method updates interface names in the same format as VPP does.
465
466         :param nodes: Topology nodes.
467         :type nodes: dict
468         """
469         for node in nodes.values():
470             if node['type'] == NodeType.DUT:
471                 InterfaceUtil.update_nic_interface_names(node)
472
473     @staticmethod
474     def update_tg_interface_data_on_node(node):
475         """Update interface name for TG/linux node in DICT__nodes.
476
477         .. note::
478             # for dev in `ls /sys/class/net/`;
479             > do echo "\"`cat /sys/class/net/$dev/address`\": \"$dev\""; done
480             "52:54:00:9f:82:63": "eth0"
481             "52:54:00:77:ae:a9": "eth1"
482             "52:54:00:e1:8a:0f": "eth2"
483             "00:00:00:00:00:00": "lo"
484
485         .. note:: TODO: parse lshw -json instead
486
487         :param node: Node selected from DICT__nodes.
488         :type node: dict
489         :raises RuntimeError: If getting of interface name and MAC fails.
490         """
491         # First setup interface driver specified in yaml file
492         InterfaceUtil.tg_set_interfaces_default_driver(node)
493
494         # Get interface names
495         ssh = SSH()
496         ssh.connect(node)
497
498         cmd = ('for dev in `ls /sys/class/net/`; do echo "\\"`cat '
499                '/sys/class/net/$dev/address`\\": \\"$dev\\""; done;')
500
501         (ret_code, stdout, _) = ssh.exec_command(cmd)
502         if int(ret_code) != 0:
503             raise RuntimeError('Get interface name and MAC failed')
504         tmp = "{" + stdout.rstrip().replace('\n', ',') + "}"
505         interfaces = JsonParser().parse_data(tmp)
506         for interface in node['interfaces'].values():
507             name = interfaces.get(interface['mac_address'])
508             if name is None:
509                 continue
510             interface['name'] = name
511
512         # Set udev rules for interfaces
513         InterfaceUtil.tg_set_interfaces_udev_rules(node)
514
515     @staticmethod
516     def iface_update_numa_node(node):
517         """For all interfaces from topology file update numa node based on
518            information from the node.
519
520         :param node: Node from topology.
521         :type node: dict
522         :returns: Nothing.
523         :raises ValueError: If numa node ia less than 0.
524         :raises RuntimeError: If update of numa node failes.
525         """
526         ssh = SSH()
527         for if_key in Topology.get_node_interfaces(node):
528             if_pci = Topology.get_interface_pci_addr(node, if_key)
529             ssh.connect(node)
530             cmd = "cat /sys/bus/pci/devices/{}/numa_node".format(if_pci)
531             for _ in range(3):
532                 (ret, out, _) = ssh.exec_command(cmd)
533                 if ret == 0:
534                     try:
535                         numa_node = int(out)
536                         if numa_node < 0:
537                             raise ValueError
538                     except ValueError:
539                         logger.trace('Reading numa location failed for: {0}'\
540                             .format(if_pci))
541                     else:
542                         Topology.set_interface_numa_node(node, if_key,
543                                                          numa_node)
544                         break
545             else:
546                 raise RuntimeError('Update numa node failed for: {0}'\
547                     .format(if_pci))
548
549     @staticmethod
550     def update_all_numa_nodes(nodes, skip_tg=False):
551         """For all nodes and all their interfaces from topology file update numa
552         node information based on information from the node.
553
554         :param nodes: Nodes in the topology.
555         :param skip_tg: Skip TG node
556         :type nodes: dict
557         :type skip_tg: bool
558         :returns: Nothing.
559         """
560         for node in nodes.values():
561             if node['type'] == NodeType.DUT:
562                 InterfaceUtil.iface_update_numa_node(node)
563             elif node['type'] == NodeType.TG and not skip_tg:
564                 InterfaceUtil.iface_update_numa_node(node)
565
566     @staticmethod
567     def update_all_interface_data_on_all_nodes(nodes, skip_tg=False,
568                                                numa_node=False):
569         """Update interface names on all nodes in DICT__nodes.
570
571         This method updates the topology dictionary by querying interface lists
572         of all nodes mentioned in the topology dictionary.
573
574         :param nodes: Nodes in the topology.
575         :param skip_tg: Skip TG node
576         :param numa_node: Retrieve numa_node location.
577         :type nodes: dict
578         :type skip_tg: bool
579         :type numa_node: bool
580         """
581         for node_data in nodes.values():
582             if node_data['type'] == NodeType.DUT:
583                 InterfaceUtil.update_vpp_interface_data_on_node(node_data)
584             elif node_data['type'] == NodeType.TG and not skip_tg:
585                 InterfaceUtil.update_tg_interface_data_on_node(node_data)
586
587             if numa_node:
588                 if node_data['type'] == NodeType.DUT:
589                     InterfaceUtil.iface_update_numa_node(node_data)
590                 elif node_data['type'] == NodeType.TG and not skip_tg:
591                     InterfaceUtil.iface_update_numa_node(node_data)
592
593     @staticmethod
594     def create_vlan_subinterface(node, interface, vlan):
595         """Create VLAN subinterface on node.
596
597         :param node: Node to add VLAN subinterface on.
598         :param interface: Interface name on which create VLAN subinterface.
599         :param vlan: VLAN ID of the subinterface to be created.
600         :type node: dict
601         :type interface: str
602         :type vlan: int
603         :returns: Name and index of created subinterface.
604         :rtype: tuple
605         :raises RuntimeError: if it is unable to create VLAN subinterface on the
606             node.
607         """
608         iface_key = Topology.get_interface_by_name(node, interface)
609         sw_if_index = Topology.get_interface_sw_index(node, iface_key)
610
611         output = VatExecutor.cmd_from_template(node, "create_vlan_subif.vat",
612                                                sw_if_index=sw_if_index,
613                                                vlan=vlan)
614         if output[0]["retval"] == 0:
615             sw_subif_idx = output[0]["sw_if_index"]
616             logger.trace('VLAN subinterface with sw_if_index {} and VLAN ID {} '
617                          'created on node {}'.format(sw_subif_idx,
618                                                      vlan, node['host']))
619             if_key = Topology.add_new_port(node, "vlan_subif")
620             Topology.update_interface_sw_if_index(node, if_key, sw_subif_idx)
621             ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_subif_idx)
622             Topology.update_interface_name(node, if_key, ifc_name)
623         else:
624             raise RuntimeError('Unable to create VLAN subinterface on node {}'
625                                .format(node['host']))
626
627         with VatTerminal(node, False) as vat:
628             vat.vat_terminal_exec_cmd('exec show interfaces')
629
630         return '{}.{}'.format(interface, vlan), sw_subif_idx
631
632     @staticmethod
633     def create_vxlan_interface(node, vni, source_ip, destination_ip):
634         """Create VXLAN interface and return sw if index of created interface.
635
636         Executes "vxlan_add_del_tunnel src {src} dst {dst} vni {vni}" VAT
637         command on the node.
638
639         :param node: Node where to create VXLAN interface.
640         :param vni: VXLAN Network Identifier.
641         :param source_ip: Source IP of a VXLAN Tunnel End Point.
642         :param destination_ip: Destination IP of a VXLAN Tunnel End Point.
643         :type node: dict
644         :type vni: int
645         :type source_ip: str
646         :type destination_ip: str
647         :returns: SW IF INDEX of created interface.
648         :rtype: int
649         :raises RuntimeError: if it is unable to create VxLAN interface on the
650             node.
651         """
652         output = VatExecutor.cmd_from_template(node, "vxlan_create.vat",
653                                                src=source_ip,
654                                                dst=destination_ip,
655                                                vni=vni)
656         output = output[0]
657
658         if output["retval"] == 0:
659             sw_if_idx = output["sw_if_index"]
660             if_key = Topology.add_new_port(node, "vxlan_tunnel")
661             Topology.update_interface_sw_if_index(node, if_key, sw_if_idx)
662             ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_idx)
663             Topology.update_interface_name(node, if_key, ifc_name)
664             return sw_if_idx
665         else:
666             raise RuntimeError("Unable to create VXLAN interface on node {0}"
667                                .format(node))
668
669     @staticmethod
670     def vxlan_dump(node, interface=None):
671         """Get VxLAN data for the given interface.
672
673         :param node: VPP node to get interface data from.
674         :param interface: Numeric index or name string of a specific interface.
675             If None, information about all VxLAN interfaces is returned.
676         :type node: dict
677         :type interface: int or str
678         :returns: Dictionary containing data for the given VxLAN interface or if
679             interface=None, the list of dictionaries with all VxLAN interfaces.
680         :rtype: dict or list
681         :raises TypeError: if the data type of interface is neither basestring
682             nor int.
683         """
684         param = "sw_if_index"
685         if interface is None:
686             param = ''
687             sw_if_index = ''
688         elif isinstance(interface, basestring):
689             sw_if_index = Topology.get_interface_sw_index(node, interface)
690         elif isinstance(interface, int):
691             sw_if_index = interface
692         else:
693             raise TypeError("Wrong interface format {0}".format(interface))
694
695         with VatTerminal(node) as vat:
696             response = vat.vat_terminal_exec_cmd_from_template(
697                 "vxlan_dump.vat", param=param, sw_if_index=sw_if_index)
698
699         if sw_if_index:
700             for vxlan in response[0]:
701                 if vxlan["sw_if_index"] == sw_if_index:
702                     return vxlan
703             return {}
704         return response[0]
705
706     @staticmethod
707     def vhost_user_dump(node):
708         """Get vhost-user data for the given node.
709
710         :param node: VPP node to get interface data from.
711         :type node: dict
712         :returns: List of dictionaries with all vhost-user interfaces.
713         :rtype: list
714         """
715         with VatTerminal(node) as vat:
716             response = vat.vat_terminal_exec_cmd_from_template(
717                 "vhost_user_dump.vat")
718
719         return response[0]
720
721     @staticmethod
722     def tap_dump(node, name=None):
723         """Get all TAP interface data from the given node, or data about
724         a specific TAP interface.
725
726         :param node: VPP node to get data from.
727         :param name: Optional name of a specific TAP interface.
728         :type node: dict
729         :type name: str
730         :returns: Dictionary of information about a specific TAP interface, or
731             a List of dictionaries containing all TAP data for the given node.
732         :rtype: dict or list
733         """
734         with VatTerminal(node) as vat:
735             response = vat.vat_terminal_exec_cmd_from_template(
736                 "tap_dump.vat")
737         if name is None:
738             return response[0]
739         else:
740             for item in response[0]:
741                 if name == item['dev_name']:
742                     return item
743             return {}
744
745     @staticmethod
746     def create_subinterface(node, interface, sub_id, outer_vlan_id=None,
747                             inner_vlan_id=None, type_subif=None):
748         """Create sub-interface on node. It is possible to set required
749         sub-interface type and VLAN tag(s).
750
751         :param node: Node to add sub-interface.
752         :param interface: Interface name on which create sub-interface.
753         :param sub_id: ID of the sub-interface to be created.
754         :param outer_vlan_id: Optional outer VLAN ID.
755         :param inner_vlan_id: Optional inner VLAN ID.
756         :param type_subif: Optional type of sub-interface. Values supported by
757             VPP: [no_tags] [one_tag] [two_tags] [dot1ad] [exact_match]
758             [default_sub]
759         :type node: dict
760         :type interface: str or int
761         :type sub_id: int
762         :type outer_vlan_id: int
763         :type inner_vlan_id: int
764         :type type_subif: str
765         :returns: Name and index of created sub-interface.
766         :rtype: tuple
767         :raises RuntimeError: If it is not possible to create sub-interface.
768         """
769
770         outer_vlan_id = 'outer_vlan_id {0}'.format(outer_vlan_id)\
771             if outer_vlan_id else ''
772
773         inner_vlan_id = 'inner_vlan_id {0}'.format(inner_vlan_id)\
774             if inner_vlan_id else ''
775
776         if type_subif is None:
777             type_subif = ''
778
779         if isinstance(interface, basestring):
780             iface_key = Topology.get_interface_by_name(node, interface)
781             sw_if_index = Topology.get_interface_sw_index(node, iface_key)
782         else:
783             sw_if_index = interface
784
785         output = VatExecutor.cmd_from_template(node, "create_sub_interface.vat",
786                                                sw_if_index=sw_if_index,
787                                                sub_id=sub_id,
788                                                outer_vlan_id=outer_vlan_id,
789                                                inner_vlan_id=inner_vlan_id,
790                                                type_subif=type_subif)
791
792         if output[0]["retval"] == 0:
793             sw_subif_idx = output[0]["sw_if_index"]
794             logger.trace('Created subinterface with index {}'
795                          .format(sw_subif_idx))
796             if_key = Topology.add_new_port(node, "subinterface")
797             Topology.update_interface_sw_if_index(node, if_key, sw_subif_idx)
798             ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_subif_idx)
799             Topology.update_interface_name(node, if_key, ifc_name)
800         else:
801             raise RuntimeError('Unable to create sub-interface on node {}'
802                                .format(node['host']))
803
804         with VatTerminal(node, json_param=False) as vat:
805             vat.vat_terminal_exec_cmd('exec show interfaces')
806
807         name = '{}.{}'.format(interface, sub_id)
808         return name, sw_subif_idx
809
810     @staticmethod
811     def create_gre_tunnel_interface(node, source_ip, destination_ip):
812         """Create GRE tunnel interface on node.
813
814         :param node: VPP node to add tunnel interface.
815         :param source_ip: Source of the GRE tunnel.
816         :param destination_ip: Destination of the GRE tunnel.
817         :type node: dict
818         :type source_ip: str
819         :type destination_ip: str
820         :returns: Name and index of created GRE tunnel interface.
821         :rtype: tuple
822         :raises RuntimeError: If unable to create GRE tunnel interface.
823         """
824         output = VatExecutor.cmd_from_template(node, "create_gre.vat",
825                                                src=source_ip,
826                                                dst=destination_ip)
827         output = output[0]
828
829         if output["retval"] == 0:
830             sw_if_idx = output["sw_if_index"]
831
832             vat_executor = VatExecutor()
833             vat_executor.execute_script_json_out("dump_interfaces.vat", node)
834             interface_dump_json = vat_executor.get_script_stdout()
835             name = VatJsonUtil.get_interface_name_from_json(
836                 interface_dump_json, sw_if_idx)
837
838             if_key = Topology.add_new_port(node, "gre_tunnel")
839             Topology.update_interface_sw_if_index(node, if_key, sw_if_idx)
840             Topology.update_interface_name(node, if_key, name)
841
842             return name, sw_if_idx
843         else:
844             raise RuntimeError('Unable to create GRE tunnel on node {}.'
845                                .format(node))
846
847     @staticmethod
848     def vpp_create_loopback(node):
849         """Create loopback interface on VPP node.
850
851         :param node: Node to create loopback interface on.
852         :type node: dict
853         :returns: SW interface index.
854         :rtype: int
855         :raises RuntimeError: If it is not possible to create loopback on the
856             node.
857         """
858         out = VatExecutor.cmd_from_template(node, "create_loopback.vat")
859         if out[0].get('retval') == 0:
860             sw_if_idx = out[0].get('sw_if_index')
861             if_key = Topology.add_new_port(node, "loopback")
862             Topology.update_interface_sw_if_index(node, if_key, sw_if_idx)
863             ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_idx)
864             Topology.update_interface_name(node, if_key, ifc_name)
865             return sw_if_idx
866         else:
867             raise RuntimeError('Create loopback failed on node "{}"'
868                                .format(node['host']))
869
870     @staticmethod
871     def vpp_create_bond_interface(node, mode, load_balance=None, mac=None):
872         """Create bond interface on VPP node.
873
874         :param node: DUT node from topology.
875         :param mode: Link bonding mode.
876         :param load_balance: Load balance (optional, valid for xor and lacp
877             modes, otherwise ignored).
878         :param mac: MAC address to assign to the bond interface (optional).
879         :type node: dict
880         :type mode: str
881         :type load_balance: str
882         :type mac: str
883         :returns: Interface key (name) in topology.
884         :rtype: str
885         :raises RuntimeError: If it is not possible to create bond interface on
886             the node.
887         """
888         hw_addr = '' if mac is None else 'hw-addr {mac}'.format(mac=mac)
889         lb = '' if load_balance is None \
890             else 'lb {lb}'.format(lb=load_balance)
891
892         output = VatExecutor.cmd_from_template(
893             node, 'create_bond_interface.vat', mode=mode, lb=lb, mac=hw_addr)
894
895         if output[0].get('retval') == 0:
896             sw_if_idx = output[0].get('sw_if_index')
897             InterfaceUtil.add_bond_eth_interface(node, sw_if_idx=sw_if_idx)
898             if_key = Topology.get_interface_by_sw_index(node, sw_if_idx)
899             return if_key
900         else:
901             raise RuntimeError('Create bond interface failed on node "{n}"'
902                                .format(n=node['host']))
903
904     @staticmethod
905     def add_bond_eth_interface(node, ifc_name=None, sw_if_idx=None):
906         """Add BondEthernet interface to current topology.
907
908         :param node: DUT node from topology.
909         :param ifc_name: Name of the BondEthernet interface.
910         :param sw_if_idx: SW interface index.
911         :type node: dict
912         :type ifc_name: str
913         :type sw_if_idx: int
914         """
915         if_key = Topology.add_new_port(node, 'eth_bond')
916
917         vat_executor = VatExecutor()
918         vat_executor.execute_script_json_out("dump_interfaces.vat", node)
919         interface_dump_json = vat_executor.get_script_stdout()
920
921         if ifc_name and sw_if_idx is None:
922             sw_if_idx = VatJsonUtil.get_interface_sw_index_from_json(
923                 interface_dump_json, ifc_name)
924         Topology.update_interface_sw_if_index(node, if_key, sw_if_idx)
925         if sw_if_idx and ifc_name is None:
926             ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_idx)
927         Topology.update_interface_name(node, if_key, ifc_name)
928         ifc_mac = VatJsonUtil.get_interface_mac_from_json(
929             interface_dump_json, sw_if_idx)
930         Topology.update_interface_mac_address(node, if_key, ifc_mac)
931
932     @staticmethod
933     def vpp_enslave_physical_interface(node, interface, bond_interface):
934         """Enslave physical interface to bond interface on VPP node.
935
936         :param node: DUT node from topology.
937         :param interface: Physical interface key from topology file.
938         :param bond_interface: Load balance
939         :type node: dict
940         :type interface: str
941         :type bond_interface: str
942         :raises RuntimeError: If it is not possible to enslave physical
943             interface to bond interface on the node.
944         """
945         ifc = Topology.get_interface_sw_index(node, interface)
946         bond_ifc = Topology.get_interface_sw_index(node, bond_interface)
947
948         output = VatExecutor.cmd_from_template(
949             node, 'enslave_physical_interface.vat', p_int=ifc, b_int=bond_ifc)
950
951         retval = output[0].get('retval', None)
952         if retval is None or int(retval) != 0:
953             raise RuntimeError('Enslave physical interface {ifc} to bond '
954                                'interface {bond} failed on node "{n}"'
955                                .format(ifc=interface, bond=bond_interface,
956                                        n=node['host']))
957
958     @staticmethod
959     def vpp_show_bond_data_on_node(node, details=False):
960         """Show (detailed) bond information on VPP node.
961
962         :param node: DUT node from topology.
963         :param details: If detailed information is required or not.
964         :type node: dict
965         :type details: bool
966         """
967         cmd = 'exec show bond details' if details else 'exec show bond'
968         with VatTerminal(node, json_param=False) as vat:
969             vat.vat_terminal_exec_cmd(cmd)
970
971     @staticmethod
972     def vpp_show_bond_data_on_all_nodes(nodes, details=False):
973         """Show (detailed) bond information on all VPP nodes in DICT__nodes.
974
975         :param nodes: Nodes in the topology.
976         :param details: If detailed information is required or not.
977         :type nodes: dict
978         :type details: bool
979         """
980         for node_data in nodes.values():
981             if node_data['type'] == NodeType.DUT:
982                 InterfaceUtil.vpp_show_bond_data_on_node(node_data, details)
983
984     @staticmethod
985     def vpp_enable_input_acl_interface(node, interface, ip_version,
986                                        table_index):
987         """Enable input acl on interface.
988
989         :param node: VPP node to setup interface for input acl.
990         :param interface: Interface to setup input acl.
991         :param ip_version: Version of IP protocol.
992         :param table_index: Classify table index.
993         :type node: dict
994         :type interface: str or int
995         :type ip_version: str
996         :type table_index: int
997         """
998         if isinstance(interface, basestring):
999             sw_if_index = Topology.get_interface_sw_index(node, interface)
1000         else:
1001             sw_if_index = interface
1002
1003         with VatTerminal(node) as vat:
1004             vat.vat_terminal_exec_cmd_from_template("input_acl_int.vat",
1005                                                     sw_if_index=sw_if_index,
1006                                                     ip_version=ip_version,
1007                                                     table_index=table_index)
1008
1009     @staticmethod
1010     def get_interface_classify_table(node, interface):
1011         """Get name of classify table for the given interface.
1012
1013         :param node: VPP node to get data from.
1014         :param interface: Name or sw_if_index of a specific interface.
1015         :type node: dict
1016         :type interface: str or int
1017         :returns: Classify table name.
1018         :rtype: str
1019         """
1020         if isinstance(interface, basestring):
1021             sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
1022         else:
1023             sw_if_index = interface
1024
1025         with VatTerminal(node) as vat:
1026             data = vat.vat_terminal_exec_cmd_from_template(
1027                 "classify_interface_table.vat",
1028                 sw_if_index=sw_if_index)
1029         return data[0]
1030
1031     @staticmethod
1032     def get_interface_vrf_table(node, interface):
1033         """Get vrf ID for the given interface.
1034
1035         :param node: VPP node.
1036         :param interface: Name or sw_if_index of a specific interface.
1037         :type node: dict
1038         :type interface: str or int
1039         :returns: vrf ID of the specified interface.
1040         :rtype: int
1041         """
1042
1043         if isinstance(interface, basestring):
1044             sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
1045         else:
1046             sw_if_index = interface
1047
1048         with VatTerminal(node) as vat:
1049             data = vat.vat_terminal_exec_cmd_from_template(
1050                 "interface_vrf_dump.vat",
1051                 sw_if_index=sw_if_index)
1052         return data[0]["vrf_id"]
1053
1054     @staticmethod
1055     def get_sw_if_index(node, interface_name):
1056         """Get sw_if_index for the given interface from actual interface dump.
1057
1058         :param node: VPP node to get interface data from.
1059         :param interface_name: Name of the specific interface.
1060         :type node: dict
1061         :type interface_name: str
1062         :returns: sw_if_index of the given interface.
1063         :rtype: str
1064         """
1065
1066         with VatTerminal(node) as vat:
1067             if_data = vat.vat_terminal_exec_cmd_from_template(
1068                 "interface_dump.vat")
1069         for interface in if_data[0]:
1070             if interface["interface_name"] == interface_name:
1071                 return interface["sw_if_index"]
1072
1073         return None
1074
1075     @staticmethod
1076     def vxlan_gpe_dump(node, interface_name=None):
1077         """Get VxLAN GPE data for the given interface.
1078
1079         :param node: VPP node to get interface data from.
1080         :param interface_name: Name of the specific interface. If None,
1081             information about all VxLAN GPE interfaces is returned.
1082         :type node: dict
1083         :type interface_name: str
1084         :returns: Dictionary containing data for the given VxLAN GPE interface
1085             or if interface=None, the list of dictionaries with all VxLAN GPE
1086             interfaces.
1087         :rtype: dict or list
1088         """
1089
1090         with VatTerminal(node) as vat:
1091             vxlan_gpe_data = vat.vat_terminal_exec_cmd_from_template(
1092                 "vxlan_gpe_dump.vat")
1093
1094         if interface_name:
1095             sw_if_index = InterfaceUtil.get_sw_if_index(node, interface_name)
1096             if sw_if_index:
1097                 for vxlan_gpe in vxlan_gpe_data[0]:
1098                     if vxlan_gpe["sw_if_index"] == sw_if_index:
1099                         return vxlan_gpe
1100             return {}
1101
1102         return vxlan_gpe_data[0]
1103
1104     @staticmethod
1105     def vpp_proxy_arp_interface_enable(node, interface):
1106         """Enable proxy ARP on interface.
1107
1108         :param node: VPP node to enable proxy ARP on interface.
1109         :param interface: Interface to enable proxy ARP.
1110         :type node: dict
1111         :type interface: str or int
1112         """
1113         if isinstance(interface, basestring):
1114             sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
1115         else:
1116             sw_if_index = interface
1117
1118         with VatTerminal(node) as vat:
1119             vat.vat_terminal_exec_cmd_from_template(
1120                 "proxy_arp_intfc_enable.vat",
1121                 sw_if_index=sw_if_index)
1122
1123     @staticmethod
1124     def vpp_ip_source_check_setup(node, interface):
1125         """Setup Reverse Path Forwarding source check on interface.
1126
1127         :param node: Node to setup RPF source check.
1128         :param interface: Interface name to setup RPF source check.
1129         :type node: dict
1130         :type interface: str
1131         """
1132         with VatTerminal(node) as vat:
1133             vat.vat_terminal_exec_cmd_from_template("ip_source_check.vat",
1134                                                     interface_name=interface)
1135
1136     @staticmethod
1137     def assign_interface_to_fib_table(node, interface, table_id, ipv6=False):
1138         """Assign VPP interface to specific VRF/FIB table.
1139
1140         :param node: VPP node where the FIB and interface are located.
1141         :param interface: Interface to be assigned to FIB.
1142         :param table_id: VRF table ID.
1143         :param ipv6: Assign to IPv6 table. Default False.
1144         :type node: dict
1145         :type interface: str or int
1146         :type table_id: int
1147         :type ipv6: bool
1148         """
1149         if isinstance(interface, basestring):
1150             sw_if_index = Topology.get_interface_sw_index(node, interface)
1151         else:
1152             sw_if_index = interface
1153
1154         ipv6 = 'ipv6' if ipv6 else ''
1155
1156         with VatTerminal(node) as vat:
1157             ret = vat.vat_terminal_exec_cmd_from_template(
1158                 "set_fib_to_interface.vat",
1159                 sw_index=sw_if_index, vrf=table_id, ipv6=ipv6)
1160
1161         if ret[0]["retval"] != 0:
1162             raise RuntimeError('Unable to assign interface to FIB node {}.'
1163                                .format(node))
1164
1165     @staticmethod
1166     def set_linux_interface_mac(node, interface, mac, namespace=None):
1167         """Set MAC address for interface in linux.
1168
1169         :param node: Node where to execute command.
1170         :param interface: Interface in namespace.
1171         :param mac: MAC to be assigned to interface.
1172         :param namespace: Execute command in namespace. Optional
1173         :type node: dict
1174         :type interface: str
1175         :type mac: str
1176         :type namespace: str
1177         """
1178         if namespace is not None:
1179             cmd = 'ip netns exec {} ip link set {} address {}'.format(
1180                 namespace, interface, mac)
1181         else:
1182             cmd = 'ip link set {} address {}'.format(interface, mac)
1183         exec_cmd_no_error(node, cmd, sudo=True)