Add optional args to traffic script arg parser
[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
176         :param node: VPP node to get interface data from.
177         :param interface: Numeric index or name string of a specific interface.
178         :type node: dict
179         :type interface: int or str
180         :return: List of dictionaries containing data for each interface, or a
181         single dictionary for the specified interface.
182         :rtype: list or dict
183         """
184         with VatTerminal(node) as vat:
185             response = vat.vat_terminal_exec_cmd_from_template(
186                 "interface_dump.vat")
187
188         data = response[0]
189
190         if interface is not None:
191             if isinstance(interface, basestring):
192                 sw_if_index = Topology.get_interface_sw_index(node, interface)
193             else:
194                 sw_if_index = interface
195
196             for data_if in data:
197                 if data_if["sw_if_index"] == sw_if_index:
198
199                     return data_if
200
201         return data
202
203     @staticmethod
204     def tg_set_interface_driver(node, pci_addr, driver):
205         """Set interface driver on the TG node.
206
207         :param node: Node to set interface driver on (must be TG node).
208         :param pci_addr: PCI address of the interface.
209         :param driver: Driver name.
210         :type node: dict
211         :type pci_addr: str
212         :type driver: str
213         """
214         old_driver = InterfaceUtil.tg_get_interface_driver(node, pci_addr)
215         if old_driver == driver:
216             return
217
218         ssh = SSH()
219         ssh.connect(node)
220
221         # Unbind from current driver
222         if old_driver is not None:
223             cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/unbind"'.format(
224                 pci_addr, old_driver)
225             (ret_code, _, _) = ssh.exec_command_sudo(cmd)
226             if int(ret_code) != 0:
227                 raise Exception("'{0}' failed on '{1}'".format(cmd,
228                                                                node['host']))
229
230         # Bind to the new driver
231         cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/bind"'.format(
232             pci_addr, driver)
233         (ret_code, _, _) = ssh.exec_command_sudo(cmd)
234         if int(ret_code) != 0:
235             raise Exception("'{0}' failed on '{1}'".format(cmd, node['host']))
236
237     @staticmethod
238     def tg_get_interface_driver(node, pci_addr):
239         """Get interface driver from the TG node.
240
241         :param node: Node to get interface driver on (must be TG node).
242         :param pci_addr: PCI address of the interface.
243         :type node: dict
244         :type pci_addr: str
245         :return: Interface driver or None if not found.
246         :rtype: str
247
248         .. note::
249             # lspci -vmmks 0000:00:05.0
250             Slot:   00:05.0
251             Class:  Ethernet controller
252             Vendor: Red Hat, Inc
253             Device: Virtio network device
254             SVendor:        Red Hat, Inc
255             SDevice:        Device 0001
256             PhySlot:        5
257             Driver: virtio-pci
258         """
259         ssh = SSH()
260         ssh.connect(node)
261
262         cmd = 'lspci -vmmks {0}'.format(pci_addr)
263
264         (ret_code, stdout, _) = ssh.exec_command(cmd)
265         if int(ret_code) != 0:
266             raise Exception("'{0}' failed on '{1}'".format(cmd, node['host']))
267
268         for line in stdout.splitlines():
269             if len(line) == 0:
270                 continue
271             (name, value) = line.split("\t", 1)
272             if name == 'Driver:':
273                 return value
274
275         return None
276
277     @staticmethod
278     def tg_set_interfaces_udev_rules(node):
279         """Set udev rules for interfaces.
280
281         Create udev rules file in /etc/udev/rules.d where are rules for each
282         interface used by TG node, based on MAC interface has specific name.
283         So after unbind and bind again to kernel driver interface has same
284         name as before. This must be called after TG has set name for each
285         port in topology dictionary.
286         udev rule example
287         SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="52:54:00:e1:8a:0f",
288         NAME="eth1"
289
290         :param node: Node to set udev rules on (must be TG node).
291         :type node: dict
292         """
293         ssh = SSH()
294         ssh.connect(node)
295
296         cmd = 'rm -f {0}'.format(InterfaceUtil.__UDEV_IF_RULES_FILE)
297         (ret_code, _, _) = ssh.exec_command_sudo(cmd)
298         if int(ret_code) != 0:
299             raise Exception("'{0}' failed on '{1}'".format(cmd, node['host']))
300
301         for interface in node['interfaces'].values():
302             rule = 'SUBSYSTEM==\\"net\\", ACTION==\\"add\\", ATTR{address}' + \
303                    '==\\"' + interface['mac_address'] + '\\", NAME=\\"' + \
304                    interface['name'] + '\\"'
305             cmd = 'sh -c "echo \'{0}\' >> {1}"'.format(
306                 rule, InterfaceUtil.__UDEV_IF_RULES_FILE)
307             (ret_code, _, _) = ssh.exec_command_sudo(cmd)
308             if int(ret_code) != 0:
309                 raise Exception("'{0}' failed on '{1}'".format(cmd,
310                                                                node['host']))
311
312         cmd = '/etc/init.d/udev restart'
313         ssh.exec_command_sudo(cmd)
314
315     @staticmethod
316     def tg_set_interfaces_default_driver(node):
317         """Set interfaces default driver specified in topology yaml file.
318
319         :param node: Node to setup interfaces driver on (must be TG node).
320         :type node: dict
321         """
322         for interface in node['interfaces'].values():
323             InterfaceUtil.tg_set_interface_driver(node,
324                                                   interface['pci_address'],
325                                                   interface['driver'])
326
327     @staticmethod
328     def update_vpp_interface_data_on_node(node):
329         """Update vpp generated interface data for a given node in DICT__nodes.
330
331         Updates interface names, software if index numbers and any other details
332         generated specifically by vpp that are unknown before testcase run.
333         It does this by dumping interface list to JSON output from all
334         devices using vpp_api_test, and pairing known information from topology
335         (mac address/pci address of interface) to state from VPP.
336
337         :param node: Node selected from DICT__nodes.
338         :type node: dict
339         """
340         vat_executor = VatExecutor()
341         vat_executor.execute_script_json_out("dump_interfaces.vat", node)
342         interface_dump_json = vat_executor.get_script_stdout()
343         VatJsonUtil.update_vpp_interface_data_from_json(node,
344                                                         interface_dump_json)
345
346     @staticmethod
347     def update_tg_interface_data_on_node(node):
348         """Update interface name for TG/linux node in DICT__nodes.
349
350         :param node: Node selected from DICT__nodes.
351         :type node: dict
352
353         .. note::
354             # for dev in `ls /sys/class/net/`;
355             > do echo "\"`cat /sys/class/net/$dev/address`\": \"$dev\""; done
356             "52:54:00:9f:82:63": "eth0"
357             "52:54:00:77:ae:a9": "eth1"
358             "52:54:00:e1:8a:0f": "eth2"
359             "00:00:00:00:00:00": "lo"
360
361         .. todo:: parse lshw -json instead
362         """
363         # First setup interface driver specified in yaml file
364         InterfaceUtil.tg_set_interfaces_default_driver(node)
365
366         # Get interface names
367         ssh = SSH()
368         ssh.connect(node)
369
370         cmd = ('for dev in `ls /sys/class/net/`; do echo "\\"`cat '
371                '/sys/class/net/$dev/address`\\": \\"$dev\\""; done;')
372
373         (ret_code, stdout, _) = ssh.exec_command(cmd)
374         if int(ret_code) != 0:
375             raise Exception('Get interface name and MAC failed')
376         tmp = "{" + stdout.rstrip().replace('\n', ',') + "}"
377         interfaces = JsonParser().parse_data(tmp)
378         for interface in node['interfaces'].values():
379             name = interfaces.get(interface['mac_address'])
380             if name is None:
381                 continue
382             interface['name'] = name
383
384         # Set udev rules for interfaces
385         InterfaceUtil.tg_set_interfaces_udev_rules(node)
386
387     @staticmethod
388     def update_all_interface_data_on_all_nodes(nodes):
389         """Update interface names on all nodes in DICT__nodes.
390
391         This method updates the topology dictionary by querying interface lists
392         of all nodes mentioned in the topology dictionary.
393
394         :param nodes: Nodes in the topology.
395         :type nodes: dict
396         """
397         for node_data in nodes.values():
398             if node_data['type'] == NodeType.DUT:
399                 InterfaceUtil.update_vpp_interface_data_on_node(node_data)
400             elif node_data['type'] == NodeType.TG:
401                 InterfaceUtil.update_tg_interface_data_on_node(node_data)
402
403     @staticmethod
404     def create_vlan_subinterface(node, interface, vlan):
405         """Create VLAN subinterface on node.
406
407         :param node: Node to add VLAN subinterface on.
408         :param interface: Interface name on which create VLAN subinterface.
409         :param vlan: VLAN ID of the subinterface to be created.
410         :type node: dict
411         :type interface: str
412         :type vlan: int
413         :return: Name and index of created subinterface.
414         :rtype: tuple
415         """
416         sw_if_index = Topology.get_interface_sw_index(node, interface)
417
418         output = VatExecutor.cmd_from_template(node, "create_vlan_subif.vat",
419                                                sw_if_index=sw_if_index,
420                                                vlan=vlan)
421         if output[0]["retval"] == 0:
422             sw_subif_index = output[0]["sw_if_index"]
423             logger.trace('VLAN subinterface with sw_if_index {} and VLAN ID {} '
424                          'created on node {}'.format(sw_subif_index,
425                                                      vlan, node['host']))
426         else:
427             raise RuntimeError('Unable to create VLAN subinterface on node {}'
428                                .format(node['host']))
429
430         with VatTerminal(node, False) as vat:
431             vat.vat_terminal_exec_cmd('exec show interfaces')
432
433         return '{}.{}'.format(interface, vlan), sw_subif_index
434
435     @staticmethod
436     def create_vxlan_interface(node, vni, source_ip, destination_ip):
437         """Create VXLAN interface and return sw if index of created interface.
438
439         Executes "vxlan_add_del_tunnel src {src} dst {dst} vni {vni}" VAT
440         command on the node.
441
442         :param node: Node where to create VXLAN interface.
443         :param vni: VXLAN Network Identifier.
444         :param source_ip: Source IP of a VXLAN Tunnel End Point.
445         :param destination_ip: Destination IP of a VXLAN Tunnel End Point.
446         :type node: dict
447         :type vni: int
448         :type source_ip: str
449         :type destination_ip: str
450         :return: SW IF INDEX of created interface.
451         :rtype: int
452         """
453         output = VatExecutor.cmd_from_template(node, "vxlan_create.vat",
454                                                src=source_ip,
455                                                dst=destination_ip,
456                                                vni=vni)
457         output = output[0]
458
459         if output["retval"] == 0:
460             return output["sw_if_index"]
461         else:
462             raise RuntimeError('Unable to create VXLAN interface on node {}'
463                                .format(node))