Skip to content

Commit 36b18ec

Browse files
authored
[SRv6] add support for SRv6 counters (#3841)
* [SRv6] add support for SRv6 counters * extend counterpoll * extend sonic-clear * add 'show srv6 stats' command Signed-off-by: Yakiv Huryk <[email protected]> * [SRv6] move SRv6Stat into a separate module under utilities_common Signed-off-by: Yakiv Huryk <[email protected]> --------- Signed-off-by: Yakiv Huryk <[email protected]>
1 parent a488022 commit 36b18ec

File tree

13 files changed

+371
-0
lines changed

13 files changed

+371
-0
lines changed

clear/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,13 @@ def tunnelcounters():
218218
command = ["tunnelstat", "-c"]
219219
run_command(command)
220220

221+
222+
@cli.command()
223+
def srv6counters():
224+
"""Clear SRv6 counters"""
225+
command = ["srv6stat", "-c"]
226+
run_command(command)
227+
221228
#
222229
# 'clear watermarks
223230
#

counterpoll/main.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,41 @@ def wredport_disable(ctx):
505505
wred_port_info['FLEX_COUNTER_STATUS'] = 'disable'
506506
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "WRED_ECN_PORT", wred_port_info)
507507

508+
509+
# SRv6 counter commands
510+
@cli.group()
511+
@click.pass_context
512+
def srv6(ctx):
513+
""" SRv6 counter commands """
514+
ctx.obj = ConfigDBConnector()
515+
ctx.obj.connect()
516+
517+
518+
@srv6.command()
519+
@click.pass_context
520+
@click.argument('poll_interval', type=click.IntRange(1000, 30000))
521+
def interval(ctx, poll_interval): # noqa: F811
522+
""" Set SRv6 counter query interval """
523+
srv6_info = {'POLL_INTERVAL': poll_interval}
524+
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "SRV6", srv6_info)
525+
526+
527+
@srv6.command()
528+
@click.pass_context
529+
def enable(ctx): # noqa: F811
530+
""" Enable SRv6 counter query """
531+
srv6_info = {'FLEX_COUNTER_STATUS': ENABLE}
532+
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "SRV6", srv6_info)
533+
534+
535+
@srv6.command()
536+
@click.pass_context
537+
def disable(ctx): # noqa: F811
538+
""" Disable SRv6 counter query """
539+
srv6_info = {'FLEX_COUNTER_STATUS': DISABLE}
540+
ctx.obj.mod_entry("FLEX_COUNTER_TABLE", "SRV6", srv6_info)
541+
542+
508543
@cli.command()
509544
def show():
510545
""" Show the counter configuration """
@@ -525,6 +560,7 @@ def show():
525560
eni_info = configdb.get_entry('FLEX_COUNTER_TABLE', ENI)
526561
wred_queue_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'WRED_ECN_QUEUE')
527562
wred_port_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'WRED_ECN_PORT')
563+
srv6_info = configdb.get_entry('FLEX_COUNTER_TABLE', 'SRV6')
528564

529565
header = ("Type", "Interval (in ms)", "Status")
530566
data = []
@@ -559,6 +595,9 @@ def show():
559595
if wred_port_info:
560596
data.append(["WRED_ECN_PORT_STAT", wred_port_info.get("POLL_INTERVAL", DEFLT_1_SEC),
561597
wred_port_info.get("FLEX_COUNTER_STATUS", DISABLE)])
598+
if srv6_info:
599+
data.append(["SRV6_STAT", srv6_info.get("POLL_INTERVAL", DEFLT_10_SEC),
600+
srv6_info.get("FLEX_COUNTER_STATUS", DISABLE)])
562601

