Skip to content

Commit 0ec2252

Browse files
Add CLI for route flow counter
1 parent ad1ed4e commit 0ec2252

File tree

19 files changed

+3106
-1531
lines changed

19 files changed

+3106
-1531
lines changed

clear/main.py

+49
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import sys
55
import click
66
import utilities_common.cli as clicommon
7+
import utilities_common.multi_asic as multi_asic_util
78
import json
89

10+
from flow_counter_util.route import check_route_flow_counter_support
911
from utilities_common import util_base
1012
from show.plugins.pbh import read_pbh_counters
1113
from . import plugins
@@ -490,6 +492,53 @@ def flowcnt_trap():
490492
run_command(command)
491493

492494

495+
# ("sonic-clear flowcnt-route")
496+
@cli.group(invoke_without_command=True)
497+
@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')
498+
@click.pass_context
499+
def flowcnt_route(ctx, namespace):
500+
"""Clear all route flow counters"""
501+
check_route_flow_counter_support()
502+
if ctx.invoked_subcommand is None:
503+
command = "flow_counters_stat -c -t route"
504+
# None namespace means default namespace
505+
if namespace is not None:
506+
command += " -n {}".format(namespace)
507+
clicommon.run_command(command)
508+
509+
510+
# ("sonic-clear flowcnt-route pattern")
511+
@flowcnt_route.command()
512+
@click.option('--vrf', help='VRF/VNET name or default VRF')
513+
@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')
514+
@click.argument('prefix-pattern', required=True)
515+
def pattern(prefix_pattern, vrf, namespace):
516+
"""Clear route flow counters by pattern"""
517+
command = "flow_counters_stat -c -t route --prefix_pattern {}".format(prefix_pattern)
518+
if vrf:
519+
command += ' --vrf {}'.format(vrf)
520+
# None namespace means default namespace
521+
if namespace is not None:
522+
command += " -n {}".format(namespace)
523+
clicommon.run_command(command)
524+
525+
526+
# ("sonic-clear flowcnt-route route")
527+
@flowcnt_route.command()
528+
@click.option('--vrf', help='VRF/VNET name or default VRF')
529+
@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')
530+
@click.argument('prefix', required=True)
531+
def route(prefix, vrf, namespace):
532+
"""Clear route flow counters by prefix"""
533+
command = "flow_counters_stat -c -t route --prefix {}".format(prefix)
534+
if vrf:
535+
command += ' --vrf {}'.format(vrf)
536+
# None namespace means default namespace
537+
if namespace is not None:
538+
command += " -n {}".format(namespace)
539+
clicommon.run_command(command)
540+
541+
493542
# Load plugins and register them
494543
helper = util_base.UtilHelper()
495544
helper.load_and_register_plugins(plugins, cli)

