Skip to content

Commit

Permalink
Add CLI for route flow counter
Browse files Browse the repository at this point in the history
  • Loading branch information
Junchao-Mellanox committed Jan 24, 2022
1 parent ad1ed4e commit 0ec2252
Show file tree
Hide file tree
Showing 19 changed files with 3,106 additions and 1,531 deletions.
49 changes: 49 additions & 0 deletions clear/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import sys
import click
import utilities_common.cli as clicommon
import utilities_common.multi_asic as multi_asic_util
import json

from flow_counter_util.route import check_route_flow_counter_support
from utilities_common import util_base
from show.plugins.pbh import read_pbh_counters
from . import plugins
Expand Down Expand Up @@ -490,6 +492,53 @@ def flowcnt_trap():
run_command(command)


# ("sonic-clear flowcnt-route")
@cli.group(invoke_without_command=True)
@click.option('--namespace', '-n', 'namespace', default=None, type=click.Choice(multi_asic_util.multi_asic_ns_choices()), show_default=True, help='Namespace name or all')
@click.pass_context
def flowcnt_route(ctx, namespace):
"""Clear all route flow counters"""
check_route_flow_counter_support()
if ctx.invoked_subcommand is None:
command = "flow_counters_stat -c -t route"
# None namespace means default namespace
if namespace is not None:
command += " -n {}".format(namespace)
clicommon.run_command(command)


# ("sonic-clear flowcnt-route pattern")
@flowcnt_route.command()
@click.option('--vrf', help='VRF/VNET name or default VRF')
@click.option('--namespace', '-n', 'namespace', default=None, type=click.Choice(multi_asic_util.multi_asic_ns_choices()), show_default=True, help='Namespace name or all')
@click.argument('prefix-pattern', required=True)
def pattern(prefix_pattern, vrf, namespace):
"""Clear route flow counters by pattern"""
command = "flow_counters_stat -c -t route --prefix_pattern {}".format(prefix_pattern)
if vrf:
command += ' --vrf {}'.format(vrf)
# None namespace means default namespace
if namespace is not None:
command += " -n {}".format(namespace)
clicommon.run_command(command)


# ("sonic-clear flowcnt-route route")
@flowcnt_route.command()
@click.option('--vrf', help='VRF/VNET name or default VRF')
@click.option('--namespace', '-n', 'namespace', default=None, type=click.Choice(multi_asic_util.multi_asic_ns_choices()), show_default=True, help='Namespace name or all')
@click.argument('prefix', required=True)
def route(prefix, vrf, namespace):
"""Clear route flow counters by prefix"""
command = "flow_counters_stat -c -t route --prefix {}".format(prefix)
if vrf:
command += ' --vrf {}'.format(vrf)
# None namespace means default namespace
if namespace is not None:
command += " -n {}".format(namespace)
clicommon.run_command(command)


# Load plugins and register them
helper = util_base.UtilHelper()
helper.load_and_register_plugins(plugins, cli)
Expand Down
154 changes: 154 additions & 0 deletions config/flow_counters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import click
import ipaddress
import sys

from flow_counter_util.route import FLOW_COUNTER_ROUTE_PATTERN_TABLE, FLOW_COUNTER_ROUTE_MAX_MATCH_FIELD, DEFAULT_VRF, PATTERN_SEPARATOR
from flow_counter_util.route import build_route_pattern, extract_route_pattern, check_route_flow_counter_support
from utilities_common.cli import AbbreviationGroup, pass_db
from utilities_common import cli # To make mock work in unit test

#
# 'flowcnt-route' group ('config flowcnt-route ...')
#


@click.group(cls=AbbreviationGroup, invoke_without_command=False)
def flowcnt_route():
"""Route flow counter related configuration tasks"""
pass


@flowcnt_route.group()
def pattern():
"""Set pattern for route flow counter"""
pass


@pattern.command(name='add')
@click.option('-y', '--yes', is_flag=True)
@click.option('--vrf', help='VRF/VNET name or default VRF')
@click.option('--max', 'max_allowed_match', type=click.IntRange(1, 50), default=30, show_default=True, help='Max allowed match count')
@click.argument('prefix-pattern', required=True)
@pass_db
def pattern_add(db, yes, vrf, max_allowed_match, prefix_pattern):
"""Add pattern for route flow counter"""
_update_route_flow_counter_config(db, vrf, max_allowed_match, prefix_pattern, True, yes)


@pattern.command(name='remove')
@click.option('--vrf', help='VRF/VNET name or default VRF')
@click.argument('prefix-pattern', required=True)
@pass_db
def pattern_remove(db, vrf, prefix_pattern):
"""Remove pattern for route flow counter"""
_update_route_flow_counter_config(db, vrf, None, prefix_pattern, False)


