Skip to content

Commit 9881f3e

Browse files
Broadcast Unknown-multicast and Unknown-unicast Storm-control (sonic-net#928)
* CLICK CLI - Configuration and show commands for BUM Storm-control feature. configuration commands ---------------------- config interface storm-control broadcast add Ethernet0 10000 config interface storm-control unknown-multicast add Ethernet0 10000 config interface storm-control unknown-unicast add Ethernet0 10000 config interface storm-control broadcast del Ethernet0 config interface storm-control unknown-multicast del Ethernet0 config interface storm-control unknown-unicast del Ethernet0 show commands ------------- show storm-control all show storm-control interface Ethernet0
1 parent 88286cb commit 9881f3e

File tree

6 files changed

+477
-0
lines changed

6 files changed

+477
-0
lines changed

config/main.py

+112
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import utilities_common.cli as clicommon
3030
from utilities_common.helper import get_port_pbh_binding, get_port_acl_binding
3131
from utilities_common.general import load_db_config, load_module_from_source
32+
import utilities_common.multi_asic as multi_asic_util
3233

3334
from .utils import log
3435

@@ -638,6 +639,78 @@ def _clear_cbf():
638639
for cbf_table in CBF_TABLE_NAMES:
639640
config_db.delete_table(cbf_table)
640641

642+
#API to validate the interface passed for storm-control configuration
643+
def storm_control_interface_validate(port_name):
644+
if clicommon.get_interface_naming_mode() == "alias":
645+
port_name = interface_alias_to_name(None, port_name)
646+
if port_name is None:
647+
click.echo("'port_name' is None!")
648+
return False
649+
650+
if (port_name.startswith("Ethernet")):
651+
if interface_name_is_valid(None, port_name) is False:
652+
click.echo("Interface name %s is invalid. Please enter a valid interface name" %(port_name))
653+
return False
654+
else:
655+
click.echo("Storm-control is supported only on Ethernet interfaces. Not supported on %s" %(port_name))
656+
return False
657+
658+
return True
659+
660+
def is_storm_control_supported(storm_type, namespace):
661+
asic_id = multi_asic.get_asic_index_from_namespace(namespace)
662+
#state_db[asic_id] = swsscommon.DBConnector("STATE_DB", REDIS_TIMEOUT_MSECS, True, namespace)
663+
#supported = state_db[asic_id].get_entry('BUM_STORM_CAPABILITY', storm_type)
664+
state_db = SonicV2Connector(host='127.0.0.1')
665+
state_db.connect(state_db.STATE_DB, False)
666+
entry_name="BUM_STORM_CAPABILITY|"+storm_type
667+
supported = state_db.get(state_db.STATE_DB, entry_name,"supported")
668+
return supported
669+
670+
#API to configure the PORT_STORM_CONTROL table
671+
def storm_control_set_entry(port_name, kbps, storm_type, namespace):
672+
673+
if storm_control_interface_validate(port_name) is False:
674+
return False
675+
676+
if is_storm_control_supported(storm_type, namespace) == 0:
677+
click.echo("Storm-control is not supported on this namespace {}".format(namespace))
678+
return False
679+
680+
#Validate kbps value
681+
config_db = ConfigDBConnector()
682+
config_db.connect()
683+
key = port_name + '|' + storm_type
684+
entry = config_db.get_entry('PORT_STORM_CONTROL', key)
685+
686+
if len(entry) == 0:
687+
config_db.set_entry('PORT_STORM_CONTROL', key, {'kbps':kbps})
688+
else:
689+
kbps_value = int(entry.get('kbps',0))
690+
if kbps_value != kbps:
691+
config_db.mod_entry('PORT_STORM_CONTROL', key, {'kbps':kbps})
692+
693+
return True
694+
695+
#API to remove an entry from PORT_STORM_CONTROL table
696+
def storm_control_delete_entry(port_name, storm_type):
697+
698+
if storm_control_interface_validate(port_name) is False:
699+
return False
700+
701+
config_db = ConfigDBConnector()
702+
config_db.connect()
703+
key = port_name + '|' + storm_type
704+
entry = config_db.get_entry('PORT_STORM_CONTROL', key)
705+
706+
if len(entry) == 0:
707+
click.echo("%s storm-control not enabled on interface %s" %(storm_type, port_name))
708+
return False
709+
else:
710+
config_db.set_entry('PORT_STORM_CONTROL', key, None)
711+
712+
return True
713+
641714

642715
def _clear_qos():
643716
QOS_TABLE_NAMES = [
@@ -5784,6 +5857,45 @@ def naming_mode_alias():
57845857
"""Set CLI interface naming mode to ALIAS (Vendor port alias)"""
57855858
set_interface_naming_mode('alias')
57865859

5860+
@interface.group('storm-control')
5861+
@click.pass_context
5862+
def storm_control(ctx):
5863+
""" Configure storm-control"""
5864+
pass
5865+
5866+
@storm_control.command('add')
5867+
@click.argument('port_name',metavar='<port_name>', required=True)
5868+
@click.argument('storm_type',metavar='<storm_type>', required=True, type=click.Choice(["broadcast", "unknown-unicast", "unknown-multicast"]))
5869+
@click.argument('kbps',metavar='<kbps_value>', required=True, type=click.IntRange(0,100000000))
5870+
@click.option('--namespace',
5871+
'-n',
5872+
'namespace',
5873+
default=None,
5874+
type=str,
5875+
show_default=True,
5876+
help='Namespace name or all',
5877+
callback=multi_asic_util.multi_asic_namespace_validation_callback)
5878+
@click.pass_context
5879+
def add_interface_storm(ctx, port_name,storm_type, kbps, namespace):
5880+
if storm_control_set_entry(port_name, kbps, storm_type, namespace) is False:
5881+
ctx.fail("Unable to add {} storm-control to interface {}".format(storm_type, port_name))
5882+
5883+
@storm_control.command('del')
5884+
@click.argument('port_name',metavar='<port_name>', required=True)
5885+
@click.argument('storm_type',metavar='<storm_type>', required=True, type=click.Choice(["broadcast", "unknown-unicast", "unknown-multicast"]))
5886+
@click.option('--namespace',
5887+
'-n',
5888+
'namespace',
5889+
default=None,
5890+
type=str,
5891+
show_default=True,
5892+
help='Namespace name or all',
5893+
callback=multi_asic_util.multi_asic_namespace_validation_callback)
5894+
@click.pass_context
5895+
def del_interface_storm(ctx,port_name,storm_type, namespace):
5896+
if storm_control_delete_entry(port_name, storm_type) is False:
5897+
ctx.fail("Unable to delete {} storm-control from interface {}".format(storm_type, port_name))
5898+
57875899
def is_loopback_name_valid(loopback_name):
57885900
"""Loopback name validation
57895901
"""

scripts/storm_control.py

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env python3
2+
3+
############################################
4+
#
5+
# script to test storm_control functionality
6+
#
7+
############################################
8+
9+
import argparse
10+
import sys
11+
import os
12+
13+
# mock the redis for unit test purposes #
14+
try:
15+
if os.environ["UTILITIES_UNIT_TESTING"] == "2":
16+
modules_path = os.path.join(os.path.dirname(__file__), "..")
17+
test_path = os.path.join(modules_path, "tests")
18+
sys.path.insert(0, modules_path)
19+
sys.path.insert(0, test_path)
20+
import mock_tables.dbconnector
21+
22+
except KeyError:
23+
pass
24+
25+
from natsort import natsorted
26+
from tabulate import tabulate
27+
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector
28+
from utilities_common.general import load_db_config
29+
30+
STORM_TABLE_NAME = "PORT_STORM_CONTROL"
31+
32+
class storm_control(object):
33+
def __init__(self):
34+
self.config_db = ConfigDBConnector()
35+
self.config_db.connect()
36+
self.db = SonicV2Connector(use_unix_socket_path=False)
37+
self.db.connect(self.db.CONFIG_DB)
38+
def show_storm_config(self, port):
39+
header = ['Interface Name', 'Storm Type', 'Rate (kbps)']
40+
storm_type_list = ['broadcast','unknown-unicast','unknown-multicast']
41+
body = []
42+
configs = self.db.get_table(STORM_TABLE_NAME)
43+
if not configs:
44+
return
45+
storm_configs = natsorted(configs)
46+
if port is not None:
47+
for storm_type in storm_type_list:
48+
storm_key = port + '|' + storm_type
49+
data = self.db.get_entry(STORM_TABLE_NAME, storm_key)
50+
if data:
51+
kbps = data['kbps']
52+
body.append([port, storm_type, kbps])
53+
else:
54+
for storm_key in storm_configs:
55+
interface_name = storm_key[0]
56+
storm_type = storm_key[1]
57+
data = self.db.get_entry(STORM_TABLE_NAME, storm_key)
58+
if data:
59+
kbps = data['kbps']
60+
body.append([interface_name, storm_type, kbps])
61+
print(tabulate(body,header,tablefmt="grid"))
62+
63+
def validate_interface(self, port):
64+
if not (port.startswith("Eth")):
65+
return False
66+
return True
67+
68+
def validate_kbps(self, kbps):
69+
return True
70+
71+
def add_storm_config(self, port, storm_type, kbps):
72+
if not validate_interface(port):
73+
print ("Invalid Interface:{}".format(port))
74+
return False
75+
if not validate_kbps(kbps):
76+
print ("Invalid kbps value:{}".format(kbps))
77+
return False
78+
key = port + '|' + storm_type
79+
entry = self.db.get_entry(STORM_TABLE_NAME,key)
80+
if len(entry) == 0:
81+
self.db.set_entry(STORM_TABLE_NAME, key, {'kbps':kbps})
82+
else:
83+
kbps_value = int(entry.get('kbps',0))
84+
if kbps_value != kbps:
85+
self.db.mod_entry(STORM_TABLE_NAME, key, {'kbps':kbps})
86+
return True
87+
88+
def del_storm_config(self, port, storm_type):
89+
if not validate_interface(port):
90+
print ("Invalid Interface:{}".format(port))
91+
return False
92+
key = port_name + '|' + storm_type
93+
entry = self.db.get_entry(STORM_TABLE_NAME, key)
94+
if len(entry):
95+
self.db.set_entry(STORM_TABLE_NAME, key, None)
96+
return True
97+
98+
def main():
99+
parser = argparse.ArgumentParser(description='Configure and Display storm-control configuration',
100+
formatter_class=argparse.RawTextHelpFormatter)
101+
parser.add_argument('-l', '--list', action='store_true', help='show storm-control configuration', default=False)
102+
parser.add_argument('-p', '--port', type=str, help='port name (e.g. Ethernet0)', required=True, default=None)
103+
parser.add_argument('-t', '--storm-type', type=str, help='storm-type (broadcast, unknown-unicast, unknown-multicast)', required=True, default=None)
104+
parser.add_argument('-r', '--rate-kbps', type=int, help='kbps value', required=True, default=None)
105+
parser.add_argument('-d', '--delete', help='delete storm-control')
106+
parser.add_argument('-f', '--filename', help='file used by mock test', type=str, default=None)
107+
args = parser.parse_args()
108+
109+
# Load database config files
110+
load_db_config()
111+
try:
112+
storm = storm_control()
113+
if args.list:
114+
input_port=""
115+
if args.port:
116+
input_port = args.port
117+
storm.show_storm_config(input_port)
118+
elif args.port and args.storm_type and args.rate_kbps:
119+
if args.delete:
120+
storm.del_storm_config(args.port, args.storm_type)
121+
else:
122+
storm.add_storm_config(args.port, args.storm_type, args.rate_kbps)
123+
else:
124+
parser.print_help()
125+
sys.exit(1)
126+
127+
except Exception as e:
128+
try:
129+
if os.environ["UTILITIES_UNIT_TESTING"] == "1" or os.environ["UTILITIES_UNIT_TESTING"] == "2":
130+
print(str(e), file=sys.stdout)
131+
except KeyError:
132+
print(str(e), file=sys.stderr)
133+
134+
sys.exit(1)
135+
136+
if __name__ == "__main__":
137+
main()

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
'scripts/memory_threshold_check.py',
150150
'scripts/memory_threshold_check_handler.py',
151151
'scripts/techsupport_cleanup.py',
152+
'scripts/storm_control.py',
152153
'scripts/check_db_integrity.py'
153154
],
154155
entry_points={

0 commit comments

Comments
 (0)