config/flow_counters.py

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import click
2+
import ipaddress
3+
import sys
4+
5+
from flow_counter_util.route import FLOW_COUNTER_ROUTE_PATTERN_TABLE, FLOW_COUNTER_ROUTE_MAX_MATCH_FIELD, DEFAULT_VRF, PATTERN_SEPARATOR
6+
from flow_counter_util.route import build_route_pattern, extract_route_pattern, check_route_flow_counter_support
7+
from utilities_common.cli import AbbreviationGroup, pass_db
8+
from utilities_common import cli # To make mock work in unit test
9+
10+
#
11+
# 'flowcnt-route' group ('config flowcnt-route ...')
12+
#
13+
14+
15+
@click.group(cls=AbbreviationGroup, invoke_without_command=False)
16+
def flowcnt_route():
17+
"""Route flow counter related configuration tasks"""
18+
pass
19+
20+
21+
@flowcnt_route.group()
22+
def pattern():
23+
"""Set pattern for route flow counter"""
24+
pass
25+
26+
27+
@pattern.command(name='add')
28+
@click.option('-y', '--yes', is_flag=True)
29+
@click.option('--vrf', help='VRF/VNET name or default VRF')
30+
@click.option('--max', 'max_allowed_match', type=click.IntRange(1, 50), default=30, show_default=True, help='Max allowed match count')
31+
@click.argument('prefix-pattern', required=True)
32+
@pass_db
33+
def pattern_add(db, yes, vrf, max_allowed_match, prefix_pattern):
34+
"""Add pattern for route flow counter"""
35+
_update_route_flow_counter_config(db, vrf, max_allowed_match, prefix_pattern, True, yes)
36+
37+
38+
@pattern.command(name='remove')
39+
@click.option('--vrf', help='VRF/VNET name or default VRF')
40+
@click.argument('prefix-pattern', required=True)
41+
@pass_db
42+
def pattern_remove(db, vrf, prefix_pattern):
43+
"""Remove pattern for route flow counter"""
44+
_update_route_flow_counter_config(db, vrf, None, prefix_pattern, False)
45+
46+
47+
def _update_route_flow_counter_config(db, vrf, max_allowed_match, prefix_pattern, add, yes=False):
48+
"""
49+
Update route flow counter config
50+
:param db: db object
51+
:param vrf: vrf string, empty vrf will be treated as default vrf
52+
:param max_allowed_match: max allowed match count, $FLOW_COUNTER_ROUTE_MAX_MATCH_FIELD will be used if not specified
53+
:param prefix_pattern: route prefix pattern, automatically add prefix length if not specified
54+
:param add: True to add/set the configuration, otherwise remove
55+
:param yes: Don't ask question if True
56+
:return:
57+
"""
58+
check_route_flow_counter_support()
59+
60+
if add:
61+
try:
62+
net = ipaddress.ip_network(prefix_pattern, strict=False)
63+
except ValueError as e:
64+
click.echo('Invalid prefix pattern: {}'.format(prefix_pattern))
65+
exit(1)
66+
67+
if '/' not in prefix_pattern:
68+
prefix_pattern += '/' + str(net.prefixlen)
69+
70+
key = build_route_pattern(vrf, prefix_pattern)
71+
for _, cfgdb in db.cfgdb_clients.items():
72+
if _try_find_existing_pattern(cfgdb, net, key, yes):
73+
entry_data = cfgdb.get_entry(FLOW_COUNTER_ROUTE_PATTERN_TABLE, key)
74+
old_max_allowed_match = entry_data.get(FLOW_COUNTER_ROUTE_MAX_MATCH_FIELD)
75+
if old_max_allowed_match is not None and int(old_max_allowed_match) == max_allowed_match:
76+
click.echo('The route pattern already exists, nothing to be changed')
77+
exit(1)
78+
cfgdb.mod_entry(FLOW_COUNTER_ROUTE_PATTERN_TABLE,
79+
key,
80+
{FLOW_COUNTER_ROUTE_MAX_MATCH_FIELD: str(max_allowed_match)})
81+
else:
82+
found = False
83+
key = build_route_pattern(vrf, prefix_pattern)
84+
for _, cfgdb in db.cfgdb_clients.items():
85+
pattern_table = cfgdb.get_table(FLOW_COUNTER_ROUTE_PATTERN_TABLE)
86+
87+
for existing_key in pattern_table:
88+
exist_vrf, existing_prefix = extract_route_pattern(existing_key)
89+
if (exist_vrf == vrf or (vrf is None and exist_vrf == DEFAULT_VRF)) and existing_prefix == prefix_pattern:
90+
found = True
91+
cfgdb.set_entry(FLOW_COUNTER_ROUTE_PATTERN_TABLE, key, None)
92+
if not found:
93+
click.echo("Failed to remove route pattern: {} does not exist".format(key))
94+
exit(1)
95+
96+
97+
def _try_find_existing_pattern(cfgdb, input_net, input_key, yes):
98+
"""Try to find the same pattern from CONFIG DB
99+
100+
Args:
101+
cfgdb (object): CONFIG DB object
102+
input_net (object): Input ip_network object
103+
input_key (str): Input key
104+
yes (bool): Whether ask user question
105+
106+
Returns:
107+
bool: True if found an existing one
108+
"""
109+
input_type = type(input_net) # IPv4 or IPv6
110+
found_invalid = []
111+
found = None
112+
pattern_table = cfgdb.get_table(FLOW_COUNTER_ROUTE_PATTERN_TABLE)
113+
for existing_key in pattern_table:
114+
if isinstance(existing_key, tuple):
115+
existing_prefix = existing_key[1]
116+
existing_key = PATTERN_SEPARATOR.join(existing_key)
117+
else:
118+
_, existing_prefix = extract_route_pattern(existing_key)
119+
120+
# In case user configures an invalid pattern via CONFIG DB.
121+
if not existing_prefix: # Invalid pattern such as: "vrf1|"
122+
click.echo('Detect invalid route pattern in existing configuration {}'.format(existing_key))
123+
found_invalid.append(existing_key)
124+
continue
125+
126+
try:
127+
existing_net = ipaddress.ip_network(existing_prefix, strict=False)
128+
except ValueError as e: # Invalid pattern such as: "vrf1|invalid"
129+
click.echo('Detect invalid route pattern in existing configuration {}'.format(existing_key))
130+
found_invalid.append(existing_key)
131+
continue
132+
133+
if type(existing_net) == input_type:
134+
found = existing_key
135+
break
136+
137+
if found == input_key:
138+
return True
139+
140+
if not found and found_invalid:
141+
# If not found but there is an invalid one, ask user to replace the invalid one
142+
found = found_invalid[0]
143+
144+
if found:
145+
if not yes:
146+
answer = cli.query_yes_no('Only support 1 IPv4 route pattern and 1 IPv6 route pattern, remove existing pattern {}?'.format(found))
147+
else:
148+
answer = True
149+
if answer:
150+
click.echo('Replacing existing route pattern {} with {}'.format(existing_key, input_key))
151+
cfgdb.set_entry(FLOW_COUNTER_ROUTE_PATTERN_TABLE, existing_key, None)
152+
else:
153+
exit(0)
154+
return False