def _update_route_flow_counter_config(db, vrf, max_allowed_match, prefix_pattern, add, yes=False):
"""
Update route flow counter config
:param db: db object
:param vrf: vrf string, empty vrf will be treated as default vrf
:param max_allowed_match: max allowed match count, $FLOW_COUNTER_ROUTE_MAX_MATCH_FIELD will be used if not specified
:param prefix_pattern: route prefix pattern, automatically add prefix length if not specified
:param add: True to add/set the configuration, otherwise remove
:param yes: Don't ask question if True
:return:
"""
check_route_flow_counter_support()

if add:
try:
net = ipaddress.ip_network(prefix_pattern, strict=False)
except ValueError as e:
click.echo('Invalid prefix pattern: {}'.format(prefix_pattern))
exit(1)

if '/' not in prefix_pattern:
prefix_pattern += '/' + str(net.prefixlen)

key = build_route_pattern(vrf, prefix_pattern)
for _, cfgdb in db.cfgdb_clients.items():
if _try_find_existing_pattern(cfgdb, net, key, yes):
entry_data = cfgdb.get_entry(FLOW_COUNTER_ROUTE_PATTERN_TABLE, key)
old_max_allowed_match = entry_data.get(FLOW_COUNTER_ROUTE_MAX_MATCH_FIELD)
if old_max_allowed_match is not None and int(old_max_allowed_match) == max_allowed_match:
click.echo('The route pattern already exists, nothing to be changed')
exit(1)
cfgdb.mod_entry(FLOW_COUNTER_ROUTE_PATTERN_TABLE,
key,
{FLOW_COUNTER_ROUTE_MAX_MATCH_FIELD: str(max_allowed_match)})
else:
found = False
key = build_route_pattern(vrf, prefix_pattern)
for _, cfgdb in db.cfgdb_clients.items():
pattern_table = cfgdb.get_table(FLOW_COUNTER_ROUTE_PATTERN_TABLE)

for existing_key in pattern_table:
exist_vrf, existing_prefix = extract_route_pattern(existing_key)
if (exist_vrf == vrf or (vrf is None and exist_vrf == DEFAULT_VRF)) and existing_prefix == prefix_pattern:
found = True
cfgdb.set_entry(FLOW_COUNTER_ROUTE_PATTERN_TABLE, key, None)
if not found:
click.echo("Failed to remove route pattern: {} does not exist".format(key))
exit(1)


def _try_find_existing_pattern(cfgdb, input_net, input_key, yes):
"""Try to find the same pattern from CONFIG DB
Args:
cfgdb (object): CONFIG DB object
input_net (object): Input ip_network object
input_key (str): Input key
yes (bool): Whether ask user question
Returns:
bool: True if found an existing one
"""
input_type = type(input_net) # IPv4 or IPv6
found_invalid = []
found = None
pattern_table = cfgdb.get_table(FLOW_COUNTER_ROUTE_PATTERN_TABLE)
for existing_key in pattern_table:
if isinstance(existing_key, tuple):
existing_prefix = existing_key[1]
existing_key = PATTERN_SEPARATOR.join(existing_key)
else:
_, existing_prefix = extract_route_pattern(existing_key)

# In case user configures an invalid pattern via CONFIG DB.
if not existing_prefix: # Invalid pattern such as: "vrf1|"
click.echo('Detect invalid route pattern in existing configuration {}'.format(existing_key))
found_invalid.append(existing_key)
continue

try:
existing_net = ipaddress.ip_network(existing_prefix, strict=False)
except ValueError as e: # Invalid pattern such as: "vrf1|invalid"
click.echo('Detect invalid route pattern in existing configuration {}'.format(existing_key))
found_invalid.append(existing_key)
continue

if type(existing_net) == input_type:
found = existing_key
break

if found == input_key:
return True

if not found and found_invalid:
# If not found but there is an invalid one, ask user to replace the invalid one
found = found_invalid[0]

if found:
if not yes:
answer = cli.query_yes_no('Only support 1 IPv4 route pattern and 1 IPv6 route pattern, remove existing pattern {}?'.format(found))
else:
answer = True
if answer:
click.echo('Replacing existing route pattern {} with {}'.format(existing_key, input_key))
cfgdb.set_entry(FLOW_COUNTER_ROUTE_PATTERN_TABLE, existing_key, None)
else:
exit(0)
return False
12 changes: 7 additions & 5 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from . import chassis_modules
from . import console
from . import feature
from . import flow_counters
from . import kdump
from . import kube
from . import muxcable
Expand Down Expand Up @@ -787,7 +788,7 @@ def _per_namespace_swss_ready(service_name):
return False

