diff --git a/clear/main.py b/clear/main.py index 5cbaee9d62..953764111d 100755 --- a/clear/main.py +++ b/clear/main.py @@ -401,8 +401,6 @@ def clear_all_fdb(): command = 'fdbclear' run_command(command) -# 'sonic-clear fdb port' and 'sonic-clear fdb vlan' will be added later -''' @fdb.command('port') @click.argument('portid', required=True) def clear_port_fdb(portid): @@ -416,7 +414,6 @@ def clear_vlan_fdb(vlanid): """Clear FDB entries learned in one VLAN""" command = 'fdbclear' + ' -v ' + vlanid run_command(command) -''' # # 'line' command diff --git a/config/main.py b/config/main.py index 4444eac7c0..6c8460883c 100644 --- a/config/main.py +++ b/config/main.py @@ -5,6 +5,7 @@ import json import jsonpatch import netaddr +import time import netifaces import os import re @@ -16,6 +17,10 @@ from collections import OrderedDict from generic_config_updater.generic_updater import GenericUpdater, ConfigFormat from minigraph import parse_device_desc_xml +from itertools import count, groupby +from utilities_common.intf_filter import parse_interface_in_filter +from portconfig import get_child_ports +from sonic_py_common import device_info from natsort import natsorted from portconfig import get_child_ports from socket import AF_INET, AF_INET6 @@ -319,6 +324,15 @@ def interface_name_is_valid(config_db, interface_name): return True return False +def vlan_id_is_valid(vid): + """Check if the vlan id is in acceptable range (between 1 and 4094) + """ + + if vid>0 and vid<4095: + return True + + return False + def interface_name_to_alias(config_db, interface_name): """Return alias interface name if default name is given as argument """ @@ -2534,6 +2548,111 @@ def delete_snmptrap_server(ctx, ver): cmd="systemctl restart snmp" os.system (cmd) +# Validate VLAN range. +# +def vlan_range_validate(ctx, vid1, vid2): + vlan1 = 'Vlan{}'.format(vid1) + vlan2 = 'Vlan{}'.format(vid2) + + if vlan_id_is_valid(vid1) is False: + ctx.fail("{} is not within allowed range of 1 through 4094".format(vlan1)) + if vlan_id_is_valid(vid2) is False: + ctx.fail("{} is not within allowed range of 1 through 4094".format(vlan2)) + + if vid2 <= vid1: + ctx.fail(" vid2 should be greater than vid1") + +# +# Return a string with ranges separated by hyphen. +# +def get_hyphenated_string(vlan_list): + vlan_list.sort() + G = (list(x) for _,x in groupby(vlan_list, lambda x,c=count(): next(c)-x)) + hyphenated_string = ",".join("-".join(map(str,(g[0],g[-1])[:len(g)])) for g in G) + return hyphenated_string + +# +# 'mac' group ('config mac ...') +# +@config.group() +@click.pass_context +@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') +def mac(ctx, redis_unix_socket_path): + """Mac-related configuration tasks""" + kwargs = {} + if redis_unix_socket_path: + kwargs['unix_socket_path'] = redis_unix_socket_path + config_db = ConfigDBConnector(**kwargs) + config_db.connect(wait_for_init=False) + ctx.obj = {'db': config_db} + +@mac.command('aging_time') +@click.argument('interval', metavar='', required=True, type=int) +@clicommon.pass_db +def set_aging_time(db, interval): + if interval not in range(0,1000001): + ctx.fail("Aging timer must be in range [0-1000000]") + db.cfgdb.set_entry('SWITCH', 'switch', {'fdb_aging_time': interval}) + +def mac_address_is_valid(mac): + """Check if the mac address is valid + """ + return bool(re.match('^' + '[\:\-]'.join(['([0-9a-f]{2})']*6) + '$', mac.lower())) + +@mac.command('add') +@click.argument('mac', metavar='', required=True) +@click.argument('vlan', metavar='', required=True, type=int) +@click.argument('interface_name', metavar='', required=True) +@clicommon.pass_db +def add_mac(db, mac, vlan, interface_name): + mac_valid = bool(mac_address_is_valid(mac)) + if mac_valid == False: + click.echo("Incorrect mac-address format!!") + + mac_valid = not bool(re.match('^' + '[\:\-]'.join(['([00]{2})']*6) + '$', mac.lower())) + if mac_valid == False: + click.echo("Invalid (Zero) mac-address!!") + + mac_valid = not bool(re.match('^' + '[\:\-]'.join(['([ff]{2})']*6) + '$', mac.lower())) + if mac_valid == False: + click.echo("Invalid (Bcast) mac-address!!") + + mac_is_multicast = int(mac[:2]) & 1; + if mac_is_multicast == True: + click.echo("Invalid (Multicast) mac-address!!") + + vlan_valid = bool(vlan_id_is_valid(vlan)) + if vlan_valid == False: + click.echo("Invalid VlanId!!") + + vlan_name = 'Vlan{}'.format(vlan) + + if clicommon.get_interface_naming_mode() == "alias": + interface_name = interface_alias_to_name(db.cfgdb, interface_name) + if interface_name is None: + click.echo("'interface_name' is None!") + + if interface_name_is_valid(db.cfgdb, interface_name) is False: + click.echo("Interface name is invalid!!") + + db.cfgdb.set_entry('FDB', (vlan_name, mac), {'port': interface_name }) + +@mac.command('del') +@click.argument('mac', metavar='', required=True) +@click.argument('vlan', metavar='', required=True, type=int) +@clicommon.pass_db +def del_mac(db, mac, vlan): + mac_valid = bool(mac_address_is_valid(mac)) + if mac_valid == False: + click.echo("Incorrect mac-address format!!") + + vlan_valid = bool(vlan_id_is_valid(vlan)) + if vlan_valid == False: + click.echo("Invalid VlanId!!") + + vlan_name = 'Vlan{}'.format(vlan) + + db.cfgdb.set_entry('FDB', (vlan_name, mac), None) # diff --git a/config/vlan.py b/config/vlan.py index 9cdb0fc348..d8355dbdcb 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -1,5 +1,8 @@ import click import utilities_common.cli as clicommon +import re +import logging +from itertools import count, groupby from time import sleep from .utils import log @@ -185,3 +188,396 @@ def del_vlan_member(db, vid, port): db.cfgdb.set_entry('VLAN_MEMBER', (vlan, port), None) +def vlan_id_is_valid(vid): + """Check if the vlan id is in acceptable range (between 1 and 4094) + """ + + if vid>0 and vid<4095: + return True + + return False + +# Validate VLAN range. +# +def vlan_range_validate(vid1, vid2): + vlan1 = 'Vlan{}'.format(vid1) + vlan2 = 'Vlan{}'.format(vid2) + + if vlan_id_is_valid(vid1) is False: + ctx.fail("{} is not within allowed range of 1 through 4094".format(vlan1)) + if vlan_id_is_valid(vid2) is False: + ctx.fail("{} is not within allowed range of 1 through 4094".format(vlan2)) + + if vid2 <= vid1: + ctx.fail(" vid2 should be greater than vid1") + +# +# Return a string with ranges separated by hyphen. +# +def get_hyphenated_string(vlan_list): + vlan_list.sort() + G = (list(x) for _,x in groupby(vlan_list, lambda x,c=count(): next(c)-x)) + hyphenated_string = ",".join("-".join(map(str,(g[0],g[-1])[:len(g)])) for g in G) + return hyphenated_string + +# +# 'range' group ('config vlan range ...') +# +@vlan.group('range') +@click.pass_context +def vlan_range(ctx): + """VLAN-range related configuration tasks""" + pass + +@vlan_range.command('add') +@click.argument('vid1', metavar='', required=True, type=int) +@click.argument('vid2', metavar='', required=True, type=int) +@click.option('-w', "--warning", is_flag=True, help='warnings are not suppressed') +@clicommon.pass_db +def add_vlan_range(db, vid1, vid2, warning): + + vlan_range_validate(vid1, vid2) + + vid2 = vid2+1 + + warning_vlans_list = [] + ctx = click.get_current_context() + ctx.obj = {'db': db.cfgdb} + + curr_vlan_count = 0 + clients = db.cfgdb.get_redis_client(db.cfgdb.CONFIG_DB) + pipe = clients.pipeline() + for vid in range (vid1, vid2): + vlan = 'Vlan{}'.format(vid) + + if len(db.cfgdb.get_entry('VLAN', vlan)) != 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + pipe.hmset('VLAN|{}'.format(vlan), {'vlanid': vid}) + curr_vlan_count += 1 + pipe.execute() + # Log warning messages if 'warning' option is enabled + if warning is True: + if len(warning_vlans_list) != 0: + logging.warning('VLANs already existing: {}'.format(get_hyphenated_string(warning_vlans_list))) + +@vlan_range.command('del') +@click.argument('vid1', metavar='', required=True, type=int) +@click.argument('vid2', metavar='', required=True, type=int) +@click.option('-w', "--warning", is_flag=True, help='warnings are not suppressed') +@clicommon.pass_db +def del_vlan_range(db, vid1, vid2, warning): + + vlan_range_validate(vid1, vid2) + + vid2 = vid2+1 + + warning_vlans_list = [] + warning_membership_list = [] + warning_ip_list = [] + ctx = click.get_current_context() + ctx.obj = {'db': db.cfgdb} + client = db.cfgdb.get_redis_client(db.cfgdb.CONFIG_DB) + pipe = client.pipeline() + + cur, vlan_member_keys = client.scan(cursor=0, match='VLAN_MEMBER*', count=50) + while cur != 0: + cur, keys = client.scan(cursor=cur, match='VLAN_MEMBER*', count=50) + vlan_member_keys.extend(keys) + + cur, vlan_temp_member_keys = client.scan(cursor=0, match='VLAN_MEMBER*', count=50) + while cur != 0: + cur, keys = client.scan(cursor=cur, match='VLAN_MEMBER*', count=50) + vlan_temp_member_keys.extend(keys) + + cur, vlan_ip_keys = client.scan(cursor=0, match='VLAN_INTERFACE*', count=50) + while cur != 0: + cur, keys = client.scan(cursor=cur, match='VLAN_INTERFACE*', count=50) + vlan_ip_keys.extend(keys) + + # Fetch the interfaces from config_db associated with VLAN_MEMBER* + stored_intf_list = [] + if vlan_temp_member_keys is not None: + for x in range(len(vlan_temp_member_keys)): + member_list = vlan_temp_member_keys[x].split('|',2) + stored_intf_list.append(str(member_list[2])) + + stored_intf_list = list(set(stored_intf_list)) + list_length = len(stored_intf_list) + + # Fetch VLAN participation list for each interface + vid = range(vid1, vid2) + if vlan_temp_member_keys is not None and list_length != 0: + for i in range(list_length): + stored_vlan_list = [] + for x in list(vlan_temp_member_keys): + member_list = x.split('|',2) + fetched_vlan = int(re.search(r'\d+', member_list[1]).group()) + if stored_intf_list[i] == str(member_list[2]): + if fetched_vlan in vid: + stored_vlan_list.append(fetched_vlan) + vlan_temp_member_keys.remove(x) + + if len(stored_vlan_list) != 0: + warning_string = str(stored_intf_list[i]) + ' is member of ' + get_hyphenated_string(stored_vlan_list) + warning_membership_list.append(warning_string) + + if vlan_ip_keys is None and vlan_member_keys is None: + for vid in range(vid1, vid2): + vlan = 'Vlan{}'.format(vid) + if len(db.cfgdb.get_entry('VLAN', vlan)) == 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + pipe.delete('VLAN|{}'.format(vlan)) + pipe.execute() + else: + if vlan_ip_keys is not None: + for v in vlan_ip_keys: + pipe.hgetall(v) + pipe.execute() + if vlan_member_keys is not None: + for v in vlan_member_keys: + pipe.hgetall(v) + pipe.execute() + for vid in range(vid1, vid2): + vlan_member_configured = False + ip_configured = False + vlan = 'Vlan{}'.format(vid) + + if len(db.cfgdb.get_entry('VLAN', vlan)) == 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + if vlan_member_keys is not None: + for x in range(len(vlan_member_keys)): + vlan_member_configured = False + member_list = vlan_member_keys[x].split('|',2) + fetched_vlan = int(re.search(r'\d+', member_list[1]).group()) + if(fetched_vlan == vid): + if "Ethernet" or "PortChannel" in str(member_list[2]): + vlan_member_configured = True + break + + if vlan_member_configured is True: + continue + + if vlan_ip_keys is not None: + for x in range(len(vlan_ip_keys)): + ip_configured = False + member_list = vlan_ip_keys[x].split('|',2) + fetched_vlan = int(re.search(r'\d+', member_list[1]).group()) + if(fetched_vlan == vid): + if warning is True: + warning_ip_list.append(vid) + ip_configured = True + break + + if ip_configured is True: + continue + + vlan = 'Vlan{}'.format(vid) + pipe.delete('VLAN|{}'.format(vlan)) + pipe.execute() + + # Log warning messages if 'warning' option is enabled + if warning is True and len(warning_vlans_list) != 0: + logging.warning('Non-existent VLANs: {}'.format(get_hyphenated_string(warning_vlans_list))) + if warning is True and len(warning_membership_list) != 0: + logging.warning('Remove VLAN membership before removing VLAN: {}'.format(warning_membership_list)) + if warning is True and len(warning_ip_list) != 0: + warning_string = 'Vlans configured with IP: ' + get_hyphenated_string(warning_ip_list) + logging.warning('Remove IP configuration before removing VLAN: {}'.format(warning_string)) + +# +# 'member range' group ('config vlan member range ...') +# +@vlan_member.group('range') +@click.pass_context +def vlan_member_range(ctx): + """VLAN member range related configuration tasks""" + pass + +# +# Returns VLAN data in a format required to perform redisDB operations. +# +def vlan_member_data(member_list): + vlan_data = {} + for key in member_list: + value = member_list[key] + if type(value) is list: + vlan_data[key+'@'] = ','.join(value) + else: + vlan_data[key] = str(value) + return vlan_data + +def interface_name_is_valid(config_db, interface_name): + """Check if the interface name is valid + """ + # If the input parameter config_db is None, derive it from interface. + # In single ASIC platform, get_port_namespace() returns DEFAULT_NAMESPACE. + if config_db is None: + namespace = get_port_namespace(interface_name) + if namespace is None: + return False + config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) + + config_db.connect() + port_dict = config_db.get_table('PORT') + port_channel_dict = config_db.get_table('PORTCHANNEL') + sub_port_intf_dict = config_db.get_table('VLAN_SUB_INTERFACE') + + if clicommon.get_interface_naming_mode() == "alias": + interface_name = interface_alias_to_name(config_db, interface_name) + + if interface_name is not None: + if not port_dict: + click.echo("port_dict is None!") + raise click.Abort() + for port_name in port_dict.keys(): + if interface_name == port_name: + return True + if port_channel_dict: + for port_channel_name in port_channel_dict.keys(): + if interface_name == port_channel_name: + return True + if sub_port_intf_dict: + for sub_port_intf_name in sub_port_intf_dict.keys(): + if interface_name == sub_port_intf_name: + return True + return False + +@vlan_member_range.command('add') +@click.argument('vid1', metavar='', required=True, type=int) +@click.argument('vid2', metavar='', required=True, type=int) +@click.argument('interface_name', metavar='', required=True) +@click.option('-u', '--untagged', is_flag=True) +@click.option('-w', "--warning", is_flag=True, help='warnings are not suppressed') +@clicommon.pass_db +def add_vlan_member_range(db, vid1, vid2, interface_name, untagged, warning): + vlan_range_validate(vid1, vid2) + ctx = click.get_current_context() + + if clicommon.get_interface_naming_mode() == "alias": + interface_name = interface_alias_to_name(interface_name) + if interface_name is None: + ctx.fail("'interface_name' is None!") + + if interface_name_is_valid(db.cfgdb, interface_name) is False: + ctx.fail("Interface name is invalid!!") + + vid2 = vid2+1 + vlan_count = vid2-vid1 + if untagged is True and (vlan_count >= 2): + ctx.fail("Same interface {} cannot be untagged member of more than one VLAN".format(interface_name)) + + warning_vlans_list = [] + warning_membership_list = [] + clients = db.cfgdb.get_redis_client(db.cfgdb.CONFIG_DB) + pipe = clients.pipeline() + + for k,v in db.cfgdb.get_table('PORTCHANNEL_MEMBER'): + if v == interface_name: + ctx.fail(" {} is configured as a port channel member".format(interface_name)) + + vlan_ports_data = db.cfgdb.get_table('VLAN_MEMBER') + for vid in range(vid1, vid2): + vlan_name = 'Vlan{}'.format(vid) + vlan = db.cfgdb.get_entry('VLAN', vlan_name) + + if len(vlan) == 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + if (vlan_name, interface_name) in vlan_ports_data.keys(): + if warning is True: + warning_membership_list.append(vid) + if clicommon.get_interface_naming_mode() == "alias": + interface_name = interface_name_to_alias(interface_name) + if interface_name is None: + ctx.fail("'interface_name' is None!") + continue + else: + continue + + pipe.hmset('VLAN_MEMBER|{}'.format(vlan_name+'|'+interface_name), {'tagging_mode': "untagged" if untagged else "tagged" }) + # If port is being made L2 port, enable STP + ctx.obj = {'db': db.cfgdb} + pipe.execute() + # Log warning messages if 'warning' option is enabled + if warning is True and len(warning_vlans_list) != 0: + logging.warning('Non-existent VLANs: {}'.format(get_hyphenated_string(warning_vlans_list))) + if warning is True and len(warning_membership_list) != 0: + if(len(warning_membership_list) == 1): + vlan_string = 'Vlan: ' + else: + vlan_string = 'Vlans: ' + warning_string = str(interface_name) + ' is already a member of ' + vlan_string + get_hyphenated_string(warning_membership_list) + logging.warning('Membership exists already: {}'.format(warning_string)) + +@vlan_member_range.command('del') +@click.argument('vid1', metavar='', required=True, type=int) +@click.argument('vid2', metavar='', required=True, type=int) +@click.argument('interface_name', metavar='', required=True) +@click.option('-w', "--warning", is_flag=True, help='warnings are not suppressed') +@clicommon.pass_db +def del_vlan_member_range(db, vid1, vid2, interface_name, warning): + vlan_range_validate(vid1, vid2) + ctx = click.get_current_context() + + if clicommon.get_interface_naming_mode() == "alias": + interface_name = interface_alias_to_name(interface_name) + if interface_name is None: + ctx.fail("'interface_name' is None!") + + if interface_name_is_valid(db.cfgdb, interface_name) is False: + ctx.fail("Interface name is invalid!!") + + vid2 = vid2+1 + + warning_vlans_list = [] + warning_membership_list = [] + clients = db.cfgdb.get_redis_client(db.cfgdb.CONFIG_DB) + pipe = clients.pipeline() + + vlan_ports_data = db.cfgdb.get_table('VLAN_MEMBER') + for vid in range(vid1, vid2): + vlan_name = 'Vlan{}'.format(vid) + vlan = db.cfgdb.get_entry('VLAN', vlan_name) + + if len(vlan) == 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + if (vlan_name, interface_name) not in vlan_ports_data.keys(): + if warning is True: + warning_membership_list.append(vid) + if clicommon.get_interface_naming_mode() == "alias": + interface_name = interface_name_to_alias(interface_name) + if interface_name is None: + ctx.fail("'interface_name' is None!") + continue + else: + continue + + pipe.delete('VLAN_MEMBER|{}'.format(vlan_name+'|'+interface_name)) + pipe.delete('STP_VLAN_PORT|{}'.format(vlan_name + '|' + interface_name)) + pipe.execute() + # If port is being made non-L2 port, disable STP on interface + ctx.obj = {'db': db.cfgdb} + # Log warning messages if 'warning' option is enabled + if warning is True and len(warning_vlans_list) != 0: + logging.warning('Non-existent VLANs: {}'.format(get_hyphenated_string(warning_vlans_list))) + if warning is True and len(warning_membership_list) != 0: + if(len(warning_membership_list) == 1): + vlan_string = 'Vlan: ' + else: + vlan_string = 'Vlans: ' + warning_string = str(interface_name) + ' is not a member of ' + vlan_string + get_hyphenated_string(warning_membership_list) + logging.warning('Non-existent membership: {}'.format(warning_string)) diff --git a/scripts/fast-reboot-dump.py b/scripts/fast-reboot-dump.py index 79347e1d3f..92a2966282 100644 --- a/scripts/fast-reboot-dump.py +++ b/scripts/fast-reboot-dump.py @@ -189,6 +189,113 @@ def get_fdb(db, vlan_name, vlan_id, bridge_id_2_iface): return fdb_entries, available_macs, map_mac_ip +def generate_fdb_entries_2(filename): + #print("START generate_fdb_entries_2 " + datetime.datetime.now().strftime("%H:%M:%S.%f")) + fdb_entries = [] + map_vlan_oid_to_vlan_id = {} + map_vlan_id_to_vlan_oid = {} + vlan_oid_list = [] + all_available_macs = set() + map_mac_ip_per_vlan = {} + + db = SonicV2Connector(use_unix_socket_path=False) + app_db = SonicV2Connector(use_unix_socket_path=False) + db.connect(db.ASIC_DB, False) # Make one attempt only + app_db.connect(app_db.APPL_DB, False) # Make one attempt only + bridge_id_2_iface = get_map_bridge_port_id_2_iface_name(db, app_db) + + vlan_ifaces = get_vlan_ifaces() + for vlan in vlan_ifaces: + map_mac_ip_per_vlan[vlan] = {} + + client = db.redis_clients["ASIC_DB"] + pipe = client.pipeline() + + #print("generate_fdb_entries_2 before vlan-key-getall" + datetime.datetime.now().strftime("%H:%M:%S.%f")) + vlan_list = db.keys(db.ASIC_DB, 'ASIC_STATE:SAI_OBJECT_TYPE_VLAN:oid:*') + vlan_list = [] if vlan_list is None else vlan_list + for vlan_entry in vlan_list: + vlan_oid = vlan_entry.replace('ASIC_STATE:SAI_OBJECT_TYPE_VLAN:', '') + map_vlan_oid_to_vlan_id[vlan_oid] = 0 + vlan_oid_list.append(vlan_oid) + pipe.hgetall(vlan_entry) + vlan_values = pipe.execute() + #print("generate_fdb_entries_2 after vlan-key-getall" + datetime.datetime.now().strftime("%H:%M:%S.%f")) + + #print("generate_fdb_entries_2 before vlan-map" + datetime.datetime.now().strftime("%H:%M:%S.%f")) + posi = 0 + for vlan_ent in vlan_values: + if 'SAI_VLAN_ATTR_VLAN_ID' not in vlan_ent: + posi = posi + 1 + continue + vlan_id = int(vlan_ent['SAI_VLAN_ATTR_VLAN_ID']) + vlan_oid = vlan_oid_list[posi] + map_vlan_id_to_vlan_oid[vlan_id] = vlan_oid + map_vlan_oid_to_vlan_id[vlan_oid] = vlan_id + posi = posi + 1 + #print("generate_fdb_entries_2 after vlan-map" + datetime.datetime.now().strftime("%H:%M:%S.%f")) + + #print("generate_fdb_entries_2 before fdb-key-getall" + datetime.datetime.now().strftime("%H:%M:%S.%f")) + pipe = client.pipeline() + cur, fdb_list = client.scan(cursor=0, match='ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY:*', count=50) + while cur != 0: + cur, keys = client.scan(cursor=cur, match='ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY:*', count=50) + fdb_list.extend(keys) + + fdb_list = [] if fdb_list is None else fdb_list + for s in fdb_list: + pipe.hgetall(s) + fdb_values = pipe.execute() + #print("generate_fdb_entries_2 after fdb-key-getall" + datetime.datetime.now().strftime("%H:%M:%S.%f")) + + #print("generate_fdb_entries_2 before fdb-process" + datetime.datetime.now().strftime("%H:%M:%S.%f")) + posi = 0 + for fdb_ent in fdb_values: + if 'SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID' not in fdb_ent: + posi = posi + 1 + continue + br_port_id = fdb_ent[b"SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID"] + if br_port_id not in bridge_id_2_iface: + posi = posi + 1 + continue + fdb_port = bridge_id_2_iface[br_port_id] + ent_type = fdb_ent[b"SAI_FDB_ENTRY_ATTR_TYPE"] + fdb_type = ['dynamic','static'][ent_type == "SAI_FDB_ENTRY_TYPE_STATIC"] + key = fdb_list[posi] + key_obj = json.loads(key.replace('ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY:', '')) + mac = str(key_obj['mac']) + vlan_id = map_vlan_oid_to_vlan_id[key_obj['bvid']] + vlan_name = "Vlan" + str(vlan_id) + if not is_mac_unicast(mac): + posi = posi + 1 + continue + all_available_macs.add((vlan_name, mac.lower())) + fdb_mac = mac.replace(':', '-') + obj = { + 'FDB_TABLE:Vlan%d:%s' % (vlan_id, fdb_mac) : { + 'type': fdb_type, + 'port': fdb_port, + }, + 'OP': 'SET' + } + + fdb_entries.append(obj) + if map_mac_ip_per_vlan.get(vlan_name) is None: + map_mac_ip_per_vlan[vlan_name] = {} + map_mac_ip_per_vlan[vlan_name][mac.lower()] = fdb_port + posi = posi + 1 + + #print("generate_fdb_entries_2 after fdb-process" + datetime.datetime.now().strftime("%H:%M:%S.%f")) + db.close(db.ASIC_DB) + + with open(filename, 'w') as fp: + json.dump(fdb_entries, fp, indent=2, separators=(',', ': ')) + + #print("map_mac_ip_per_vlan :" + str(map_mac_ip_per_vlan)) + #print("all_available_macs :" + str(all_available_macs)) + #print("END generate_fdb_entries_2 " + datetime.datetime.now().strftime("%H:%M:%S.%f")) + return all_available_macs, map_mac_ip_per_vlan + def generate_fdb_entries(filename): asic_db = SonicV2Connector(use_unix_socket_path=False) app_db = SonicV2Connector(use_unix_socket_path=False) @@ -328,7 +435,8 @@ def main(): if not os.path.isdir(root_dir): print("Target directory '%s' not found" % root_dir) return 3 - all_available_macs, map_mac_ip_per_vlan = generate_fdb_entries(root_dir + '/fdb.json') + + all_available_macs, map_mac_ip_per_vlan = generate_fdb_entries_2(root_dir + '/fdb.json') neighbor_entries = generate_neighbor_entries(root_dir + '/arp.json', all_available_macs) generate_default_route_entries(root_dir + '/default_routes.json') send_garp_nd(neighbor_entries, map_mac_ip_per_vlan) diff --git a/scripts/fdbclear b/scripts/fdbclear index 7b17459bab..d0e2ffbede 100755 --- a/scripts/fdbclear +++ b/scripts/fdbclear @@ -42,6 +42,7 @@ def main(): try: fdb = FdbClear() + if args.vlan is not None and args.port is not None: fdb.send_notification("PORTVLAN", args.port+'|'+args.vlan) print("Port {} + Vlan{} FDB entries are cleared.".format(args.port, args.vlan)) @@ -53,7 +54,7 @@ def main(): print("Port {} FDB entries are cleared.".format(args.port)) else: fdb.send_notification("ALL", "ALL") - print("FDB entries are cleared.") + print("Dynamic FDB entries are cleared.") except Exception as e: print(str(e)) sys.exit(1) diff --git a/show/main.py b/show/main.py index f4998218f2..db01158390 100755 --- a/show/main.py +++ b/show/main.py @@ -694,7 +694,6 @@ def pwm_buffer_pool(): command = 'watermarkstat -p -t buffer_pool' run_command(command) - # # 'headroom-pool' group ("show headroom-pool ...") # @@ -720,22 +719,79 @@ def pwm_headroom_pool(): # 'mac' command ("show mac ...") # -@cli.command() +@cli.group(invoke_without_command=True) +@click.pass_context @click.option('-v', '--vlan') @click.option('-p', '--port') @click.option('--verbose', is_flag=True, help="Enable verbose output") -def mac(vlan, port, verbose): +def mac(ctx, vlan, port, verbose): """Show MAC (FDB) entries""" + if ctx.invoked_subcommand is None: + cmd = "fdbshow" + if vlan is not None: + cmd += " -v {}".format(vlan) + if port is not None: + cmd += " -p {}".format(port) + run_command(cmd, display_cmd=verbose) - cmd = "fdbshow" +@mac.command() +@clicommon.pass_db +def aging_time(db): + """Show MAC Aging-Time""" + + # Fetching data from config_db for SWITCH + switch_table = db.cfgdb.get_table('SWITCH') + switch_keys = switch_table.keys() + + age_time = 0 + for key in switch_keys: + if key == "switch": + try: + age_time = switch_table[key]['fdb_aging_time'] + except KeyError: + age_time = '0' + output = 'Mac Aging-Time : ' + output += ('%s seconds\n' % (str(age_time))) + click.echo(output) - if vlan is not None: - cmd += " -v {}".format(vlan) +@mac.command() +def count(): + """Show MAC count""" + db = SonicV2Connector(host="127.0.0.1") + db.connect(db.ASIC_DB) - if port is not None: - cmd += " -p {}".format(port) + # Fetching FDB keys from ASIC DB + fdb_keys = db.keys('ASIC_DB', "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY:*") - run_command(cmd, display_cmd=verbose) + if not fdb_keys: + fdb_count = 0 + else: + fdb_count = len(fdb_keys) + + output = 'Total MAC count:' + output += ('%s \n' % (str(fdb_count))) + click.echo(output) + +@mac.command() +@clicommon.pass_db +def config(db): + data = db.cfgdb.get_table('FDB') + keys = list(data.keys()) + + def tablelize(keys, data): + table = [] + + for k in natsorted(keys): + r = [] + r.append(k[0]) + r.append(k[1]) + r.append(data[k]['port']) + table.append(r) + + return table + + header = ['Vlan', 'MAC', 'Port'] + click.echo(tabulate(tablelize(keys, data), header)) # # 'show route-map' command ("show route-map") diff --git a/show/vlan.py b/show/vlan.py index b27f282a49..9ee8715085 100644 --- a/show/vlan.py +++ b/show/vlan.py @@ -177,4 +177,20 @@ def tablelize(keys, data): header = ['Name', 'VID', 'Member', 'Mode'] click.echo(tabulate(tablelize(keys, data), header)) + +@vlan.command() +@clicommon.pass_db +def count(db): + """Show Vlan count""" + + data = db.cfgdb.get_table('VLAN') + vlan_keys = data.keys() + + if not vlan_keys: + vlan_count = 0 + else: + vlan_count = len(vlan_keys) + output = 'Total Vlan count:' + output += ('%s \n' % (str(vlan_count))) + click.echo(output) diff --git a/tests/fdb_test.py b/tests/fdb_test.py new file mode 100644 index 0000000000..5cc1223d31 --- /dev/null +++ b/tests/fdb_test.py @@ -0,0 +1,73 @@ +import os +import traceback +from unittest import mock + +from click.testing import CliRunner + +import config.main as config +import show.main as show +from utilities_common.db import Db + +show_mac_aging_time="""\ +Mac Aging-Time : 300 seconds + +""" + +show_mac_config_add="""\ +Vlan MAC Port +------- ----------------- --------- +Vlan100 00:00:00:00:00:01 Ethernet4 +""" + +show_mac_config_del="""\ +Vlan MAC Port +------ ----- ------ +""" + +class TestFdb(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "1" + print("SETUP") + + def test_fdb_aging_time(self): + runner = CliRunner() + db = Db() + result = runner.invoke(config.config.commands["mac"].commands["aging_time"], ["300"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + result = runner.invoke(show.cli.commands["mac"].commands["aging-time"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_mac_aging_time + + def test_fdb_mac_add(self): + runner = CliRunner() + db = Db() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["100"], obj=db) + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["100", "Ethernet4"], obj=db) + result = runner.invoke(config.config.commands["mac"].commands["add"], ["00:00:00:00:00:01", "100", "Ethernet4"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + result = runner.invoke(show.cli.commands["mac"].commands["config"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_mac_config_add + result = runner.invoke(config.config.commands["mac"].commands["del"], ["00:00:00:00:00:01", "100"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + result = runner.invoke(show.cli.commands["mac"].commands["config"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_mac_config_del + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + print("TEARDOWN") diff --git a/tests/vlan_test.py b/tests/vlan_test.py index a7f533a824..a861de79d5 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -90,6 +90,41 @@ Vlan4000 4000 PortChannel1001 tagged """ +show_vlan_config_output_range="""\ +Name VID Member Mode +-------- ----- --------------- -------- +Vlan1000 1000 Ethernet4 untagged +Vlan1000 1000 Ethernet8 untagged +Vlan1000 1000 Ethernet12 untagged +Vlan1000 1000 Ethernet16 untagged +Vlan2000 2000 Ethernet24 untagged +Vlan2000 2000 Ethernet28 untagged +Vlan3000 3000 +Vlan3001 3001 Ethernet4 tagged +Vlan3001 3001 Ethernet8 tagged +Vlan3002 3002 Ethernet4 tagged +Vlan3002 3002 Ethernet8 tagged +Vlan3003 3003 Ethernet4 tagged +Vlan3003 3003 Ethernet8 tagged +Vlan4000 4000 PortChannel1001 tagged +""" + +show_vlan_config_output_range_after_del="""\ +Name VID Member Mode +-------- ----- --------------- -------- +Vlan1000 1000 Ethernet4 untagged +Vlan1000 1000 Ethernet8 untagged +Vlan1000 1000 Ethernet12 untagged +Vlan1000 1000 Ethernet16 untagged +Vlan2000 2000 Ethernet24 untagged +Vlan2000 2000 Ethernet28 untagged +Vlan3000 3000 +Vlan3001 3001 +Vlan3002 3002 +Vlan3003 3003 +Vlan4000 4000 PortChannel1001 tagged +""" + show_vlan_config_in_alias_mode_output="""\ Name VID Member Mode -------- ----- --------------- -------- @@ -460,6 +495,40 @@ def test_config_add_del_vlan_and_vlan_member_in_alias_mode(self): os.environ['SONIC_CLI_IFACE_MODE'] = "default" + def test_vlan_config_range(self): + runner = CliRunner() + db = Db() + result = runner.invoke(config.config.commands["vlan"].commands["range"].commands["add"], ["3001", "3003"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["range"].commands["add"], ["3001", "3003", "Ethernet4"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["range"].commands["add"], ["3001", "3003", "Ethernet8"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + result = runner.invoke(show.cli.commands["vlan"].commands["config"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_config_output_range + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["range"].commands["del"], ["3001", "3003", "Ethernet4"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["range"].commands["del"], ["3001", "3003", "Ethernet8"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + result = runner.invoke(show.cli.commands["vlan"].commands["config"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_config_output_range_after_del + def test_config_vlan_proxy_arp_with_nonexist_vlan_intf_table(self): modes = ["enabled", "disabled"] runner = CliRunner()