Skip to content

Commit

Permalink
Broadcast Unknown-multicast and Unknown-unicast Storm-control (#928)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mohan-selvaraj authored May 18, 2022
1 parent 88286cb commit 9881f3e
Show file tree
Hide file tree
Showing 6 changed files with 477 additions and 0 deletions.
112 changes: 112 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import utilities_common.cli as clicommon
from utilities_common.helper import get_port_pbh_binding, get_port_acl_binding
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 @@ -638,6 +639,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):
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):

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 @@ -5784,6 +5857,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):
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 @@ -149,6 +149,7 @@
'scripts/memory_threshold_check.py',
'scripts/memory_threshold_check_handler.py',
'scripts/techsupport_cleanup.py',
'scripts/storm_control.py',
'scripts/check_db_integrity.py'
],
entry_points={
Expand Down
Loading

0 comments on commit 9881f3e

Please sign in to comment.