From bd3e4e919608697625121f1acf012fa8b8236748 Mon Sep 17 00:00:00 2001 From: Rajesh Sankaran Date: Tue, 7 Apr 2020 23:20:41 -0700 Subject: [PATCH] JIRA-SONIC-1234: Add EVPN realted VxLAN commands cherry-pick from ec201911 branch bb054420 kh_shi, JIRA-SONIC-720: [EVPN] fix typo and ip valid function for sync EVPN community PRs 1f248d84 Tapash Das, LTGM Warning Fix b8b50794 Tapash Das, Added VRF VNI Map CLI and Neighbor Suppression CLI. 807a0553 Rajesh Sankaran, Fixed LGTM Warnings 847bd9f7 Rajesh Sankaran, Changed the same for REMOTE_VNI Table. 53027878 Rajesh Sankaran, Changes to support EVPN VXLAN Please refer to https://github.com/Azure/SONiC/pull/437 Change-Id: I9bbc2d56bb413bf3dc9c5d0efbd72c92eb351eb5 --- config/main.py | 435 ++++++++++++++++++++++++++++++++++++++++++-- scripts/fast-reboot | 1 + show/main.py | 333 ++++++++++++++++++++++++++++++++- 3 files changed, 745 insertions(+), 24 deletions(-) diff --git a/config/main.py b/config/main.py index fe573e85bf..d8c5b03892 100755 --- a/config/main.py +++ b/config/main.py @@ -399,6 +399,45 @@ 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 vni_id_is_valid(vni): + """Check if the vni id is in acceptable range (between 1 and 2^24) + """ + + if (vni < 1) or (vni > 16777215): + return False + + return True + +def is_vni_vrf_mapped(ctx, vni): + """Check if the vni is mapped to vrf + """ + + found = 0 + db = ctx.obj['db'] + vrf_table = db.get_table('VRF') + vrf_keys = vrf_table.keys() + if vrf_keys is not None: + for vrf_key in vrf_keys: + if ('vni' in vrf_table[vrf_key] and vrf_table[vrf_key]['vni'] == vni): + found = 1 + break + + if (found == 1): + print "VNI {} mapped to Vrf {}, Please remove VRF VNI mapping".format(vni, vrf_key) + return False + + return True + def interface_name_to_alias(interface_name): """Return alias interface name if default name is given as argument """ @@ -538,7 +577,7 @@ def _is_neighbor_ipaddress(config_db, ipaddress): def _get_all_neighbor_ipaddresses(config_db, ignore_local_hosts=False): """Returns list of strings containing IP addresses of all BGP neighbors - if the flag ignore_local_hosts is set to True, additional check to see if + if the flag ignore_local_hosts is set to True, additional check to see if if the BGP neighbor AS number is same as local BGP AS number, if so ignore that neigbor. """ addrs = [] @@ -907,7 +946,7 @@ def load(filename, yes): # if any of the config files in linux host OR namespace is not present, return if not os.path.isfile(file): click.echo("The config_db file {} doesn't exist".format(file)) - return + return if namespace is None: command = "{} -j {} --write-to-db".format(SONIC_CFGGEN_PATH, file) @@ -1125,7 +1164,7 @@ def load_minigraph(no_service_restart): if os.path.isfile('/etc/sonic/acl.json'): run_command("acl-loader update full /etc/sonic/acl.json", display_cmd=True) - + # Write latest db version string into db db_migrator='/usr/bin/db_migrator.py' if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK): @@ -1135,7 +1174,7 @@ def load_minigraph(no_service_restart): else: cfggen_namespace_option = " -n {}".format(namespace) run_command(db_migrator + ' -o set_version' + cfggen_namespace_option) - + # We first run "systemctl reset-failed" to remove the "failed" # status from all services before we attempt to restart them if not no_service_restart: @@ -1260,7 +1299,7 @@ def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer): if queue is not None: session_info['queue'] = queue - + """ For multi-npu platforms we need to program all front asic namespaces """ @@ -1585,7 +1624,7 @@ def add_vlan_member(ctx, vid, interface_name, untagged): for entry in interface_table: if (interface_name == entry[0]): ctx.fail("{} is a L3 interface!".format(interface_name)) - + members.append(interface_name) vlan['members'] = members db.set_entry('VLAN', vlan_name, vlan) @@ -1683,7 +1722,7 @@ def add_snmp_agent_address(ctx, agentip, port, vrf): #Construct SNMP_AGENT_ADDRESS_CONFIG table key in the format ip|| key = agentip+'|' if port: - key = key+port + key = key+port key = key+'|' if vrf: key = key+vrf @@ -1704,7 +1743,7 @@ def del_snmp_agent_address(ctx, agentip, port, vrf): key = agentip+'|' if port: - key = key+port + key = key+port key = key+'|' if vrf: key = key+vrf @@ -1966,7 +2005,7 @@ def all(verbose): config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) config_db.connect() bgp_neighbor_ip_list = _get_all_neighbor_ipaddresses(config_db, ignore_local_hosts) - for ipaddress in bgp_neighbor_ip_list: + for ipaddress in bgp_neighbor_ip_list: _change_bgp_session_status_by_addr(config_db, ipaddress, 'up', verbose) # 'neighbor' subcommand @@ -2261,7 +2300,7 @@ def breakout(ctx, interface_name, mode, verbose, force_remove_dependencies, load sys.exit(0) def _get_all_mgmtinterface_keys(): - """Returns list of strings containing mgmt interface keys + """Returns list of strings containing mgmt interface keys """ config_db = ConfigDBConnector() config_db.connect() @@ -2595,6 +2634,56 @@ def del_vrf(ctx, vrf_name): config_db.set_entry('VRF', vrf_name, None) +@vrf.command('add_vrf_vni_map') +@click.argument('vrfname', metavar='', required=True, type=str) +@click.argument('vni', metavar='', required=True) +@click.pass_context +def add_vrf_vni_map(ctx, vrfname, vni): + db = ctx.obj['config_db'] + found = 0 + if vrfname not in db.get_table('VRF').keys(): + ctx.fail("vrf {} doesnt exists".format(vrfname)) + if not vni.isdigit(): + ctx.fail("Invalid VNI {}. Only valid VNI is accepted".format(vni)) + + if (int(vni) < 1) or (int(vni) > 16777215): + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni)) + + vxlan_table = db.get_table('VXLAN_TUNNEL_MAP') + vxlan_keys = vxlan_table.keys() + if vxlan_keys is not None: + for key in vxlan_keys: + if (vxlan_table[key]['vni'] == vni): + found = 1 + break + + if (found == 0): + ctx.fail(" VLAN VNI not mapped. Please create VLAN VNI map entry first ") + + found = 0 + vrf_table = db.get_table('VRF') + vrf_keys = vrf_table.keys() + if vrf_keys is not None: + for vrf_key in vrf_keys: + if ('vni' in vrf_table[vrf_key] and vrf_table[vrf_key]['vni'] == vni): + found = 1 + break + + if (found == 1): + ctx.fail("VNI already mapped to vrf {}".format(vrf_key)) + + db.mod_entry('VRF', vrfname, {"vni": vni}) + +@vrf.command('del_vrf_vni_map') +@click.argument('vrfname', metavar='', required=True, type=str) +@click.pass_context +def del_vrf_vni_map(ctx, vrfname): + db = ctx.obj['config_db'] + if vrfname not in db.get_table('VRF').keys(): + ctx.fail("vrf {} doesnt exists".format(vrfname)) + + db.mod_entry('VRF', vrfname, {"vni": 0}) + # # 'route' group ('config route ...') # @@ -2985,9 +3074,9 @@ def priority(ctx, interface_name, priority, status): interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") - + run_command("pfc config priority {0} {1} {2}".format(status, interface_name, priority)) - + # # 'platform' group ('config platform ...') # @@ -3086,10 +3175,10 @@ def naming_mode_alias(): def is_loopback_name_valid(loopback_name): """Loopback name validation """ - + if loopback_name[:CFG_LOOPBACK_PREFIX_LEN] != CFG_LOOPBACK_PREFIX : return False - if (loopback_name[CFG_LOOPBACK_PREFIX_LEN:].isdigit() is False or + if (loopback_name[CFG_LOOPBACK_PREFIX_LEN:].isdigit() is False or int(loopback_name[CFG_LOOPBACK_PREFIX_LEN:]) > CFG_LOOPBACK_ID_MAX_VAL) : return False if len(loopback_name) > CFG_LOOPBACK_NAME_TOTAL_LEN_MAX: @@ -3256,7 +3345,7 @@ def add_ntp_server(ctx, ntp_ip_address): if ntp_ip_address in ntp_servers: click.echo("NTP server {} is already configured".format(ntp_ip_address)) return - else: + else: db.set_entry('NTP_SERVER', ntp_ip_address, {'NULL': 'NULL'}) click.echo("NTP server {} added to configuration".format(ntp_ip_address)) try: @@ -3277,7 +3366,7 @@ def del_ntp_server(ctx, ntp_ip_address): if ntp_ip_address in ntp_servers: db.set_entry('NTP_SERVER', '{}'.format(ntp_ip_address), None) click.echo("NTP server {} removed from configuration".format(ntp_ip_address)) - else: + else: ctx.fail("NTP server {} is not configured.".format(ntp_ip_address)) try: click.echo("Restarting ntp-config service...") @@ -3565,7 +3654,7 @@ def delete(ctx): # # 'feature' command ('config feature name state') -# +# @config.command('feature') @click.argument('name', metavar='', required=True) @click.argument('state', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) @@ -3617,5 +3706,317 @@ def autorestart(container_name, autorestart_status): config_db.mod_entry('CONTAINER_FEATURE', container_name, {'auto_restart': autorestart_status}) +# +# 'vxlan' group ('config vxlan ...') +# +@config.group() +@click.pass_context +def vxlan(ctx): + config_db = ConfigDBConnector() + config_db.connect() + ctx.obj = {'db': config_db} + +@vxlan.command('add') +@click.argument('vxlan_name', metavar='', required=True) +@click.argument('src_ip', metavar='', required=True) +@click.pass_context +def add_vxlan(ctx, vxlan_name, src_ip): + """Add VXLAN""" + if not is_ipaddress(src_ip): + ctx.fail("{} invalid src ip address".format(src_ip)) + db = ctx.obj['db'] + + vxlan_keys = db.keys('CONFIG_DB', "VXLAN_TUNNEL|*") + if not vxlan_keys: + vxlan_count = 0 + else: + vxlan_count = len(vxlan_keys) + + if(vxlan_count > 0): + ctx.fail("VTEP already configured.") + + fvs = {'src_ip': src_ip} + db.set_entry('VXLAN_TUNNEL', vxlan_name, fvs) + +@vxlan.command('del') +@click.argument('vxlan_name', metavar='', required=True) +@click.pass_context +def del_vxlan(ctx, vxlan_name): + """Del VXLAN""" + db = ctx.obj['db'] + + vxlan_keys = db.keys('CONFIG_DB', "VXLAN_EVPN_NVO|*") + if not vxlan_keys: + vxlan_count = 0 + else: + vxlan_count = len(vxlan_keys) + + if(vxlan_count > 0): + ctx.fail("Please delete the EVPN NVO configuration.") + + vxlan_keys = db.keys('CONFIG_DB', "VXLAN_TUNNEL_MAP|*") + if not vxlan_keys: + vxlan_count = 0 + else: + vxlan_count = len(vxlan_keys) + + if(vxlan_count > 0): + ctx.fail("Please delete all VLAN VNI mappings.") + + db.set_entry('VXLAN_TUNNEL', vxlan_name, None) + +@vxlan.group('evpn_nvo') +@click.pass_context +def vxlan_evpn_nvo(ctx): + pass + +@vxlan_evpn_nvo.command('add') +@click.argument('nvo_name', metavar='', required=True) +@click.argument('vxlan_name', metavar='', required=True) +@click.pass_context +def add_vxlan_evpn_nvo(ctx, nvo_name, vxlan_name): + """Add NVO""" + db = ctx.obj['db'] + vxlan_keys = db.keys('CONFIG_DB', "VXLAN_EVPN_NVO|*") + if not vxlan_keys: + vxlan_count = 0 + else: + vxlan_count = len(vxlan_keys) + + if(vxlan_count > 0): + ctx.fail("EVPN NVO already configured") + + if len(db.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + + fvs = {'source_vtep': vxlan_name} + db.set_entry('VXLAN_EVPN_NVO', nvo_name, fvs) + +@vxlan_evpn_nvo.command('del') +@click.argument('nvo_name', metavar='', required=True) +@click.pass_context +def del_vxlan_evpn_nvo(ctx, nvo_name): + """Del NVO""" + db = ctx.obj['db'] + vxlan_keys = db.keys('CONFIG_DB', "VXLAN_TUNNEL_MAP|*") + if not vxlan_keys: + vxlan_count = 0 + else: + vxlan_count = len(vxlan_keys) + + if(vxlan_count > 0): + ctx.fail("Please delete all VLAN VNI mappings.") + db.set_entry('VXLAN_EVPN_NVO', nvo_name, None) + +@vxlan.group('map') +@click.pass_context +def vxlan_map(ctx): + pass + +@vxlan_map.command('add') +@click.argument('vxlan_name', metavar='', required=True) +@click.argument('vlan', metavar='', required=True) +@click.argument('vni', metavar='', required=True) +@click.pass_context +def add_vxlan_map(ctx, vxlan_name, vlan, vni): + """Add VLAN-VNI map entry""" + if not vlan.isdigit(): + ctx.fail("Invalid vlan {}. Only valid vlan is accepted".format(vni)) + if vlan_id_is_valid(int(vlan)) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + if not vni.isdigit(): + ctx.fail("Invalid VNI {}. Only valid VNI is accepted".format(vni)) + #if (int(vni) < 1) or (int(vni) > 16777215): + if vni_id_is_valid(int(vni)) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni)) + + db = ctx.obj['db'] + vlan_name = "Vlan" + vlan + + if len(db.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + + if len(db.get_entry('VLAN', vlan_name)) == 0: + ctx.fail("{} not configured".format(vlan_name)) + + vxlan_table = db.get_table('VXLAN_TUNNEL_MAP') + vxlan_keys = vxlan_table.keys() + if vxlan_keys is not None: + for key in vxlan_keys: + if (vxlan_table[key]['vlan'] == vlan_name): + ctx.fail(" Vlan Id already mapped ") + if (vxlan_table[key]['vni'] == vni): + ctx.fail(" VNI Id already mapped ") + + fvs = {'vni': vni, + 'vlan' : vlan_name} + mapname = vxlan_name + '|' + 'map_' + vni + '_' + vlan_name + db.set_entry('VXLAN_TUNNEL_MAP', mapname, fvs) + +@vxlan_map.command('del') +@click.argument('vxlan_name', metavar='', required=True) +@click.argument('vlan', metavar='', required=True) +@click.argument('vni', metavar='', required=True) +@click.pass_context +def del_vxlan_map(ctx, vxlan_name, vlan, vni): + """Del VLAN-VNI map entry""" + if not vlan.isdigit(): + ctx.fail("Invalid vlan {}. Only valid vlan is accepted".format(vni)) + if vlan_id_is_valid(int(vlan)) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + if not vni.isdigit(): + ctx.fail("Invalid VNI {}. Only valid VNI is accepted".format(vni)) + #if (int(vni) < 1) or (int(vni) > 16777215): + if vni_id_is_valid(int(vni)) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni)) + + db = ctx.obj['db'] + if len(db.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + found = 0 + vrf_table = db.get_table('VRF') + vrf_keys = vrf_table.keys() + if vrf_keys is not None: + for vrf_key in vrf_keys: + if ('vni' in vrf_table[vrf_key] and vrf_table[vrf_key]['vni'] == vni): + found = 1 + break + + if (found == 1): + ctx.fail("VNI mapped to vrf {}, Please remove VRF VNI mapping".format(vrf_key)) + + mapname = vxlan_name + '|' + 'map_' + vni + '_' + vlan + db.set_entry('VXLAN_TUNNEL_MAP', mapname, None) + mapname = vxlan_name + '|' + 'map_' + vni + '_Vlan' + vlan + db.set_entry('VXLAN_TUNNEL_MAP', mapname, None) + +@vxlan.group('map_range') +@click.pass_context +def vxlan_map_range(ctx): + pass + +@vxlan_map_range.command('add') +@click.argument('vxlan_name', metavar='', required=True) +@click.argument('vlan_start', metavar='', required=True, type=int) +@click.argument('vlan_end', metavar='', required=True, type=int) +@click.argument('vni_start', metavar='', required=True, type=int) +@click.pass_context +def add_vxlan_map_range(ctx, vxlan_name, vlan_start, vlan_end, vni_start): + """Add Range of vlan-vni mappings""" + if vlan_id_is_valid(vlan_start) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + if vlan_id_is_valid(vlan_end) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + if (vlan_start > vlan_end): + ctx.fail("vlan_end should be greater or equal to vlan_start") + if vni_id_is_valid(vni_start) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni_start)) + if vni_id_is_valid(vni_start+vlan_end-vlan_start) is False: + ctx.fail("Invalid VNI End {}. Valid range [1 to 16777215].".format(vni_start)) + + db = ctx.obj['db'] + if len(db.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + vlan_end = vlan_end + 1 + vxlan_table = db.get_table('VXLAN_TUNNEL_MAP') + vxlan_keys = vxlan_table.keys() + + for vid in range (vlan_start, vlan_end): + vlan_name = 'Vlan{}'.format(vid) + vnid = vni_start+vid-vlan_start + vni_name = '{}'.format(vnid) + match_found = 'no' + if len(db.get_entry('VLAN', vlan_name)) == 0: + click.echo("{} not configured".format(vlan_name)) + continue + if vxlan_keys is not None: + for key in vxlan_keys: + if (vxlan_table[key]['vlan'] == vlan_name): + print(vlan_name + " already mapped") + match_found = 'yes' + break + if (vxlan_table[key]['vni'] == vni_name): + print("VNI:" + vni_name + " already mapped ") + match_found = 'yes' + break + if (match_found == 'yes'): + continue + fvs = {'vni': vni_name, + 'vlan' : vlan_name} + mapname = vxlan_name + '|' + 'map_' + vni_name + '_' + vlan_name + db.set_entry('VXLAN_TUNNEL_MAP', mapname, fvs) + +@vxlan_map_range.command('del') +@click.argument('vxlan_name', metavar='', required=True) +@click.argument('vlan_start', metavar='', required=True, type=int) +@click.argument('vlan_end', metavar='', required=True, type=int) +@click.argument('vni_start', metavar='', required=True, type=int) +@click.pass_context +def del_vxlan_map_range(ctx, vxlan_name, vlan_start, vlan_end, vni_start): + """Del Range of vlan-vni mappings""" + if vlan_id_is_valid(vlan_start) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + if vlan_id_is_valid(vlan_end) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + if (vlan_start > vlan_end): + ctx.fail("vlan_end should be greater or equal to vlan_start") + if vni_id_is_valid(vni_start) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni_start)) + if vni_id_is_valid(vni_start+vlan_end-vlan_start) is False: + ctx.fail("Invalid VNI End {}. Valid range [1 to 16777215].".format(vni_start)) + + db = ctx.obj['db'] + if len(db.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + + vlan_end = vlan_end + 1 + for vid in range (vlan_start, vlan_end): + vlan_name = 'Vlan{}'.format(vid) + vnid = vni_start+vid-vlan_start + vni_name = '{}'.format(vnid) + if is_vni_vrf_mapped(ctx, vni_name) is False: + print "Skipping Vlan {} VNI {} mapped delete. ".format(vlan_name, vni_name) + continue + + mapname = vxlan_name + '|' + 'map_' + vni_name + '_' + vlan_name + db.set_entry('VXLAN_TUNNEL_MAP', mapname, None) + +####### +# +# 'neigh_suppress' group ('config neigh_suppress...') +# +@config.group() +@click.pass_context +def neigh_suppress(ctx): + """ Neighbour Suppress VLAN-related configuration """ + config_db = ConfigDBConnector() + config_db.connect() + ctx.obj = {'db': config_db} + +@neigh_suppress.command('enable') +@click.argument('vid', metavar='', required=True, type=int) +@click.pass_context +def enable_neigh_suppress(ctx, vid): + db = ctx.obj['db'] + if vlan_id_is_valid(vid) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + vlan = 'Vlan{}'.format(vid) + if len(db.get_entry('VLAN', vlan)) == 0: + click.echo("{} doesn't exist".format(vlan)) + return + fvs = {'suppress': "on"} + db.set_entry('SUPPRESS_VLAN_NEIGH', vlan, fvs) + +@neigh_suppress.command('disable') +@click.argument('vid', metavar='', required=True, type=int) +@click.pass_context +def disable_neigh_suppress(ctx, vid): + db = ctx.obj['db'] + if vlan_id_is_valid(vid) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + vlan = 'Vlan{}'.format(vid) + db.set_entry('SUPPRESS_VLAN_NEIGH', vlan, None) + if __name__ == '__main__': config() + diff --git a/scripts/fast-reboot b/scripts/fast-reboot index f368d06480..3768d33a86 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -240,6 +240,7 @@ function backup_database() if not string.match(k, 'FDB_TABLE|') and not string.match(k, 'WARM_RESTART_TABLE|') \ and not string.match(k, 'MIRROR_SESSION_TABLE|') \ and not string.match(k, 'PIM_TABLE|') \ + and not string.match(k, 'VXLAN_TUNNEL_TABLE|') \ and not string.match(k, 'WARM_RESTART_ENABLE_TABLE|') then redis.call('del', k) end diff --git a/show/main.py b/show/main.py index b3e24d0532..3066daa278 100755 --- a/show/main.py +++ b/show/main.py @@ -38,6 +38,15 @@ # noinspection PyUnresolvedReferences import configparser +def is_ipaddress(val): + """ Validate if an entry is a valid IP """ + if not val: + return False + try: + netaddr.IPAddress(str(val)) + except: + return False + return True # This is from the aliases example: # https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py @@ -1179,8 +1188,8 @@ def priority(interface): cmd = 'pfc show priority' if interface is not None and get_interface_mode() == "alias": interface = iface_alias_converter.alias_to_name(interface) - - if interface is not None: + + if interface is not None: cmd += ' {0}'.format(interface) run_command(cmd) @@ -1192,8 +1201,8 @@ def asymmetric(interface): cmd = 'pfc show asymmetric' if interface is not None and get_interface_mode() == "alias": interface = iface_alias_converter.alias_to_name(interface) - - if interface is not None: + + if interface is not None: cmd += ' {0}'.format(interface) run_command(cmd) @@ -1727,7 +1736,7 @@ def protocol(verbose): from .bgp_quagga_v6 import bgp ipv6.add_command(bgp) elif routing_stack == "frr": - from .bgp_frr_v4 import bgp + from .bgp_frr_v4 import bgp ip.add_command(bgp) from .bgp_frr_v6 import bgp ipv6.add_command(bgp) @@ -2173,7 +2182,7 @@ def ntp(ctx, verbose): ntpstat_cmd = "ntpstat" ntpcmd = "ntpq -p -n" if is_mgmt_vrf_enabled(ctx) is True: - #ManagementVRF is enabled. Call ntpq using "ip vrf exec" or cgexec based on linux version + #ManagementVRF is enabled. Call ntpq using "ip vrf exec" or cgexec based on linux version os_info = os.uname() release = os_info[2].split('-') if parse_version(release[0]) > parse_version("4.9.0"): @@ -2971,7 +2980,7 @@ def pool(verbose): # Define GEARBOX commands only if GEARBOX is configured app_db = SonicV2Connector(host='127.0.0.1') -app_db.connect(app_db.APPL_DB) +app_db.connect(app_db.APPL_DB) if app_db.keys(app_db.APPL_DB, '_GEARBOX_TABLE:phy:*'): @cli.group(cls=AliasedGroup) @@ -3438,5 +3447,315 @@ def status(slot_index, verbose): run_command(cmd, display_cmd=verbose) + +# +# 'vxlan' group ("show vxlan ...") +# + +@cli.group(cls=AliasedGroup) +def vxlan(): + """Show VXLAN information""" + pass + +@vxlan.command() +def interface(): + """Show VXLAN VTEP Information""" + + config_db = ConfigDBConnector() + config_db.connect() + + # Fetching VTEP keys from config DB + click.secho('VTEP Information:\n', bold=True, underline=True) + vxlan_table = config_db.get_table('VXLAN_TUNNEL') + vxlan_keys = vxlan_table.keys() + vtep_sip = '0.0.0.0' + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + key1 = key.split('|',1) + vtepname = key1.pop(); + if 'src_ip' in vxlan_table[key]: + vtep_sip = vxlan_table[key]['src_ip'] + if vtep_sip is not '0.0.0.0': + output = '\tVTEP Name : ' + vtepname + ', SIP : ' + vxlan_table[key]['src_ip'] + else: + output = '\tVTEP Name : ' + vtepname + + click.echo(output) + + if vtep_sip is not '0.0.0.0': + vxlan_table = config_db.get_table('EVPN_NVO') + vxlan_keys = vxlan_table.keys() + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + key1 = key.split('|',1) + vtepname = key1.pop(); + output = '\tNVO Name : ' + vtepname + ', VTEP : ' + vxlan_table[key]['source_vtep'] + click.echo(output) + + vxlan_keys = config_db.keys('CONFIG_DB', "LOOPBACK_INTERFACE|*") + loopback = 'Not Configured' + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + key1 = key.split('|',2) + if len(key1) == 3 and key1[2] == vtep_sip+'/32': + loopback = key1[1] + break + output = '\tSource interface : ' + loopback + if vtep_sip != '0.0.0.0': + click.echo(output) + +@vxlan.command() +@click.argument('count', required=False) +def vlanvnimap(count): + """Show VLAN VNI Mapping Information""" + + header = ['VLAN', 'VNI'] + body = [] + + config_db = ConfigDBConnector() + config_db.connect() + + if count is not None: + vxlan_keys = config_db.keys('CONFIG_DB', "VXLAN_TUNNEL_MAP|*") + + if not vxlan_keys: + vxlan_count = 0 + else: + vxlan_count = len(vxlan_keys) + + output = 'Total mapping count:' + output += ('%s \n' % (str(vxlan_count))) + click.echo(output) + else: + vxlan_table = config_db.get_table('VXLAN_TUNNEL_MAP') + vxlan_keys = vxlan_table.keys() + num=0 + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + body.append([vxlan_table[key]['vlan'], vxlan_table[key]['vni']]) + num += 1 + click.echo(tabulate(body, header, tablefmt="grid")) + output = 'Total count : ' + output += ('%s \n' % (str(num))) + click.echo(output) + +@vxlan.command() +def vrfvnimap(): + """Show VRF VNI Mapping Information""" + + header = ['VRF', 'VNI'] + body = [] + + config_db = ConfigDBConnector() + config_db.connect() + + vrf_table = config_db.get_table('VRF') + vrf_keys = vrf_table.keys() + num=0 + if vrf_keys is not None: + for key in natsorted(vrf_keys): + if ('vni' in vrf_table[key]): + body.append([key, vrf_table[key]['vni']]) + num += 1 + click.echo(tabulate(body, header, tablefmt="grid")) + output = 'Total count : ' + output += ('%s \n' % (str(num))) + click.echo(output) + +@vxlan.command() +@click.argument('count', required=False) +def tunnel(count): + """Show All VXLAN Tunnels Information""" + + if (count is not None) and (count != 'count'): + click.echo("Unacceptable argument {}".format(count)) + return + + header = ['SIP', 'DIP', 'Creation Source', 'OperStatus'] + body = [] + db = SonicV2Connector(host='127.0.0.1') + db.connect(db.STATE_DB) + + vxlan_keys = db.keys(db.STATE_DB, 'VXLAN_TUNNEL_TABLE|*') + + if vxlan_keys is not None: + vxlan_count = len(vxlan_keys) + else: + vxlan_count = 0 + + if (count is not None): + output = 'Total mapping count:' + output += ('%s \n' % (str(vxlan_count))) + click.echo(output) + else: + num = 0 + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + vxlan_table = db.get_all(db.STATE_DB, key); + if vxlan_table is None: + continue + body.append([vxlan_table['src_ip'], vxlan_table['dst_ip'], vxlan_table['tnl_src'], 'oper_' + vxlan_table['operstatus']]) + num += 1 + click.echo(tabulate(body, header, tablefmt="grid")) + output = 'Total count : ' + output += ('%s \n' % (str(num))) + click.echo(output) + +@vxlan.command() +@click.argument('remote_vtep_ip', required=True) +@click.argument('count', required=False) +def remote_vni(remote_vtep_ip, count): + """Show Vlans extended to the remote VTEP""" + + if (remote_vtep_ip != 'all') and (is_ipaddress(remote_vtep_ip) is False): + click.echo("Remote VTEP IP {} invalid format".format(remote_vtep_ip)) + return + + header = ['VLAN', 'RemoteVTEP', 'VNI'] + body = [] + db = SonicV2Connector(host='127.0.0.1') + db.connect(db.APPL_DB) + + if(remote_vtep_ip == 'all'): + vxlan_keys = db.keys(db.APPL_DB, 'VXLAN_REMOTE_VNI_TABLE:*') + else: + vxlan_keys = db.keys(db.APPL_DB, 'VXLAN_REMOTE_VNI_TABLE:*' + remote_vtep_ip + '*') + + if count is not None: + if not vxlan_keys: + vxlan_count = 0 + else: + vxlan_count = len(vxlan_keys) + + output = 'Total mapping count:' + output += ('%s \n' % (str(vxlan_count))) + click.echo(output) + else: + num = 0 + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + key1 = key.split(':') + rmtip = key1.pop(); + #if remote_vtep_ip != 'all' and rmtip != remote_vtep_ip: + # continue + vxlan_table = db.get_all(db.APPL_DB, key); + if vxlan_table is None: + continue + body.append([key1.pop(), rmtip, vxlan_table['vni']]) + num += 1 + click.echo(tabulate(body, header, tablefmt="grid")) + output = 'Total count : ' + output += ('%s \n' % (str(num))) + click.echo(output) + +@vxlan.command() +@click.argument('remote_vtep_ip', required=True) +@click.argument('count', required=False) +def remote_mac(remote_vtep_ip, count): + """Show MACs pointing to the remote VTEP""" + + if (remote_vtep_ip != 'all') and (is_ipaddress(remote_vtep_ip) is False): + click.echo("Remote VTEP IP {} invalid format".format(remote_vtep_ip)) + return + + header = ['VLAN', 'MAC', 'RemoteVTEP', 'VNI', 'Type'] + body = [] + db = SonicV2Connector(host='127.0.0.1') + db.connect(db.APPL_DB) + + vxlan_keys = db.keys(db.APPL_DB, 'VXLAN_FDB_TABLE:*') + + if ((count is not None) and (remote_vtep_ip == 'all')): + if not vxlan_keys: + vxlan_count = 0 + else: + vxlan_count = len(vxlan_keys) + + output = 'Total count:' + output += ('%s \n' % (str(vxlan_count))) + click.echo(output) + else: + num = 0 + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + key1 = key.split(':',2) + mac = key1.pop(); + vlan = key1.pop(); + vxlan_table = db.get_all(db.APPL_DB, key); + if vxlan_table is None: + continue + rmtip = vxlan_table['remote_vtep'] + if remote_vtep_ip != 'all' and rmtip != remote_vtep_ip: + continue + if count is None: + body.append([vlan, mac, rmtip, vxlan_table['vni'], vxlan_table['type']]) + num += 1 + if count is None: + click.echo(tabulate(body, header, tablefmt="grid")) + output = 'Total count : ' + output += ('%s \n' % (str(num))) + click.echo(output) + +#Neigh Suppress +@cli.group('neigh-suppress') +def neigh_suppress(): + """ show neigh_suppress """ + pass +@neigh_suppress.command('all') +def neigh_suppress_all(): + """ Show neigh_suppress all """ + + header = ['VLAN', 'STATUS', 'ASSOCIATED_NETDEV'] + body = [] + + config_db = ConfigDBConnector() + config_db.connect() + + vxlan_table = config_db.get_table('VXLAN_TUNNEL_MAP') + suppress_table = config_db.get_table('SUPPRESS_VLAN_NEIGH') + vxlan_keys = vxlan_table.keys() + num=0 + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + key1 = vxlan_table[key]['vlan'] + netdev = vxlan_keys[0][0]+"-"+key1[4:] + if key1 not in suppress_table: + supp_str = "Not Configured" + else: + supp_str = "Configured" + body.append([vxlan_table[key]['vlan'], supp_str, netdev]) + num += 1 + click.echo(tabulate(body, header, tablefmt="grid")) + output = 'Total count : ' + output += ('%s \n' % (str(num))) + click.echo(output) + +@neigh_suppress.command('vlan') +@click.argument('vid', metavar='', required=True, type=int) +def neigh_suppress_vlan(vid): + """ Show neigh_suppress vlan""" + header = ['VLAN', 'STATUS', 'ASSOCIATED_NETDEV'] + body = [] + + config_db = ConfigDBConnector() + config_db.connect() + + vxlan_table = config_db.get_table('VXLAN_TUNNEL_MAP') + suppress_table = config_db.get_table('SUPPRESS_VLAN_NEIGH') + vlan = 'Vlan{}'.format(vid) + vxlan_keys = vxlan_table.keys() + + if vxlan_keys is not None: + for key in natsorted(vxlan_keys): + key1 = vxlan_table[key]['vlan'] + if(key1 == vlan): + netdev = vxlan_keys[0][0]+"-"+key1[4:] + if key1 in suppress_table: + supp_str = "Configured" + body.append([vxlan_table[key]['vlan'], supp_str, netdev]) + click.echo(tabulate(body, header, tablefmt="grid")) + return + print(vlan + " is not configured in vxlan tunnel map table") + if __name__ == '__main__': cli()