Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broadcast Unknown-multicast and Unknown-unicast Storm-control #928

Merged
merged 39 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
46a0f9a
CLICK CLI - Configuration and show commands for BUM Storm-control fea…
mohan-selvaraj Jun 1, 2020
c5385d5
Merge branch 'master' of https:/Azure/sonic-utilities int…
mohan-selvaraj Nov 2, 2021
6be166a
Merge branch 'Azure-master' into storm_control
mohan-selvaraj Nov 2, 2021
a073d93
Update parameters to interface_alias_to_name and interface_name_is_va…
mohan-selvaraj Nov 2, 2021
37b41f0
addressed review comments.
mohan-selvaraj Nov 3, 2021
f5b354e
utilities test code for storm-control
mohan-selvaraj Nov 9, 2021
55768bd
mohan-selvaraj Nov 9, 2021
7542172
mohan-selvaraj Nov 9, 2021
4c9945f
Merge branch 'Azure:master' into storm_control
mohan-selvaraj Nov 15, 2021
8147999
Merge branch 'master' into storm_control
mohan-selvaraj Nov 17, 2021
91e70f5
mohan-selvaraj Nov 23, 2021
e6aec85
Merge branch 'storm_control' of https:/mohan-selvaraj/son…
mohan-selvaraj Nov 23, 2021
5194411
Modified storm-control configuration commands to
mohan-selvaraj Nov 23, 2021
6297b03
Merge branch 'Azure:master' into storm_control
mohan-selvaraj Nov 24, 2021
90d6f5f
Merge branch 'master' into storm_control
mohan-selvaraj Nov 24, 2021
12d7ce6
Merge branch 'storm_control' of https:/mohan-selvaraj/son…
mohan-selvaraj Nov 24, 2021
61e2107
modified storm-control show commands
mohan-selvaraj Nov 24, 2021
30c6fa7
mohan-selvaraj Nov 24, 2021
0e3fbd2
Merge branch 'Azure:master' into storm_control
mohan-selvaraj Nov 26, 2021
779ab88
support for capability check before proceeding with updating CONFIG_D…
mohan-selvaraj Nov 30, 2021
46433b8
Merge branch 'Azure:master' into storm_control
mohan-selvaraj Nov 30, 2021
7c850c8
mohan-selvaraj Nov 30, 2021
44996b7
Merge branch 'storm_control' of https:/mohan-selvaraj/son…
mohan-selvaraj Nov 30, 2021
f1996c5
mohan-selvaraj Nov 30, 2021
60dc372
mohan-selvaraj Dec 1, 2021
df63165
mohan-selvaraj Dec 1, 2021
e250b6a
Merge branch 'Azure:master' into storm_control
mohan-selvaraj Dec 1, 2021
d5c7f15
Merge branch 'master' into storm_control
mohan-selvaraj Dec 23, 2021
4c0e8ba
Merge branch 'Azure:master' into storm_control
mohan-selvaraj Apr 11, 2022
991210c
Merge branch 'Azure:master' into storm_control
mohan-selvaraj May 5, 2022
1ed0c15
Merge branch 'master' into storm_control
mohan-selvaraj May 5, 2022
93c01c3
Merge branch 'storm_control' of https:/mohan-selvaraj/son…
mohan-selvaraj May 5, 2022
d288b08
storm-control config/show test code
mohan-selvaraj May 5, 2022
9a85aad
mohan-selvaraj May 5, 2022
5b63779
mohan-selvaraj May 5, 2022
f1bb1cb
mohan-selvaraj May 5, 2022
e4389e5
mohan-selvaraj May 5, 2022
afe1122
Merge branch 'master' into storm_control
mohan-selvaraj May 9, 2022
f2c32ca
Merge branch 'Azure:master' into storm_control
mohan-selvaraj May 11, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from utilities_common import bgp_util
import utilities_common.cli as clicommon
from utilities_common.general import load_db_config, load_module_from_source
import utilities_common.multi_asic as multi_asic_util

from .utils import log

Expand Down Expand Up @@ -637,6 +638,78 @@ def _clear_cbf():
for cbf_table in CBF_TABLE_NAMES:
config_db.delete_table(cbf_table)

#API to validate the interface passed for storm-control configuration
def storm_control_interface_validate(port_name):
mohan-selvaraj marked this conversation as resolved.
Show resolved Hide resolved
if clicommon.get_interface_naming_mode() == "alias":
port_name = interface_alias_to_name(None, port_name)
if port_name is None:
click.echo("'port_name' is None!")
return False

