Interface VxLAN test
[csit.git] / resources / libraries / python / InterfaceUtil.py
1 # Copyright (c) 2016 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):
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 name or sw_if_index.
42         :param state: One of 'up' or 'down'.
43         :type node: dict
44         :type interface: str or int
45         :type state: str
46         :return: nothing
47         """
48         if node['type'] == NodeType.DUT:
49             if state == 'up':
50                 state = 'admin-up'
51             elif state == 'down':
52                 state = 'admin-down'
53             else:
54                 raise ValueError('Unexpected interface state: {}'.format(state))
55
56             if isinstance(interface, basestring):
57                 sw_if_index = Topology.get_interface_sw_index(node, interface)
58             else:
59                 sw_if_index = interface
60
61             VatExecutor.cmd_from_template(node, 'set_if_state.vat',
62                                           sw_if_index=sw_if_index, state=state)
63
64         elif node['type'] == NodeType.TG or node['type'] == NodeType.VM:
65             cmd = 'ip link set {} {}'.format(interface, state)
66             exec_cmd_no_error(node, cmd, sudo=True)
67         else:
68             raise Exception('Node {} has unknown NodeType: "{}"'.
69                             format(node['host'], node['type']))
70
71     @staticmethod
72     def set_interface_ethernet_mtu(node, interface, mtu):
73         """Set Ethernet MTU for specified interface.
74
75         Function can be used only for TGs.
76
77         :param node: Node where the interface is.
78         :param interface: Interface name.
79         :param mtu: MTU to set.
80         :type node: dict
81         :type interface: str
82         :type mtu: int
83         :return: nothing
84         """
85         if node['type'] == NodeType.DUT:
86             ValueError('Node {}: Setting Ethernet MTU for interface '
87                        'on DUT nodes not supported', node['host'])
88         elif node['type'] == NodeType.TG:
89             cmd = 'ip link set {} mtu {}'.format(interface, mtu)
90             exec_cmd_no_error(node, cmd, sudo=True)
91         else:
92             raise ValueError('Node {} has unknown NodeType: "{}"'.
93                              format(node['host'], node['type']))
94
95     @staticmethod
96     def set_default_ethernet_mtu_on_all_interfaces_on_node(node):
97         """Set default Ethernet MTU on all interfaces on node.
98
99         Function can be used only for TGs.
100
101         :param node: Node where to set default MTU.
102         :type node: dict
103         :return: nothing
104         """
105         for ifc in node['interfaces'].values():
106             InterfaceUtil.set_interface_ethernet_mtu(node, ifc['name'], 1500)
107
108     @staticmethod
109     def vpp_node_interfaces_ready_wait(node, timeout=10):
110         """Wait until all interfaces with admin-up are in link-up state.
111
112         :param node: Node to wait on.
113         :param timeout: Waiting timeout in seconds (optional, default 10s).
114         :type node: dict
115         :type timeout: int
116         :raises: RuntimeError if the timeout period value has elapsed.
117         """
118         if_ready = False
119         not_ready = []
120         start = time()
121         while not if_ready:
122             out = InterfaceUtil.vpp_get_interface_data(node)
123             if time() - start > timeout:
124                 for interface in out:
125                     if interface.get('admin_up_down') == 1:
126                         if interface.get('link_up_down') != 1:
127                             logger.debug('{0} link-down'.format(
128                                 interface.get('interface_name')))
129                 raise RuntimeError('timeout, not up {0}'.format(not_ready))
130             not_ready = []
131             for interface in out:
132                 if interface.get('admin_up_down') == 1:
133                     if interface.get('link_up_down') != 1:
134                         not_ready.append(interface.get('interface_name'))
135             if not not_ready:
136                 if_ready = True
137             else:
138                 logger.debug('Interfaces still in link-down state: {0}, '
139                              'waiting...'.format(not_ready))
140                 sleep(1)
141
142     @staticmethod
143     def vpp_nodes_interfaces_ready_wait(nodes, timeout=10):
144         """Wait until all interfaces with admin-up are in link-up state for
145         listed nodes.
146
147         :param nodes: List of nodes to wait on.
148         :param timeout: Seconds to wait per node for all interfaces to come up.
149         :type nodes: list
150         :type timeout: int
151         :raises: RuntimeError if the timeout period value has elapsed.
152         """
153         for node in nodes:
154             InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout)
155
156     @staticmethod
157     def all_vpp_interfaces_ready_wait(nodes, timeout=10):
158         """Wait until all interfaces with admin-up are in link-up state for all
159         nodes in the topology.
160
161         :param nodes: Nodes in the topology.
162         :param timeout: Seconds to wait per node for all interfaces to come up.
163         :type nodes: dict
164         :type timeout: int
165         :raises: RuntimeError if the timeout period value has elapsed.
166         """
167         for node in nodes.values():
168             if node['type'] == NodeType.DUT:
169                 InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout)
170
171     @staticmethod
172     def vpp_get_interface_data(node, interface=None):
173         """Get all interface data from a VPP node. If a name or
174         sw_interface_index is provided, return only data for the matching
175         interface.
176
177         :param node: VPP node to get interface data from.
178         :param interface: Numeric index or name string of a specific interface.
179         :type node: dict
180         :type interface: int or str
181         :return: List of dictionaries containing data for each interface, or a
182         single dictionary for the specified interface.
183         :rtype: list or dict
184         """
185         with VatTerminal(node) as vat:
186             response = vat.vat_terminal_exec_cmd_from_template(
187                 "interface_dump.vat")
188
189         data = response[0]
190
191         if interface is not None:
192             if isinstance(interface, basestring):
193                 sw_if_index = Topology.get_interface_sw_index(node, interface)
194             else:
195                 sw_if_index = interface
196
197             for data_if in data:
198                 if data_if["sw_if_index"] == sw_if_index:
199
200                     return data_if
201
202         return data
203
204     @staticmethod
205     def vpp_get_interface_ip_addresses(node, interface, ip_version):
206         """Get list of IP addresses from an interface on a VPP node.
207
208          :param node: VPP node to get data from.
209          :param interface: Name of an interface on the VPP node.
210          :param ip_version: IP protocol version (ipv4 or ipv6).
211          :type node: dict
212          :type interface: str
213          :type ip_version: str
214          :return: List of dictionaries, each containing IP address, subnet
215          prefix length and also the subnet mask for ipv4 addresses.
216          Note: A single interface may have multiple IP addresses assigned.
217          :rtype: list
218         """
219         sw_if_index = Topology.get_interface_sw_index(node, interface)
220
221         with VatTerminal(node) as vat:
222             response = vat.vat_terminal_exec_cmd_from_template(
223                 "ip_address_dump.vat", ip_version=ip_version,
224                 sw_if_index=sw_if_index)
225
226         data = response[0]
227
228         if ip_version == "ipv4":
229             for item in data:
230                 item["netmask"] = convert_ipv4_netmask_prefix(
231                     item["prefix_length"])
232         return data
233
234     @staticmethod
235     def tg_set_interface_driver(node, pci_addr, driver):
236         """Set interface driver on the TG node.
237
238         :param node: Node to set interface driver on (must be TG node).
239         :param pci_addr: PCI address of the interface.
240         :param driver: Driver name.
241         :type node: dict
242         :type pci_addr: str
243         :type driver: str
244         """
245         old_driver = InterfaceUtil.tg_get_interface_driver(node, pci_addr)
246         if old_driver == driver:
247             return
248
249         ssh = SSH()
250         ssh.connect(node)
251
252         # Unbind from current driver
253         if old_driver is not None:
254             cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/unbind"'.format(
255                 pci_addr, old_driver)
256             (ret_code, _, _) = ssh.exec_command_sudo(cmd)
257             if int(ret_code) != 0:
258                 raise Exception("'{0}' failed on '{1}'".format(cmd,
259                                                                node['host']))
260
261         # Bind to the new driver
262         cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/bind"'.format(
263             pci_addr, driver)
264         (ret_code, _, _) = ssh.exec_command_sudo(cmd)
265         if int(ret_code) != 0:
266             raise Exception("'{0}' failed on '{1}'".format(cmd, node['host']))
267
268     @staticmethod
269     def tg_get_interface_driver(node, pci_addr):
270         """Get interface driver from the TG node.
271
272         :param node: Node to get interface driver on (must be TG node).
273         :param pci_addr: PCI address of the interface.
274         :type node: dict
275         :type pci_addr: str
276         :return: Interface driver or None if not found.
277         :rtype: str
278
279         .. note::
280             # lspci -vmmks 0000:00:05.0
281             Slot:   00:05.0
282             Class:  Ethernet controller
283             Vendor: Red Hat, Inc
284             Device: Virtio network device
285             SVendor:        Red Hat, Inc
286             SDevice:        Device 0001
287             PhySlot:        5
288             Driver: virtio-pci
289         """
290         ssh = SSH()
291         ssh.connect(node)
292
293         cmd = 'lspci -vmmks {0}'.format(pci_addr)
294
295         (ret_code, stdout, _) = ssh.exec_command(cmd)
296         if int(ret_code) != 0:
297             raise Exception("'{0}' failed on '{1}'".format(cmd, node['host']))
298
299         for line in stdout.splitlines():
300             if len(line) == 0:
301                 continue
302             (name, value) = line.split("\t", 1)
303             if name == 'Driver:':
304                 return value
305
306         return None
307
308     @staticmethod
309     def tg_set_interfaces_udev_rules(node):
310         """Set udev rules for interfaces.
311
312         Create udev rules file in /etc/udev/rules.d where are rules for each
313         interface used by TG node, based on MAC interface has specific name.
314         So after unbind and bind again to kernel driver interface has same
315         name as before. This must be called after TG has set name for each
316         port in topology dictionary.
317         udev rule example
318         SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="52:54:00:e1:8a:0f",
319         NAME="eth1"
320
321         :param node: Node to set udev rules on (must be TG node).
322         :type node: dict
323         """
324         ssh = SSH()
325         ssh.connect(node)
326
327         cmd = 'rm -f {0}'.format(InterfaceUtil.__UDEV_IF_RULES_FILE)
328         (ret_code, _, _) = ssh.exec_command_sudo(cmd)
329         if int(ret_code) != 0:
330             raise Exception("'{0}' failed on '{1}'".format(cmd, node['host']))
331
332         for interface in node['interfaces'].values():
333             rule = 'SUBSYSTEM==\\"net\\", ACTION==\\"add\\", ATTR{address}' + \
334                    '==\\"' + interface['mac_address'] + '\\", NAME=\\"' + \
335                    interface['name'] + '\\"'
336             cmd = 'sh -c "echo \'{0}\' >> {1}"'.format(
337                 rule, InterfaceUtil.__UDEV_IF_RULES_FILE)
338             (ret_code, _, _) = ssh.exec_command_sudo(cmd)
339             if int(ret_code) != 0:
340                 raise Exception("'{0}' failed on '{1}'".format(cmd,
341                                                                node['host']))
342
343         cmd = '/etc/init.d/udev restart'
344         ssh.exec_command_sudo(cmd)
345
346     @staticmethod
347     def tg_set_interfaces_default_driver(node):
348         """Set interfaces default driver specified in topology yaml file.
349
350         :param node: Node to setup interfaces driver on (must be TG node).
351         :type node: dict
352         """
353         for interface in node['interfaces'].values():
354             InterfaceUtil.tg_set_interface_driver(node,
355                                                   interface['pci_address'],
356                                                   interface['driver'])
357
358     @staticmethod
359     def update_vpp_interface_data_on_node(node):
360         """Update vpp generated interface data for a given node in DICT__nodes.
361
362         Updates interface names, software if index numbers and any other details
363         generated specifically by vpp that are unknown before testcase run.
364         It does this by dumping interface list to JSON output from all
365         devices using vpp_api_test, and pairing known information from topology
366         (mac address/pci address of interface) to state from VPP.
367
368         :param node: Node selected from DICT__nodes.
369         :type node: dict
370         """
371         vat_executor = VatExecutor()
372         vat_executor.execute_script_json_out("dump_interfaces.vat", node)
373         interface_dump_json = vat_executor.get_script_stdout()
374         VatJsonUtil.update_vpp_interface_data_from_json(node,
375                                                         interface_dump_json)
376
377     @staticmethod
378     def update_tg_interface_data_on_node(node):
379         """Update interface name for TG/linux node in DICT__nodes.
380
381         :param node: Node selected from DICT__nodes.
382         :type node: dict
383
384         .. note::
385             # for dev in `ls /sys/class/net/`;
386             > do echo "\"`cat /sys/class/net/$dev/address`\": \"$dev\""; done
387             "52:54:00:9f:82:63": "eth0"
388             "52:54:00:77:ae:a9": "eth1"
389             "52:54:00:e1:8a:0f": "eth2"
390             "00:00:00:00:00:00": "lo"
391
392         .. todo:: parse lshw -json instead
393         """
394         # First setup interface driver specified in yaml file
395         InterfaceUtil.tg_set_interfaces_default_driver(node)
396
397         # Get interface names
398         ssh = SSH()
399         ssh.connect(node)
400
401         cmd = ('for dev in `ls /sys/class/net/`; do echo "\\"`cat '
402                '/sys/class/net/$dev/address`\\": \\"$dev\\""; done;')
403
404         (ret_code, stdout, _) = ssh.exec_command(cmd)
405         if int(ret_code) != 0:
406             raise Exception('Get interface name and MAC failed')
407         tmp = "{" + stdout.rstrip().replace('\n', ',') + "}"
408         interfaces = JsonParser().parse_data(tmp)
409         for interface in node['interfaces'].values():
410             name = interfaces.get(interface['mac_address'])
411             if name is None:
412                 continue
413             interface['name'] = name
414
415         # Set udev rules for interfaces
416         InterfaceUtil.tg_set_interfaces_udev_rules(node)
417
418     @staticmethod
419     def update_all_interface_data_on_all_nodes(nodes):
420         """Update interface names on all nodes in DICT__nodes.
421
422         This method updates the topology dictionary by querying interface lists
423         of all nodes mentioned in the topology dictionary.
424
425         :param nodes: Nodes in the topology.
426         :type nodes: dict
427         """
428         for node_data in nodes.values():
429             if node_data['type'] == NodeType.DUT:
430                 InterfaceUtil.update_vpp_interface_data_on_node(node_data)
431             elif node_data['type'] == NodeType.TG:
432                 InterfaceUtil.update_tg_interface_data_on_node(node_data)
433
434     @staticmethod
435     def create_vlan_subinterface(node, interface, vlan):
436         """Create VLAN subinterface on node.
437
438         :param node: Node to add VLAN subinterface on.
439         :param interface: Interface name on which create VLAN subinterface.
440         :param vlan: VLAN ID of the subinterface to be created.
441         :type node: dict
442         :type interface: str
443         :type vlan: int
444         :return: Name and index of created subinterface.
445         :rtype: tuple
446         """
447         sw_if_index = Topology.get_interface_sw_index(node, interface)
448
449         output = VatExecutor.cmd_from_template(node, "create_vlan_subif.vat",
450                                                sw_if_index=sw_if_index,
451                                                vlan=vlan)
452         if output[0]["retval"] == 0:
453             sw_subif_index = output[0]["sw_if_index"]
454             logger.trace('VLAN subinterface with sw_if_index {} and VLAN ID {} '
455                          'created on node {}'.format(sw_subif_index,
456                                                      vlan, node['host']))
457         else:
458             raise RuntimeError('Unable to create VLAN subinterface on node {}'
459                                .format(node['host']))
460
461         with VatTerminal(node, False) as vat:
462             vat.vat_terminal_exec_cmd('exec show interfaces')
463
464         return '{}.{}'.format(interface, vlan), sw_subif_index
465
466     @staticmethod
467     def create_vxlan_interface(node, vni, source_ip, destination_ip):
468         """Create VXLAN interface and return sw if index of created interface.
469
470         Executes "vxlan_add_del_tunnel src {src} dst {dst} vni {vni}" VAT
471         command on the node.
472
473         :param node: Node where to create VXLAN interface.
474         :param vni: VXLAN Network Identifier.
475         :param source_ip: Source IP of a VXLAN Tunnel End Point.
476         :param destination_ip: Destination IP of a VXLAN Tunnel End Point.
477         :type node: dict
478         :type vni: int
479         :type source_ip: str
480         :type destination_ip: str
481         :return: SW IF INDEX of created interface.
482         :rtype: int
483         """
484         output = VatExecutor.cmd_from_template(node, "vxlan_create.vat",
485                                                src=source_ip,
486                                                dst=destination_ip,
487                                                vni=vni)
488         output = output[0]
489
490         if output["retval"] == 0:
491             return output["sw_if_index"]
492         else:
493             raise RuntimeError('Unable to create VXLAN interface on node {}'
494                                .format(node))
495
496     @staticmethod
497     def vxlan_dump(node, interface):
498         """Get VxLAN data for the given interface.
499
500         :param node: VPP node to get interface data from.
501         :param interface: Numeric index or name string of a specific interface.
502         :type node: dict
503         :type interface: int or str
504         :return: Dictionary containing data for the given VxLAN.
505         :rtype dict
506         """
507
508         if isinstance(interface, basestring):
509             sw_if_index = Topology.get_interface_sw_index(node, interface)
510         else:
511             sw_if_index = interface
512
513         with VatTerminal(node) as vat:
514             response = vat.vat_terminal_exec_cmd_from_template(
515                 "vxlan_dump.vat", sw_if_index=sw_if_index)
516
517         for vxlan in response[0]:
518             if vxlan["sw_if_index"] == sw_if_index:
519                 return vxlan
520         return {}
521
522     @staticmethod
523     def create_subinterface(node, interface, sub_id, outer_vlan_id,
524                             inner_vlan_id, type_subif):
525         """Create sub-interface on node.
526
527         :param node: Node to add sub-interface.
528         :param interface: Interface name on which create sub-interface.
529         :param sub_id: ID of the sub-interface to be created.
530         :param outer_vlan_id: Outer VLAN ID.
531         :param inner_vlan_id: Inner VLAN ID.
532         :param type_subif: Type of sub-interface.
533         :type node: dict
534         :type interface: str or int
535         :type sub_id: int
536         :type outer_vlan_id: int
537         :type inner_vlan_id: int
538         :type type_subif: str
539         :return: name and index of created sub-interface
540         :rtype: tuple
541         """
542
543         if isinstance(interface, basestring):
544             sw_if_index = Topology.get_interface_sw_index(node, interface)
545         else:
546             sw_if_index = interface
547
548         output = VatExecutor.cmd_from_template(node, "create_sub_interface.vat",
549                                                sw_if_index=sw_if_index,
550                                                sub_id=sub_id,
551                                                outer_vlan_id=outer_vlan_id,
552                                                inner_vlan_id=inner_vlan_id,
553                                                type_subif=type_subif)
554
555         if output[0]["retval"] == 0:
556             sw_subif_index = output[0]["sw_if_index"]
557             logger.trace('Created subinterface with index {}'
558                          .format(sw_subif_index))
559         else:
560             raise RuntimeError('Unable to create subinterface on node {}'
561                                .format(node['host']))
562
563         with VatTerminal(node) as vat:
564             vat.vat_terminal_exec_cmd('exec show interfaces')
565
566         return '{}.{}'.format(interface, sub_id), sw_subif_index
567
568     @staticmethod
569     def vpp_create_loopback(node):
570         """Create loopback interface on VPP node.
571
572         :param node: Node to create loopback interface on.
573         :type node: dict
574         :return: SW interface index.
575         :rtype: int
576         """
577         out = VatExecutor.cmd_from_template(node, "create_loopback.vat")
578         if out[0].get('retval') == 0:
579             return out[0].get('sw_if_index')
580         else:
581             raise RuntimeError('Create loopback failed on node "{}"'
582                                .format(node['host']))