diff --git a/config/main.py b/config/main.py index 731043a668..77e6a2f4ef 100755 --- a/config/main.py +++ b/config/main.py @@ -7,6 +7,7 @@ import netaddr import re import syslog +import logging import time import netifaces @@ -14,6 +15,8 @@ import ipaddress from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig from minigraph import parse_device_desc_xml +from itertools import count, groupby +from click_default_group import DefaultGroup from utilities_common.intf_filter import parse_interface_in_filter import aaa @@ -231,6 +234,15 @@ def interface_name_is_valid(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<1 or vid>4094: + return False + + return True + def interface_name_to_alias(interface_name): """Return alias interface name if default name is given as argument """ @@ -1644,6 +1656,371 @@ def del_vlan_dhcp_relay_destination(ctx, vid, dhcp_relay_destination_ip): else: ctx.fail("{} is not a DHCP relay destination for {}".format(dhcp_relay_destination_ip, vlan_name)) +# 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 + +# +# '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') +@click.pass_context +def add_vlan_range(ctx, vid1, vid2, warning): + db = ctx.obj['db'] + + vlan_range_validate(ctx, vid1, vid2) + + vid2 = vid2+1 + + warning_vlans_list = [] + curr_vlan_count = 0 + config_db = ConfigDBConnector() + config_db.connect() + payload = [] + for vid in range (vid1, vid2): + vlan = 'Vlan{}'.format(vid) + + if len(db.get_entry('VLAN', vlan)) != 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + payload.append(('VLAN|{}'.format(vlan), {'vlanid': vid}) + curr_vlan_count += 1 + db.set_bulk(payload) + # 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') +@click.pass_context +def del_vlan_range(ctx, vid1, vid2, warning): + db = ctx.obj['db'] + + vlan_range_validate(ctx, vid1, vid2) + + vid2 = vid2+1 + + warning_vlans_list = [] + warning_membership_list = [] + warning_ip_list = [] + config_db = ConfigDBConnector() + config_db.connect() + vlan_members = [] + vlan_member_keys = db.keys('CONFIG_DB', "*VLAN_MEMBER*") + vlan_temp_member_keys = db.keys('CONFIG_DB', "*VLAN_MEMBER*") + vlan_ip_members = [] + vlan_ip_keys = db.keys('CONFIG_DB', "*VLAN_INTERFACE*") + + # 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: + payload = [] + for vid in range(vid1, vid2): + vlan = 'Vlan{}'.format(vid) + if len(db.get_entry('VLAN', vlan)) == 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + payload.append(('VLAN|{}'.format(vlan))) + db.del_bulk(payload + else: + if vlan_ip_keys is not None: + payload = [] + for v in vlan_ip_keys: + payload.append(v) + vlan_ip_members = db.get_bulk(payload + if vlan_member_keys is not None: + payload = [] + for v in vlan_member_keys: + payload.append(v) + vlan_members = db.get_bulk(payload) + payload = [] + for vid in range(vid1, vid2): + vlan_member_configured = False + ip_configured = False + vlan = 'Vlan{}'.format(vid) + + if len(db.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) + payload.append(('VLAN|{}'.format(vlan))) + db.del_bulk(payload) + + # 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 + +@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') +@click.pass_context +def add_vlan_member_range(ctx, vid1, vid2, interface_name, untagged, warning): + db = ctx.obj['db'] + + vlan_range_validate(ctx, vid1, vid2) + + if 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(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 = [] + config_db = ConfigDBConnector() + config_db.connect() + payload = [] + + # Validate if interface has IP configured + # in physical and port channel tables + for k,v in db.get_table('INTERFACE').iteritems(): + if k == interface_name: + ctx.fail(" {} has ip address configured".format(interface_name)) + + for k,v in db.get_table('PORTCHANNEL_INTERFACE').iteritems(): + if k == interface_name: + ctx.fail(" {} has ip address configured".format(interface_name)) + + for k,v in db.get_table('PORTCHANNEL_MEMBER'): + if v == interface_name: + ctx.fail(" {} is configured as a port channel member".format(interface_name)) + + for vid in range(vid1, vid2): + vlan_name = 'Vlan{}'.format(vid) + vlan = db.get_entry('VLAN', vlan_name) + + if len(vlan) == 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + members = vlan.get('members', []) + if interface_name in members: + if warning is True: + warning_membership_list.append(vid) + if 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 + + members.append(interface_name) + vlan['members'] = members + payload.append(('VLAN|{}'.format(vlan_name), vlan_member_data(vlan))) + payload.append(('VLAN_MEMBER|{}'.format(vlan_name+'|'+interface_name), {'tagging_mode': "untagged" if untagged else "tagged" })) + db.set_bulk(payload) + # 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') +@click.pass_context +def del_vlan_member_range(ctx, vid1, vid2, interface_name, warning): + db = ctx.obj['db'] + + vlan_range_validate(ctx, vid1, vid2) + + if 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(interface_name) is False: + ctx.fail("Interface name is invalid!!") + + vid2 = vid2+1 + + warning_vlans_list = [] + warning_membership_list = [] + config_db = ConfigDBConnector() + config_db.connect() + set_payload = [] + hdel_payload = [] + del_payload = [] + + for vid in range(vid1, vid2): + vlan_name = 'Vlan{}'.format(vid) + vlan = db.get_entry('VLAN', vlan_name) + + if len(vlan) == 0: + if warning is True: + warning_vlans_list.append(vid) + continue + + members = vlan.get('members', []) + if interface_name not in members: + if warning is True: + warning_membership_list.append(vid) + if 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 + + members.remove(interface_name) + if len(members) == 0: + hdel_payload.append(('VLAN|{}'.format(vlan_name), 'members@') + else: + vlan['members'] = members + set_payload.append(('VLAN|{}'.format(vlan_name), vlan_member_data(vlan))) + + del_payload.append(('VLAN_MEMBER|{}'.format(vlan_name+'|'+interface_name))) + db.hdel_bulk(hdel_payload) + db.del_bulk(del_payload) + db.set_bulk(set_payload) + + # 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)) + # # 'bgp' group ('config bgp ...') #