if (port_name.startswith("Ethernet")):
if interface_name_is_valid(None, port_name) is False:
click.echo("Interface name %s is invalid. Please enter a valid interface name" %(port_name))
return False
else:
click.echo("Storm-control is supported only on Ethernet interfaces. Not supported on %s" %(port_name))
return False

return True

def is_storm_control_supported(storm_type, namespace):
asic_id = multi_asic.get_asic_index_from_namespace(namespace)
#state_db[asic_id] = swsscommon.DBConnector("STATE_DB", REDIS_TIMEOUT_MSECS, True, namespace)
#supported = state_db[asic_id].get_entry('BUM_STORM_CAPABILITY', storm_type)
state_db = SonicV2Connector(host='127.0.0.1')
state_db.connect(state_db.STATE_DB, False)
entry_name="BUM_STORM_CAPABILITY|"+storm_type
supported = state_db.get(state_db.STATE_DB, entry_name,"supported")
return supported

#API to configure the PORT_STORM_CONTROL table
def storm_control_set_entry(port_name, kbps, storm_type, namespace):

if storm_control_interface_validate(port_name) is False:
return False

if is_storm_control_supported(storm_type, namespace) == 0:
click.echo("Storm-control is not supported on this namespace {}".format(namespace))
return False

#Validate kbps value
config_db = ConfigDBConnector()
config_db.connect()
key = port_name + '|' + storm_type
entry = config_db.get_entry('PORT_STORM_CONTROL', key)

if len(entry) == 0:
config_db.set_entry('PORT_STORM_CONTROL', key, {'kbps':kbps})
else:
kbps_value = int(entry.get('kbps',0))
if kbps_value != kbps:
config_db.mod_entry('PORT_STORM_CONTROL', key, {'kbps':kbps})

return True

#API to remove an entry from PORT_STORM_CONTROL table
def storm_control_delete_entry(port_name, storm_type):
mohan-selvaraj marked this conversation as resolved.
Show resolved Hide resolved

if storm_control_interface_validate(port_name) is False:
return False

config_db = ConfigDBConnector()
config_db.connect()
key = port_name + '|' + storm_type
entry = config_db.get_entry('PORT_STORM_CONTROL', key)

if len(entry) == 0:
click.echo("%s storm-control not enabled on interface %s" %(storm_type, port_name))
return False
else:
config_db.set_entry('PORT_STORM_CONTROL', key, None)

return True


