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