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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Interface util library"""
16 from time import time, sleep
18 from robot.api import logger
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
29 class InterfaceUtil(object):
30 """General utilities for managing interfaces"""
32 __UDEV_IF_RULES_FILE = '/etc/udev/rules.d/10-network.rules'
35 def set_interface_state(node, interface, state):
36 """Set interface state on a node.
38 Function can be used for DUTs as well as for TGs.
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'.
44 :type interface: str or int
48 if node['type'] == NodeType.DUT:
54 raise ValueError('Unexpected interface state: {}'.format(state))
56 if isinstance(interface, basestring):
57 sw_if_index = Topology.get_interface_sw_index(node, interface)
59 sw_if_index = interface
61 VatExecutor.cmd_from_template(node, 'set_if_state.vat',
62 sw_if_index=sw_if_index, state=state)
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)
68 raise Exception('Node {} has unknown NodeType: "{}"'.
69 format(node['host'], node['type']))
72 def set_interface_ethernet_mtu(node, interface, mtu):
73 """Set Ethernet MTU for specified interface.
75 Function can be used only for TGs.
77 :param node: Node where the interface is.
78 :param interface: Interface name.
79 :param mtu: MTU to set.
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)
92 raise ValueError('Node {} has unknown NodeType: "{}"'.
93 format(node['host'], node['type']))
96 def set_default_ethernet_mtu_on_all_interfaces_on_node(node):
97 """Set default Ethernet MTU on all interfaces on node.
99 Function can be used only for TGs.
101 :param node: Node where to set default MTU.
105 for ifc in node['interfaces'].values():
106 InterfaceUtil.set_interface_ethernet_mtu(node, ifc['name'], 1500)
109 def vpp_node_interfaces_ready_wait(node, timeout=10):
110 """Wait until all interfaces with admin-up are in link-up state.
112 :param node: Node to wait on.
113 :param timeout: Waiting timeout in seconds (optional, default 10s).
116 :raises: RuntimeError if the timeout period value has elapsed.
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))
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'))
138 logger.debug('Interfaces still in link-down state: {0}, '
139 'waiting...'.format(not_ready))
143 def vpp_nodes_interfaces_ready_wait(nodes, timeout=10):
144 """Wait until all interfaces with admin-up are in link-up state for
147 :param nodes: List of nodes to wait on.
148 :param timeout: Seconds to wait per node for all interfaces to come up.
151 :raises: RuntimeError if the timeout period value has elapsed.
154 InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout)
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.
161 :param nodes: Nodes in the topology.
162 :param timeout: Seconds to wait per node for all interfaces to come up.
165 :raises: RuntimeError if the timeout period value has elapsed.
167 for node in nodes.values():
168 if node['type'] == NodeType.DUT:
169 InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout)
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
177 :param node: VPP node to get interface data from.
178 :param interface: Numeric index or name string of a specific interface.
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.
185 with VatTerminal(node) as vat:
186 response = vat.vat_terminal_exec_cmd_from_template(
187 "interface_dump.vat")
191 if interface is not None:
192 if isinstance(interface, basestring):
193 param = "interface_name"
194 elif isinstance(interface, int):
195 param = "sw_if_index"
199 if data_if[param] == interface:
205 def vpp_get_interface_ip_addresses(node, interface, ip_version):
206 """Get list of IP addresses from an interface on a VPP node.
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).
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.
219 sw_if_index = Topology.get_interface_sw_index(node, interface)
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)
228 if ip_version == "ipv4":
230 item["netmask"] = convert_ipv4_netmask_prefix(
231 item["prefix_length"])
235 def tg_set_interface_driver(node, pci_addr, driver):
236 """Set interface driver on the TG node.
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.
245 old_driver = InterfaceUtil.tg_get_interface_driver(node, pci_addr)
246 if old_driver == driver:
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,
261 # Bind to the new driver
262 cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/bind"'.format(
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']))
269 def tg_get_interface_driver(node, pci_addr):
270 """Get interface driver from the TG node.
272 :param node: Node to get interface driver on (must be TG node).
273 :param pci_addr: PCI address of the interface.
276 :return: Interface driver or None if not found.
280 # lspci -vmmks 0000:00:05.0
282 Class: Ethernet controller
284 Device: Virtio network device
285 SVendor: Red Hat, Inc
293 cmd = 'lspci -vmmks {0}'.format(pci_addr)
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']))
299 for line in stdout.splitlines():
302 (name, value) = line.split("\t", 1)
303 if name == 'Driver:':
309 def tg_set_interfaces_udev_rules(node):
310 """Set udev rules for interfaces.
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.
318 SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="52:54:00:e1:8a:0f",
321 :param node: Node to set udev rules on (must be TG node).
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']))
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,
343 cmd = '/etc/init.d/udev restart'
344 ssh.exec_command_sudo(cmd)
347 def tg_set_interfaces_default_driver(node):
348 """Set interfaces default driver specified in topology yaml file.
350 :param node: Node to setup interfaces driver on (must be TG node).
353 for interface in node['interfaces'].values():
354 InterfaceUtil.tg_set_interface_driver(node,
355 interface['pci_address'],
359 def update_vpp_interface_data_on_node(node):
360 """Update vpp generated interface data for a given node in DICT__nodes.
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.
368 :param node: Node selected from DICT__nodes.
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,
378 def update_tg_interface_data_on_node(node):
379 """Update interface name for TG/linux node in DICT__nodes.
381 :param node: Node selected from DICT__nodes.
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"
392 .. todo:: parse lshw -json instead
394 # First setup interface driver specified in yaml file
395 InterfaceUtil.tg_set_interfaces_default_driver(node)
397 # Get interface names
401 cmd = ('for dev in `ls /sys/class/net/`; do echo "\\"`cat '
402 '/sys/class/net/$dev/address`\\": \\"$dev\\""; done;')
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'])
413 interface['name'] = name
415 # Set udev rules for interfaces
416 InterfaceUtil.tg_set_interfaces_udev_rules(node)
419 def update_all_interface_data_on_all_nodes(nodes):
420 """Update interface names on all nodes in DICT__nodes.
422 This method updates the topology dictionary by querying interface lists
423 of all nodes mentioned in the topology dictionary.
425 :param nodes: Nodes in the topology.
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)
435 def create_vlan_subinterface(node, interface, vlan):
436 """Create VLAN subinterface on node.
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.
444 :return: Name and index of created subinterface.
447 sw_if_index = Topology.get_interface_sw_index(node, interface)
449 output = VatExecutor.cmd_from_template(node, "create_vlan_subif.vat",
450 sw_if_index=sw_if_index,
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,
458 raise RuntimeError('Unable to create VLAN subinterface on node {}'
459 .format(node['host']))
461 with VatTerminal(node, False) as vat:
462 vat.vat_terminal_exec_cmd('exec show interfaces')
464 return '{}.{}'.format(interface, vlan), sw_subif_index
467 def create_vxlan_interface(node, vni, source_ip, destination_ip):
468 """Create VXLAN interface and return sw if index of created interface.
470 Executes "vxlan_add_del_tunnel src {src} dst {dst} vni {vni}" VAT
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.
480 :type destination_ip: str
481 :return: SW IF INDEX of created interface.
484 output = VatExecutor.cmd_from_template(node, "vxlan_create.vat",
490 if output["retval"] == 0:
491 return output["sw_if_index"]
493 raise RuntimeError('Unable to create VXLAN interface on node {0}'
497 def vxlan_dump(node, interface=None):
498 """Get VxLAN data for the given interface.
500 :param node: VPP node to get interface data from.
501 :param interface: Numeric index or name string of a specific interface.
502 If None, information about all VxLAN interfaces is returned.
504 :type interface: int or str
505 :return: Dictionary containing data for the given VxLAN interface or if
506 interface=None, the list of dictionaries with all VxLAN interfaces.
509 param = "sw_if_index"
510 if interface is None:
513 elif isinstance(interface, basestring):
514 sw_if_index = Topology.get_interface_sw_index(node, interface)
515 elif isinstance(interface, int):
516 sw_if_index = interface
518 raise Exception("Wrong interface format {0}".format(interface))
520 with VatTerminal(node) as vat:
521 response = vat.vat_terminal_exec_cmd_from_template(
522 "vxlan_dump.vat", param=param, sw_if_index=sw_if_index)
525 for vxlan in response[0]:
526 if vxlan["sw_if_index"] == sw_if_index:
532 def vhost_user_dump(node):
533 """Get vhost-user data for the given node.
535 :param node: VPP node to get interface data from.
537 :return: List of dictionaries with all vhost-user interfaces.
540 with VatTerminal(node) as vat:
541 response = vat.vat_terminal_exec_cmd_from_template(
542 "vhost_user_dump.vat")
547 def tap_dump(node, name=None):
548 """Get all TAP interface data from the given node, or data about
549 a specific TAP interface.
551 :param node: VPP node to get data from.
552 :param name: Optional name of a specific TAP interface.
555 :return: Dictionary of information about a specific TAP interface, or
556 a List of dictionaries containing all TAP data for the given node.
559 with VatTerminal(node) as vat:
560 response = vat.vat_terminal_exec_cmd_from_template(
565 for item in response[0]:
566 if name == item['dev_name']:
571 def create_subinterface(node, interface, sub_id, outer_vlan_id,
572 inner_vlan_id, type_subif):
573 """Create sub-interface on node.
575 :param node: Node to add sub-interface.
576 :param interface: Interface name on which create sub-interface.
577 :param sub_id: ID of the sub-interface to be created.
578 :param outer_vlan_id: Outer VLAN ID.
579 :param inner_vlan_id: Inner VLAN ID.
580 :param type_subif: Type of sub-interface.
582 :type interface: str or int
584 :type outer_vlan_id: int
585 :type inner_vlan_id: int
586 :type type_subif: str
587 :return: name and index of created sub-interface
591 if isinstance(interface, basestring):
592 sw_if_index = Topology.get_interface_sw_index(node, interface)
594 sw_if_index = interface
596 output = VatExecutor.cmd_from_template(node, "create_sub_interface.vat",
597 sw_if_index=sw_if_index,
599 outer_vlan_id=outer_vlan_id,
600 inner_vlan_id=inner_vlan_id,
601 type_subif=type_subif)
603 if output[0]["retval"] == 0:
604 sw_subif_index = output[0]["sw_if_index"]
605 logger.trace('Created subinterface with index {}'
606 .format(sw_subif_index))
608 raise RuntimeError('Unable to create subinterface on node {}'
609 .format(node['host']))
611 with VatTerminal(node) as vat:
612 vat.vat_terminal_exec_cmd('exec show interfaces')
614 name = '{}.{}'.format(interface, sub_id)
615 return name, sw_subif_index
618 def create_gre_tunnel_interface(node, source_ip, destination_ip):
619 """Create GRE tunnel interface on node.
621 :param node: VPP node to add tunnel interface.
622 :param source_ip: Source of the GRE tunnel.
623 :param destination_ip: Destination of the GRE tunnel.
626 :type destination_ip: str
627 :return: Name and index of created GRE tunnel interface.
629 :raises RuntimeError: If unable to create GRE tunnel interface.
631 output = VatExecutor.cmd_from_template(node, "create_gre.vat",
636 if output["retval"] == 0:
637 sw_if_index = output["sw_if_index"]
639 vat_executor = VatExecutor()
640 vat_executor.execute_script_json_out("dump_interfaces.vat", node)
641 interface_dump_json = vat_executor.get_script_stdout()
642 name = VatJsonUtil.get_interface_name_from_json(
643 interface_dump_json, sw_if_index)
644 return name, sw_if_index
646 raise RuntimeError('Unable to create GRE tunnel on node {}.'
650 def vpp_create_loopback(node):
651 """Create loopback interface on VPP node.
653 :param node: Node to create loopback interface on.
655 :return: SW interface index.
658 out = VatExecutor.cmd_from_template(node, "create_loopback.vat")
659 if out[0].get('retval') == 0:
660 return out[0].get('sw_if_index')
662 raise RuntimeError('Create loopback failed on node "{}"'
663 .format(node['host']))
666 def vpp_enable_input_acl_interface(node, interface, ip_version,
668 """Enable input acl on interface.
670 :param node: VPP node to setup interface for input acl.
671 :param interface: Interface to setup input acl.
672 :param ip_version: Version of IP protocol.
673 :param table_index: Classify table index.
675 :type interface: str or int
676 :type ip_version: str
677 :type table_index: int
679 if isinstance(interface, basestring):
680 sw_if_index = Topology.get_interface_sw_index(node, interface)
682 sw_if_index = interface
684 with VatTerminal(node) as vat:
685 vat.vat_terminal_exec_cmd_from_template("input_acl_int.vat",
686 sw_if_index=sw_if_index,
687 ip_version=ip_version,
688 table_index=table_index)
691 def get_sw_if_index(node, interface_name):
692 """Get sw_if_index for the given interface from actual interface dump.
694 :param node: VPP node to get interface data from.
695 :param interface_name: Name of the specific interface.
697 :type interface_name: str
698 :return: sw_if_index of the given interface.
702 with VatTerminal(node) as vat:
703 if_data = vat.vat_terminal_exec_cmd_from_template(
704 "interface_dump.vat")
705 for interface in if_data[0]:
706 if interface["interface_name"] == interface_name:
707 return interface["sw_if_index"]
712 def vxlan_gpe_dump(node, interface_name=None):
713 """Get VxLAN GPE data for the given interface.
715 :param node: VPP node to get interface data from.
716 :param interface_name: Name of the specific interface. If None,
717 information about all VxLAN GPE interfaces is returned.
719 :type interface_name: str
720 :return: Dictionary containing data for the given VxLAN GPE interface or
721 if interface=None, the list of dictionaries with all VxLAN GPE
726 with VatTerminal(node) as vat:
727 vxlan_gpe_data = vat.vat_terminal_exec_cmd_from_template(
728 "vxlan_gpe_dump.vat")
731 sw_if_index = InterfaceUtil.get_sw_if_index(node, interface_name)
733 for vxlan_gpe in vxlan_gpe_data[0]:
734 if vxlan_gpe["sw_if_index"] == sw_if_index:
738 return vxlan_gpe_data[0]