def _clear_qos():
QOS_TABLE_NAMES = [
Expand Down Expand Up @@ -5736,6 +5809,45 @@ def naming_mode_alias():
"""Set CLI interface naming mode to ALIAS (Vendor port alias)"""
set_interface_naming_mode('alias')

@interface.group('storm-control')
@click.pass_context
def storm_control(ctx):
""" Configure storm-control"""
pass

@storm_control.command('add')
@click.argument('port_name',metavar='<port_name>', required=True)
@click.argument('storm_type',metavar='<storm_type>', required=True, type=click.Choice(["broadcast", "unknown-unicast", "unknown-multicast"]))
@click.argument('kbps',metavar='<kbps_value>', required=True, type=click.IntRange(0,100000000))
@click.option('--namespace',
'-n',
'namespace',
default=None,
type=str,
show_default=True,
help='Namespace name or all',
callback=multi_asic_util.multi_asic_namespace_validation_callback)
@click.pass_context
def add_interface_storm(ctx, port_name,storm_type, kbps, namespace):
if storm_control_set_entry(port_name, kbps, storm_type, namespace) is False:
ctx.fail("Unable to add {} storm-control to interface {}".format(storm_type, port_name))

@storm_control.command('del')
@click.argument('port_name',metavar='<port_name>', required=True)
@click.argument('storm_type',metavar='<storm_type>', required=True, type=click.Choice(["broadcast", "unknown-unicast", "unknown-multicast"]))
@click.option('--namespace',
'-n',
'namespace',
default=None,
type=str,
show_default=True,
help='Namespace name or all',
callback=multi_asic_util.multi_asic_namespace_validation_callback)
@click.pass_context
def del_interface_storm(ctx,port_name,storm_type, namespace):
if storm_control_delete_entry(port_name, storm_type) is False:
ctx.fail("Unable to delete {} storm-control from interface {}".format(storm_type, port_name))

def is_loopback_name_valid(loopback_name):
"""Loopback name validation
"""
Expand Down
137 changes: 137 additions & 0 deletions scripts/storm_control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python3

############################################
#
# script to test storm_control functionality
#
############################################

import argparse
import sys
import os

# mock the redis for unit test purposes #
try:
if os.environ["UTILITIES_UNIT_TESTING"] == "2":
modules_path = os.path.join(os.path.dirname(__file__), "..")
test_path = os.path.join(modules_path, "tests")
sys.path.insert(0, modules_path)
sys.path.insert(0, test_path)
import mock_tables.dbconnector

except KeyError:
pass

from natsort import natsorted
from tabulate import tabulate
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector
from utilities_common.general import load_db_config

STORM_TABLE_NAME = "PORT_STORM_CONTROL"

class storm_control(object):
def __init__(self):
self.config_db = ConfigDBConnector()
self.config_db.connect()
self.db = SonicV2Connector(use_unix_socket_path=False)
self.db.connect(self.db.CONFIG_DB)
def show_storm_config(self, port):
header = ['Interface Name', 'Storm Type', 'Rate (kbps)']
storm_type_list = ['broadcast','unknown-unicast','unknown-multicast']
body = []
configs = self.db.get_table(STORM_TABLE_NAME)
if not configs:
return
storm_configs = natsorted(configs)
if port is not None:
for storm_type in storm_type_list:
storm_key = port + '|' + storm_type
data = self.db.get_entry(STORM_TABLE_NAME, storm_key)
if data:
kbps = data['kbps']
body.append([port, storm_type, kbps])
else:
for storm_key in storm_configs:
interface_name = storm_key[0]
storm_type = storm_key[1]
data = self.db.get_entry(STORM_TABLE_NAME, storm_key)
if data:
kbps = data['kbps']
body.append([interface_name, storm_type, kbps])
print(tabulate(body,header,tablefmt="grid"))

def validate_interface(self, port):
if not (port.startswith("Eth")):
return False
return True

def validate_kbps(self, kbps):
return True

def add_storm_config(self, port, storm_type, kbps):
Copy link
Contributor

@gechiang gechiang Nov 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you handle the case where if the HWSKU does not support this rate limiting on the interface?
You should add query capability on the port attribute to ensure that the attribute is supported instead of blindly accept the configuration and failed at Orchagent layer.
I do h=not see this being considered at the HLD either.
Please add this so that the configuration will not be allowed if the HWSKU being configured does not support such operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added code to store capability in STATE_DB and check before proceeding with CONFIG_DB update.

if not validate_interface(port):
print ("Invalid Interface:{}".format(port))
return False
if not validate_kbps(kbps):
print ("Invalid kbps value:{}".format(kbps))
return False
key = port + '|' + storm_type
entry = self.db.get_entry(STORM_TABLE_NAME,key)
if len(entry) == 0:
self.db.set_entry(STORM_TABLE_NAME, key, {'kbps':kbps})
else:
kbps_value = int(entry.get('kbps',0))
if kbps_value != kbps:
self.db.mod_entry(STORM_TABLE_NAME, key, {'kbps':kbps})
return True

def del_storm_config(self, port, storm_type):
if not validate_interface(port):
print ("Invalid Interface:{}".format(port))
return False
key = port_name + '|' + storm_type
entry = self.db.get_entry(STORM_TABLE_NAME, key)
if len(entry):
self.db.set_entry(STORM_TABLE_NAME, key, None)
return True

def main():
parser = argparse.ArgumentParser(description='Configure and Display storm-control configuration',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-l', '--list', action='store_true', help='show storm-control configuration', default=False)
parser.add_argument('-p', '--port', type=str, help='port name (e.g. Ethernet0)', required=True, default=None)
parser.add_argument('-t', '--storm-type', type=str, help='storm-type (broadcast, unknown-unicast, unknown-multicast)', required=True, default=None)
parser.add_argument('-r', '--rate-kbps', type=int, help='kbps value', required=True, default=None)
parser.add_argument('-d', '--delete', help='delete storm-control')
parser.add_argument('-f', '--filename', help='file used by mock test', type=str, default=None)
args = parser.parse_args()

# Load database config files
load_db_config()
try:
storm = storm_control()
if args.list:
input_port=""
if args.port:
input_port = args.port
storm.show_storm_config(input_port)
elif args.port and args.storm_type and args.rate_kbps:
if args.delete:
storm.del_storm_config(args.port, args.storm_type)
else:
storm.add_storm_config(args.port, args.storm_type, args.rate_kbps)
else:
parser.print_help()
sys.exit(1)

except Exception as e:
try:
if os.environ["UTILITIES_UNIT_TESTING"] == "1" or os.environ["UTILITIES_UNIT_TESTING"] == "2":
print(str(e), file=sys.stdout)
except KeyError:
print(str(e), file=sys.stderr)

sys.exit(1)

if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
'scripts/null_route_helper',
'scripts/coredump_gen_handler.py',
'scripts/techsupport_cleanup.py',
'scripts/storm_control.py',
'scripts/check_db_integrity.py'
],
entry_points={
Expand Down
Loading