def _swss_ready():
list_of_swss = []
list_of_swss = []
num_asics = multi_asic.get_num_asics()
if num_asics == 1:
list_of_swss.append("swss.service")
Expand All @@ -800,7 +801,7 @@ def _swss_ready():
if _per_namespace_swss_ready(service_name) == False:
return False

return True
return True

def _is_system_starting():
out = clicommon.run_command("sudo systemctl is-system-running", return_cmd=True)
Expand Down Expand Up @@ -1061,6 +1062,7 @@ def config(ctx):
config.add_command(chassis_modules.chassis)
config.add_command(console.console)
config.add_command(feature.feature)
config.add_command(flow_counters.flowcnt_route)
config.add_command(kdump.kdump)
config.add_command(kube.kubernetes)
config.add_command(muxcable.muxcable)
Expand Down Expand Up @@ -1467,10 +1469,10 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, disable_arp_cach


config_gen_opts = ""

if os.path.isfile(INIT_CFG_FILE):
config_gen_opts += " -j {} ".format(INIT_CFG_FILE)

if file_format == 'config_db':
config_gen_opts += ' -j {} '.format(file)
else:
Expand Down Expand Up @@ -6224,7 +6226,7 @@ def del_subinterface(ctx, subinterface_name):
sub_intfs = [k for k,v in subintf_config_db.items() if type(k) != tuple]
if subinterface_name not in sub_intfs:
ctx.fail("{} does not exists".format(subinterface_name))

ips = {}
ips = [ k[1] for k in config_db.get_table('VLAN_SUB_INTERFACE') if type(k) == tuple and k[0] == subinterface_name ]
for ip in ips:
Expand Down
39 changes: 39 additions & 0 deletions counterpoll/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import click
import json
from flow_counter_util.route import check_route_flow_counter_support
from swsscommon.swsscommon import ConfigDBConnector
from tabulate import tabulate

Expand Down Expand Up @@ -347,6 +348,40 @@ def disable(ctx):
fc_info['FLEX_COUNTER_STATUS'] = 'disable'
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "FLOW_CNT_TRAP", fc_info)

# Route flow counter commands
@cli.group()
@click.pass_context
def flowcnt_route(ctx):
""" Route flow counter commands """
check_route_flow_counter_support()
ctx.obj = ConfigDBConnector()
ctx.obj.connect()

@flowcnt_route.command()
@click.argument('poll_interval', type=click.IntRange(1000, 30000))
@click.pass_context
def interval(ctx, poll_interval):
""" Set route flow counter query interval """
fc_info = {}
fc_info['POLL_INTERVAL'] = poll_interval
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "FLOW_CNT_ROUTE", fc_info)

@flowcnt_route.command()
@click.pass_context
def enable(ctx):
""" Enable route flow counter query """
fc_info = {}
fc_info['FLEX_COUNTER_STATUS'] = 'enable'
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "FLOW_CNT_ROUTE", fc_info)

@flowcnt_route.command()
@click.pass_context
def disable(ctx):
""" Disable route flow counter query """
fc_info = {}
fc_info['FLEX_COUNTER_STATUS'] = 'disable'
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "FLOW_CNT_ROUTE", fc_info)

@cli.command()
def show():
""" Show the counter configuration """
Expand All @@ -363,6 +398,7 @@ def show():
acl_info = configdb.get_entry('FLEX_COUNTER_TABLE', ACL)
tunnel_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'TUNNEL')
trap_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'FLOW_CNT_TRAP')
route_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'FLOW_CNT_ROUTE')

header = ("Type", "Interval (in ms)", "Status")
data = []
Expand All @@ -388,6 +424,9 @@ def show():
data.append(["TUNNEL_STAT", rif_info.get("POLL_INTERVAL", DEFLT_10_SEC), rif_info.get("FLEX_COUNTER_STATUS", DISABLE)])
if trap_info:
data.append(["FLOW_CNT_TRAP_STAT", trap_info.get("POLL_INTERVAL", DEFLT_10_SEC), trap_info.get("FLEX_COUNTER_STATUS", DISABLE)])
if route_info:
data.append(["FLOW_CNT_ROUTE_STAT", route_info.get("POLL_INTERVAL", DEFLT_10_SEC),
route_info.get("FLEX_COUNTER_STATUS", DISABLE)])

click.echo(tabulate(data, headers=header, tablefmt="simple", missingval=""))

Expand Down
Empty file added flow_counter_util/__init__.py
Empty file.
Loading

0 comments on commit 0ec2252

Please sign in to comment.