From 8af9aeed753c438d08cc3315b7c788af9dda62e6 Mon Sep 17 00:00:00 2001 From: kktheballer Date: Thu, 29 Oct 2020 15:01:16 -0700 Subject: [PATCH] Show FG_NHG CLI Commands Added (#1056) 1. show fg-nhg-hash-view (shows the current hash bucket view of fg nhg) 2. show fg-nhg-active-hops (shows which set of next-hops are active) --- show/fgnhg.py | 169 ++++++++++++++++++++ show/main.py | 2 + tests/fgnhg_test.py | 254 +++++++++++++++++++++++++++++++ tests/mock_tables/config_db.json | 6 + tests/mock_tables/state_db.json | 69 +++++++++ 5 files changed, 500 insertions(+) create mode 100644 show/fgnhg.py create mode 100644 tests/fgnhg_test.py diff --git a/show/fgnhg.py b/show/fgnhg.py new file mode 100644 index 000000000000..c3fa50a30be1 --- /dev/null +++ b/show/fgnhg.py @@ -0,0 +1,169 @@ +import click +import ipaddress +from tabulate import tabulate +from swsssdk import ConfigDBConnector +from swsssdk import SonicV2Connector +import utilities_common.cli as clicommon +from collections import OrderedDict + +@click.group(cls=clicommon.AliasedGroup) +def fgnhg(): + """Show FGNHG information""" + pass + +@fgnhg.command() +@click.argument('nhg', required=False) +def active_hops(nhg): + config_db = ConfigDBConnector() + config_db.connect() + fg_nhg_prefix_table = {} + fg_nhg_alias = {} + fg_nhg_prefix_table = config_db.get_table('FG_NHG_PREFIX') + + for key, value in fg_nhg_prefix_table.items(): + fg_nhg_alias[key] = value['FG_NHG'] + + state_db = SonicV2Connector(host='127.0.0.1') + state_db.connect(state_db.STATE_DB, False) # Make one attempt only STATE_DB + + TABLE_NAME_SEPARATOR = '|' + prefix = 'FG_ROUTE_TABLE' + TABLE_NAME_SEPARATOR + _hash = '{}{}'.format(prefix, '*') + table_keys = [] + table_keys = state_db.keys(state_db.STATE_DB, _hash) + t_dict = {} + table = [] + output_dict = {} + + if nhg is None: + for nhg_prefix in table_keys : + t_dict = state_db.get_all(state_db.STATE_DB, nhg_prefix) + vals = sorted(set([val for val in t_dict.values()])) + for nh_ip in vals: + if nhg_prefix in output_dict: + output_dict[nhg_prefix].append(nh_ip.split("@")[0]) + else: + output_dict[nhg_prefix] = [nh_ip.split("@")[0]] + + nhg_prefix_report = (nhg_prefix.split("|")[1]) + header = ["FG_NHG_PREFIX", "Active Next Hops"] + formatted_nhps = ','.replace(',', '\n').join(output_dict[nhg_prefix]) + table.append([nhg_prefix_report, formatted_nhps]) + + click.echo(tabulate(table, header, tablefmt = "grid")) + + else: + for nhg_prefix, alias in fg_nhg_alias.items(): + if nhg == alias: + if ":" in nhg_prefix: + for key in table_keys: + mod_key = key.split("|")[1].split("/")[0] + mod_nhg_prefix = nhg_prefix.split("/")[0] + if ipaddress.ip_address(unicode(mod_key)).exploded == ipaddress.ip_address(unicode(mod_nhg_prefix)).exploded: + t_dict = state_db.get_all(state_db.STATE_DB, key) + nhg_prefix = "FG_ROUTE_TABLE|" + nhg_prefix + else: + nhg_prefix = "FG_ROUTE_TABLE|" + nhg_prefix + t_dict = state_db.get_all(state_db.STATE_DB, nhg_prefix) + + vals = sorted(set([val for val in t_dict.values()])) + + for nh_ip in vals: + if nhg_prefix in output_dict: + output_dict[nhg_prefix].append(nh_ip.split("@")[0]) + else: + output_dict[nhg_prefix] = [nh_ip.split("@")[0]] + + nhg_prefix_report = (nhg_prefix.split("|")[1]) + formatted_nhps = ','.replace(',', '\n').join(output_dict[nhg_prefix]) + table.append([nhg_prefix_report, formatted_nhps]) + header = ["FG_NHG_PREFIX", "Active Next Hops"] + click.echo(tabulate(table, header, tablefmt = "grid")) + + +@fgnhg.command() +@click.argument('nhg', required=False) +def hash_view(nhg): + config_db = ConfigDBConnector() + config_db.connect() + fg_nhg_prefix_table = {} + fg_nhg_alias = {} + fg_nhg_prefix_table = config_db.get_table('FG_NHG_PREFIX') + + for key, value in fg_nhg_prefix_table.items(): + fg_nhg_alias[key] = value['FG_NHG'] + + state_db = SonicV2Connector(host='127.0.0.1') + state_db.connect(state_db.STATE_DB, False) # Make one attempt only STATE_DB + + TABLE_NAME_SEPARATOR = '|' + prefix = 'FG_ROUTE_TABLE' + TABLE_NAME_SEPARATOR + _hash = '{}{}'.format(prefix, '*') + table_keys = [] + table_keys = state_db.keys(state_db.STATE_DB, _hash) + t_dict = {} + table = [] + output_dict = {} + bank_dict = {} + + if nhg is None: + for nhg_prefix in table_keys : + bank_dict = {} + t_dict = state_db.get_all(state_db.STATE_DB, nhg_prefix) + vals = sorted(set([val for val in t_dict.values()])) + + for nh_ip in vals: + bank_ids = sorted([int(k) for k, v in t_dict.items() if v == nh_ip]) + + bank_ids = [str(x) for x in bank_ids] + + if nhg_prefix in output_dict: + output_dict[nhg_prefix].append(nh_ip.split("@")[0]) + else: + output_dict[nhg_prefix] = [nh_ip.split("@")[0]] + bank_dict[nh_ip.split("@")[0]] = bank_ids + + bank_dict = OrderedDict(sorted(bank_dict.items())) + nhg_prefix_report = (nhg_prefix.split("|")[1]) + header = ["FG_NHG_PREFIX", "Next Hop", "Hash buckets"] + + for nhip,val in bank_dict.items(): + formatted_banks = ','.replace(',', '\n').join(bank_dict[nhip]) + table.append([nhg_prefix_report, nhip, formatted_banks]) + + click.echo(tabulate(table, header, tablefmt = "grid")) + + else: + for nhg_prefix, alias in fg_nhg_alias.items(): + if nhg == alias: + if ":" in nhg_prefix: + for key in table_keys: + mod_key = key.split("|")[1].split("/")[0] + mod_nhg_prefix = nhg_prefix.split("/")[0] + if ipaddress.ip_address(unicode(mod_key)).exploded == ipaddress.ip_address(unicode(mod_nhg_prefix)).exploded: + t_dict = state_db.get_all(state_db.STATE_DB, key) + nhg_prefix = "FG_ROUTE_TABLE|" + nhg_prefix + else: + nhg_prefix = "FG_ROUTE_TABLE|" + nhg_prefix + t_dict = state_db.get_all(state_db.STATE_DB, nhg_prefix) + + vals = sorted(set([val for val in t_dict.values()])) + + for nh_ip in vals: + bank_ids = sorted([int(k) for k, v in t_dict.items() if v == nh_ip]) + bank_ids = [str(x) for x in bank_ids] + if nhg_prefix in output_dict: + output_dict[nhg_prefix].append(nh_ip.split("@")[0]) + else: + output_dict[nhg_prefix] = [nh_ip.split("@")[0]] + bank_dict[nh_ip.split("@")[0]] = bank_ids + + nhg_prefix_report = (nhg_prefix.split("|")[1]) + bank_dict = OrderedDict(sorted(bank_dict.items())) + header = ["FG_NHG_PREFIX", "Next Hop", "Hash buckets"] + + for nhip,val in bank_dict.items(): + formatted_banks = ','.replace(',', '\n').join(bank_dict[nhip]) + table.append([nhg_prefix_report, nhip, formatted_banks]) + + click.echo(tabulate(table, header, tablefmt = "grid")) diff --git a/show/main.py b/show/main.py index 8be39b27ac5a..ed3939427d7b 100755 --- a/show/main.py +++ b/show/main.py @@ -18,6 +18,7 @@ import utilities_common.cli as clicommon import vlan import system_health +import fgnhg from sonic_py_common import device_info, multi_asic from swsssdk import ConfigDBConnector, SonicV2Connector @@ -130,6 +131,7 @@ def cli(ctx): cli.add_command(kube.kubernetes) cli.add_command(vlan.vlan) cli.add_command(system_health.system_health) +cli.add_command(fgnhg.fgnhg) # # 'vrf' command ("show vrf") diff --git a/tests/fgnhg_test.py b/tests/fgnhg_test.py new file mode 100644 index 000000000000..e120d60e47d0 --- /dev/null +++ b/tests/fgnhg_test.py @@ -0,0 +1,254 @@ +import os +import traceback + +from click.testing import CliRunner + +import config.main as config +import show.main as show +from utilities_common.db import Db + + +show_fgnhg_hash_view_output="""\ ++-----------------+--------------------+----------------+ +| FG_NHG_PREFIX | Next Hop | Hash buckets | ++=================+====================+================+ +| 100.50.25.12/32 | 200.200.200.4 | 0 | +| | | 1 | +| | | 2 | +| | | 3 | +| | | 4 | +| | | 5 | +| | | 6 | +| | | 7 | +| | | 8 | +| | | 9 | +| | | 10 | +| | | 11 | +| | | 12 | +| | | 13 | +| | | 14 | +| | | 15 | ++-----------------+--------------------+----------------+ +| 100.50.25.12/32 | 200.200.200.5 | 16 | +| | | 17 | +| | | 18 | +| | | 19 | +| | | 20 | +| | | 21 | +| | | 22 | +| | | 23 | +| | | 24 | +| | | 25 | +| | | 26 | +| | | 27 | +| | | 28 | +| | | 29 | +| | | 30 | +| | | 31 | ++-----------------+--------------------+----------------+ +| fc:5::/128 | 200:200:200:200::4 | 0 | +| | | 1 | +| | | 2 | +| | | 3 | +| | | 4 | +| | | 5 | +| | | 6 | +| | | 7 | +| | | 8 | +| | | 9 | +| | | 10 | +| | | 11 | +| | | 12 | +| | | 13 | +| | | 14 | +| | | 15 | ++-----------------+--------------------+----------------+ +| fc:5::/128 | 200:200:200:200::5 | 16 | +| | | 17 | +| | | 18 | +| | | 19 | +| | | 20 | +| | | 21 | +| | | 22 | +| | | 23 | +| | | 24 | +| | | 25 | +| | | 26 | +| | | 27 | +| | | 28 | +| | | 29 | +| | | 30 | +| | | 31 | ++-----------------+--------------------+----------------+ +""" + +show_fgnhgv4_hash_view_output="""\ ++-----------------+---------------+----------------+ +| FG_NHG_PREFIX | Next Hop | Hash buckets | ++=================+===============+================+ +| 100.50.25.12/32 | 200.200.200.4 | 0 | +| | | 1 | +| | | 2 | +| | | 3 | +| | | 4 | +| | | 5 | +| | | 6 | +| | | 7 | +| | | 8 | +| | | 9 | +| | | 10 | +| | | 11 | +| | | 12 | +| | | 13 | +| | | 14 | +| | | 15 | ++-----------------+---------------+----------------+ +| 100.50.25.12/32 | 200.200.200.5 | 16 | +| | | 17 | +| | | 18 | +| | | 19 | +| | | 20 | +| | | 21 | +| | | 22 | +| | | 23 | +| | | 24 | +| | | 25 | +| | | 26 | +| | | 27 | +| | | 28 | +| | | 29 | +| | | 30 | +| | | 31 | ++-----------------+---------------+----------------+ +""" + +show_fgnhgv6_hash_view_output="""\ ++-----------------+--------------------+----------------+ +| FG_NHG_PREFIX | Next Hop | Hash buckets | ++=================+====================+================+ +| fc:05::/128 | 200:200:200:200::4 | 0 | +| | | 1 | +| | | 2 | +| | | 3 | +| | | 4 | +| | | 5 | +| | | 6 | +| | | 7 | +| | | 8 | +| | | 9 | +| | | 10 | +| | | 11 | +| | | 12 | +| | | 13 | +| | | 14 | +| | | 15 | ++-----------------+--------------------+----------------+ +| fc:05::/128 | 200:200:200:200::5 | 16 | +| | | 17 | +| | | 18 | +| | | 19 | +| | | 20 | +| | | 21 | +| | | 22 | +| | | 23 | +| | | 24 | +| | | 25 | +| | | 26 | +| | | 27 | +| | | 28 | +| | | 29 | +| | | 30 | +| | | 31 | ++-----------------+--------------------+----------------+ +""" + +show_fgnhg_active_hops_output="""\ ++-----------------+--------------------+ +| FG_NHG_PREFIX | Active Next Hops | ++=================+====================+ +| 100.50.25.12/32 | 200.200.200.4 | +| | 200.200.200.5 | ++-----------------+--------------------+ +| fc:5::/128 | 200:200:200:200::4 | +| | 200:200:200:200::5 | ++-----------------+--------------------+ +""" + +show_fgnhgv4_active_hops_output="""\ ++-----------------+--------------------+ +| FG_NHG_PREFIX | Active Next Hops | ++=================+====================+ +| 100.50.25.12/32 | 200.200.200.4 | +| | 200.200.200.5 | ++-----------------+--------------------+ +""" + +show_fgnhgv6_active_hops_output="""\ ++-----------------+--------------------+ +| FG_NHG_PREFIX | Active Next Hops | ++=================+====================+ +| fc:05::/128 | 200:200:200:200::4 | +| | 200:200:200:200::5 | ++-----------------+--------------------+ +""" + + + +class TestFineGrainedNexthopGroup(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "1" + print("SETUP") + + def test_show_fgnhg_hash_view(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["fgnhg"].commands["hash-view"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_fgnhg_hash_view_output + + def test_show_fgnhgv4_hash_view(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["fgnhg"].commands["hash-view"], ["fgnhg_v4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_fgnhgv4_hash_view_output + + def test_show_fgnhgv6_hash_view(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["fgnhg"].commands["hash-view"], ["fgnhg_v6"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_fgnhgv6_hash_view_output + + def test_show_fgnhg_active_hops(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["fgnhg"].commands["active-hops"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_fgnhg_active_hops_output + + def test_show_fgnhgv4_active_hops(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["fgnhg"].commands["active-hops"], ["fgnhg_v4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_fgnhgv4_active_hops_output + + def test_show_fgnhgv6_active_hops(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["fgnhg"].commands["active-hops"], ["fgnhg_v6"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_fgnhgv6_active_hops_output + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + print("TEARDOWN") diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 7ec074b08233..a7e8041fbf22 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -782,6 +782,12 @@ "mgmt_addr": "10.250.0.54", "type": "LeafRouter" }, + "FG_NHG_PREFIX|100.50.25.12/32": { + "FG_NHG": "fgnhg_v4" + }, + "FG_NHG_PREFIX|fc:05::/128": { + "FG_NHG": "fgnhg_v6" + }, "BGP_NEIGHBOR|10.0.0.1": { "asn": "65200", "holdtime": "10", diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index 07939bc0078b..b635c78e2de4 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -62,6 +62,7 @@ "status": "true", "led_status": "green" }, + "SWITCH_CAPABILITY|switch": { "MIRROR": "true", "MIRRORV6": "true", @@ -203,5 +204,73 @@ "speed_target": "50", "led_status": "green", "timestamp": "20200813 01:32:30" + }, + "FG_ROUTE_TABLE|fc:5::/128": { + "0": "200:200:200:200::4@Vlan1000", + "1": "200:200:200:200::4@Vlan1000", + "2": "200:200:200:200::4@Vlan1000", + "3": "200:200:200:200::4@Vlan1000", + "4": "200:200:200:200::4@Vlan1000", + "5" : "200:200:200:200::4@Vlan1000", + "6" : "200:200:200:200::4@Vlan1000", + "7" : "200:200:200:200::4@Vlan1000", + "8" : "200:200:200:200::4@Vlan1000", + "9" : "200:200:200:200::4@Vlan1000", + "10" : "200:200:200:200::4@Vlan1000", + "11" : "200:200:200:200::4@Vlan1000", + "12" : "200:200:200:200::4@Vlan1000", + "13" : "200:200:200:200::4@Vlan1000", + "14" : "200:200:200:200::4@Vlan1000", + "15" : "200:200:200:200::4@Vlan1000", + "16" : "200:200:200:200::5@Vlan1000", + "17" : "200:200:200:200::5@Vlan1000", + "18" : "200:200:200:200::5@Vlan1000", + "19" : "200:200:200:200::5@Vlan1000", + "20" : "200:200:200:200::5@Vlan1000", + "21" : "200:200:200:200::5@Vlan1000", + "22" : "200:200:200:200::5@Vlan1000", + "23" : "200:200:200:200::5@Vlan1000", + "24" : "200:200:200:200::5@Vlan1000", + "25" : "200:200:200:200::5@Vlan1000", + "26" : "200:200:200:200::5@Vlan1000", + "27" : "200:200:200:200::5@Vlan1000", + "28" : "200:200:200:200::5@Vlan1000", + "29" : "200:200:200:200::5@Vlan1000", + "30" : "200:200:200:200::5@Vlan1000", + "31" : "200:200:200:200::5@Vlan1000" + }, + "FG_ROUTE_TABLE|100.50.25.12/32": { + "0": "200.200.200.4@Vlan1000", + "1": "200.200.200.4@Vlan1000", + "2": "200.200.200.4@Vlan1000", + "3": "200.200.200.4@Vlan1000", + "4": "200.200.200.4@Vlan1000", + "5" : "200.200.200.4@Vlan1000", + "6" : "200.200.200.4@Vlan1000", + "7" : "200.200.200.4@Vlan1000", + "8" : "200.200.200.4@Vlan1000", + "9" : "200.200.200.4@Vlan1000", + "10" : "200.200.200.4@Vlan1000", + "11" : "200.200.200.4@Vlan1000", + "12" : "200.200.200.4@Vlan1000", + "13" : "200.200.200.4@Vlan1000", + "14" : "200.200.200.4@Vlan1000", + "15" : "200.200.200.4@Vlan1000", + "16" : "200.200.200.5@Vlan1000", + "17" : "200.200.200.5@Vlan1000", + "18" : "200.200.200.5@Vlan1000", + "19" : "200.200.200.5@Vlan1000", + "20" : "200.200.200.5@Vlan1000", + "21" : "200.200.200.5@Vlan1000", + "22" : "200.200.200.5@Vlan1000", + "23" : "200.200.200.5@Vlan1000", + "24" : "200.200.200.5@Vlan1000", + "25" : "200.200.200.5@Vlan1000", + "26" : "200.200.200.5@Vlan1000", + "27" : "200.200.200.5@Vlan1000", + "28" : "200.200.200.5@Vlan1000", + "29" : "200.200.200.5@Vlan1000", + "30" : "200.200.200.5@Vlan1000", + "31" : "200.200.200.5@Vlan1000" } }