diff --git a/config/main.py b/config/main.py index a074a55924..8af7abb3b7 100644 --- a/config/main.py +++ b/config/main.py @@ -23,7 +23,6 @@ import utilities_common.cli as clicommon from .utils import log - from . import aaa from . import chassis_modules from . import console @@ -34,6 +33,7 @@ from . import muxcable from . import nat from . import vlan +from . import vxlan from .config_mgmt import ConfigMgmtDPB CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) @@ -884,6 +884,7 @@ def config(ctx): config.add_command(muxcable.muxcable) config.add_command(nat.nat) config.add_command(vlan.vlan) +config.add_command(vxlan.vxlan) @config.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, @@ -2930,6 +2931,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): + config_db = ctx.obj['config_db'] + found = 0 + if vrfname not in config_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 clicommon.vni_id_is_valid(int(vni)) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni)) + + vxlan_table = config_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 = config_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)) + + config_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): + config_db = ctx.obj['config_db'] + if vrfname not in config_db.get_table('VRF').keys(): + ctx.fail("vrf {} doesnt exists".format(vrfname)) + + config_db.mod_entry('VRF', vrfname, {"vni": 0}) + # # 'route' group ('config route ...') # diff --git a/config/vxlan.py b/config/vxlan.py new file mode 100644 index 0000000000..382ab72815 --- /dev/null +++ b/config/vxlan.py @@ -0,0 +1,273 @@ +import click +import utilities_common.cli as clicommon + +# +# 'vxlan' group ('config vxlan ...') +# +@click.group() +def vxlan(): + pass + +@vxlan.command('add') +@click.argument('vxlan_name', metavar='', required=True) +@click.argument('src_ip', metavar='', required=True) +@clicommon.pass_db +def add_vxlan(db, vxlan_name, src_ip): + """Add VXLAN""" + ctx = click.get_current_context() + + if not clicommon.is_ipaddress(src_ip): + ctx.fail("{} invalid src ip address".format(src_ip)) + + vxlan_keys = db.cfgdb.get_keys('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.cfgdb.set_entry('VXLAN_TUNNEL', vxlan_name, fvs) + +@vxlan.command('del') +@click.argument('vxlan_name', metavar='', required=True) +@clicommon.pass_db +def del_vxlan(db, vxlan_name): + """Del VXLAN""" + ctx = click.get_current_context() + + vxlan_keys = db.cfgdb.get_keys('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.cfgdb.get_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.cfgdb.set_entry('VXLAN_TUNNEL', vxlan_name, None) + +@vxlan.group('evpn_nvo') +def vxlan_evpn_nvo(): + pass + +@vxlan_evpn_nvo.command('add') +@click.argument('nvo_name', metavar='', required=True) +@click.argument('vxlan_name', metavar='', required=True) +@clicommon.pass_db +def add_vxlan_evpn_nvo(db, nvo_name, vxlan_name): + """Add NVO""" + ctx = click.get_current_context() + vxlan_keys = db.cfgdb.get_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.cfgdb.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + + fvs = {'source_vtep': vxlan_name} + db.cfgdb.set_entry('VXLAN_EVPN_NVO', nvo_name, fvs) + +@vxlan_evpn_nvo.command('del') +@click.argument('nvo_name', metavar='', required=True) +@clicommon.pass_db +def del_vxlan_evpn_nvo(db, nvo_name): + """Del NVO""" + ctx = click.get_current_context() + vxlan_keys = db.cfgdb.get_keys('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.cfgdb.set_entry('VXLAN_EVPN_NVO', nvo_name, None) + +@vxlan.group('map') +def vxlan_map(): + pass + +@vxlan_map.command('add') +@click.argument('vxlan_name', metavar='', required=True) +@click.argument('vlan', metavar='', required=True) +@click.argument('vni', metavar='', required=True) +@clicommon.pass_db +def add_vxlan_map(db, vxlan_name, vlan, vni): + """Add VLAN-VNI map entry""" + ctx = click.get_current_context() + + if not vlan.isdigit(): + ctx.fail("Invalid vlan {}. Only valid vlan is accepted".format(vni)) + if clicommon.is_vlanid_in_range(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 clicommon.vni_id_is_valid(int(vni)) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni)) + + vlan_name = "Vlan" + vlan + + if len(db.cfgdb.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + + if len(db.cfgdb.get_entry('VLAN', vlan_name)) == 0: + ctx.fail("{} not configured".format(vlan_name)) + + vxlan_table = db.cfgdb.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.cfgdb.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) +@clicommon.pass_db +def del_vxlan_map(db, vxlan_name, vlan, vni): + """Del VLAN-VNI map entry""" + ctx = click.get_current_context() + + if not vlan.isdigit(): + ctx.fail("Invalid vlan {}. Only valid vlan is accepted".format(vni)) + if clicommon.is_vlanid_in_range(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 clicommon.vni_id_is_valid(int(vni)) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni)) + + if len(db.cfgdb.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + found = 0 + vrf_table = db.cfgdb.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.cfgdb.set_entry('VXLAN_TUNNEL_MAP', mapname, None) + mapname = vxlan_name + '|' + 'map_' + vni + '_Vlan' + vlan + db.cfgdb.set_entry('VXLAN_TUNNEL_MAP', mapname, None) + +@vxlan.group('map_range') +def vxlan_map_range(): + 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) +@clicommon.pass_db +def add_vxlan_map_range(db, vxlan_name, vlan_start, vlan_end, vni_start): + """Add Range of vlan-vni mappings""" + ctx = click.get_current_context() + if clicommon.is_vlanid_in_range(vlan_start) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + if clicommon.is_vlanid_in_range(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 clicommon.vni_id_is_valid(vni_start) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni_start)) + if clicommon.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)) + + if len(db.cfgdb.get_entry('VXLAN_TUNNEL', vxlan_name)) == 0: + ctx.fail("VTEP {} not configured".format(vxlan_name)) + vlan_end = vlan_end + 1 + vxlan_table = db.cfgdb.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.cfgdb.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.cfgdb.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) +@clicommon.pass_db +def del_vxlan_map_range(db, vxlan_name, vlan_start, vlan_end, vni_start): + """Del Range of vlan-vni mappings""" + ctx = click.get_current_context() + if clicommon.is_vlanid_in_range(vlan_start) is False: + ctx.fail(" Invalid Vlan Id , Valid Range : 1 to 4094 ") + if clicommon.is_vlanid_in_range(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 clicommon.vni_id_is_valid(vni_start) is False: + ctx.fail("Invalid VNI {}. Valid range [1 to 16777215].".format(vni_start)) + if clicommon.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)) + + if len(db.cfgdb.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 clicommon.is_vni_vrf_mapped(db, 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.cfgdb.set_entry('VXLAN_TUNNEL_MAP', mapname, None) + diff --git a/scripts/fast-reboot b/scripts/fast-reboot index a042f71091..8918620201 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -248,6 +248,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, 'WARM_RESTART_ENABLE_TABLE|') \ + and not string.match(k, 'VXLAN_TUNNEL_TABLE|') \ and not string.match(k, 'BUFFER_MAX_PARAM_TABLE|') then redis.call('del', k) end diff --git a/show/main.py b/show/main.py index c6f02987ff..f6816a58d4 100644 --- a/show/main.py +++ b/show/main.py @@ -1522,6 +1522,5 @@ def ztp(status, verbose): cmd = cmd + " --verbose" run_command(cmd, display_cmd=verbose) - if __name__ == '__main__': cli() diff --git a/show/vxlan.py b/show/vxlan.py index d9b0de85f0..a9f18759e3 100644 --- a/show/vxlan.py +++ b/show/vxlan.py @@ -2,9 +2,9 @@ import utilities_common.cli as clicommon from natsort import natsorted from swsssdk import ConfigDBConnector +from swsssdk import SonicV2Connector from tabulate import tabulate - # # 'vxlan' command ("show vxlan") # @@ -13,7 +13,6 @@ def vxlan(): """Show vxlan related information""" pass - @vxlan.command() @click.argument('vxlan_name', required=True) def name(vxlan_name): @@ -41,7 +40,6 @@ def name(vxlan_name): click.echo(tabulate(table, header)) - @vxlan.command() def tunnel(): """Show vxlan tunnel information""" @@ -68,3 +66,243 @@ def tunnel(): table.append(r) click.echo(tabulate(table, header)) + +@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('VXLAN_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 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 remotevtep(count): + """Show All Remote VTEP 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 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 remotevni(remote_vtep_ip, count): + """Show Vlans extended to the remote VTEP""" + + if (remote_vtep_ip != 'all') and (clicommon.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 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 remotemac(remote_vtep_ip, count): + """Show MACs pointing to the remote VTEP""" + + if (remote_vtep_ip != 'all') and (clicommon.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) + diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index 5cdb65a180..803473b9c4 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -169,5 +169,14 @@ }, "SWITCH_TABLE:switch": { "vxlan_port": "13550" + }, + "VXLAN_REMOTE_VNI_TABLE:Vlan200:25.25.25.25": { + "vni": "200" + }, + "VXLAN_REMOTE_VNI_TABLE:Vlan200:25.25.25.26": { + "vni": "200" + }, + "VXLAN_REMOTE_VNI_TABLE:Vlan200:25.25.25.27": { + "vni": "200" } } diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 55bf817587..06e1f65f80 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -1333,6 +1333,32 @@ "PEER_SWITCH|sonic-switch" : { "address_ipv4": "10.2.2.2" }, + "VXLAN_TUNNEL|vtep1": { + "src_ip": "1.1.1.1" + }, + "VXLAN_EVPN_NVO|nvo1": { + "source_vtep": "vtep1" + }, + "VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan100": { + "vni" : "100", + "vlan": "Vlan100" + }, + "VXLAN_TUNNEL_MAP|vtep1|map_101_Vlan101": { + "vni" : "101", + "vlan": "Vlan101" + }, + "VXLAN_TUNNEL_MAP|vtep1|map_102_Vlan102": { + "vni" : "102", + "vlan": "Vlan102" + }, + "VXLAN_TUNNEL_MAP|vtep1|map_200_Vlan200": { + "vni" : "200", + "vlan": "Vlan200" + }, + "VRF|Vrf1": { + "fallback": "false", + "vni" : "1000" + }, "BUFFER_POOL|egress_lossless_pool": { "mode": "dynamic", "size": "13945824", diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index b1c2f1cea1..7f563d8842 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -328,6 +328,24 @@ "MUX_CABLE_TABLE|Ethernet12": { "state": "unknown" }, + "VXLAN_TUNNEL_TABLE|EVPN_25.25.25.25": { + "src_ip": "1.1.1.1", + "dst_ip": "25.25.25.25", + "tnl_src": "EVPN", + "operstatus": "down" + }, + "VXLAN_TUNNEL_TABLE|EVPN_25.25.25.26": { + "src_ip": "1.1.1.1", + "dst_ip": "25.25.25.26", + "tnl_src": "EVPN", + "operstatus": "down" + }, + "VXLAN_TUNNEL_TABLE|EVPN_25.25.25.27": { + "src_ip": "1.1.1.1", + "dst_ip": "25.25.25.27", + "tnl_src": "EVPN", + "operstatus": "down" + }, "BUFFER_POOL_TABLE|egress_lossless_pool": { "mode": "dynamic", "size": "13945824", diff --git a/tests/vxlan_test.py b/tests/vxlan_test.py new file mode 100644 index 0000000000..74819f9f61 --- /dev/null +++ b/tests/vxlan_test.py @@ -0,0 +1,248 @@ +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_vxlan_interface_output="""\ +VTEP Information: + + VTEP Name : vtep1, SIP : 1.1.1.1 + NVO Name : nvo1, VTEP : vtep1 +""" + +show_vxlan_vlanvnimap_output="""\ ++---------+-------+ +| VLAN | VNI | ++=========+=======+ +| Vlan100 | 100 | ++---------+-------+ +| Vlan101 | 101 | ++---------+-------+ +| Vlan102 | 102 | ++---------+-------+ +| Vlan200 | 200 | ++---------+-------+ +Total count : 4 + +""" + +show_vxlan_vrfvnimap_output="""\ ++-------+-------+ +| VRF | VNI | ++=======+=======+ +| Vrf1 | 1000 | ++-------+-------+ +Total count : 1 + +""" + + +show_vxlan_tunnel_output="""\ ++---------+-------------+-------------------+--------------+ +| SIP | DIP | Creation Source | OperStatus | ++=========+=============+===================+==============+ +| 1.1.1.1 | 25.25.25.25 | EVPN | oper_down | ++---------+-------------+-------------------+--------------+ +| 1.1.1.1 | 25.25.25.26 | EVPN | oper_down | ++---------+-------------+-------------------+--------------+ +| 1.1.1.1 | 25.25.25.27 | EVPN | oper_down | ++---------+-------------+-------------------+--------------+ +Total count : 3 + +""" + +show_vxlan_remotevni_output="""\ ++---------+--------------+-------+ +| VLAN | RemoteVTEP | VNI | ++=========+==============+=======+ +| Vlan200 | 25.25.25.25 | 200 | ++---------+--------------+-------+ +| Vlan200 | 25.25.25.26 | 200 | ++---------+--------------+-------+ +| Vlan200 | 25.25.25.27 | 200 | ++---------+--------------+-------+ +Total count : 3 + +""" + +show_vxlan_remotevni_specific_output="""\ ++---------+--------------+-------+ +| VLAN | RemoteVTEP | VNI | ++=========+==============+=======+ +| Vlan200 | 25.25.25.27 | 200 | ++---------+--------------+-------+ +Total count : 1 + +""" +show_vxlan_vlanvnimap_cnt_output="""\ +Total count : 4 + +""" + +show_vxlan_tunnel_cnt_output="""\ +Total count : 3 + +""" + +show_vxlan_remotevni_cnt_output="""\ +Total count : 3 + +""" + +show_vxlan_remotevni_specific_cnt_output="""\ +Total count : 1 + +""" + +class TestVxlan(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "1" + print("SETUP") + + def test_show_vxlan_interface(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["interface"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_interface_output + + def test_show_vxlan_vlanvnimap(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["vlanvnimap"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_vlanvnimap_output + + def test_show_vxlan_vrfvnimap(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["vrfvnimap"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_vrfvnimap_output + + def test_show_vxlan_tunnel(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["remotevtep"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_tunnel_output + + def test_show_vxlan_remotevni(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["remotevni"], ["all"]) + #result = runner.invoke(show.cli.commands["vxlan"].commands["remotevni"].commands["all"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_remotevni_output + + def test_show_vxlan_remotevni_specific(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["remotevni"],["25.25.25.27"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_remotevni_specific_output + + def test_show_vxlan_vlanvnimap_cnt(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["vlanvnimap"],["count"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_vlanvnimap_cnt_output + + def test_show_vxlan_tunnel_cnt(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["remotevtep"], ["count"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_tunnel_cnt_output + + def test_show_vxlan_remotevni_cnt(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["remotevni"], ["all", "count"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_remotevni_cnt_output + + def test_show_vxlan_remotevni_specific_cnt(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vxlan"].commands["remotevni"], ["25.25.25.25", "count"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_remotevni_specific_cnt_output + + def test_config_vxlan_add(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["vxlan"].commands["map"].commands["del"], ["vtep1", "200", "200"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vxlan"].commands["map_range"].commands["del"], ["vtep1", "100", "102", "100"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vxlan"].commands["evpn_nvo"].commands["del"], ["nvo1"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vxlan"].commands["del"], ["vtep1"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vxlan"].commands["add"], ["vtep1", "1.1.1.1"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(show.cli.commands["vxlan"].commands["interface"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_interface_output + + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["100"], obj=db) + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["101"], obj=db) + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["102"], obj=db) + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["200"], obj=db) + + result = runner.invoke(config.config.commands["vxlan"].commands["map"].commands["add"], ["vtep1", "200", "200"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vxlan"].commands["map_range"].commands["add"], ["vtep1", "100", "102", "100"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(show.cli.commands["vxlan"].commands["vlanvnimap"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vxlan_vlanvnimap_output + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + print("TEARDOWN") diff --git a/utilities_common/cli.py b/utilities_common/cli.py index 2379b4a603..3644f71b10 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -301,6 +301,34 @@ def is_port_mirror_dst_port(config_db, port): return False +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(db, vni): + """Check if the vni is mapped to vrf + """ + + found = 0 + vrf_table = db.cfgdb.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_has_mirror_config(mirror_table, interface_name): """Check if port is already configured with mirror config """ for _,v in mirror_table.items():