563602
if is_dpu(configdb) and eni_info:
564603
data.append(["ENI_STAT", eni_info.get("POLL_INTERVAL", DEFLT_10_SEC),

scripts/srv6stat

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/python3
2+
3+
import argparse
4+
import sys
5+
6+
from utilities_common.srv6stat import SRv6Stat
7+
8+
def main():
9+
parser = argparse.ArgumentParser(description='Display SONiC SRv6 MySID counters',
10+
formatter_class=argparse.RawTextHelpFormatter)
11+
parser.add_argument('-c', '--clear', action='store_true', help='Clear SRv6 MySID counters statistics')
12+
parser.add_argument('-d', '--delete', action='store_true', help='Delete saved SRv6 MySID counters statistics')
13+
parser.add_argument('-s', '--sid', type=str, help='Show a specific SID', default=None)
14+
parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0')
15+
args = parser.parse_args()
16+
17+
try:
18+
stats = SRv6Stat()
19+
if args.clear:
20+
stats.clear()
21+
return
22+
if args.delete:
23+
stats.remove_cache()
24+
return
25+
stats.show(args.sid)
26+
27+
except Exception as e:
28+
print(str(e), file=sys.stderr)
29+
sys.exit(1)
30+
31+
32+
if __name__ == "__main__":
33+
main()

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
'scripts/soft-reboot',
171171
'scripts/storyteller',
172172
'scripts/syseeprom-to-json',
173+
'scripts/srv6stat',
173174
'scripts/teamd_increase_retry_count.py',
174175
'scripts/tempershow',
175176
'scripts/tunnelstat',

show/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from . import dns
6969
from . import bgp_cli
7070
from . import stp
71+
from . import srv6
7172

7273
# Global Variables
7374
PLATFORM_JSON = 'platform.json'
@@ -320,6 +321,7 @@ def cli(ctx):
320321
cli.add_command(warm_restart.warm_restart)
321322
cli.add_command(dns.dns)
322323
cli.add_command(stp.spanning_tree)
324+
cli.add_command(srv6.srv6)
323325

324326
# syslog module
325327
cli.add_command(syslog.syslog)

show/srv6.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import click
2+
import utilities_common.cli as clicommon
3+
4+
5+
@click.group(cls=clicommon.AliasedGroup)
6+
def srv6():
7+
"""Show SRv6 related information"""
8+
pass
9+
10+
11+
# 'stats' subcommand ("show srv6 stats")
12+
@srv6.command()
13+
@click.option('--verbose', is_flag=True, help="Enable verbose output")
14+
@click.argument('sid', required=False)
15+
def stats(verbose, sid):
16+
"""Show SRv6 counter statistic"""
17+
cmd = ['srv6stat']
18+
if sid:
19+
cmd += ['-s', sid]
20+
clicommon.run_command(cmd, display_cmd=verbose)

tests/clear_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ def test_clear_fdb(self, run_command):
101101
assert result.exit_code == 0
102102
run_command.assert_called_with(['fdbclear'])
103103

104+
@patch('clear.main.run_command')
105+
def test_clear_srv6counters(self, run_command):
106+
runner = CliRunner()
107+
result = runner.invoke(clear.cli.commands['srv6counters'])
108+
assert result.exit_code == 0
109+
run_command.assert_called_with(['srv6stat', '-c'])
110+
104111
def teardown(self):
105112
print('TEAR DOWN')
106113

tests/counterpoll_test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
FLOW_CNT_ROUTE_STAT 10000 enable
3434
WRED_ECN_QUEUE_STAT 10000 enable
3535
WRED_ECN_PORT_STAT 1000 enable
36+
SRV6_STAT 10000 enable
3637
"""
3738

3839
expected_counterpoll_show_dpu = """Type Interval (in ms) Status
@@ -49,6 +50,7 @@
4950
FLOW_CNT_ROUTE_STAT 10000 enable
5051
WRED_ECN_QUEUE_STAT 10000 enable
5152
WRED_ECN_PORT_STAT 1000 enable
53+
SRV6_STAT 10000 enable
5254
ENI_STAT 1000 enable
5355
"""
5456

@@ -335,6 +337,31 @@ def test_update_wred_queue_counter_interval(self):
335337
print(table)
336338
assert test_interval == table["WRED_ECN_QUEUE"]["POLL_INTERVAL"]
337339

340+
@pytest.mark.parametrize("status", ["disable", "enable"])
341+
def test_update_srv6_status(self, status):
342+
runner = CliRunner()
343+
db = Db()
344+
345+
result = runner.invoke(counterpoll.cli.commands["srv6"].commands[status], [], obj=db.cfgdb)
346+
print(result.exit_code, result.output)
347+
assert result.exit_code == 0
348+
349+
table = db.cfgdb.get_table("FLEX_COUNTER_TABLE")
350+
assert status == table["SRV6"]["FLEX_COUNTER_STATUS"]
351+
352+
def test_update_srv6_interval(self):
353+
runner = CliRunner()
354+
db = Db()
355+
test_interval = "20000"
356+
357+
result = runner.invoke(counterpoll.cli.commands["srv6"].commands["interval"], [test_interval], obj=db.cfgdb)
358+
print(result.exit_code, result.output)
359+
assert result.exit_code == 0
360+
361+
table = db.cfgdb.get_table("FLEX_COUNTER_TABLE")
362+
assert test_interval == table["SRV6"]["POLL_INTERVAL"]
363+
364+
338365
@classmethod
339366
def teardown_class(cls):
340367
print("TEARDOWN")

tests/mock_tables/config_db.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,6 +1828,10 @@
18281828
"POLL_INTERVAL": "1000",
18291829
"FLEX_COUNTER_STATUS": "enable"
18301830
},
1831+
"FLEX_COUNTER_TABLE|SRV6": {
1832+
"POLL_INTERVAL": "10000",
1833+
"FLEX_COUNTER_STATUS": "enable"
1834+
},
18311835
"PFC_WD|Ethernet0": {
18321836
"action": "drop",
18331837
"detection_time": "600",

tests/mock_tables/counters_db.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2853,5 +2853,17 @@
28532853
"COUNTERS:oid:0x1600000000035f":{
28542854
"SAI_COUNTER_STAT_PACKETS": 1000,
28552855
"SAI_COUNTER_STAT_BYTES": 25000
2856+
},
2857+
"COUNTERS_SRV6_NAME_MAP": {
2858+
"1000:2:30::/48": "oid:0x17000000001000",
2859+
"2000:2:30::/48": "oid:0x17000000002000"
2860+
},
2861+
"COUNTERS:oid:0x17000000001000":{
2862+
"SAI_COUNTER_STAT_PACKETS": 100,
2863+
"SAI_COUNTER_STAT_BYTES": 102400
2864+
},
2865+
"COUNTERS:oid:0x17000000002000":{
2866+
"SAI_COUNTER_STAT_PACKETS": 200,
2867+
"SAI_COUNTER_STAT_BYTES": 204800
28562868
}
28572869
}

tests/show_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,3 +1109,29 @@ def test_ntp(self):
11091109
@classmethod
11101110
def teardown_class(cls):
11111111
print('TEARDOWN')
1112+
1113+
1114+
class TestShowSRv6Counters(object):
1115+
def setup(self):
1116+
print('SETUP')
1117+
1118+
@patch('utilities_common.cli.run_command')
1119+
def test_srv6_stats(self, mock_run_command):
1120+
runner = CliRunner()
1121+
result = runner.invoke(show.cli.commands['srv6'].commands['stats'], ['--verbose'])
1122+
print(result.exit_code)
1123+
print(result.output)
1124+
assert result.exit_code == 0
1125+
mock_run_command.assert_called_once_with(['srv6stat'], display_cmd=True)
1126+
1127+
@patch('utilities_common.cli.run_command')
1128+
def test_srv6_stats_with_sid(self, mock_run_command):
1129+
runner = CliRunner()
1130+
result = runner.invoke(show.cli.commands['srv6'].commands['stats'], ['1000:2:30::/48', '--verbose'])
1131+
print(result.exit_code)
1132+
print(result.output)
1133+
assert result.exit_code == 0
1134+
mock_run_command.assert_called_once_with(['srv6stat', '-s', '1000:2:30::/48'], display_cmd=True)
1135+
1136+
def teardown(self):
1137+
print('TEAR DOWN')

tests/srv6stat_test.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import os
2+
import sys
3+
from io import StringIO
4+
from unittest import mock
5+
6+
from utilities_common.general import load_module_from_source
7+
from .mock_tables import dbconnector
8+
9+
test_path = os.path.dirname(os.path.abspath(__file__))
10+
modules_path = os.path.dirname(test_path)
11+
scripts_path = os.path.join(modules_path, "scripts")
12+
sys.path.insert(0, modules_path)
13+
14+
srv6stat_path = os.path.join(scripts_path, 'srv6stat')
15+
srv6stat = load_module_from_source('srv6stat', srv6stat_path)
16+
17+
18+
# srv6stat
19+
default_output = """\
20+
MySID Packets Bytes
21+
-------------- --------- -------
22+
1000:2:30::/48 100 102400
23+
2000:2:30::/48 200 204800
24+
"""
25+
26+
specific_sid_output = """\
27+
MySID Packets Bytes
28+
-------------- --------- -------
29+
1000:2:30::/48 100 102400
30+
"""
31+
# srv6stat -c
32+
clear_output = ''
33+
34+
# srv6stat after clear
35+
after_clear_output = """\
36+
MySID Packets Bytes
37+
-------------- --------- -------
38+
1000:2:30::/48 0 0
39+
2000:2:30::/48 0 0
40+
"""
41+
42+
update_after_clear_output = """\
43+
MySID Packets Bytes
44+
-------------- --------- -------
45+
1000:2:30::/48 200 2000
46+
2000:2:30::/48 300 3000
47+
"""
48+
49+
50+
class SRv6StatRunner():
51+
def __init__(self, delete_cache_on_start=True, clear=None, delete=None, sid=None):
52+
self.setup(delete_cache_on_start)
53+
self.result = self.run_srv6stat(clear=clear, delete=delete, sid=sid)
54+
55+
def get_result(self):
56+
return self.result
57+
58+
def setup(self, delete_cache_on_start):
59+
if delete_cache_on_start:
60+
self.run_srv6stat(delete=True)
61+
62+
def run_srv6stat(self, clear=None, delete=None, sid=None):
63+
old_stdout = sys.stdout
64+
result = StringIO()
65+
sys.stdout = result
66+
with mock.patch.object(srv6stat.argparse.ArgumentParser,
67+
'parse_args',
68+
return_value=srv6stat.argparse.Namespace(clear=clear, delete=delete, sid=sid)):
69+
srv6stat.main()
70+
sys.stdout = old_stdout
71+
return result.getvalue()
72+
73+
74+
def test_show():
75+
result = SRv6StatRunner().get_result()
76+
assert result == default_output
77+
78+
79+
def test_show_specific_sid():
80+
result = SRv6StatRunner(sid='1000:2:30::/48').get_result()
81+
assert result == specific_sid_output
82+
83+
84+
def test_clear():
85+
result = SRv6StatRunner(clear=True).get_result()
86+
assert result == clear_output
87+
88+
result = SRv6StatRunner(delete_cache_on_start=False).get_result()
89+
assert result == after_clear_output
90+
91+
92+
def test_delete_cache():
93+
SRv6StatRunner(clear=True)
94+
SRv6StatRunner(delete=True)
95+
result = SRv6StatRunner().get_result()
96+
assert result == default_output
97+
98+
99+
def test_clear_and_populate_counters_db():
100+
SRv6StatRunner(clear=True)
101+
db = dbconnector.SonicV2Connector()
102+
db.connect(db.COUNTERS_DB)
103+
db.set(db.COUNTERS_DB, srv6stat.SRv6Stat.SRV6_COUNTERS_MAP, '1000:2:30::/48', 'oid:0x17000000001000')
104+
db.set(db.COUNTERS_DB, srv6stat.SRv6Stat.SRV6_COUNTERS_MAP, '2000:2:30::/48', 'oid:0x17000000002000')
105+
db.set(db.COUNTERS_DB, 'COUNTERS:oid:0x17000000001000', srv6stat.SRv6Stat.COUNTER_PACKETS, '300')
106+
db.set(db.COUNTERS_DB, 'COUNTERS:oid:0x17000000001000', srv6stat.SRv6Stat.COUNTER_BYTES, '104400')
107+
db.set(db.COUNTERS_DB, 'COUNTERS:oid:0x17000000002000', srv6stat.SRv6Stat.COUNTER_PACKETS, '500')
108+
db.set(db.COUNTERS_DB, 'COUNTERS:oid:0x17000000002000', srv6stat.SRv6Stat.COUNTER_BYTES, '207800')
109+
with mock.patch('utilities_common.srv6stat.SonicV2Connector', return_value=db):
110+
result = SRv6StatRunner(delete_cache_on_start=False).get_result()
111+
assert result == update_after_clear_output

0 commit comments

Comments
 (0)