diff --git a/scripts/ipintutil b/scripts/ipintutil new file mode 100755 index 0000000000..1762ddbac1 --- /dev/null +++ b/scripts/ipintutil @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import sys + +import netaddr +import netifaces +from natsort import natsorted +from tabulate import tabulate + +from sonic_py_common import multi_asic +from swsssdk import ConfigDBConnector, SonicDBConfig +from utilities_common import constants +from utilities_common import multi_asic as multi_asic_util + + +try: + if os.environ["UTILITIES_UNIT_TESTING"] == "2": + + modules_path = os.path.join(os.path.dirname(__file__), "..") + tests_path = os.path.join(modules_path, "tests") + sys.path.insert(0, modules_path) + sys.path.insert(0, tests_path) + import mock_tables.dbconnector + if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic": + import mock_tables.mock_multi_asic + mock_tables.dbconnector.load_namespace_config() + else: + import mock_tables.mock_single_asic +except KeyError: + pass + + +def get_bgp_peer(): + """ + collects local and bgp neighbor ip along with device name in below format + { + 'local_addr1':['neighbor_device1_name', 'neighbor_device1_ip'], + 'local_addr2':['neighbor_device2_name', 'neighbor_device2_ip'] + } + """ + bgp_peer = {} + config_db = ConfigDBConnector() + config_db.connect() + data = config_db.get_table('BGP_NEIGHBOR') + + for neighbor_ip in data.keys(): + local_addr = data[neighbor_ip]['local_addr'] + neighbor_name = data[neighbor_ip]['name'] + bgp_peer.setdefault(local_addr, [neighbor_name, neighbor_ip]) + return bgp_peer + + +def skip_ip_intf_display(interface, display_option): + if display_option != constants.DISPLAY_ALL: + if interface.startswith('Ethernet') and multi_asic.is_port_internal(interface): + return True + elif interface.startswith('PortChannel') and multi_asic.is_port_channel_internal(interface): + return True + elif interface.startswith('Loopback4096'): + return True + elif interface.startswith('eth0'): + return True + elif interface.startswith('veth'): + return True + return False + + +def get_if_admin_state(iface, namespace): + """ + Given an interface name, return its admin state reported by the kernel + """ + cmd = "cat /sys/class/net/{0}/flags".format(iface) + if namespace != constants.DEFAULT_NAMESPACE: + cmd = "sudo ip netns exec {} {}".format(namespace, cmd) + try: + proc = subprocess.Popen( + cmd, + shell=True, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + text=True) + state_file = proc.communicate()[0] + proc.wait() + + except OSError: + print("Error: unable to get admin state for {}".format(iface)) + return "error" + + try: + content = state_file.rstrip() + flags = int(content, 16) + except ValueError: + return "error" + + if flags & 0x1: + return "up" + else: + return "down" + + +def get_if_oper_state(iface, namespace): + """ + Given an interface name, return its oper state reported by the kernel. + """ + cmd = "cat /sys/class/net/{0}/carrier".format(iface) + if namespace != constants.DEFAULT_NAMESPACE: + cmd = "sudo ip netns exec {} {}".format(namespace, cmd) + try: + proc = subprocess.Popen( + cmd, + shell=True, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + text=True) + state_file = proc.communicate()[0] + proc.wait() + + except OSError: + print("Error: unable to get oper state for {}".format(iface)) + return "error" + + oper_state = state_file.rstrip() + if oper_state == "1": + return "up" + else: + return "down" + + +def get_if_master(iface): + """ + Given an interface name, return its master reported by the kernel. + """ + oper_file = "/sys/class/net/{0}/master" + if os.path.exists(oper_file.format(iface)): + real_path = os.path.realpath(oper_file.format(iface)) + return os.path.basename(real_path) + else: + return "" + + +def get_ip_intfs_in_namespace(af, namespace, display): + """ + Get all the ip intefaces from the kernel for the given namespace + """ + ip_intfs = {} + interfaces = multi_asic_util.multi_asic_get_ip_intf_from_ns(namespace) + bgp_peer = get_bgp_peer() + for iface in interfaces: + ip_intf_attr = [] + if namespace != constants.DEFAULT_NAMESPACE and skip_ip_intf_display(iface, display): + continue + ipaddresses = multi_asic_util.multi_asic_get_ip_intf_addr_from_ns(namespace, iface) + if af in ipaddresses: + ifaddresses = [] + bgp_neighs = {} + ip_intf_attr = [] + for ipaddr in ipaddresses[af]: + neighbor_name = 'N/A' + neighbor_ip = 'N/A' + local_ip = str(ipaddr['addr']) + if af == netifaces.AF_INET: + netmask = netaddr.IPAddress(ipaddr['netmask']).netmask_bits() + else: + netmask = ipaddr['netmask'].split('/', 1)[-1] + local_ip_with_mask = "{}/{}".format(local_ip, str(netmask)) + ifaddresses.append(["", local_ip_with_mask]) + try: + neighbor_name = bgp_peer[local_ip][0] + neighbor_ip = bgp_peer[local_ip][1] + except KeyError: + pass + + bgp_neighs.update({local_ip_with_mask: [neighbor_name, neighbor_ip]}) + + if len(ifaddresses) > 0: + admin = get_if_admin_state(iface, namespace) + oper = get_if_oper_state(iface, namespace) + master = get_if_master(iface) + + ip_intf_attr = { + "vrf": master, + "ipaddr": natsorted(ifaddresses), + "admin": admin, + "oper": oper, + "bgp_neighs": bgp_neighs, + "ns": namespace + } + + ip_intfs[iface] = ip_intf_attr + return ip_intfs + + +def display_ip_intfs(ip_intfs): + header = ['Interface', 'Master', 'IPv4 address/mask', + 'Admin/Oper', 'BGP Neighbor', 'Neighbor IP'] + data = [] + for ip_intf, v in natsorted(ip_intfs.items()): + ip_address = v['ipaddr'][0][1] + neighbour_name = v['bgp_neighs'][ip_address][0] + neighbour_ip = v['bgp_neighs'][ip_address][1] + data.append([ip_intf, v['vrf'], v['ipaddr'][0][1], v['admin'] + "/" + v['oper'], neighbour_name, neighbour_ip]) + for ifaddr in v['ipaddr'][1:]: + neighbour_name = v['bgp_neighs'][ifaddr[1]][0] + neighbour_ip = v['bgp_neighs'][ifaddr[1]][1] + data.append(["", "", ifaddr[1], "", neighbour_name, neighbour_ip]) + print(tabulate(data, header, tablefmt="simple", stralign='left', missingval="")) + + +def get_ip_intfs(af, namespace, display): + ''' + Get all the ip interface present on the device. + This include ip interfaces on the host as well as ip + interfaces in each network namespace + ''' + device = multi_asic_util.MultiAsic(namespace_option=namespace, + display_option=display) + namespace_list = device.get_ns_list_based_on_options() + + # for single asic devices there is one namespace DEFAULT_NAMESPACE + # for multi asic devices, there is one network namespace + # for each asic and one on the host + if device.is_multi_asic: + namespace_list.append(constants.DEFAULT_NAMESPACE) + + ip_intfs = {} + for namespace in namespace_list: + ip_intfs_in_ns = get_ip_intfs_in_namespace(af, namespace, display) + # multi asic device can have same ip interface in different namespace + # so remove the duplicates + if device.is_multi_asic: + for ip_intf, v in ip_intfs_in_ns.items(): + if ip_intf in ip_intfs: + if v['ipaddr'] != ip_intfs[ip_intf]['ipaddr']: + ip_intfs[ip_intf]['ipaddr'] += (v['ipaddr']) + ip_intfs[ip_intf]['bgp_neighs'].update(v['bgp_neighs']) + continue + else: + ip_intfs[ip_intf] = v + else: + ip_intfs.update(ip_intfs_in_ns) + return ip_intfs + + +def main(): + # This script gets the ip interfaces from different linux + # network namespaces. This can be only done from root user. + if os.geteuid() != 0 and os.environ.get("UTILITIES_UNIT_TESTING", "0") != "2": + sys.exit("Root privileges required for this operation") + + parser = multi_asic_util.multi_asic_args() + parser.add_argument('-a', '--address_family', type=str, help='ipv4 or ipv6 interfaces', default="ipv4") + args = parser.parse_args() + namespace = args.namespace + display = args.display + + if args.address_family == "ipv4": + af = netifaces.AF_INET + elif args.address_family == "ipv6": + af = netifaces.AF_INET6 + else: + sys.exit("Invalid argument -a {}".format(args.address_family)) + + SonicDBConfig.load_sonic_global_db_config() + ip_intfs = get_ip_intfs(af, namespace, display) + display_ip_intfs(ip_intfs) + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index 46dec7037d..2a74d59af3 100644 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ 'scripts/generate_dump', 'scripts/intfutil', 'scripts/intfstat', + 'scripts/ipintutil', 'scripts/lldpshow', 'scripts/log_ssd_health', 'scripts/mellanox_buffer_migrator.py', @@ -164,6 +165,7 @@ 'netaddr>=0.8.0', 'netifaces>=0.10.7', 'pexpect>=4.8.0', + 'pyroute2==0.5.14', 'requests>=2.25.0', 'sonic-platform-common', 'sonic-py-common', diff --git a/show/main.py b/show/main.py index 61f0fd9af9..0697977693 100644 --- a/show/main.py +++ b/show/main.py @@ -5,7 +5,6 @@ import re import click -import netifaces import utilities_common.cli as clicommon import utilities_common.multi_asic as multi_asic_util from natsort import natsorted @@ -731,65 +730,6 @@ def ip(): pass -# -# get_if_admin_state -# -# Given an interface name, return its admin state reported by the kernel. -# -def get_if_admin_state(iface): - admin_file = "/sys/class/net/{0}/flags" - - try: - state_file = open(admin_file.format(iface), "r") - except IOError as e: - print("Error: unable to open file: %s" % str(e)) - return "error" - - content = state_file.readline().rstrip() - flags = int(content, 16) - - if flags & 0x1: - return "up" - else: - return "down" - - -# -# get_if_oper_state -# -# Given an interface name, return its oper state reported by the kernel. -# -def get_if_oper_state(iface): - oper_file = "/sys/class/net/{0}/carrier" - - try: - state_file = open(oper_file.format(iface), "r") - except IOError as e: - print("Error: unable to open file: %s" % str(e)) - return "error" - - oper_state = state_file.readline().rstrip() - if oper_state == "1": - return "up" - else: - return "down" - - -# -# get_if_master -# -# Given an interface name, return its master reported by the kernel. -# -def get_if_master(iface): - oper_file = "/sys/class/net/{0}/master" - - if os.path.exists(oper_file.format(iface)): - real_path = os.path.realpath(oper_file.format(iface)) - return os.path.basename(real_path) - else: - return "" - - # # 'show ip interfaces' command # @@ -798,75 +738,14 @@ def get_if_master(iface): # excluded. # @ip.command() -def interfaces(): - """Show interfaces IPv4 address""" - import netaddr - header = ['Interface', 'Master', 'IPv4 address/mask', 'Admin/Oper', 'BGP Neighbor', 'Neighbor IP'] - data = [] - bgp_peer = get_bgp_peer() - - interfaces = natsorted(netifaces.interfaces()) - - for iface in interfaces: - ipaddresses = netifaces.ifaddresses(iface) - - if netifaces.AF_INET in ipaddresses: - ifaddresses = [] - neighbor_info = [] - for ipaddr in ipaddresses[netifaces.AF_INET]: - neighbor_name = 'N/A' - neighbor_ip = 'N/A' - local_ip = str(ipaddr['addr']) - netmask = netaddr.IPAddress(ipaddr['netmask']).netmask_bits() - ifaddresses.append(["", local_ip + "/" + str(netmask)]) - try: - neighbor_name = bgp_peer[local_ip][0] - neighbor_ip = bgp_peer[local_ip][1] - except Exception: - pass - neighbor_info.append([neighbor_name, neighbor_ip]) - - if len(ifaddresses) > 0: - admin = get_if_admin_state(iface) - if admin == "up": - oper = get_if_oper_state(iface) - else: - oper = "down" - master = get_if_master(iface) - if clicommon.get_interface_naming_mode() == "alias": - iface = iface_alias_converter.name_to_alias(iface) - - data.append([iface, master, ifaddresses[0][1], admin + "/" + oper, neighbor_info[0][0], neighbor_info[0][1]]) - neighbor_info.pop(0) - - for ifaddr in ifaddresses[1:]: - data.append(["", "", ifaddr[1], admin + "/" + oper, neighbor_info[0][0], neighbor_info[0][1]]) - neighbor_info.pop(0) - - print(tabulate(data, header, tablefmt="simple", stralign='left', missingval="")) - -# get bgp peering info -def get_bgp_peer(): - """ - collects local and bgp neighbor ip along with device name in below format - { - 'local_addr1':['neighbor_device1_name', 'neighbor_device1_ip'], - 'local_addr2':['neighbor_device2_name', 'neighbor_device2_ip'] - } - """ - config_db = ConfigDBConnector() - config_db.connect() - bgp_peer = {} - bgp_neighbor_tables = ['BGP_NEIGHBOR', 'BGP_INTERNAL_NEIGHBOR'] - - for table in bgp_neighbor_tables: - data = config_db.get_table(table) - for neighbor_ip in data: - local_addr = data[neighbor_ip]['local_addr'] - neighbor_name = data[neighbor_ip]['name'] - bgp_peer.setdefault(local_addr, [neighbor_name, neighbor_ip]) +@multi_asic_util.multi_asic_click_options +def interfaces(namespace, display): + cmd = "sudo ipintutil -a ipv4" + if namespace is not None: + cmd += " -n {}".format(namespace) - return bgp_peer + cmd += " -d {}".format(display) + clicommon.run_command(cmd) # # 'route' subcommand ("show ip route") @@ -942,49 +821,16 @@ def prefix_list(prefix_list_name, verbose): # excluded. # @ipv6.command() -def interfaces(): - """Show interfaces IPv6 address""" - header = ['Interface', 'Master', 'IPv6 address/mask', 'Admin/Oper', 'BGP Neighbor', 'Neighbor IP'] - data = [] - bgp_peer = get_bgp_peer() - - interfaces = natsorted(netifaces.interfaces()) - - for iface in interfaces: - ipaddresses = netifaces.ifaddresses(iface) - - if netifaces.AF_INET6 in ipaddresses: - ifaddresses = [] - neighbor_info = [] - for ipaddr in ipaddresses[netifaces.AF_INET6]: - neighbor_name = 'N/A' - neighbor_ip = 'N/A' - local_ip = str(ipaddr['addr']) - netmask = ipaddr['netmask'].split('/', 1)[-1] - ifaddresses.append(["", local_ip + "/" + str(netmask)]) - try: - neighbor_name = bgp_peer[local_ip][0] - neighbor_ip = bgp_peer[local_ip][1] - except Exception: - pass - neighbor_info.append([neighbor_name, neighbor_ip]) - - if len(ifaddresses) > 0: - admin = get_if_admin_state(iface) - if admin == "up": - oper = get_if_oper_state(iface) - else: - oper = "down" - master = get_if_master(iface) - if clicommon.get_interface_naming_mode() == "alias": - iface = iface_alias_converter.name_to_alias(iface) - data.append([iface, master, ifaddresses[0][1], admin + "/" + oper, neighbor_info[0][0], neighbor_info[0][1]]) - neighbor_info.pop(0) - for ifaddr in ifaddresses[1:]: - data.append(["", "", ifaddr[1], admin + "/" + oper, neighbor_info[0][0], neighbor_info[0][1]]) - neighbor_info.pop(0) - - print(tabulate(data, header, tablefmt="simple", stralign='left', missingval="")) +@multi_asic_util.multi_asic_click_options +def interfaces(namespace, display): + cmd = "sudo ipintutil -a ipv6" + + if namespace is not None: + cmd += " -n {}".format(namespace) + + cmd += " -d {}".format(display) + + clicommon.run_command(cmd) # diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 336dc0f6eb..16b2680375 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -1725,6 +1725,26 @@ "yellow_drop_probability": "5", "red_drop_probability": "5" }, + "BGP_NEIGHBOR|20.1.1.5": { + "rrclient": "0", + "name": "T2-Peer", + "local_addr": "20.1.1.1", + "nhopself": "0", + "admin_status": "up", + "holdtime": "10", + "asn": "65200", + "keepalive": "3" + }, + "BGP_NEIGHBOR|30.1.1.5": { + "rrclient": "0", + "name": "T0-Peer", + "local_addr": "30.1.1.1", + "nhopself": "0", + "admin_status": "up", + "holdtime": "10", + "asn": "65200", + "keepalive": "3" + }, "SCHEDULER|scheduler.0": { "type": "DWRR", "weight": "14" diff --git a/tests/mock_tables/mock_multi_asic.py b/tests/mock_tables/mock_multi_asic.py index 3e875af36a..46f943369d 100644 --- a/tests/mock_tables/mock_multi_asic.py +++ b/tests/mock_tables/mock_multi_asic.py @@ -2,6 +2,57 @@ from unittest import mock from sonic_py_common import multi_asic +from utilities_common import multi_asic as multi_asic_util + +mock_intf_table = { + '': { + 'eth0': { + 2: [{'addr': '10.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '10.1.1.1'}], + 10: [{'addr': '3100::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}] + }, + 'lo': { + 2: [{'addr': '127.0.0.1', 'netmask': '255.0.0.0', 'broadcast': '127.255.255.255'}], + 10: [{'addr': '::1', 'netmask':'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'}] + } + }, + 'asic0': { + 'Loopback0': { + 17: [{'addr': '62:a5:9d:f4:16:96', 'broadcast': 'ff:ff:ff:ff:ff:ff'}], + 2: [{'addr': '40.1.1.1', 'netmask': '255.255.255.255', 'broadcast': '40.1.1.1'}], + 10: [{'addr': 'fe80::60a5:9dff:fef4:1696%Loopback0', 'netmask': 'ffff:ffff:ffff:ffff::/64'}] + }, + 'PortChannel0001': { + 17: [{'addr': '82:fd:d1:5b:45:2f', 'broadcast': 'ff:ff:ff:ff:ff:ff'}], + 2: [{'addr': '20.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '20.1.1.1'}], + 10: [{'addr': 'aa00::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}, {'addr': 'fe80::80fd:d1ff:fe5b:452f', 'netmask': 'ffff:ffff:ffff:ffff::/64'}] + }, + 'Loopback4096': { + 2: [{'addr': '1.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '1.1.1.1'}] + }, + 'veth@eth1': { + 2: [{'addr': '192.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '192.1.1.1'}] + } + }, + 'asic1': { + 'Loopback0': { + 17: [{'addr': '62:a5:9d:f4:16:96', 'broadcast': 'ff:ff:ff:ff:ff:ff'}], + 2: [{'addr': '40.1.1.1', 'netmask': '255.255.255.255', 'broadcast': '40.1.1.1'}], + 10: [{'addr': 'fe80::60a5:9dff:fef4:1696%Loopback0', 'netmask': 'ffff:ffff:ffff:ffff::/64'}] + }, + 'PortChannel0002': { + 17: [{'addr': '82:fd:d1:5b:45:2f', 'broadcast': 'ff:ff:ff:ff:ff:ff'}], + 2: [{'addr': '30.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '30.1.1.1'}], + 10: [{'addr': 'bb00::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}, {'addr': 'fe80::80fd:abff:fe5b:452f', 'netmask': 'ffff:ffff:ffff:ffff::/64'}] + }, + 'Loopback4096': { + 2: [{'addr': '2.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '2.1.1.1'}] + }, + 'veth@eth2': { + 2: [{'addr': '193.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '193.1.1.1'}] + } + } +} + def mock_get_num_asics(): return 2 @@ -15,7 +66,27 @@ def mock_get_namespace_list(namespace=None): return ['asic0', 'asic1'] +def mock_multi_asic_get_ip_intf_from_ns(namespace): + interfaces = [] + try: + interfaces = list(mock_intf_table[namespace].keys()) + except KeyError: + pass + return interfaces + + +def mock_multi_asic_get_ip_intf_addr_from_ns(namespace, iface): + ipaddresses = [] + try: + ipaddresses = mock_intf_table[namespace][iface] + except KeyError: + pass + return ipaddresses + + multi_asic.get_num_asics = mock_get_num_asics multi_asic.is_multi_asic = mock_is_multi_asic multi_asic.get_namespace_list = mock_get_namespace_list multi_asic.get_namespaces_from_linux = mock_get_namespace_list +multi_asic_util.multi_asic_get_ip_intf_from_ns = mock_multi_asic_get_ip_intf_from_ns +multi_asic_util.multi_asic_get_ip_intf_addr_from_ns = mock_multi_asic_get_ip_intf_addr_from_ns diff --git a/tests/mock_tables/mock_single_asic.py b/tests/mock_tables/mock_single_asic.py index f6607cf362..08c2157c9d 100644 --- a/tests/mock_tables/mock_single_asic.py +++ b/tests/mock_tables/mock_single_asic.py @@ -2,6 +2,49 @@ from unittest import mock from sonic_py_common import multi_asic +from utilities_common import multi_asic as multi_asic_util + +mock_intf_table = { + '': { + 'eth0': { + 2: [{'addr': '10.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '10.1.1.1'}], + 10: [{'addr': '3100::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}] + }, + 'Ethernet0': { + 17: [{'addr': '82:fd:d1:5b:45:2f', 'broadcast': 'ff:ff:ff:ff:ff:ff'}], + 2: [ + {'addr': '20.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '20.1.1.1'}, + {'addr': '21.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '21.1.1.1'} + ], + 10: [ + {'addr': 'aa00::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}, + {'addr': '2100::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}, + {'addr': 'fe80::64be:a1ff:fe85:c6c4%Ethernet0', 'netmask': 'ffff:ffff:ffff:ffff::/64'} + ] + }, + 'PortChannel0001': { + 17: [{'addr': '82:fd:d1:5b:45:2f', 'broadcast': 'ff:ff:ff:ff:ff:ff'}], + 2: [{'addr': '30.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '30.1.1.1'}], + 10: [ + {'addr': 'ab00::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}, + {'addr': 'fe80::cc8d:60ff:fe08:139f%PortChannel0001', 'netmask': 'ffff:ffff:ffff:ffff::/64'} + ] + }, + 'Vlan100': { + 17: [{'addr': '82:fd:d1:5b:45:2f', 'broadcast': 'ff:ff:ff:ff:ff:ff'}], + 2: [{'addr': '40.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '30.1.1.1'}], + 10: [ + {'addr': 'cc00::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}, + {'addr': 'fe80::c029:3fff:fe41:cf56%Vlan100', 'netmask': 'ffff:ffff:ffff:ffff::/64'} + ] + }, + 'lo': { + 2: [{'addr': '127.0.0.1', 'netmask': '255.0.0.0', 'broadcast': '127.255.255.255'}], + 10: [{'addr': '::1', 'netmask':'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'}] + } + } +} + def mock_get_num_asics(): return 1 @@ -12,6 +55,27 @@ def mock_is_multi_asic(): def mock_get_namespace_list(namespace=None): return [''] + +def mock_single_asic_get_ip_intf_from_ns(namespace): + interfaces = [] + try: + interfaces = list(mock_intf_table[namespace].keys()) + except KeyError: + pass + return interfaces + + +def mock_single_asic_get_ip_intf_addr_from_ns(namespace, iface): + ipaddresses = [] + try: + ipaddresses = mock_intf_table[namespace][iface] + except KeyError: + pass + return ipaddresses + + multi_asic.is_multi_asic = mock_is_multi_asic multi_asic.get_num_asics = mock_get_num_asics multi_asic.get_namespace_list = mock_get_namespace_list +multi_asic_util.multi_asic_get_ip_intf_from_ns = mock_single_asic_get_ip_intf_from_ns +multi_asic_util.multi_asic_get_ip_intf_addr_from_ns = mock_single_asic_get_ip_intf_addr_from_ns \ No newline at end of file diff --git a/tests/show_ip_int_test.py b/tests/show_ip_int_test.py new file mode 100644 index 0000000000..31350d3ea5 --- /dev/null +++ b/tests/show_ip_int_test.py @@ -0,0 +1,156 @@ +import os +import pytest +import subprocess +from click.testing import CliRunner + +import show.main as show +from .utils import get_result_and_return_code + +root_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(root_path) +scripts_path = os.path.join(modules_path, "scripts") + +show_ipv4_intf_with_multple_ips = """\ +Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP +--------------- -------- ------------------- ------------ -------------- ------------- +Ethernet0 20.1.1.1/24 error/down T2-Peer 20.1.1.5 + 21.1.1.1/24 N/A N/A +PortChannel0001 30.1.1.1/24 error/down T0-Peer 30.1.1.5 +Vlan100 40.1.1.1/24 error/down N/A N/A""" + +show_ipv6_intf_with_multiple_ips = """\ +Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP +--------------- -------- -------------------------------------------- ------------ -------------- ------------- +Ethernet0 2100::1/64 error/down N/A N/A + aa00::1/64 N/A N/A + fe80::64be:a1ff:fe85:c6c4%Ethernet0/64 N/A N/A +PortChannel0001 ab00::1/64 error/down N/A N/A + fe80::cc8d:60ff:fe08:139f%PortChannel0001/64 N/A N/A +Vlan100 cc00::1/64 error/down N/A N/A + fe80::c029:3fff:fe41:cf56%Vlan100/64 N/A N/A""" + +show_multi_asic_ip_intf = """\ +Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP +--------------- -------- ------------------- ------------ -------------- ------------- +Loopback0 40.1.1.1/32 error/down N/A N/A +PortChannel0001 20.1.1.1/24 error/down T2-Peer 20.1.1.5""" + +show_multi_asic_ipv6_intf = """\ +Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP +--------------- -------- -------------------------------------- ------------ -------------- ------------- +Loopback0 fe80::60a5:9dff:fef4:1696%Loopback0/64 error/down N/A N/A +PortChannel0001 aa00::1/64 error/down N/A N/A + fe80::80fd:d1ff:fe5b:452f/64 N/A N/A""" + +show_multi_asic_ip_intf_all = """\ +Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP +--------------- -------- ------------------- ------------ -------------- ------------- +Loopback0 40.1.1.1/32 error/down N/A N/A +Loopback4096 1.1.1.1/24 error/down N/A N/A + 2.1.1.1/24 N/A N/A +PortChannel0001 20.1.1.1/24 error/down T2-Peer 20.1.1.5 +PortChannel0002 30.1.1.1/24 error/down T0-Peer 30.1.1.5 +veth@eth1 192.1.1.1/24 error/down N/A N/A +veth@eth2 193.1.1.1/24 error/down N/A N/A""" + +show_multi_asic_ipv6_intf_all = """\ +Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP +--------------- -------- -------------------------------------- ------------ -------------- ------------- +Loopback0 fe80::60a5:9dff:fef4:1696%Loopback0/64 error/down N/A N/A +PortChannel0001 aa00::1/64 error/down N/A N/A + fe80::80fd:d1ff:fe5b:452f/64 N/A N/A +PortChannel0002 bb00::1/64 error/down N/A N/A + fe80::80fd:abff:fe5b:452f/64 N/A N/A""" + +show_error_invalid_af = """Invalid argument -a ipv5""" + + +@pytest.fixture(scope="class") +def setup_teardown_single_asic(): + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "2" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + yield + + os.environ["UTILITIES_UNIT_TESTING"] = "0" + + +@pytest.fixture(scope="class") +def setup_teardown_multi_asic(): + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "2" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + yield + os.environ["UTILITIES_UNIT_TESTING"] = "0" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + + +def verify_output(output, expected_output): + lines = output.splitlines() + ignored_intfs = ['eth0', 'lo'] + for intf in ignored_intfs: + # the output should have line to display the ip address of eth0 and lo + assert len([line for line in lines if line.startswith(intf)]) == 1 + + new_output = '\n'.join([line for line in lines if not any(i in line for i in ignored_intfs)]) + print(new_output) + assert new_output == expected_output + + +@pytest.mark.usefixtures('setup_teardown_single_asic') +class TestShowIpInt(object): + + def test_show_ip_intf_v4(self): + return_code, result = get_result_and_return_code(" ipintutil") + assert return_code == 0 + verify_output(result, show_ipv4_intf_with_multple_ips) + + def test_show_ip_intf_v6(self): + return_code, result = get_result_and_return_code(" ipintutil -a ipv6") + + assert return_code == 0 + verify_output(result, show_ipv6_intf_with_multiple_ips) + + def test_show_intf_invalid_af_option(self): + return_code, result = get_result_and_return_code(" ipintutil -a ipv5") + assert return_code == 1 + assert result == show_error_invalid_af + + +@pytest.mark.usefixtures('setup_teardown_multi_asic') +class TestMultiAsicShowIpInt(object): + + def test_show_ip_intf_v4(self): + return_code, result = get_result_and_return_code("ipintutil") + assert return_code == 0 + verify_output(result, show_multi_asic_ip_intf) + + def test_show_ip_intf_v4_asic0(self): + return_code, result = get_result_and_return_code("ipintutil -n asic0") + assert return_code == 0 + verify_output(result, show_multi_asic_ip_intf) + + def test_show_ip_intf_v4_all(self): + return_code, result = get_result_and_return_code("ipintutil -d all") + assert return_code == 0 + verify_output(result, show_multi_asic_ip_intf_all) + + def test_show_ip_intf_v6(self): + return_code, result = get_result_and_return_code("ipintutil -a ipv6") + assert return_code == 0 + verify_output(result, show_multi_asic_ipv6_intf) + + def test_show_ip_intf_v6_asic0(self): + return_code, result = get_result_and_return_code("ipintutil -a ipv6 -n asic0") + assert return_code == 0 + verify_output(result, show_multi_asic_ipv6_intf) + + def test_show_ip_intf_v6_all(self): + return_code, result = get_result_and_return_code("ipintutil -a ipv6 -d all") + assert return_code == 0 + verify_output(result, show_multi_asic_ipv6_intf_all) + + def test_show_intf_invalid_af_option(self): + return_code, result = get_result_and_return_code(" ipintutil -a ipv5") + assert return_code == 1 + assert result == show_error_invalid_af diff --git a/utilities_common/cli.py b/utilities_common/cli.py index d1b7d4e903..b121e15c3e 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -467,6 +467,13 @@ def run_command_in_alias_mode(command): if "Vlan" in output: output = output.replace('Vlan', ' Vlan') print_output_in_alias_mode(output, index) + elif command.startswith("sudo ipintutil"): + """show ip(v6) int""" + index = 0 + if output.startswith("Interface"): + output = output.replace("Interface", "Interface".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) else: """ diff --git a/utilities_common/multi_asic.py b/utilities_common/multi_asic.py index fe906b35d3..d98b26b399 100644 --- a/utilities_common/multi_asic.py +++ b/utilities_common/multi_asic.py @@ -2,6 +2,9 @@ import functools import click +import netifaces +import pyroute2 +from natsort import natsorted from sonic_py_common import multi_asic from utilities_common import constants @@ -148,3 +151,24 @@ def multi_asic_args(parser=None): parser.add_argument('-n', '--namespace', default=None, help='Display interfaces for specific namespace') return parser + +def multi_asic_get_ip_intf_from_ns(namespace): + if namespace != constants.DEFAULT_NAMESPACE: + pyroute2.netns.pushns(namespace) + interfaces = natsorted(netifaces.interfaces()) + + if namespace != constants.DEFAULT_NAMESPACE: + pyroute2.netns.popns() + + return interfaces + + +def multi_asic_get_ip_intf_addr_from_ns(namespace, iface): + if namespace != constants.DEFAULT_NAMESPACE: + pyroute2.netns.pushns(namespace) + ipaddresses = netifaces.ifaddresses(iface) + + if namespace != constants.DEFAULT_NAMESPACE: + pyroute2.netns.popns() + + return ipaddresses