config/main.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from . import chassis_modules
3636
from . import console
3737
from . import feature
38+
from . import flow_counters
3839
from . import kdump
3940
from . import kube
4041
from . import muxcable
@@ -787,7 +788,7 @@ def _per_namespace_swss_ready(service_name):
787788
return False
788789

789790
def _swss_ready():
790-
list_of_swss = []
791+
list_of_swss = []
791792
num_asics = multi_asic.get_num_asics()
792793
if num_asics == 1:
793794
list_of_swss.append("swss.service")
@@ -800,7 +801,7 @@ def _swss_ready():
800801
if _per_namespace_swss_ready(service_name) == False:
801802
return False
802803

803-
return True
804+
return True
804805

805806
def _is_system_starting():
806807
out = clicommon.run_command("sudo systemctl is-system-running", return_cmd=True)
@@ -1061,6 +1062,7 @@ def config(ctx):
10611062
config.add_command(chassis_modules.chassis)
10621063
config.add_command(console.console)
10631064
config.add_command(feature.feature)
1065+
config.add_command(flow_counters.flowcnt_route)
10641066
config.add_command(kdump.kdump)
10651067
config.add_command(kube.kubernetes)
10661068
config.add_command(muxcable.muxcable)
@@ -1467,10 +1469,10 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, disable_arp_cach
14671469

14681470

14691471
config_gen_opts = ""
1470-
1472+
14711473
if os.path.isfile(INIT_CFG_FILE):
14721474
config_gen_opts += " -j {} ".format(INIT_CFG_FILE)
1473-
1475+
14741476
if file_format == 'config_db':
14751477
config_gen_opts += ' -j {} '.format(file)
14761478
else:
@@ -6224,7 +6226,7 @@ def del_subinterface(ctx, subinterface_name):
62246226
sub_intfs = [k for k,v in subintf_config_db.items() if type(k) != tuple]
62256227
if subinterface_name not in sub_intfs:
62266228
ctx.fail("{} does not exists".format(subinterface_name))
6227-
6229+
62286230
ips = {}
62296231
ips = [ k[1] for k in config_db.get_table('VLAN_SUB_INTERFACE') if type(k) == tuple and k[0] == subinterface_name ]
62306232
for ip in ips:

counterpoll/main.py

+39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import click
22
import json
3+
from flow_counter_util.route import check_route_flow_counter_support
34
from swsscommon.swsscommon import ConfigDBConnector
45
from tabulate import tabulate
56

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

351+
# Route flow counter commands
352+
@cli.group()
353+
@click.pass_context
354+
def flowcnt_route(ctx):
355+
""" Route flow counter commands """
356+
check_route_flow_counter_support()
357+
ctx.obj = ConfigDBConnector()
358+
ctx.obj.connect()
359+
360+
@flowcnt_route.command()
361+
@click.argument('poll_interval', type=click.IntRange(1000, 30000))
362+
@click.pass_context
363+
def interval(ctx, poll_interval):
364+
""" Set route flow counter query interval """
365+
fc_info = {}
366+
fc_info['POLL_INTERVAL'] = poll_interval
367+
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "FLOW_CNT_ROUTE", fc_info)
368+
369+
@flowcnt_route.command()
370+
@click.pass_context
371+
def enable(ctx):
372+
""" Enable route flow counter query """
373+
fc_info = {}
374+
fc_info['FLEX_COUNTER_STATUS'] = 'enable'
375+
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "FLOW_CNT_ROUTE", fc_info)
376+
377+
@flowcnt_route.command()
378+
@click.pass_context
379+
def disable(ctx):
380+
""" Disable route flow counter query """
381+
fc_info = {}
382+
fc_info['FLEX_COUNTER_STATUS'] = 'disable'
383+
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "FLOW_CNT_ROUTE", fc_info)
384+
350385
@cli.command()
351386
def show():
352387
""" Show the counter configuration """
@@ -363,6 +398,7 @@ def show():
363398
acl_info = configdb.get_entry('FLEX_COUNTER_TABLE', ACL)
364399
tunnel_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'TUNNEL')
365400
trap_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'FLOW_CNT_TRAP')
401+
route_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'FLOW_CNT_ROUTE')
366402

367403
header = ("Type", "Interval (in ms)", "Status")
368404
data = []
@@ -388,6 +424,9 @@ def show():
388424
data.append(["TUNNEL_STAT", rif_info.get("POLL_INTERVAL", DEFLT_10_SEC), rif_info.get("FLEX_COUNTER_STATUS", DISABLE)])
389425
if trap_info:
390426
data.append(["FLOW_CNT_TRAP_STAT", trap_info.get("POLL_INTERVAL", DEFLT_10_SEC), trap_info.get("FLEX_COUNTER_STATUS", DISABLE)])
427+
if route_info:
428+
data.append(["FLOW_CNT_ROUTE_STAT", route_info.get("POLL_INTERVAL", DEFLT_10_SEC),
429+
route_info.get("FLEX_COUNTER_STATUS", DISABLE)])
391430

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

flow_counter_util/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)