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