From 972e4be562c8a2bdfcfc69d2c8a3cfb6c859638c Mon Sep 17 00:00:00 2001 From: Praveen Madhusudhana Date: Mon, 16 Sep 2019 23:29:03 -0700 Subject: [PATCH 1/6] PVSTP feature implementation - Added config/show/debug/clear handling for STP - Modified other utilities script to include STP --- clear/main.py | 6 + clear/stp.py | 46 ++ config/main.py | 5 + config/stp.py | 995 ++++++++++++++++++++++++++++++++++++++++++ debug/main.py | 6 + debug/stp.py | 117 +++++ scripts/fast-reboot | 5 + scripts/generate_dump | 6 + show/main.py | 19 + show/stp.py | 420 ++++++++++++++++++ 10 files changed, 1625 insertions(+) create mode 100644 clear/stp.py create mode 100644 config/stp.py create mode 100644 debug/stp.py create mode 100644 show/stp.py diff --git a/clear/main.py b/clear/main.py index 4302ae00aa..8cfa6bd7af 100755 --- a/clear/main.py +++ b/clear/main.py @@ -124,6 +124,12 @@ def cli(): """SONiC command line - 'Clear' command""" pass +# +# 'STP' +# +from .stp import spanning_tree +cli.add_command(spanning_tree) + # # 'ip' group ### # diff --git a/clear/stp.py b/clear/stp.py new file mode 100644 index 0000000000..c540672a1b --- /dev/null +++ b/clear/stp.py @@ -0,0 +1,46 @@ +import click +from clear.main import AliasedGroup,cli +from clear.main import run_command + +# +# This group houses Spanning_tree commands and subgroups +# +@cli.group(cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@click.pass_context +def spanning_tree(ctx): + '''Clear Spanning-tree counters''' + pass + +@spanning_tree.group('statistics', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@click.pass_context +def stp_clr_stats(ctx): + if ctx.invoked_subcommand is None: + command = 'sudo stpctl clrstsall' + run_command(command) + pass + +@stp_clr_stats.command('interface') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_clr_stats_intf(ctx, interface_name): + command = 'sudo stpctl clrstsintf ' + interface_name + run_command(command) + pass + +@stp_clr_stats.command('vlan') +@click.argument('vlan_id', metavar='', required=True) +@click.pass_context +def stp_clr_stats_vlan(ctx, vlan_id): + command = 'sudo stpctl clrstsvlan ' + vlan_id + run_command(command) + pass + +@stp_clr_stats.command('vlan-interface') +@click.argument('vlan_id', metavar='', required=True) +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_clr_stats_vlan_intf(ctx, vlan_id, interface_name): + command = 'sudo stpctl clrstsvlanintf ' + vlan_id + ' ' + interface_name + run_command(command) + pass + diff --git a/config/main.py b/config/main.py index e5a3cf6d0f..cf24e94fc7 100644 --- a/config/main.py +++ b/config/main.py @@ -53,6 +53,10 @@ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) +import aaa +import mlnx +import stp + SONIC_GENERATED_SERVICE_PATH = '/etc/sonic/generated_services.conf' SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' VLAN_SUB_INTERFACE_SEPARATOR = '.' @@ -875,6 +879,7 @@ def config(ctx): config.add_command(nat.nat) config.add_command(vlan.vlan) config.add_command(vxlan.vxlan) +config.add_command(stp.spanning_tree) @config.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, diff --git a/config/stp.py b/config/stp.py new file mode 100644 index 0000000000..1ed68b174c --- /dev/null +++ b/config/stp.py @@ -0,0 +1,995 @@ +#!/usr/sbin/env python +# +# 'spanning-tree' group ('config spanning-tree ...') +# + +import click +import netaddr +from swsssdk import ConfigDBConnector +from swsssdk import SonicV2Connector +from natsort import natsorted +import logging + +STP_MIN_ROOT_GUARD_TIMEOUT = 5 +STP_MAX_ROOT_GUARD_TIMEOUT = 600 +STP_DEFAULT_ROOT_GUARD_TIMEOUT = 30 + +STP_MIN_FORWARD_DELAY = 4 +STP_MAX_FORWARD_DELAY = 30 +STP_DEFAULT_FORWARD_DELAY = 15 + +STP_MIN_HELLO_INTERVAL = 1 +STP_MAX_HELLO_INTERVAL = 10 +STP_DEFAULT_HELLO_INTERVAL = 2 + +STP_MIN_MAX_AGE = 6 +STP_MAX_MAX_AGE = 40 +STP_DEFAULT_MAX_AGE = 20 + +STP_MIN_BRIDGE_PRIORITY = 0 +STP_MAX_BRIDGE_PRIORITY = 61440 +STP_DEFAULT_BRIDGE_PRIORITY = 32768 + +PVST_MAX_INSTANCES = 255 + + +def get_intf_list_in_vlan_member_table(config_db): + """ + Get info from REDIS ConfigDB and create interface to vlan mapping + """ + get_int_vlan_configdb_info = config_db.get_table('VLAN_MEMBER') + int_list = [] + for key in get_int_vlan_configdb_info: + interface = key[1] + if interface not in int_list: + int_list.append(interface) + return int_list + +################################## +# STP parameter validations +################################## + + +def is_valid_root_guard_timeout(ctx, root_guard_timeout): + if root_guard_timeout not in range(STP_MIN_ROOT_GUARD_TIMEOUT, STP_MAX_ROOT_GUARD_TIMEOUT + 1): + ctx.fail("STP root guard timeout must be in range 5-600") + pass + + +def is_valid_forward_delay(ctx, forward_delay): + if forward_delay not in range(STP_MIN_FORWARD_DELAY, STP_MAX_FORWARD_DELAY + 1): + ctx.fail("STP forward delay value must be in range 4-30") + pass + + +def is_valid_hello_interval(ctx, hello_interval): + if hello_interval not in range(STP_MIN_HELLO_INTERVAL, STP_MAX_HELLO_INTERVAL + 1): + ctx.fail("STP hello timer must be in range 1-10") + pass + + +def is_valid_max_age(ctx, max_age): + if max_age not in range(STP_MIN_MAX_AGE, STP_MAX_MAX_AGE + 1): + ctx.fail("STP max age value must be in range 6-40") + pass + + +def is_valid_bridge_priority(ctx, priority): + if priority not in range(STP_MIN_BRIDGE_PRIORITY, STP_MAX_BRIDGE_PRIORITY + 1): + ctx.fail("STP bridge priority must be in range 0-61440") + if priority % 4096 != 0: + ctx.fail("STP bridge priority must be multiple of 4096") + pass + + +def validate_params(forward_delay, max_age, hello_time): + if (2 * (int(forward_delay) - 1)) >= int(max_age) >= (2 * (int(hello_time) + 1)): + return True + else: + return False + + +def is_valid_stp_vlan_parameters(ctx, vlan_name, param_type, new_value): + db = ctx.obj['db'] + stp_vlan_entry = db.get_entry('STP_VLAN', vlan_name) + cfg_vlan_forward_delay = stp_vlan_entry.get("forward_delay") + cfg_vlan_max_age = stp_vlan_entry.get("max_age") + cfg_vlan_hello_time = stp_vlan_entry.get("hello_time") + ret_val = False + if param_type == parameter_forward_delay: + ret_val = validate_params(new_value, cfg_vlan_max_age, cfg_vlan_hello_time) + elif param_type == parameter_max_age: + ret_val = validate_params(cfg_vlan_forward_delay, new_value, cfg_vlan_hello_time) + elif param_type == parameter_hello_time: + ret_val = validate_params(cfg_vlan_forward_delay, cfg_vlan_max_age, new_value) + + if ret_val is not True: + ctx.fail("2*(forward_delay-1) >= max_age >= 2*(hello_time +1 ) not met for VLAN") + + +def is_valid_stp_global_parameters(ctx, param_type, new_value): + db = ctx.obj['db'] + stp_global_entry = db.get_entry('STP', "GLOBAL") + cfg_forward_delay = stp_global_entry.get("forward_delay") + cfg_max_age = stp_global_entry.get("max_age") + cfg_hello_time = stp_global_entry.get("hello_time") + ret_val = False + if param_type == parameter_forward_delay: + ret_val = validate_params(new_value, cfg_max_age, cfg_hello_time) + elif param_type == parameter_max_age: + ret_val = validate_params(cfg_forward_delay, new_value, cfg_hello_time) + elif param_type == parameter_hello_time: + ret_val = validate_params(cfg_forward_delay, cfg_max_age, new_value) + + if ret_val is not True: + ctx.fail("2*(forward_delay-1) >= max_age >= 2*(hello_time +1 ) not met") + + +parameter_forward_delay = 1 +parameter_hello_time = 2 +parameter_max_age = 3 +parameter_bridge_priority = 4 + +def get_max_stp_instances(): + state_db = SonicV2Connector(host='127.0.0.1') + state_db.connect(state_db.STATE_DB, False) + max_inst = state_db.get(state_db.STATE_DB, "STP_TABLE|GLOBAL", "max_stp_inst") + if max_inst != None and max_inst != 0 and max_inst < PVST_MAX_INSTANCES: + return max_inst + else: + return PVST_MAX_INSTANCES + +def update_stp_vlan_parameter(ctx, param_type, new_value): + db = ctx.obj['db'] + stp_global_entry = db.get_entry('STP', "GLOBAL") + + if param_type == parameter_forward_delay: + current_global_value = stp_global_entry.get("forward_delay") + elif param_type == parameter_hello_time: + current_global_value = stp_global_entry.get("hello_time") + elif param_type == parameter_max_age: + current_global_value = stp_global_entry.get("max_age") + elif param_type == parameter_bridge_priority: + current_global_value = stp_global_entry.get("priority") + + vlan_dict = db.get_table('STP_VLAN') + for vlan in vlan_dict.keys(): + vlan_entry = db.get_entry('STP_VLAN', vlan) + if param_type == parameter_forward_delay: + current_vlan_value = vlan_entry.get("forward_delay") + if current_global_value == current_vlan_value: + db.mod_entry('STP_VLAN', vlan, {'forward_delay': new_value}) + elif param_type == parameter_hello_time: + current_vlan_value = vlan_entry.get("hello_time") + if current_global_value == current_vlan_value: + db.mod_entry('STP_VLAN', vlan, {'hello_time': new_value}) + elif param_type == parameter_max_age: + current_vlan_value = vlan_entry.get("max_age") + if current_global_value == current_vlan_value: + db.mod_entry('STP_VLAN', vlan, {'max_age': new_value}) + elif param_type == parameter_bridge_priority: + current_vlan_value = vlan_entry.get("priority") + if current_global_value == current_vlan_value: + db.mod_entry('STP_VLAN', vlan, {'priority': new_value}) + pass + + +def check_if_vlan_exist_in_db(ctx, vid): + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + vlan = db.get_entry('VLAN', vlan_name) + if len(vlan) == 0: + ctx.fail("{} doesn't exist".format(vlan_name)) + pass + + +def enable_stp_for_vlans(ctx): + db = ctx.obj['db'] + vlan_count = 0 + fvs = {'enabled': 'true', + 'forward_delay': get_global_stp_forward_delay(ctx), + 'hello_time': get_global_stp_hello_time(ctx), + 'max_age': get_global_stp_max_age(ctx), + 'priority': get_global_stp_priority(ctx) + } + clients = db.redis_clients["CONFIG_DB"] + pipe = clients.pipeline() + vlan_dict = natsorted(db.get_table('VLAN')) + max_stp_instances = get_max_stp_instances() + for vlan_key in vlan_dict: + if vlan_count >= max_stp_instances: + logging.warning("Exceeded maximum STP configurable VLAN instances for {}".format(vlan_key)) + break + pipe.hmset('STP_VLAN|{}'.format(vlan_key), fvs) + vlan_count += 1 + pipe.execute() + pass + + +def get_stp_enabled_vlan_count(ctx): + db = ctx.obj['db'] + count = 0 + stp_vlan_keys = db.get_table('STP_VLAN').keys() + for key in stp_vlan_keys: + if db.get_entry('STP_VLAN', key).get('enabled') == 'true': + count += 1 + return count + + +def vlan_enable_stp(ctx, vlan_name): + db = ctx.obj['db'] + fvs = {'enabled': 'true', + 'forward_delay': get_global_stp_forward_delay(ctx), + 'hello_time': get_global_stp_hello_time(ctx), + 'max_age': get_global_stp_max_age(ctx), + 'priority': get_global_stp_priority(ctx) + } + if get_stp_enabled_vlan_count(ctx) < get_max_stp_instances(): + db.set_entry('STP_VLAN', vlan_name, fvs) + else: + logging.warning("Exceeded maximum STP configurable VLAN instances for {}".format(vlan_name)) + + +def vlan_disable_stp(ctx, vlan_name): + vlan_set_stp_enable(ctx, vlan_name, 'false') + + +def vlan_set_stp_enable(ctx, vlan_name, state): + db = ctx.obj['db'] + db.set_entry('STP_VLAN', vlan_name, {'enabled': state}) + + +def interface_enable_stp(ctx, interface_name): + db = ctx.obj['db'] + fvs = {'enabled': 'true', + 'root_guard': 'false', + 'bpdu_guard': 'false', + 'bpdu_guard_do_disable': 'false', + 'portfast': 'true', + 'uplink_fast': 'false' + } + db.set_entry('STP_INTF', interface_name, fvs) + + +def interface_disable_stp(ctx, interface_name): + interface_set_stp_enable(ctx, interface_name, 'false') + + +def interface_set_stp_enable(ctx, interface_name, state): + db = ctx.obj['db'] + db.set_entry('STP_INTF', interface_name, {'enabled': state}) + + +def is_vlan_configured_interface(ctx, interface_name): + db = ctx.obj['db'] + intf_to_vlan_list = get_vlan_list_for_interface(db, interface_name) + if intf_to_vlan_list: # if empty + return True + else: + return False + + +def is_interface_vlan_member(ctx, vlan_name, interface_name): + db = ctx.obj['db'] + key = vlan_name + '|' + interface_name + entry = db.get_entry('VLAN_MEMBER', key) + if not entry: # if empty + ctx.fail("{} is not member of {}".format(interface_name, vlan_name)) + +def get_vlan_list_for_interface(db, interface_name): + vlan_intf_info = db.get_table('VLAN_MEMBER') + vlan_list = [] + for line in vlan_intf_info: + if interface_name == line[1]: + vlan_name = line[0] + vlan_list.append(vlan_name) + return vlan_list + + +def get_pc_member_port_list(db): + pc_member_info = db.get_table('PORTCHANNEL_MEMBER') + pc_member_port_list = [] + for line in pc_member_info: + intf_name = line[1] + pc_member_port_list.append(intf_name) + return pc_member_port_list + + +def get_vlan_list_from_stp_vlan_intf_table(db, intf_name): + stp_vlan_intf_info = db.get_table('STP_VLAN_INTF') + vlan_list = [] + for line in stp_vlan_intf_info: + if line[1] == intf_name: + vlan_list.append(line[0]) + return vlan_list + + +def get_intf_list_from_stp_vlan_intf_table(db, vlan_name): + stp_vlan_intf_info = db.get_table('STP_VLAN_INTF') + intf_list = [] + for line in stp_vlan_intf_info: + if line[0] == vlan_name: + intf_list.append(line[1]) + return intf_list + + +def is_portchannel_member_port(ctx, interface_name): + db = ctx.obj['db'] + pc_member_port_list = get_pc_member_port_list(db) + if interface_name in pc_member_port_list: + return True + else: + return False + + +def enable_stp_for_interfaces(ctx): + db = ctx.obj['db'] + fvs = {'enabled': 'true', + 'root_guard': 'false', + 'bpdu_guard': 'false', + 'bpdu_guard_do_disable': 'false', + 'portfast': 'true', + 'uplink_fast': 'false' + } + clients = db.redis_clients["CONFIG_DB"] + pipe = clients.pipeline() + port_dict = natsorted(db.get_table('PORT')) + intf_list_in_vlan_member_table = get_intf_list_in_vlan_member_table(db) + + for port_key in port_dict: + if port_key in intf_list_in_vlan_member_table: + pipe.hmset('STP_INTF|{}'.format(port_key), fvs) + + po_ch_dict = natsorted(db.get_table('PORTCHANNEL')) + for po_ch_key in po_ch_dict: + if po_ch_key in intf_list_in_vlan_member_table: + pipe.hmset('STP_INTF|{}'.format(po_ch_key), fvs) + pipe.execute() + pass + + +def is_global_stp_enabled(ctx): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP', "GLOBAL") + mode = stp_entry.get("mode") + if mode: + return True + else: + return False + + +def check_if_global_stp_enabled(ctx): + if not is_global_stp_enabled(ctx): + ctx.fail("Global STP is not enabled - first configure STP mode") + + +def get_global_stp_mode(ctx): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP', "GLOBAL") + mode = stp_entry.get("mode") + return mode + + +def get_global_stp_root_guard_timeout(ctx): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP', "GLOBAL") + root_guard_timeout = stp_entry.get("root_guard_timeout") + return root_guard_timeout + + +def get_global_stp_forward_delay(ctx): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP', "GLOBAL") + forward_delay = stp_entry.get("forward_delay") + return forward_delay + + +def get_global_stp_hello_time(ctx): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP', "GLOBAL") + hello_time = stp_entry.get("hello_time") + return hello_time + + +def get_global_stp_max_age(ctx): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP', "GLOBAL") + max_age = stp_entry.get("max_age") + return max_age + + +def get_global_stp_priority(ctx): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP', "GLOBAL") + priority = stp_entry.get("priority") + return priority + + +@click.group() +@click.pass_context +def spanning_tree(ctx): + """STP command line""" + config_db = ConfigDBConnector() + config_db.connect() + ctx.obj = {'db': config_db} + pass + + +############################################### +# STP Global commands implementation +############################################### + +# cmd: STP enable +@spanning_tree.command('enable') +@click.argument('mode', metavar='', required=True, type=click.Choice(["pvst"])) +@click.pass_context +def spanning_tree_enable(ctx, mode): + """enable STP """ + if mode == "pvst" and get_global_stp_mode(ctx) == "pvst": + ctx.fail("PVST is already configured") + db = ctx.obj['db'] + fvs = {'mode': mode, + 'rootguard_timeout': STP_DEFAULT_ROOT_GUARD_TIMEOUT, + 'forward_delay': STP_DEFAULT_FORWARD_DELAY, + 'hello_time': STP_DEFAULT_HELLO_INTERVAL, + 'max_age': STP_DEFAULT_MAX_AGE, + 'priority': STP_DEFAULT_BRIDGE_PRIORITY + } + db.set_entry('STP', "GLOBAL", fvs) + # Enable STP for VLAN by default + enable_stp_for_interfaces(ctx) + enable_stp_for_vlans(ctx) + pass + + +# cmd: STP disable +@spanning_tree.command('disable') +@click.argument('mode', metavar='', required=True, type=click.Choice(["pvst"])) +@click.pass_context +def stp_disable(ctx, mode): + """disable STP """ + db = ctx.obj['db'] + db.set_entry('STP', "GLOBAL", None) + # Disable STP for all VLANs and interfaces + db.delete_table('STP_VLAN') + db.delete_table('STP_INTF') + db.delete_table('STP_VLAN_INTF') + pass + + +# cmd: STP global root guard timeout +@spanning_tree.command('root_guard_timeout') +@click.argument('root_guard_timeout', metavar='<5-600 seconds>', required=True, type=int) +@click.pass_context +def stp_global_root_guard_timeout(ctx, root_guard_timeout): + """Configure STP global root guard timeout value""" + check_if_global_stp_enabled(ctx) + is_valid_root_guard_timeout(ctx, root_guard_timeout) + db = ctx.obj['db'] + db.mod_entry('STP', "GLOBAL", {'rootguard_timeout': root_guard_timeout}) + pass + + +# cmd: STP global forward delay +@spanning_tree.command('forward_delay') +@click.argument('forward_delay', metavar='<4-30 seconds>', required=True, type=int) +@click.pass_context +def stp_global_forward_delay(ctx, forward_delay): + """Configure STP global forward delay""" + check_if_global_stp_enabled(ctx) + is_valid_forward_delay(ctx, forward_delay) + is_valid_stp_global_parameters(ctx, parameter_forward_delay, forward_delay) + db = ctx.obj['db'] + update_stp_vlan_parameter(ctx, parameter_forward_delay, forward_delay) + db.mod_entry('STP', "GLOBAL", {'forward_delay': forward_delay}) + pass + + +# cmd: STP global hello interval +@spanning_tree.command('hello') +@click.argument('hello_interval', metavar='<1-10 seconds>', required=True, type=int) +@click.pass_context +def stp_global_hello_interval(ctx, hello_interval): + """Configure STP global hello interval""" + check_if_global_stp_enabled(ctx) + is_valid_hello_interval(ctx, hello_interval) + is_valid_stp_global_parameters(ctx, parameter_hello_time, hello_interval) + db = ctx.obj['db'] + update_stp_vlan_parameter(ctx, parameter_hello_time, hello_interval) + db.mod_entry('STP', "GLOBAL", {'hello_time': hello_interval}) + pass + + +# cmd: STP global max age +@spanning_tree.command('max_age') +@click.argument('max_age', metavar='<6-40 seconds>', required=True, type=int) +@click.pass_context +def stp_global_max_age(ctx, max_age): + """Configure STP global max_age""" + check_if_global_stp_enabled(ctx) + is_valid_max_age(ctx, max_age) + is_valid_stp_global_parameters(ctx, parameter_max_age, max_age) + db = ctx.obj['db'] + update_stp_vlan_parameter(ctx, parameter_max_age, max_age) + db.mod_entry('STP', "GLOBAL", {'max_age': max_age}) + pass + + +# cmd: STP global bridge priority +@spanning_tree.command('priority') +@click.argument('priority', metavar='<0-61440>', required=True, type=int) +@click.pass_context +def stp_global_priority(ctx, priority): + """Configure STP global bridge priority""" + check_if_global_stp_enabled(ctx) + is_valid_bridge_priority(ctx, priority) + db = ctx.obj['db'] + update_stp_vlan_parameter(ctx, parameter_bridge_priority, priority) + db.mod_entry('STP', "GLOBAL", {'priority': priority}) + pass + + +############################################### +# STP VLAN commands implementation +############################################### +@spanning_tree.group('vlan') +@click.pass_context +def spanning_tree_vlan(ctx): + """Configure STP for a VLAN""" + pass + + +def is_stp_enabled_for_vlan(ctx, vlan_name): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP_VLAN', vlan_name) + stp_enabled = stp_entry.get("enabled") + if stp_enabled == "true": + return True + else: + return False + + +def check_if_stp_enabled_for_vlan(ctx, vlan_name): + if not is_stp_enabled_for_vlan(ctx, vlan_name): + ctx.fail("STP is not enabled for VLAN") + + +@spanning_tree_vlan.command('enable') +@click.argument('vid', metavar='', required=True, type=int) +@click.pass_context +def stp_vlan_enable(ctx, vid): + """Enable STP for a VLAN""" + check_if_vlan_exist_in_db(ctx, vid) + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + if is_stp_enabled_for_vlan(ctx, vlan_name): + ctx.fail("STP is already enabled for " + vlan_name) + if get_stp_enabled_vlan_count(ctx) >= get_max_stp_instances(): + ctx.fail("Exceeded maximum STP configurable VLAN instances") + check_if_global_stp_enabled(ctx) + # when enabled for first time, create VLAN entry with + # global values - else update only VLAN STP state + stp_vlan_entry = db.get_entry('STP_VLAN', vlan_name) + if len(stp_vlan_entry) == 0: + fvs = {'enabled': 'true', + 'forward_delay': get_global_stp_forward_delay(ctx), + 'hello_time': get_global_stp_hello_time(ctx), + 'max_age': get_global_stp_max_age(ctx), + 'priority': get_global_stp_priority(ctx) + } + db.set_entry('STP_VLAN', vlan_name, fvs) + else: + db.mod_entry('STP_VLAN', vlan_name, {'enabled': 'true'}) + # Refresh stp_vlan_intf entry for vlan + for vlan, intf in db.get_table('STP_VLAN_INTF'): + if vlan == vlan_name: + vlan_intf_key = "{}|{}".format(vlan_name, intf) + vlan_intf_entry = db.get_entry('STP_VLAN_INTF', vlan_intf_key) + db.mod_entry('STP_VLAN_INTF', vlan_intf_key, vlan_intf_entry) + pass + + +@spanning_tree_vlan.command('disable') +@click.argument('vid', metavar='', required=True, type=int) +@click.pass_context +def stp_vlan_disable(ctx, vid): + """Disable STP for a VLAN""" + check_if_vlan_exist_in_db(ctx, vid) + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + db.mod_entry('STP_VLAN', vlan_name, {'enabled': 'false'}) + pass + + +@spanning_tree_vlan.command('forward_delay') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('forward_delay', metavar='<4-30 seconds>', required=True, type=int) +@click.pass_context +def stp_vlan_forward_delay(ctx, vid, forward_delay): + """Configure STP forward delay for VLAN""" + check_if_vlan_exist_in_db(ctx, vid) + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + check_if_stp_enabled_for_vlan(ctx, vlan_name) + is_valid_forward_delay(ctx, forward_delay) + is_valid_stp_vlan_parameters(ctx, vlan_name, parameter_forward_delay, forward_delay) + db.mod_entry('STP_VLAN', vlan_name, {'forward_delay': forward_delay}) + pass + + +@spanning_tree_vlan.command('hello') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('hello_interval', metavar='<1-10 seconds>', required=True, type=int) +@click.pass_context +def stp_vlan_hello_interval(ctx, vid, hello_interval): + """Configure STP hello interval for VLAN""" + check_if_vlan_exist_in_db(ctx, vid) + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + check_if_stp_enabled_for_vlan(ctx, vlan_name) + is_valid_hello_interval(ctx, hello_interval) + is_valid_stp_vlan_parameters(ctx, vlan_name, parameter_hello_time, hello_interval) + db.mod_entry('STP_VLAN', vlan_name, {'hello_time': hello_interval}) + pass + + +@spanning_tree_vlan.command('max_age') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('max_age', metavar='<6-40 seconds>', required=True, type=int) +@click.pass_context +def stp_vlan_max_age(ctx, vid, max_age): + """Configure STP max age for VLAN""" + check_if_vlan_exist_in_db(ctx, vid) + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + check_if_stp_enabled_for_vlan(ctx, vlan_name) + is_valid_max_age(ctx, max_age) + is_valid_stp_vlan_parameters(ctx, vlan_name, parameter_max_age, max_age) + db.mod_entry('STP_VLAN', vlan_name, {'max_age': max_age}) + pass + + +@spanning_tree_vlan.command('priority') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('priority', metavar='<0-61440>', required=True, type=int) +@click.pass_context +def stp_vlan_priority(ctx, vid, priority): + """Configure STP bridge priority for VLAN""" + check_if_vlan_exist_in_db(ctx, vid) + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + check_if_stp_enabled_for_vlan(ctx, vlan_name) + is_valid_bridge_priority(ctx, priority) + db.mod_entry('STP_VLAN', vlan_name, {'priority': priority}) + pass + + +############################################### +# STP interface commands implementation +############################################### + + +def is_stp_enabled_for_interface(ctx, intf_name): + db = ctx.obj['db'] + stp_entry = db.get_entry('STP_INTF', intf_name) + stp_enabled = stp_entry.get("enabled") + if stp_enabled == "true": + return True + else: + return False + + +def check_if_stp_enabled_for_interface(ctx, intf_name): + if not is_stp_enabled_for_interface(ctx, intf_name): + ctx.fail("STP is not enabled for interface {}".format(intf_name)) + + +def check_if_interface_is_valid(ctx, interface_name): + from main import interface_name_is_valid + db = ctx.obj['db'] + if interface_name_is_valid(db, interface_name) is False: + ctx.fail("Interface name is invalid. Please enter a valid interface name!!") + for k, v in db.get_table('INTERFACE').iteritems(): + if k == interface_name: + ctx.fail(" {} has ip address {} configured - It's not a L2 interface".format(interface_name, v)) + if is_portchannel_member_port(ctx, interface_name): + ctx.fail(" {} is a portchannel member port - STP can't be configured".format(interface_name)) + if not is_vlan_configured_interface(ctx, interface_name): + ctx.fail(" {} has no VLAN configured - It's not a L2 interface".format(interface_name)) + pass + + +@spanning_tree.group('interface') +@click.pass_context +def spanning_tree_interface(ctx): + """Configure STP for interface""" + pass + + +@spanning_tree_interface.command('enable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_enable(ctx, interface_name): + """Enable STP for interface""" + check_if_global_stp_enabled(ctx) + if is_stp_enabled_for_interface(ctx, interface_name): + ctx.fail("STP is already enabled for " + interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + stp_intf_entry = db.get_entry('STP_INTF', interface_name) + if len(stp_intf_entry) == 0: + fvs = {'enabled': 'true', + 'root_guard': 'false', + 'bpdu_guard': 'false', + 'bpdu_guard_do_disable': 'false', + 'portfast': 'true', + 'uplink_fast': 'false' + } + db.set_entry('STP_INTF', interface_name, fvs) + else: + db.mod_entry('STP_INTF', interface_name, {'enabled': 'true'}) + + +@spanning_tree_interface.command('disable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_disable(ctx, interface_name): + """Disable STP for interface""" + check_if_global_stp_enabled(ctx) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + db.mod_entry('STP_INTF', interface_name, {'enabled': 'false'}) + + +# STP interface port priority +STP_INTERFACE_MIN_PRIORITY = 0 +STP_INTERFACE_MAX_PRIORITY = 240 +STP_INTERFACE_DEFAULT_PRIORITY = 128 + + +def is_valid_interface_priority(ctx, intf_priority): + if intf_priority not in range(STP_INTERFACE_MIN_PRIORITY, STP_INTERFACE_MAX_PRIORITY + 1): + ctx.fail("STP interface priority must be in range 0-240") + + +@spanning_tree_interface.command('priority') +@click.argument('interface_name', metavar='', required=True) +@click.argument('priority', metavar='<0-240>', required=True, type=int) +@click.pass_context +def stp_interface_priority(ctx, interface_name, priority): + """Configure STP port priority for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + is_valid_interface_priority(ctx, priority) + db = ctx.obj['db'] + curr_intf_proirty = db.get_entry('STP_INTF', interface_name).get('priority') + db.mod_entry('STP_INTF', interface_name, {'priority': priority}) + # update interface priority in all stp_vlan_intf entries if entry exists + for vlan, intf in db.get_table('STP_VLAN_INTF'): + if intf == interface_name: + vlan_intf_key = "{}|{}".format(vlan, interface_name) + vlan_intf_entry = db.get_entry('STP_VLAN_INTF', vlan_intf_key) + if len(vlan_intf_entry) != 0: + vlan_intf_priority = vlan_intf_entry.get('priority') + if curr_intf_proirty == vlan_intf_priority: + db.mod_entry('STP_VLAN_INTF', vlan_intf_key, {'priority': priority}) + # end + + +# STP interface port path cost +STP_INTERFACE_MIN_PATH_COST = 1 +STP_INTERFACE_MAX_PATH_COST = 200000000 + + +def is_valid_interface_path_cost(ctx, intf_path_cost): + if intf_path_cost < STP_INTERFACE_MIN_PATH_COST or intf_path_cost > STP_INTERFACE_MAX_PATH_COST: + ctx.fail("STP interface path cost must be in range 1-200000000") + + +@spanning_tree_interface.command('cost') +@click.argument('interface_name', metavar='', required=True) +@click.argument('cost', metavar='<1-200000000>', required=True, type=int) +@click.pass_context +def stp_interface_path_cost(ctx, interface_name, cost): + """Configure STP path cost for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + is_valid_interface_path_cost(ctx, cost) + db = ctx.obj['db'] + curr_intf_cost = db.get_entry('STP_INTF', interface_name).get('path_cost') + db.mod_entry('STP_INTF', interface_name, {'path_cost': cost}) + # update interface path_cost in all stp_vlan_intf entries if entry exists + for vlan, intf in db.get_table('STP_VLAN_INTF'): + if intf == interface_name: + vlan_intf_key = "{}|{}".format(vlan, interface_name) + vlan_intf_entry = db.get_entry('STP_VLAN_INTF', vlan_intf_key) + if len(vlan_intf_entry) != 0: + vlan_intf_cost = vlan_intf_entry.get('path_cost') + if curr_intf_cost == vlan_intf_cost: + db.mod_entry('STP_VLAN_INTF', vlan_intf_key, {'path_cost': cost}) + # end + + +# STP interface root guard +@spanning_tree_interface.group('root_guard') +@click.pass_context +def spanning_tree_interface_root_guard(ctx): + """Configure STP root guard for interface""" + pass + + +@spanning_tree_interface_root_guard.command('enable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_root_guard_enable(ctx, interface_name): + """Enable STP root guard for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + db.mod_entry('STP_INTF', interface_name, {'root_guard': 'true'}) + + +@spanning_tree_interface_root_guard.command('disable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_root_guard_disable(ctx, interface_name): + """Disable STP root guard for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + db.mod_entry('STP_INTF', interface_name, {'root_guard': 'false'}) + + +# STP interface bpdu guard +@spanning_tree_interface.group('bpdu_guard') +@click.pass_context +def spanning_tree_interface_bpdu_guard(ctx): + """Configure STP bpdu guard for interface""" + pass + + +@spanning_tree_interface_bpdu_guard.command('enable') +@click.argument('interface_name', metavar='', required=True) +@click.option('-s', '--shutdown', is_flag=True) +@click.pass_context +def stp_interface_bpdu_guard_enable(ctx, interface_name, shutdown): + """Enable STP bpdu guard for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + if shutdown is True: + bpdu_guard_do_disable = 'true' + else: + bpdu_guard_do_disable = 'false' + fvs = {'bpdu_guard': 'true', + 'bpdu_guard_do_disable': bpdu_guard_do_disable + } + db.mod_entry('STP_INTF', interface_name, fvs) + + +@spanning_tree_interface_bpdu_guard.command('disable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_bpdu_guard_disable(ctx, interface_name): + """Disable STP bpdu guard for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + db.mod_entry('STP_INTF', interface_name, {'bpdu_guard': 'false'}) + + +# STP interface portfast +@spanning_tree_interface.group('portfast') +@click.pass_context +def spanning_tree_interface_portfast(ctx): + """Configure STP portfast for interface""" + pass + + +@spanning_tree_interface_portfast.command('enable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_portfast_enable(ctx, interface_name): + """Enable STP portfast for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + db.mod_entry('STP_INTF', interface_name, {'portfast': 'true'}) + + +@spanning_tree_interface_portfast.command('disable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_portfast_disable(ctx, interface_name): + """Disable STP portfast for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + db.mod_entry('STP_INTF', interface_name, {'portfast': 'false'}) + + +# STP interface root uplink_fast +@spanning_tree_interface.group('uplink_fast') +@click.pass_context +def spanning_tree_interface_uplink_fast(ctx): + """Configure STP uplink fast for interface""" + pass + + +@spanning_tree_interface_uplink_fast.command('enable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_uplink_fast_enable(ctx, interface_name): + """Enable STP uplink fast for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + db.mod_entry('STP_INTF', interface_name, {'uplink_fast': 'true'}) + + +@spanning_tree_interface_uplink_fast.command('disable') +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def stp_interface_uplink_fast_disable(ctx, interface_name): + """Disable STP uplink fast for interface""" + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_interface_is_valid(ctx, interface_name) + db = ctx.obj['db'] + db.mod_entry('STP_INTF', interface_name, {'uplink_fast': 'false'}) + + +############################################### +# STP interface per VLAN commands implementation +############################################### +@spanning_tree_vlan.group('interface') +@click.pass_context +def spanning_tree_vlan_interface(ctx): + """Configure STP parameters for interface per VLAN""" + pass + + +# STP interface per vlan port priority +def is_valid_vlan_interface_priority(ctx, priority): + if priority not in range(STP_INTERFACE_MIN_PRIORITY, STP_INTERFACE_MAX_PRIORITY + 1): + ctx.fail("STP per vlan port priority must be in range 0-240") + + +@spanning_tree_vlan_interface.command('priority') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('interface_name', metavar='', required=True) +@click.argument('priority', metavar='<0-240>', required=True, type=int) +@click.pass_context +def stp_vlan_interface_priority(ctx, vid, interface_name, priority): + """Configure STP per vlan port priority for interface""" + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + check_if_stp_enabled_for_vlan(ctx, vlan_name) + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_vlan_exist_in_db(ctx, vid) + is_interface_vlan_member(ctx, vlan_name, interface_name) + is_valid_vlan_interface_priority(ctx, priority) + vlan_interface = str(vlan_name) + "|" + interface_name + db.mod_entry('STP_VLAN_INTF', vlan_interface, {'priority': priority}) + + +@spanning_tree_vlan_interface.command('cost') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('interface_name', metavar='', required=True) +@click.argument('cost', metavar='<1-200000000>', required=True, type=int) +@click.pass_context +def stp_vlan_interface_cost(ctx, vid, interface_name, cost): + """Configure STP per vlan path cost for interface""" + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + check_if_stp_enabled_for_vlan(ctx, vlan_name) + check_if_stp_enabled_for_interface(ctx, interface_name) + check_if_vlan_exist_in_db(ctx, vid) + is_interface_vlan_member(ctx, vlan_name, interface_name) + is_valid_interface_path_cost(ctx, cost) + vlan_interface = str(vlan_name) + "|" + interface_name + db.mod_entry('STP_VLAN_INTF', vlan_interface, {'path_cost': cost}) + + +# Invoke main() +if __name__ == '__main__': + spanning_tree() diff --git a/debug/main.py b/debug/main.py index 8c502c96ad..86f51f123f 100755 --- a/debug/main.py +++ b/debug/main.py @@ -21,6 +21,12 @@ def cli(): """SONiC command line - 'debug' command""" pass +# +# STP +# +from .stp import spanning_tree +cli.add_command(spanning_tree) + p = subprocess.check_output(["sudo vtysh -c 'show version'"], shell=True, text=True) if 'FRRouting' in p: diff --git a/debug/stp.py b/debug/stp.py new file mode 100644 index 0000000000..431ff1494d --- /dev/null +++ b/debug/stp.py @@ -0,0 +1,117 @@ +import click +from debug.main import AliasedGroup,cli +from debug.main import run_command + +# +# This group houses Spanning_tree commands and subgroups +# +@cli.group(cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@click.pass_context +def spanning_tree(ctx): + '''debug spanning_tree commands''' + if ctx.invoked_subcommand is None: + command = 'sudo stpctl dbg enable' + run_command(command) + pass + +@spanning_tree.group('dump', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +def stp_debug_dump(): + pass + +@stp_debug_dump.command('global') +def stp_debug_dump_global(): + command = 'sudo stpctl global' + run_command(command) + pass + +@stp_debug_dump.command('vlan') +@click.argument('vlan_id', metavar='', required=True) +def stp_debug_dump_vlan(vlan_id): + command = 'sudo stpctl vlan ' + vlan_id + run_command(command) + pass + +@stp_debug_dump.command('interface') +@click.argument('vlan_id', metavar='', required=True) +@click.argument('interface_name', metavar='', required=True) +def stp_debug_dump_vlan_intf(vlan_id, interface_name): + command = 'sudo stpctl port ' + vlan_id + " " + interface_name + run_command(command) + pass + +@spanning_tree.command('show') +def stp_debug_show(): + command = 'sudo stpctl dbg show' + run_command(command) + pass + +@spanning_tree.command('reset') +def stp_debug_reset(): + command = 'sudo stpctl dbg disable' + run_command(command) + pass + +@spanning_tree.command('bpdu') +@click.argument('mode', metavar='{rx|tx}', required=False) +@click.option('-d', '--disable', is_flag=True) +def stp_debug_bpdu(mode, disable): + if disable: + if mode == 'rx': + command = 'sudo stpctl dbg bpdu rx-off' + elif mode == 'tx': + command = 'sudo stpctl dbg bpdu tx-off' + else: + command = 'sudo stpctl dbg bpdu off' + else: + if mode == 'rx': + command = 'sudo stpctl dbg bpdu rx-on' + elif mode == 'tx': + command = 'sudo stpctl dbg bpdu tx-on' + else: + command = 'sudo stpctl dbg bpdu on' + run_command(command) + pass + +@spanning_tree.command('verbose') +@click.option('-d', '--disable', is_flag=True) +def stp_debug_verbose(disable): + if disable: + command = 'sudo stpctl dbg verbose off' + else: + command = 'sudo stpctl dbg verbose on' + run_command(command) + pass + +@spanning_tree.command('event') +@click.option('-d', '--disable', is_flag=True) +def stp_debug_event(disable): + if disable: + command = 'sudo stpctl dbg event off' + else: + command = 'sudo stpctl dbg event on' + run_command(command) + pass + +@spanning_tree.command('vlan') +@click.argument('vlan_id', metavar='', required=True) +@click.option('-d', '--disable', is_flag=True) +def stp_debug_vlan(vlan_id, disable): + if disable: + command = 'sudo stpctl dbg vlan ' + vlan_id + ' off' + else: + command = 'sudo stpctl dbg vlan ' + vlan_id + ' on' + run_command(command) + pass + +@spanning_tree.command('interface') +@click.argument('interface_name', metavar='', required=True) +@click.option('-d', '--disable', is_flag=True) +def stp_debug_intf(interface_name, disable): + if disable: + command = 'sudo stpctl dbg port ' + interface_name + ' off' + else: + command = 'sudo stpctl dbg port ' + interface_name + ' on' + run_command(command) + pass + + diff --git a/scripts/fast-reboot b/scripts/fast-reboot index c782265e6b..1cd0367366 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -597,6 +597,11 @@ debug "Stopped sflow ..." container kill lldp &> /dev/null || debug "Docker lldp is not running ($?) ..." systemctl stop lldp +# Kill stp docker +debug "Stopping stp ..." +docker kill stp > /dev/null +debug "Stopped stp ..." + if [[ "$REBOOT_TYPE" = "fast-reboot" ]]; then debug "Stopping teamd ..." systemctl stop teamd diff --git a/scripts/generate_dump b/scripts/generate_dump index 21d4a4e0c8..a7d33e48fb 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -1103,6 +1103,12 @@ main() { save_cmd "docker logs swss" "docker.swss.log" fi + save_cmd "stpctl all" "stp.log" + save_cmd "show spanning_tree" "stp.show" + save_cmd "show spanning_tree statistics" "stp.stats" + save_cmd "show spanning_tree bpdu_guard" "stp.bg" + save_cmd "show spanning_tree root_guard" "stp.rg" + save_cmd "ps aux" "ps.aux" save_cmd "top -b -n 1" "top" save_cmd "free" "free" diff --git a/show/main.py b/show/main.py index 16f5d8cec7..2dc9f73181 100755 --- a/show/main.py +++ b/show/main.py @@ -177,6 +177,9 @@ def cli(ctx): if is_gearbox_configured(): cli.add_command(gearbox.gearbox) +from .stp import spanning_tree +cli.add_command(spanning_tree) + # # 'vrf' command ("show vrf") @@ -1151,6 +1154,22 @@ def syslog(verbose): print(tabulate(syslog_dict, headers=list(syslog_dict.keys()), tablefmt="simple", stralign='left', missingval="")) +# 'spanning-tree' subcommand ("show runningconfiguration spanning_tree") +@runningconfiguration.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def spanning_tree(verbose): + """Show spanning_tree running configuration""" + config_db = ConfigDBConnector(host="127.0.0.1") + config_db.connect() + + stp_list = ["STP", "STP_INTF", "STP_VLAN", "STP_VLAN_INTF"] + for key in stp_list: + table = config_db.get_table(key) + if len(table): + print "\"" + key + "\":" + cmd = 'sudo sonic-cfggen -d --var-json ' + key + run_command(cmd, display_cmd=verbose) + # # 'startupconfiguration' group ("show startupconfiguration ...") # diff --git a/show/stp.py b/show/stp.py new file mode 100644 index 0000000000..7002da0324 --- /dev/null +++ b/show/stp.py @@ -0,0 +1,420 @@ +import time +import re +import sys +import click +import subprocess +from show.main import AliasedGroup,cli +from swsssdk import SonicV2Connector +from swsssdk import ConfigDBConnector + + +############################################################################## +# 'spanning_tree' group ("show spanning_tree ...") +############################################################################### +# STP show commands:- +# show spanning_tree +# show spanning_tree vlan +# show spanning_tree vlan interface +# show spanning_tree bpdu_guard +# show spanning_tree statistics +# show spanning_tree statistics vlan +# +############################################################################### +g_stp_vlanid = 0 + +# +# Utility API's +# +def is_stp_docker_running(): + running_docker = subprocess.check_output('docker ps', shell=True) + if running_docker.find('docker-stp') == -1: + return False + else: + return True + +def connect_to_cfg_db(): + config_db = ConfigDBConnector(host="127.0.0.1") + config_db.connect() + return config_db + +def connect_to_appl_db(): + appl_db = SonicV2Connector(host="127.0.0.1") + appl_db.connect(appl_db.APPL_DB) + return appl_db + +# Redis DB only supports limiter pattern search wildcards. +# check https://redis.io/commands/KEYS before using this api +# Redis-db uses glob-style patterns not regex +def stp_get_key_from_pattern(db_connect, db, pattern): + keys = db_connect.keys(db, pattern) + if keys: + return keys[0] + else: + return None + +#get_all doesnt accept regex patterns, it requires exact key +def stp_get_all_from_pattern(db_connect, db, pattern): + key = stp_get_key_from_pattern(db_connect, db, pattern) + if key: + entry = db_connect.get_all(db, key) + return entry + + +def stp_is_port_fast_enabled(ifname): + app_db_entry = stp_get_all_from_pattern(g_stp_appl_db, g_stp_appl_db.APPL_DB, "*STP_INTF_TABLE:{}".format(ifname)) + if (\ + not app_db_entry or\ + not ('port_fast' in app_db_entry) or\ + app_db_entry['port_fast'] == 'no'): + return False + return True + + +def stp_is_uplink_fast_enabled(ifname): + entry = g_stp_cfg_db.get_entry("STP_INTF", ifname) + if (\ + entry and \ + ('uplink_fast' in entry) and \ + entry['uplink_fast'] == 'true'): + return True + return False + + +def stp_get_entry_from_vlan_tb(db, vlanid): + entry = stp_get_all_from_pattern(db, db.APPL_DB, "*STP_VLAN_TABLE:Vlan{}".format(vlanid)) + if not entry: + return entry + + if 'bridge_id' not in entry: + entry['bridge_id'] = 'NA' + if 'max_age' not in entry: + entry['max_age'] = '0' + if 'hello_time' not in entry: + entry['hello_time'] = '0' + if 'forward_delay' not in entry: + entry['forward_delay'] = '0' + if 'hold_time' not in entry: + entry['hold_time'] = '0' + if 'last_topology_change' not in entry: + entry['last_topology_change'] = '0' + if 'topology_change_count' not in entry: + entry['topology_change_count'] = '0' + if 'root_bridge_id' not in entry: + entry['root_bridge_id'] = 'NA' + if 'root_path_cost' not in entry: + entry['root_path_cost'] = '0' + if 'desig_bridge_id' not in entry: + entry['desig_bridge_id'] = 'NA' + if 'root_port' not in entry: + entry['root_port'] = 'NA' + if 'root_max_age' not in entry: + entry['root_max_age'] = '0' + if 'root_hello_time' not in entry: + entry['root_hello_time'] = '0' + if 'root_forward_delay' not in entry: + entry['root_forward_delay'] = '0' + if 'stp_instance' not in entry: + entry['stp_instance'] = '65535' + + return entry + + +def stp_get_entry_from_vlan_intf_tb(db, vlanid, ifname): + entry = stp_get_all_from_pattern(db, db.APPL_DB, "*STP_VLAN_INTF_TABLE:Vlan{}:{}".format(vlanid,ifname)) + if not entry: + return entry + + if 'port_num' not in entry: + entry['port_num'] = 'NA' + if 'priority' not in entry: + entry['priority'] = '0' + if 'path_cost' not in entry: + entry['path_cost'] = '0' + if 'root_guard' not in entry: + entry['root_guard'] = 'NA' + if 'bpdu_guard' not in entry: + entry['bpdu_guard'] = 'NA' + if 'port_state' not in entry: + entry['port_state'] = 'NA' + if 'desig_cost' not in entry: + entry['desig_cost'] = '0' + if 'desig_root' not in entry: + entry['desig_root'] = 'NA' + if 'desig_bridge' not in entry: + entry['desig_bridge'] = 'NA' + + return entry + +# +# This group houses Spanning_tree commands and subgroups +# +@cli.group(cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@click.pass_context +def spanning_tree(ctx): + """Show spanning_tree commands""" + global g_stp_appl_db + global g_stp_cfg_db + + if not is_stp_docker_running(): + ctx.fail("STP docker is not running") + + g_stp_appl_db = connect_to_appl_db() + g_stp_cfg_db = connect_to_cfg_db() + + global_cfg = g_stp_cfg_db.get_entry("STP", "GLOBAL") + if not global_cfg: + click.echo("Spanning-tree is not configured") + return + + global g_stp_mode + if 'pvst' == global_cfg['mode']: + g_stp_mode = 'PVST' + + if ctx.invoked_subcommand is None: + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_TABLE:Vlan*") + if not keys: + return + + vlan_list=[] + for key in keys: + result = re.search('.STP_VLAN_TABLE:Vlan(.*)',key) + vlanid = result.group(1) + vlan_list.append(int(vlanid)) + + vlan_list.sort() + for vlanid in vlan_list: + ctx.invoke(show_stp_vlan, vlanid=vlanid) + + pass + + +@spanning_tree.group('vlan', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@click.argument('vlanid', metavar='', required=True, type=int) +@click.pass_context +def show_stp_vlan(ctx, vlanid): + """Show spanning_tree vlan information""" + global g_stp_vlanid + g_stp_vlanid = vlanid + + vlan_tb_entry = stp_get_entry_from_vlan_tb(g_stp_appl_db, g_stp_vlanid) + if not vlan_tb_entry: + return + + global g_stp_mode + if g_stp_mode: + click.echo("Spanning-tree Mode: {}".format(g_stp_mode)) + #reset so we dont print again + g_stp_mode = '' + + click.echo("") + click.echo("VLAN {} - STP instance {}".format(g_stp_vlanid, vlan_tb_entry['stp_instance'])) + click.echo("--------------------------------------------------------------------") + click.echo("STP Bridge Parameters:") + + click.echo("{:17}{:7}{:7}{:7}{:6}{:13}{:8}".format("Bridge" ,"Bridge","Bridge","Bridge","Hold","LastTopology","Topology")) + click.echo("{:17}{:7}{:7}{:7}{:6}{:13}{:8}".format("Identifier","MaxAge","Hello" ,"FwdDly","Time","Change" ,"Change")) + click.echo("{:17}{:7}{:7}{:7}{:6}{:13}{:8}".format("hex" ,"sec" ,"sec" ,"sec" ,"sec" ,"sec" ,"cnt")) + click.echo("{:17}{:7}{:7}{:7}{:6}{:13}{:8}".format( + vlan_tb_entry['bridge_id'], + vlan_tb_entry['max_age'], + vlan_tb_entry['hello_time'], + vlan_tb_entry['forward_delay'], + vlan_tb_entry['hold_time'], + vlan_tb_entry['last_topology_change'], + vlan_tb_entry['topology_change_count'])) + + click.echo("") + click.echo("{:17}{:10}{:18}{:19}{:4}{:4}{:4}".format("RootBridge","RootPath","DesignatedBridge","RootPort","Max","Hel","Fwd")) + click.echo("{:17}{:10}{:18}{:19}{:4}{:4}{:4}".format("Identifier","Cost" ,"Identifier" ,"" ,"Age","lo","Dly")) + click.echo("{:17}{:10}{:18}{:19}{:4}{:4}{:4}".format("hex" ,"" ,"hex" ,"" ,"sec","sec","sec")) + click.echo("{:17}{:10}{:18}{:19}{:4}{:4}{:4}".format( + vlan_tb_entry['root_bridge_id'], + vlan_tb_entry['root_path_cost'], + vlan_tb_entry['desig_bridge_id'], + vlan_tb_entry['root_port'], + vlan_tb_entry['root_max_age'], + vlan_tb_entry['root_hello_time'], + vlan_tb_entry['root_forward_delay'])) + + click.echo("") + click.echo("STP Port Parameters:") + click.echo("{:17}{:5}{:10}{:5}{:7}{:14}{:12}{:17}{:17}".format("Port","Prio","Path","Port","Uplink" ,"State","Designated","Designated","Designated")) + click.echo("{:17}{:5}{:10}{:5}{:7}{:14}{:12}{:17}{:17}".format("Name","rity","Cost","Fast","Fast" ,"" ,"Cost" ,"Root" ,"Bridge")) + + if ctx.invoked_subcommand is None: + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_INTF_TABLE:Vlan{}:*".format(vlanid)) + if not keys: + return + + intf_list = [] + for key in keys: + result = re.search('.STP_VLAN_INTF_TABLE:Vlan{}:(.*)'.format(vlanid),key) + ifname = result.group(1) + intf_list.append(ifname) + + eth_list = [int(ifname[len("Ethernet"):]) for ifname in intf_list if ifname.startswith("Ethernet")] + po_list = [int(ifname[len("PortChannel"):]) for ifname in intf_list if ifname.startswith("PortChannel")] + + eth_list.sort() + po_list.sort() + for port_num in eth_list: + ctx.invoke(show_stp_interface, ifname="Ethernet"+str(port_num)) + for port_num in po_list: + ctx.invoke(show_stp_interface, ifname="PortChannel"+str(port_num)) + + +@show_stp_vlan.command('interface') +@click.argument('ifname', metavar='', required=True) +@click.pass_context +def show_stp_interface(ctx, ifname): + """Show spanning_tree vlan interface information""" + + vlan_intf_tb_entry = stp_get_entry_from_vlan_intf_tb(g_stp_appl_db, g_stp_vlanid, ifname) + if not vlan_intf_tb_entry: + return + + click.echo("{:17}{:5}{:10}{:5}{:7}{:14}{:12}{:17}{:17}".format( + ifname, + vlan_intf_tb_entry['priority'], + vlan_intf_tb_entry['path_cost'], + 'Y' if (stp_is_port_fast_enabled(ifname)) else 'N', + 'Y' if (stp_is_uplink_fast_enabled(ifname)) else 'N', + vlan_intf_tb_entry['port_state'], + vlan_intf_tb_entry['desig_cost'], + vlan_intf_tb_entry['desig_root'], + vlan_intf_tb_entry['desig_bridge'] + )) + + + pass + + +@spanning_tree.command('bpdu_guard') +@click.pass_context +def show_stp_bpdu_guard(ctx): + """Show spanning_tree bpdu_guard""" + + print_header = 1 + ifname_all = g_stp_cfg_db.get_keys("STP_INTF") + for ifname in ifname_all: + cfg_entry = g_stp_cfg_db.get_entry("STP_INTF", ifname) + if cfg_entry['bpdu_guard'] == 'true' and cfg_entry['enabled'] == 'true': + if print_header: + click.echo("{:17}{:13}{}".format("PortNum","Shutdown","Port Shut")) + click.echo("{:17}{:13}{}".format("","Configured","due to BPDU guard")) + click.echo("-------------------------------------------") + print_header=0 + + if cfg_entry['bpdu_guard_do_disable'] == 'true': + disabled = 'No' + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_INTF_TABLE:{}".format(ifname)) + #only 1 key per ifname is expected in BPDU_GUARD_TABLE. + if keys: + appdb_entry = g_stp_appl_db.get_all(g_stp_appl_db.APPL_DB, keys[0]) + if appdb_entry and 'bpdu_guard_shutdown' in appdb_entry: + if appdb_entry['bpdu_guard_shutdown'] == 'yes': + disabled = 'Yes' + click.echo("{:17}{:13}{}".format(ifname,"Yes",disabled)) + else: + click.echo("{:17}{:13}{}".format(ifname,"No","NA")) + + pass + + +@spanning_tree.command('root_guard') +@click.pass_context +def show_stp_root_guard(ctx): + """Show spanning_tree root_guard""" + + print_header = 1 + ifname_all = g_stp_cfg_db.get_keys("STP_INTF") + for ifname in ifname_all: + entry = g_stp_cfg_db.get_entry("STP_INTF", ifname) + if entry['root_guard'] == 'true' and entry['enabled'] == 'true': + if print_header: + global_entry = g_stp_cfg_db.get_entry("STP", "GLOBAL") + click.echo("Root guard timeout: {} secs".format(global_entry['rootguard_timeout'])) + click.echo("") + click.echo("{:17}{:7}{}".format("Port","VLAN","Current State")) + click.echo("-------------------------------------------") + print_header=0 + + state = '' + vlanid = '' + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_INTF_TABLE:*:{}".format(ifname)) + if keys: + for key in keys: + entry = g_stp_appl_db.get_all(g_stp_appl_db.APPL_DB, key) + if entry and 'root_guard_timer' in entry: + if entry['root_guard_timer'] == '0': + state = 'Consistent state' + else: + state = 'Inconsistent state ({} seconds left on timer)'.format(entry['root_guard_timer']) + + vlanid = re.search(':Vlan(.*):', key) + if vlanid: + click.echo("{:17}{:7}{}".format(ifname,vlanid.group(1),state)) + else: + click.echo("{:17}{:7}{}".format(ifname,vlanid,state)) + + + + pass + + +@spanning_tree.group('statistics', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@click.pass_context +def show_stp_statistics(ctx): + """Show spanning_tree statistics""" + + if ctx.invoked_subcommand is None: + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_TABLE:Vlan*") + if not keys: + return + + vlan_list = [] + for key in keys: + result = re.search('.STP_VLAN_TABLE:Vlan(.*)',key) + vlanid = result.group(1) + vlan_list.append(int(vlanid)) + + vlan_list.sort() + for vlanid in vlan_list: + ctx.invoke(show_stp_vlan_statistics, vlanid=vlanid) + pass + + +@show_stp_statistics.command('vlan') +@click.argument('vlanid', metavar='', required=True, type=int) +@click.pass_context +def show_stp_vlan_statistics(ctx, vlanid): + """Show spanning_tree statistics vlan""" + + stp_inst_entry = stp_get_all_from_pattern(g_stp_appl_db, g_stp_appl_db.APPL_DB, "*STP_VLAN_INSTANCE_TABLE:Vlan{}".format(vlanid)) + if not stp_inst_entry: + return + + click.echo("VLAN {} - STP instance {}".format(vlanid, stp_inst_entry['stp_instance'])) + click.echo("--------------------------------------------------------------------") + click.echo("{:17}{:15}{:15}{:15}{:15}".format("PortNum","BPDU Tx","BPDU Rx","TCN Tx","TCN Rx")) + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_INTF_TABLE:Vlan{}:*".format(vlanid)) + if keys: + for key in keys: + result = re.search('.STP_VLAN_INTF_TABLE:Vlan(.*):(.*)', key) + ifname = result.group(2) + entry = g_stp_appl_db.get_all(g_stp_appl_db.APPL_DB, key) + if entry: + if 'bpdu_sent' not in entry: + entry['bpdu_sent'] = '-' + if 'bpdu_received' not in entry: + entry['bpdu_received'] = '-' + if 'tc_sent' not in entry: + entry['tc_sent'] = '-' + if 'tc_received' not in entry: + entry['tc_received'] = '-' + + click.echo("{:17}{:15}{:15}{:15}{:15}".format(ifname, entry['bpdu_sent'], entry['bpdu_received'], entry['tc_sent'], entry['tc_received'])) + + pass + + From 31879232cfffd9bde305925318e70711f2611ba3 Mon Sep 17 00:00:00 2001 From: Praveen Madhusudhana Date: Sun, 10 Nov 2019 20:00:43 -0800 Subject: [PATCH 2/6] PVST feature implementation. Updated after review comments --- config/stp.py | 72 +++++++++++++++++++++++++-------------------------- show/main.py | 2 +- show/stp.py | 27 +++++++++---------- 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/config/stp.py b/config/stp.py index 1ed68b174c..8de0206414 100644 --- a/config/stp.py +++ b/config/stp.py @@ -248,7 +248,7 @@ def interface_enable_stp(ctx, interface_name): 'portfast': 'true', 'uplink_fast': 'false' } - db.set_entry('STP_INTF', interface_name, fvs) + db.set_entry('STP_PORT', interface_name, fvs) def interface_disable_stp(ctx, interface_name): @@ -257,7 +257,7 @@ def interface_disable_stp(ctx, interface_name): def interface_set_stp_enable(ctx, interface_name, state): db = ctx.obj['db'] - db.set_entry('STP_INTF', interface_name, {'enabled': state}) + db.set_entry('STP_PORT', interface_name, {'enabled': state}) def is_vlan_configured_interface(ctx, interface_name): @@ -296,7 +296,7 @@ def get_pc_member_port_list(db): def get_vlan_list_from_stp_vlan_intf_table(db, intf_name): - stp_vlan_intf_info = db.get_table('STP_VLAN_INTF') + stp_vlan_intf_info = db.get_table('STP_VLAN_PORT') vlan_list = [] for line in stp_vlan_intf_info: if line[1] == intf_name: @@ -305,7 +305,7 @@ def get_vlan_list_from_stp_vlan_intf_table(db, intf_name): def get_intf_list_from_stp_vlan_intf_table(db, vlan_name): - stp_vlan_intf_info = db.get_table('STP_VLAN_INTF') + stp_vlan_intf_info = db.get_table('STP_VLAN_PORT') intf_list = [] for line in stp_vlan_intf_info: if line[0] == vlan_name: @@ -338,12 +338,12 @@ def enable_stp_for_interfaces(ctx): for port_key in port_dict: if port_key in intf_list_in_vlan_member_table: - pipe.hmset('STP_INTF|{}'.format(port_key), fvs) + pipe.hmset('STP_PORT|{}'.format(port_key), fvs) po_ch_dict = natsorted(db.get_table('PORTCHANNEL')) for po_ch_key in po_ch_dict: if po_ch_key in intf_list_in_vlan_member_table: - pipe.hmset('STP_INTF|{}'.format(po_ch_key), fvs) + pipe.hmset('STP_PORT|{}'.format(po_ch_key), fvs) pipe.execute() pass @@ -452,8 +452,8 @@ def stp_disable(ctx, mode): db.set_entry('STP', "GLOBAL", None) # Disable STP for all VLANs and interfaces db.delete_table('STP_VLAN') - db.delete_table('STP_INTF') - db.delete_table('STP_VLAN_INTF') + db.delete_table('STP_PORT') + db.delete_table('STP_VLAN_PORT') pass @@ -581,11 +581,11 @@ def stp_vlan_enable(ctx, vid): else: db.mod_entry('STP_VLAN', vlan_name, {'enabled': 'true'}) # Refresh stp_vlan_intf entry for vlan - for vlan, intf in db.get_table('STP_VLAN_INTF'): + for vlan, intf in db.get_table('STP_VLAN_PORT'): if vlan == vlan_name: vlan_intf_key = "{}|{}".format(vlan_name, intf) - vlan_intf_entry = db.get_entry('STP_VLAN_INTF', vlan_intf_key) - db.mod_entry('STP_VLAN_INTF', vlan_intf_key, vlan_intf_entry) + vlan_intf_entry = db.get_entry('STP_VLAN_PORT', vlan_intf_key) + db.mod_entry('STP_VLAN_PORT', vlan_intf_key, vlan_intf_entry) pass @@ -671,7 +671,7 @@ def stp_vlan_priority(ctx, vid, priority): def is_stp_enabled_for_interface(ctx, intf_name): db = ctx.obj['db'] - stp_entry = db.get_entry('STP_INTF', intf_name) + stp_entry = db.get_entry('STP_PORT', intf_name) stp_enabled = stp_entry.get("enabled") if stp_enabled == "true": return True @@ -716,7 +716,7 @@ def stp_interface_enable(ctx, interface_name): ctx.fail("STP is already enabled for " + interface_name) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - stp_intf_entry = db.get_entry('STP_INTF', interface_name) + stp_intf_entry = db.get_entry('STP_PORT', interface_name) if len(stp_intf_entry) == 0: fvs = {'enabled': 'true', 'root_guard': 'false', @@ -725,9 +725,9 @@ def stp_interface_enable(ctx, interface_name): 'portfast': 'true', 'uplink_fast': 'false' } - db.set_entry('STP_INTF', interface_name, fvs) + db.set_entry('STP_PORT', interface_name, fvs) else: - db.mod_entry('STP_INTF', interface_name, {'enabled': 'true'}) + db.mod_entry('STP_PORT', interface_name, {'enabled': 'true'}) @spanning_tree_interface.command('disable') @@ -738,7 +738,7 @@ def stp_interface_disable(ctx, interface_name): check_if_global_stp_enabled(ctx) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - db.mod_entry('STP_INTF', interface_name, {'enabled': 'false'}) + db.mod_entry('STP_PORT', interface_name, {'enabled': 'false'}) # STP interface port priority @@ -762,17 +762,17 @@ def stp_interface_priority(ctx, interface_name, priority): check_if_interface_is_valid(ctx, interface_name) is_valid_interface_priority(ctx, priority) db = ctx.obj['db'] - curr_intf_proirty = db.get_entry('STP_INTF', interface_name).get('priority') - db.mod_entry('STP_INTF', interface_name, {'priority': priority}) + curr_intf_proirty = db.get_entry('STP_PORT', interface_name).get('priority') + db.mod_entry('STP_PORT', interface_name, {'priority': priority}) # update interface priority in all stp_vlan_intf entries if entry exists - for vlan, intf in db.get_table('STP_VLAN_INTF'): + for vlan, intf in db.get_table('STP_VLAN_PORT'): if intf == interface_name: vlan_intf_key = "{}|{}".format(vlan, interface_name) - vlan_intf_entry = db.get_entry('STP_VLAN_INTF', vlan_intf_key) + vlan_intf_entry = db.get_entry('STP_VLAN_PORT', vlan_intf_key) if len(vlan_intf_entry) != 0: vlan_intf_priority = vlan_intf_entry.get('priority') if curr_intf_proirty == vlan_intf_priority: - db.mod_entry('STP_VLAN_INTF', vlan_intf_key, {'priority': priority}) + db.mod_entry('STP_VLAN_PORT', vlan_intf_key, {'priority': priority}) # end @@ -796,17 +796,17 @@ def stp_interface_path_cost(ctx, interface_name, cost): check_if_interface_is_valid(ctx, interface_name) is_valid_interface_path_cost(ctx, cost) db = ctx.obj['db'] - curr_intf_cost = db.get_entry('STP_INTF', interface_name).get('path_cost') - db.mod_entry('STP_INTF', interface_name, {'path_cost': cost}) + curr_intf_cost = db.get_entry('STP_PORT', interface_name).get('path_cost') + db.mod_entry('STP_PORT', interface_name, {'path_cost': cost}) # update interface path_cost in all stp_vlan_intf entries if entry exists - for vlan, intf in db.get_table('STP_VLAN_INTF'): + for vlan, intf in db.get_table('STP_VLAN_PORT'): if intf == interface_name: vlan_intf_key = "{}|{}".format(vlan, interface_name) - vlan_intf_entry = db.get_entry('STP_VLAN_INTF', vlan_intf_key) + vlan_intf_entry = db.get_entry('STP_VLAN_PORT', vlan_intf_key) if len(vlan_intf_entry) != 0: vlan_intf_cost = vlan_intf_entry.get('path_cost') if curr_intf_cost == vlan_intf_cost: - db.mod_entry('STP_VLAN_INTF', vlan_intf_key, {'path_cost': cost}) + db.mod_entry('STP_VLAN_PORT', vlan_intf_key, {'path_cost': cost}) # end @@ -826,7 +826,7 @@ def stp_interface_root_guard_enable(ctx, interface_name): check_if_stp_enabled_for_interface(ctx, interface_name) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - db.mod_entry('STP_INTF', interface_name, {'root_guard': 'true'}) + db.mod_entry('STP_PORT', interface_name, {'root_guard': 'true'}) @spanning_tree_interface_root_guard.command('disable') @@ -837,7 +837,7 @@ def stp_interface_root_guard_disable(ctx, interface_name): check_if_stp_enabled_for_interface(ctx, interface_name) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - db.mod_entry('STP_INTF', interface_name, {'root_guard': 'false'}) + db.mod_entry('STP_PORT', interface_name, {'root_guard': 'false'}) # STP interface bpdu guard @@ -864,7 +864,7 @@ def stp_interface_bpdu_guard_enable(ctx, interface_name, shutdown): fvs = {'bpdu_guard': 'true', 'bpdu_guard_do_disable': bpdu_guard_do_disable } - db.mod_entry('STP_INTF', interface_name, fvs) + db.mod_entry('STP_PORT', interface_name, fvs) @spanning_tree_interface_bpdu_guard.command('disable') @@ -875,7 +875,7 @@ def stp_interface_bpdu_guard_disable(ctx, interface_name): check_if_stp_enabled_for_interface(ctx, interface_name) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - db.mod_entry('STP_INTF', interface_name, {'bpdu_guard': 'false'}) + db.mod_entry('STP_PORT', interface_name, {'bpdu_guard': 'false'}) # STP interface portfast @@ -894,7 +894,7 @@ def stp_interface_portfast_enable(ctx, interface_name): check_if_stp_enabled_for_interface(ctx, interface_name) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - db.mod_entry('STP_INTF', interface_name, {'portfast': 'true'}) + db.mod_entry('STP_PORT', interface_name, {'portfast': 'true'}) @spanning_tree_interface_portfast.command('disable') @@ -905,7 +905,7 @@ def stp_interface_portfast_disable(ctx, interface_name): check_if_stp_enabled_for_interface(ctx, interface_name) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - db.mod_entry('STP_INTF', interface_name, {'portfast': 'false'}) + db.mod_entry('STP_PORT', interface_name, {'portfast': 'false'}) # STP interface root uplink_fast @@ -924,7 +924,7 @@ def stp_interface_uplink_fast_enable(ctx, interface_name): check_if_stp_enabled_for_interface(ctx, interface_name) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - db.mod_entry('STP_INTF', interface_name, {'uplink_fast': 'true'}) + db.mod_entry('STP_PORT', interface_name, {'uplink_fast': 'true'}) @spanning_tree_interface_uplink_fast.command('disable') @@ -935,7 +935,7 @@ def stp_interface_uplink_fast_disable(ctx, interface_name): check_if_stp_enabled_for_interface(ctx, interface_name) check_if_interface_is_valid(ctx, interface_name) db = ctx.obj['db'] - db.mod_entry('STP_INTF', interface_name, {'uplink_fast': 'false'}) + db.mod_entry('STP_PORT', interface_name, {'uplink_fast': 'false'}) ############################################### @@ -969,7 +969,7 @@ def stp_vlan_interface_priority(ctx, vid, interface_name, priority): is_interface_vlan_member(ctx, vlan_name, interface_name) is_valid_vlan_interface_priority(ctx, priority) vlan_interface = str(vlan_name) + "|" + interface_name - db.mod_entry('STP_VLAN_INTF', vlan_interface, {'priority': priority}) + db.mod_entry('STP_VLAN_PORT', vlan_interface, {'priority': priority}) @spanning_tree_vlan_interface.command('cost') @@ -987,7 +987,7 @@ def stp_vlan_interface_cost(ctx, vid, interface_name, cost): is_interface_vlan_member(ctx, vlan_name, interface_name) is_valid_interface_path_cost(ctx, cost) vlan_interface = str(vlan_name) + "|" + interface_name - db.mod_entry('STP_VLAN_INTF', vlan_interface, {'path_cost': cost}) + db.mod_entry('STP_VLAN_PORT', vlan_interface, {'path_cost': cost}) # Invoke main() diff --git a/show/main.py b/show/main.py index 2dc9f73181..f2ae5eab1d 100755 --- a/show/main.py +++ b/show/main.py @@ -1162,7 +1162,7 @@ def spanning_tree(verbose): config_db = ConfigDBConnector(host="127.0.0.1") config_db.connect() - stp_list = ["STP", "STP_INTF", "STP_VLAN", "STP_VLAN_INTF"] + stp_list = ["STP", "STP_PORT", "STP_VLAN", "STP_VLAN_PORT"] for key in stp_list: table = config_db.get_table(key) if len(table): diff --git a/show/stp.py b/show/stp.py index 7002da0324..d3a5437c8b 100644 --- a/show/stp.py +++ b/show/stp.py @@ -61,7 +61,8 @@ def stp_get_all_from_pattern(db_connect, db, pattern): def stp_is_port_fast_enabled(ifname): - app_db_entry = stp_get_all_from_pattern(g_stp_appl_db, g_stp_appl_db.APPL_DB, "*STP_INTF_TABLE:{}".format(ifname)) + app_db_entry = stp_get_all_from_pattern(g_stp_appl_db, + g_stp_appl_db.APPL_DB, "*STP_PORT_TABLE:{}".format(ifname)) if (\ not app_db_entry or\ not ('port_fast' in app_db_entry) or\ @@ -71,7 +72,7 @@ def stp_is_port_fast_enabled(ifname): def stp_is_uplink_fast_enabled(ifname): - entry = g_stp_cfg_db.get_entry("STP_INTF", ifname) + entry = g_stp_cfg_db.get_entry("STP_PORT", ifname) if (\ entry and \ ('uplink_fast' in entry) and \ @@ -120,7 +121,7 @@ def stp_get_entry_from_vlan_tb(db, vlanid): def stp_get_entry_from_vlan_intf_tb(db, vlanid, ifname): - entry = stp_get_all_from_pattern(db, db.APPL_DB, "*STP_VLAN_INTF_TABLE:Vlan{}:{}".format(vlanid,ifname)) + entry = stp_get_all_from_pattern(db, db.APPL_DB, "*STP_VLAN_PORT_TABLE:Vlan{}:{}".format(vlanid,ifname)) if not entry: return entry @@ -242,13 +243,13 @@ def show_stp_vlan(ctx, vlanid): click.echo("{:17}{:5}{:10}{:5}{:7}{:14}{:12}{:17}{:17}".format("Name","rity","Cost","Fast","Fast" ,"" ,"Cost" ,"Root" ,"Bridge")) if ctx.invoked_subcommand is None: - keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_INTF_TABLE:Vlan{}:*".format(vlanid)) + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_PORT_TABLE:Vlan{}:*".format(vlanid)) if not keys: return intf_list = [] for key in keys: - result = re.search('.STP_VLAN_INTF_TABLE:Vlan{}:(.*)'.format(vlanid),key) + result = re.search('.STP_VLAN_PORT_TABLE:Vlan{}:(.*)'.format(vlanid),key) ifname = result.group(1) intf_list.append(ifname) @@ -295,9 +296,9 @@ def show_stp_bpdu_guard(ctx): """Show spanning_tree bpdu_guard""" print_header = 1 - ifname_all = g_stp_cfg_db.get_keys("STP_INTF") + ifname_all = g_stp_cfg_db.get_keys("STP_PORT") for ifname in ifname_all: - cfg_entry = g_stp_cfg_db.get_entry("STP_INTF", ifname) + cfg_entry = g_stp_cfg_db.get_entry("STP_PORT", ifname) if cfg_entry['bpdu_guard'] == 'true' and cfg_entry['enabled'] == 'true': if print_header: click.echo("{:17}{:13}{}".format("PortNum","Shutdown","Port Shut")) @@ -307,7 +308,7 @@ def show_stp_bpdu_guard(ctx): if cfg_entry['bpdu_guard_do_disable'] == 'true': disabled = 'No' - keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_INTF_TABLE:{}".format(ifname)) + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_PORT_TABLE:{}".format(ifname)) #only 1 key per ifname is expected in BPDU_GUARD_TABLE. if keys: appdb_entry = g_stp_appl_db.get_all(g_stp_appl_db.APPL_DB, keys[0]) @@ -327,9 +328,9 @@ def show_stp_root_guard(ctx): """Show spanning_tree root_guard""" print_header = 1 - ifname_all = g_stp_cfg_db.get_keys("STP_INTF") + ifname_all = g_stp_cfg_db.get_keys("STP_PORT") for ifname in ifname_all: - entry = g_stp_cfg_db.get_entry("STP_INTF", ifname) + entry = g_stp_cfg_db.get_entry("STP_PORT", ifname) if entry['root_guard'] == 'true' and entry['enabled'] == 'true': if print_header: global_entry = g_stp_cfg_db.get_entry("STP", "GLOBAL") @@ -341,7 +342,7 @@ def show_stp_root_guard(ctx): state = '' vlanid = '' - keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_INTF_TABLE:*:{}".format(ifname)) + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_PORT_TABLE:*:{}".format(ifname)) if keys: for key in keys: entry = g_stp_appl_db.get_all(g_stp_appl_db.APPL_DB, key) @@ -397,10 +398,10 @@ def show_stp_vlan_statistics(ctx, vlanid): click.echo("VLAN {} - STP instance {}".format(vlanid, stp_inst_entry['stp_instance'])) click.echo("--------------------------------------------------------------------") click.echo("{:17}{:15}{:15}{:15}{:15}".format("PortNum","BPDU Tx","BPDU Rx","TCN Tx","TCN Rx")) - keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_INTF_TABLE:Vlan{}:*".format(vlanid)) + keys = g_stp_appl_db.keys(g_stp_appl_db.APPL_DB, "*STP_VLAN_PORT_TABLE:Vlan{}:*".format(vlanid)) if keys: for key in keys: - result = re.search('.STP_VLAN_INTF_TABLE:Vlan(.*):(.*)', key) + result = re.search('.STP_VLAN_PORT_TABLE:Vlan(.*):(.*)', key) ifname = result.group(2) entry = g_stp_appl_db.get_all(g_stp_appl_db.APPL_DB, key) if entry: From 2ab974656ccb1fde0572f16a2237e0023621060c Mon Sep 17 00:00:00 2001 From: Praveen Madhusudhana Date: Mon, 19 Apr 2021 22:04:18 -0700 Subject: [PATCH 3/6] STP PR - Adding sonic-utilities tests --- clear/stp.py | 4 +- config/main.py | 5 +- config/stp.py | 522 +++++++++++++++---------------- config/vlan.py | 23 ++ show/main.py | 6 +- show/stp.py | 16 +- sonic_installer/main.py | 1 + tests/mock_tables/appl_db.json | 36 ++- tests/mock_tables/config_db.json | 23 ++ tests/mock_tables/state_db.json | 3 + 10 files changed, 348 insertions(+), 291 deletions(-) diff --git a/clear/stp.py b/clear/stp.py index c540672a1b..79d4e5d85c 100644 --- a/clear/stp.py +++ b/clear/stp.py @@ -5,13 +5,13 @@ # # This group houses Spanning_tree commands and subgroups # -@cli.group(cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@cli.group(cls=AliasedGroup) @click.pass_context def spanning_tree(ctx): '''Clear Spanning-tree counters''' pass -@spanning_tree.group('statistics', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@spanning_tree.group('statistics', cls=AliasedGroup, invoke_without_command=True) @click.pass_context def stp_clr_stats(ctx): if ctx.invoked_subcommand is None: diff --git a/config/main.py b/config/main.py index cf24e94fc7..a67304c83f 100644 --- a/config/main.py +++ b/config/main.py @@ -34,6 +34,7 @@ from . import vlan from . import vxlan from . import plugins +from . import stp from .config_mgmt import ConfigMgmtDPB # mock masic APIs for unit test @@ -53,10 +54,6 @@ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) -import aaa -import mlnx -import stp - SONIC_GENERATED_SERVICE_PATH = '/etc/sonic/generated_services.conf' SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' VLAN_SUB_INTERFACE_SEPARATOR = '.' diff --git a/config/stp.py b/config/stp.py index 8de0206414..97d89f598a 100644 --- a/config/stp.py +++ b/config/stp.py @@ -4,9 +4,9 @@ # import click +import utilities_common.cli as clicommon import netaddr -from swsssdk import ConfigDBConnector -from swsssdk import SonicV2Connector +from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector from natsort import natsorted import logging @@ -89,8 +89,7 @@ def validate_params(forward_delay, max_age, hello_time): return False -def is_valid_stp_vlan_parameters(ctx, vlan_name, param_type, new_value): - db = ctx.obj['db'] +def is_valid_stp_vlan_parameters(ctx, db, vlan_name, param_type, new_value): stp_vlan_entry = db.get_entry('STP_VLAN', vlan_name) cfg_vlan_forward_delay = stp_vlan_entry.get("forward_delay") cfg_vlan_max_age = stp_vlan_entry.get("max_age") @@ -107,8 +106,7 @@ def is_valid_stp_vlan_parameters(ctx, vlan_name, param_type, new_value): ctx.fail("2*(forward_delay-1) >= max_age >= 2*(hello_time +1 ) not met for VLAN") -def is_valid_stp_global_parameters(ctx, param_type, new_value): - db = ctx.obj['db'] +def is_valid_stp_global_parameters(ctx, db, param_type, new_value): stp_global_entry = db.get_entry('STP', "GLOBAL") cfg_forward_delay = stp_global_entry.get("forward_delay") cfg_max_age = stp_global_entry.get("max_age") @@ -134,13 +132,14 @@ def get_max_stp_instances(): state_db = SonicV2Connector(host='127.0.0.1') state_db.connect(state_db.STATE_DB, False) max_inst = state_db.get(state_db.STATE_DB, "STP_TABLE|GLOBAL", "max_stp_inst") + #if max_inst == "": + return PVST_MAX_INSTANCES if max_inst != None and max_inst != 0 and max_inst < PVST_MAX_INSTANCES: return max_inst else: return PVST_MAX_INSTANCES -def update_stp_vlan_parameter(ctx, param_type, new_value): - db = ctx.obj['db'] +def update_stp_vlan_parameter(db, param_type, new_value): stp_global_entry = db.get_entry('STP', "GLOBAL") if param_type == parameter_forward_delay: @@ -174,8 +173,7 @@ def update_stp_vlan_parameter(ctx, param_type, new_value): pass -def check_if_vlan_exist_in_db(ctx, vid): - db = ctx.obj['db'] +def check_if_vlan_exist_in_db(db, ctx, vid): vlan_name = 'Vlan{}'.format(vid) vlan = db.get_entry('VLAN', vlan_name) if len(vlan) == 0: @@ -183,31 +181,26 @@ def check_if_vlan_exist_in_db(ctx, vid): pass -def enable_stp_for_vlans(ctx): - db = ctx.obj['db'] +def enable_stp_for_vlans(db): vlan_count = 0 fvs = {'enabled': 'true', - 'forward_delay': get_global_stp_forward_delay(ctx), - 'hello_time': get_global_stp_hello_time(ctx), - 'max_age': get_global_stp_max_age(ctx), - 'priority': get_global_stp_priority(ctx) + 'forward_delay': get_global_stp_forward_delay(db), + 'hello_time': get_global_stp_hello_time(db), + 'max_age': get_global_stp_max_age(db), + 'priority': get_global_stp_priority(db) } - clients = db.redis_clients["CONFIG_DB"] - pipe = clients.pipeline() vlan_dict = natsorted(db.get_table('VLAN')) max_stp_instances = get_max_stp_instances() for vlan_key in vlan_dict: if vlan_count >= max_stp_instances: logging.warning("Exceeded maximum STP configurable VLAN instances for {}".format(vlan_key)) break - pipe.hmset('STP_VLAN|{}'.format(vlan_key), fvs) + db.set_entry('STP_VLAN', vlan_key, fvs) vlan_count += 1 - pipe.execute() pass -def get_stp_enabled_vlan_count(ctx): - db = ctx.obj['db'] +def get_stp_enabled_vlan_count(db): count = 0 stp_vlan_keys = db.get_table('STP_VLAN').keys() for key in stp_vlan_keys: @@ -216,52 +209,31 @@ def get_stp_enabled_vlan_count(ctx): return count -def vlan_enable_stp(ctx, vlan_name): - db = ctx.obj['db'] +def vlan_enable_stp(db, vlan_name): fvs = {'enabled': 'true', - 'forward_delay': get_global_stp_forward_delay(ctx), - 'hello_time': get_global_stp_hello_time(ctx), - 'max_age': get_global_stp_max_age(ctx), - 'priority': get_global_stp_priority(ctx) + 'forward_delay': get_global_stp_forward_delay(db), + 'hello_time': get_global_stp_hello_time(db), + 'max_age': get_global_stp_max_age(db), + 'priority': get_global_stp_priority(db) } - if get_stp_enabled_vlan_count(ctx) < get_max_stp_instances(): + if get_stp_enabled_vlan_count(db) < get_max_stp_instances(): db.set_entry('STP_VLAN', vlan_name, fvs) else: logging.warning("Exceeded maximum STP configurable VLAN instances for {}".format(vlan_name)) -def vlan_disable_stp(ctx, vlan_name): - vlan_set_stp_enable(ctx, vlan_name, 'false') - - -def vlan_set_stp_enable(ctx, vlan_name, state): - db = ctx.obj['db'] - db.set_entry('STP_VLAN', vlan_name, {'enabled': state}) - - -def interface_enable_stp(ctx, interface_name): - db = ctx.obj['db'] +def interface_enable_stp(db, interface_name): fvs = {'enabled': 'true', 'root_guard': 'false', 'bpdu_guard': 'false', 'bpdu_guard_do_disable': 'false', - 'portfast': 'true', + 'portfast': 'false', 'uplink_fast': 'false' } db.set_entry('STP_PORT', interface_name, fvs) -def interface_disable_stp(ctx, interface_name): - interface_set_stp_enable(ctx, interface_name, 'false') - - -def interface_set_stp_enable(ctx, interface_name, state): - db = ctx.obj['db'] - db.set_entry('STP_PORT', interface_name, {'enabled': state}) - - -def is_vlan_configured_interface(ctx, interface_name): - db = ctx.obj['db'] +def is_vlan_configured_interface(db, interface_name): intf_to_vlan_list = get_vlan_list_for_interface(db, interface_name) if intf_to_vlan_list: # if empty return True @@ -269,11 +241,11 @@ def is_vlan_configured_interface(ctx, interface_name): return False -def is_interface_vlan_member(ctx, vlan_name, interface_name): - db = ctx.obj['db'] +def is_interface_vlan_member(db, vlan_name, interface_name): + ctx = click.get_current_context() key = vlan_name + '|' + interface_name entry = db.get_entry('VLAN_MEMBER', key) - if not entry: # if empty + if len(entry) == 0: # if empty ctx.fail("{} is not member of {}".format(interface_name, vlan_name)) def get_vlan_list_for_interface(db, interface_name): @@ -313,8 +285,7 @@ def get_intf_list_from_stp_vlan_intf_table(db, vlan_name): return intf_list -def is_portchannel_member_port(ctx, interface_name): - db = ctx.obj['db'] +def is_portchannel_member_port(db, interface_name): pc_member_port_list = get_pc_member_port_list(db) if interface_name in pc_member_port_list: return True @@ -322,34 +293,29 @@ def is_portchannel_member_port(ctx, interface_name): return False -def enable_stp_for_interfaces(ctx): - db = ctx.obj['db'] +def enable_stp_for_interfaces(db): fvs = {'enabled': 'true', 'root_guard': 'false', 'bpdu_guard': 'false', 'bpdu_guard_do_disable': 'false', - 'portfast': 'true', + 'portfast': 'false', 'uplink_fast': 'false' } - clients = db.redis_clients["CONFIG_DB"] - pipe = clients.pipeline() port_dict = natsorted(db.get_table('PORT')) intf_list_in_vlan_member_table = get_intf_list_in_vlan_member_table(db) for port_key in port_dict: if port_key in intf_list_in_vlan_member_table: - pipe.hmset('STP_PORT|{}'.format(port_key), fvs) + db.set_entry('STP_PORT', port_key, fvs) po_ch_dict = natsorted(db.get_table('PORTCHANNEL')) for po_ch_key in po_ch_dict: if po_ch_key in intf_list_in_vlan_member_table: - pipe.hmset('STP_PORT|{}'.format(po_ch_key), fvs) - pipe.execute() + db.set_entry('STP_PORT', po_ch_key, fvs) pass -def is_global_stp_enabled(ctx): - db = ctx.obj['db'] +def is_global_stp_enabled(db): stp_entry = db.get_entry('STP', "GLOBAL") mode = stp_entry.get("mode") if mode: @@ -358,60 +324,45 @@ def is_global_stp_enabled(ctx): return False -def check_if_global_stp_enabled(ctx): - if not is_global_stp_enabled(ctx): +def check_if_global_stp_enabled(db, ctx): + if not is_global_stp_enabled(db): ctx.fail("Global STP is not enabled - first configure STP mode") -def get_global_stp_mode(ctx): - db = ctx.obj['db'] +def get_global_stp_mode(db): stp_entry = db.get_entry('STP', "GLOBAL") mode = stp_entry.get("mode") return mode -def get_global_stp_root_guard_timeout(ctx): - db = ctx.obj['db'] - stp_entry = db.get_entry('STP', "GLOBAL") - root_guard_timeout = stp_entry.get("root_guard_timeout") - return root_guard_timeout - - -def get_global_stp_forward_delay(ctx): - db = ctx.obj['db'] +def get_global_stp_forward_delay(db): stp_entry = db.get_entry('STP', "GLOBAL") forward_delay = stp_entry.get("forward_delay") return forward_delay -def get_global_stp_hello_time(ctx): - db = ctx.obj['db'] +def get_global_stp_hello_time(db): stp_entry = db.get_entry('STP', "GLOBAL") hello_time = stp_entry.get("hello_time") return hello_time -def get_global_stp_max_age(ctx): - db = ctx.obj['db'] +def get_global_stp_max_age(db): stp_entry = db.get_entry('STP', "GLOBAL") max_age = stp_entry.get("max_age") return max_age -def get_global_stp_priority(ctx): - db = ctx.obj['db'] +def get_global_stp_priority(db): stp_entry = db.get_entry('STP', "GLOBAL") priority = stp_entry.get("priority") return priority @click.group() -@click.pass_context -def spanning_tree(ctx): +@clicommon.pass_db +def spanning_tree(_db): """STP command line""" - config_db = ConfigDBConnector() - config_db.connect() - ctx.obj = {'db': config_db} pass @@ -422,12 +373,13 @@ def spanning_tree(ctx): # cmd: STP enable @spanning_tree.command('enable') @click.argument('mode', metavar='', required=True, type=click.Choice(["pvst"])) -@click.pass_context -def spanning_tree_enable(ctx, mode): +@clicommon.pass_db +def spanning_tree_enable(_db, mode): """enable STP """ - if mode == "pvst" and get_global_stp_mode(ctx) == "pvst": + ctx = click.get_current_context() + db = _db.cfgdb + if mode == "pvst" and get_global_stp_mode(db) == "pvst": ctx.fail("PVST is already configured") - db = ctx.obj['db'] fvs = {'mode': mode, 'rootguard_timeout': STP_DEFAULT_ROOT_GUARD_TIMEOUT, 'forward_delay': STP_DEFAULT_FORWARD_DELAY, @@ -437,18 +389,18 @@ def spanning_tree_enable(ctx, mode): } db.set_entry('STP', "GLOBAL", fvs) # Enable STP for VLAN by default - enable_stp_for_interfaces(ctx) - enable_stp_for_vlans(ctx) + enable_stp_for_interfaces(db) + enable_stp_for_vlans(db) pass # cmd: STP disable @spanning_tree.command('disable') @click.argument('mode', metavar='', required=True, type=click.Choice(["pvst"])) -@click.pass_context -def stp_disable(ctx, mode): +@clicommon.pass_db +def stp_disable(_db, mode): """disable STP """ - db = ctx.obj['db'] + db = _db.cfgdb db.set_entry('STP', "GLOBAL", None) # Disable STP for all VLANs and interfaces db.delete_table('STP_VLAN') @@ -460,12 +412,13 @@ def stp_disable(ctx, mode): # cmd: STP global root guard timeout @spanning_tree.command('root_guard_timeout') @click.argument('root_guard_timeout', metavar='<5-600 seconds>', required=True, type=int) -@click.pass_context -def stp_global_root_guard_timeout(ctx, root_guard_timeout): +@clicommon.pass_db +def stp_global_root_guard_timeout(_db, root_guard_timeout): """Configure STP global root guard timeout value""" - check_if_global_stp_enabled(ctx) + ctx = click.get_current_context() + db = _db.cfgdb + check_if_global_stp_enabled(db, ctx) is_valid_root_guard_timeout(ctx, root_guard_timeout) - db = ctx.obj['db'] db.mod_entry('STP', "GLOBAL", {'rootguard_timeout': root_guard_timeout}) pass @@ -473,14 +426,15 @@ def stp_global_root_guard_timeout(ctx, root_guard_timeout): # cmd: STP global forward delay @spanning_tree.command('forward_delay') @click.argument('forward_delay', metavar='<4-30 seconds>', required=True, type=int) -@click.pass_context -def stp_global_forward_delay(ctx, forward_delay): +@clicommon.pass_db +def stp_global_forward_delay(_db, forward_delay): """Configure STP global forward delay""" - check_if_global_stp_enabled(ctx) + ctx = click.get_current_context() + db = _db.cfgdb + check_if_global_stp_enabled(db, ctx) is_valid_forward_delay(ctx, forward_delay) - is_valid_stp_global_parameters(ctx, parameter_forward_delay, forward_delay) - db = ctx.obj['db'] - update_stp_vlan_parameter(ctx, parameter_forward_delay, forward_delay) + is_valid_stp_global_parameters(ctx, db, parameter_forward_delay, forward_delay) + update_stp_vlan_parameter(db, parameter_forward_delay, forward_delay) db.mod_entry('STP', "GLOBAL", {'forward_delay': forward_delay}) pass @@ -488,14 +442,15 @@ def stp_global_forward_delay(ctx, forward_delay): # cmd: STP global hello interval @spanning_tree.command('hello') @click.argument('hello_interval', metavar='<1-10 seconds>', required=True, type=int) -@click.pass_context -def stp_global_hello_interval(ctx, hello_interval): +@clicommon.pass_db +def stp_global_hello_interval(_db, hello_interval): """Configure STP global hello interval""" - check_if_global_stp_enabled(ctx) + ctx = click.get_current_context() + db = _db.cfgdb + check_if_global_stp_enabled(db, ctx) is_valid_hello_interval(ctx, hello_interval) - is_valid_stp_global_parameters(ctx, parameter_hello_time, hello_interval) - db = ctx.obj['db'] - update_stp_vlan_parameter(ctx, parameter_hello_time, hello_interval) + is_valid_stp_global_parameters(ctx, db, parameter_hello_time, hello_interval) + update_stp_vlan_parameter(db, parameter_hello_time, hello_interval) db.mod_entry('STP', "GLOBAL", {'hello_time': hello_interval}) pass @@ -503,14 +458,15 @@ def stp_global_hello_interval(ctx, hello_interval): # cmd: STP global max age @spanning_tree.command('max_age') @click.argument('max_age', metavar='<6-40 seconds>', required=True, type=int) -@click.pass_context -def stp_global_max_age(ctx, max_age): +@clicommon.pass_db +def stp_global_max_age(_db, max_age): """Configure STP global max_age""" - check_if_global_stp_enabled(ctx) + ctx = click.get_current_context() + db = _db.cfgdb + check_if_global_stp_enabled(db, ctx) is_valid_max_age(ctx, max_age) - is_valid_stp_global_parameters(ctx, parameter_max_age, max_age) - db = ctx.obj['db'] - update_stp_vlan_parameter(ctx, parameter_max_age, max_age) + is_valid_stp_global_parameters(ctx, db, parameter_max_age, max_age) + update_stp_vlan_parameter(db, parameter_max_age, max_age) db.mod_entry('STP', "GLOBAL", {'max_age': max_age}) pass @@ -518,13 +474,14 @@ def stp_global_max_age(ctx, max_age): # cmd: STP global bridge priority @spanning_tree.command('priority') @click.argument('priority', metavar='<0-61440>', required=True, type=int) -@click.pass_context -def stp_global_priority(ctx, priority): +@clicommon.pass_db +def stp_global_priority(_db, priority): """Configure STP global bridge priority""" - check_if_global_stp_enabled(ctx) + ctx = click.get_current_context() + db = _db.cfgdb + check_if_global_stp_enabled(db, ctx) is_valid_bridge_priority(ctx, priority) - db = ctx.obj['db'] - update_stp_vlan_parameter(ctx, parameter_bridge_priority, priority) + update_stp_vlan_parameter(db, parameter_bridge_priority, priority) db.mod_entry('STP', "GLOBAL", {'priority': priority}) pass @@ -533,14 +490,13 @@ def stp_global_priority(ctx, priority): # STP VLAN commands implementation ############################################### @spanning_tree.group('vlan') -@click.pass_context -def spanning_tree_vlan(ctx): +@clicommon.pass_db +def spanning_tree_vlan(_db): """Configure STP for a VLAN""" pass -def is_stp_enabled_for_vlan(ctx, vlan_name): - db = ctx.obj['db'] +def is_stp_enabled_for_vlan(db, vlan_name): stp_entry = db.get_entry('STP_VLAN', vlan_name) stp_enabled = stp_entry.get("enabled") if stp_enabled == "true": @@ -549,33 +505,34 @@ def is_stp_enabled_for_vlan(ctx, vlan_name): return False -def check_if_stp_enabled_for_vlan(ctx, vlan_name): - if not is_stp_enabled_for_vlan(ctx, vlan_name): +def check_if_stp_enabled_for_vlan(ctx, db, vlan_name): + if not is_stp_enabled_for_vlan(db, vlan_name): ctx.fail("STP is not enabled for VLAN") @spanning_tree_vlan.command('enable') @click.argument('vid', metavar='', required=True, type=int) -@click.pass_context -def stp_vlan_enable(ctx, vid): +@clicommon.pass_db +def stp_vlan_enable(_db, vid): """Enable STP for a VLAN""" - check_if_vlan_exist_in_db(ctx, vid) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_vlan_exist_in_db(db, ctx, vid) vlan_name = 'Vlan{}'.format(vid) - if is_stp_enabled_for_vlan(ctx, vlan_name): + if is_stp_enabled_for_vlan(db, vlan_name): ctx.fail("STP is already enabled for " + vlan_name) - if get_stp_enabled_vlan_count(ctx) >= get_max_stp_instances(): + if get_stp_enabled_vlan_count(db) >= get_max_stp_instances(): ctx.fail("Exceeded maximum STP configurable VLAN instances") - check_if_global_stp_enabled(ctx) + check_if_global_stp_enabled(db, ctx) # when enabled for first time, create VLAN entry with # global values - else update only VLAN STP state stp_vlan_entry = db.get_entry('STP_VLAN', vlan_name) if len(stp_vlan_entry) == 0: fvs = {'enabled': 'true', - 'forward_delay': get_global_stp_forward_delay(ctx), - 'hello_time': get_global_stp_hello_time(ctx), - 'max_age': get_global_stp_max_age(ctx), - 'priority': get_global_stp_priority(ctx) + 'forward_delay': get_global_stp_forward_delay(db), + 'hello_time': get_global_stp_hello_time(db), + 'max_age': get_global_stp_max_age(db), + 'priority': get_global_stp_priority(db) } db.set_entry('STP_VLAN', vlan_name, fvs) else: @@ -591,11 +548,12 @@ def stp_vlan_enable(ctx, vid): @spanning_tree_vlan.command('disable') @click.argument('vid', metavar='', required=True, type=int) -@click.pass_context -def stp_vlan_disable(ctx, vid): +@clicommon.pass_db +def stp_vlan_disable(_db, vid): """Disable STP for a VLAN""" - check_if_vlan_exist_in_db(ctx, vid) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_vlan_exist_in_db(db, ctx, vid) vlan_name = 'Vlan{}'.format(vid) db.mod_entry('STP_VLAN', vlan_name, {'enabled': 'false'}) pass @@ -604,15 +562,16 @@ def stp_vlan_disable(ctx, vid): @spanning_tree_vlan.command('forward_delay') @click.argument('vid', metavar='', required=True, type=int) @click.argument('forward_delay', metavar='<4-30 seconds>', required=True, type=int) -@click.pass_context -def stp_vlan_forward_delay(ctx, vid, forward_delay): +@clicommon.pass_db +def stp_vlan_forward_delay(_db, vid, forward_delay): """Configure STP forward delay for VLAN""" - check_if_vlan_exist_in_db(ctx, vid) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_vlan_exist_in_db(db, ctx, vid) vlan_name = 'Vlan{}'.format(vid) - check_if_stp_enabled_for_vlan(ctx, vlan_name) + check_if_stp_enabled_for_vlan(ctx, db, vlan_name) is_valid_forward_delay(ctx, forward_delay) - is_valid_stp_vlan_parameters(ctx, vlan_name, parameter_forward_delay, forward_delay) + is_valid_stp_vlan_parameters(ctx, db, vlan_name, parameter_forward_delay, forward_delay) db.mod_entry('STP_VLAN', vlan_name, {'forward_delay': forward_delay}) pass @@ -620,15 +579,16 @@ def stp_vlan_forward_delay(ctx, vid, forward_delay): @spanning_tree_vlan.command('hello') @click.argument('vid', metavar='', required=True, type=int) @click.argument('hello_interval', metavar='<1-10 seconds>', required=True, type=int) -@click.pass_context -def stp_vlan_hello_interval(ctx, vid, hello_interval): +@clicommon.pass_db +def stp_vlan_hello_interval(_db, vid, hello_interval): """Configure STP hello interval for VLAN""" - check_if_vlan_exist_in_db(ctx, vid) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_vlan_exist_in_db(db, ctx, vid) vlan_name = 'Vlan{}'.format(vid) - check_if_stp_enabled_for_vlan(ctx, vlan_name) + check_if_stp_enabled_for_vlan(ctx, db, vlan_name) is_valid_hello_interval(ctx, hello_interval) - is_valid_stp_vlan_parameters(ctx, vlan_name, parameter_hello_time, hello_interval) + is_valid_stp_vlan_parameters(ctx, db, vlan_name, parameter_hello_time, hello_interval) db.mod_entry('STP_VLAN', vlan_name, {'hello_time': hello_interval}) pass @@ -636,15 +596,16 @@ def stp_vlan_hello_interval(ctx, vid, hello_interval): @spanning_tree_vlan.command('max_age') @click.argument('vid', metavar='', required=True, type=int) @click.argument('max_age', metavar='<6-40 seconds>', required=True, type=int) -@click.pass_context -def stp_vlan_max_age(ctx, vid, max_age): +@clicommon.pass_db +def stp_vlan_max_age(_db, vid, max_age): """Configure STP max age for VLAN""" - check_if_vlan_exist_in_db(ctx, vid) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_vlan_exist_in_db(db, ctx, vid) vlan_name = 'Vlan{}'.format(vid) - check_if_stp_enabled_for_vlan(ctx, vlan_name) + check_if_stp_enabled_for_vlan(ctx, db, vlan_name) is_valid_max_age(ctx, max_age) - is_valid_stp_vlan_parameters(ctx, vlan_name, parameter_max_age, max_age) + is_valid_stp_vlan_parameters(ctx, db, vlan_name, parameter_max_age, max_age) db.mod_entry('STP_VLAN', vlan_name, {'max_age': max_age}) pass @@ -652,13 +613,14 @@ def stp_vlan_max_age(ctx, vid, max_age): @spanning_tree_vlan.command('priority') @click.argument('vid', metavar='', required=True, type=int) @click.argument('priority', metavar='<0-61440>', required=True, type=int) -@click.pass_context -def stp_vlan_priority(ctx, vid, priority): +@clicommon.pass_db +def stp_vlan_priority(_db, vid, priority): """Configure STP bridge priority for VLAN""" - check_if_vlan_exist_in_db(ctx, vid) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_vlan_exist_in_db(db, ctx, vid) vlan_name = 'Vlan{}'.format(vid) - check_if_stp_enabled_for_vlan(ctx, vlan_name) + check_if_stp_enabled_for_vlan(ctx, db, vlan_name) is_valid_bridge_priority(ctx, priority) db.mod_entry('STP_VLAN', vlan_name, {'priority': priority}) pass @@ -669,8 +631,7 @@ def stp_vlan_priority(ctx, vid, priority): ############################################### -def is_stp_enabled_for_interface(ctx, intf_name): - db = ctx.obj['db'] +def is_stp_enabled_for_interface(db, intf_name): stp_entry = db.get_entry('STP_PORT', intf_name) stp_enabled = stp_entry.get("enabled") if stp_enabled == "true": @@ -679,50 +640,52 @@ def is_stp_enabled_for_interface(ctx, intf_name): return False -def check_if_stp_enabled_for_interface(ctx, intf_name): - if not is_stp_enabled_for_interface(ctx, intf_name): +def check_if_stp_enabled_for_interface(ctx, db, intf_name): + if not is_stp_enabled_for_interface(db, intf_name): ctx.fail("STP is not enabled for interface {}".format(intf_name)) -def check_if_interface_is_valid(ctx, interface_name): - from main import interface_name_is_valid - db = ctx.obj['db'] +def check_if_interface_is_valid(ctx, db, interface_name): + from config.main import interface_name_is_valid if interface_name_is_valid(db, interface_name) is False: ctx.fail("Interface name is invalid. Please enter a valid interface name!!") - for k, v in db.get_table('INTERFACE').iteritems(): - if k == interface_name: - ctx.fail(" {} has ip address {} configured - It's not a L2 interface".format(interface_name, v)) - if is_portchannel_member_port(ctx, interface_name): + for key in db.get_table('INTERFACE'): + if type(key) != tuple: + continue + if key[0] == interface_name: + ctx.fail(" {} has ip address {} configured - It's not a L2 interface".format(interface_name, key[1])) + if is_portchannel_member_port(db, interface_name): ctx.fail(" {} is a portchannel member port - STP can't be configured".format(interface_name)) - if not is_vlan_configured_interface(ctx, interface_name): + if not is_vlan_configured_interface(db, interface_name): ctx.fail(" {} has no VLAN configured - It's not a L2 interface".format(interface_name)) pass @spanning_tree.group('interface') -@click.pass_context -def spanning_tree_interface(ctx): +@clicommon.pass_db +def spanning_tree_interface(_db): """Configure STP for interface""" pass @spanning_tree_interface.command('enable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_enable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_enable(_db, interface_name): """Enable STP for interface""" - check_if_global_stp_enabled(ctx) - if is_stp_enabled_for_interface(ctx, interface_name): + ctx = click.get_current_context() + db = _db.cfgdb + check_if_global_stp_enabled(db, ctx) + if is_stp_enabled_for_interface(db, interface_name): ctx.fail("STP is already enabled for " + interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + check_if_interface_is_valid(ctx, db, interface_name) stp_intf_entry = db.get_entry('STP_PORT', interface_name) if len(stp_intf_entry) == 0: fvs = {'enabled': 'true', 'root_guard': 'false', 'bpdu_guard': 'false', 'bpdu_guard_do_disable': 'false', - 'portfast': 'true', + 'portfast': 'false', 'uplink_fast': 'false' } db.set_entry('STP_PORT', interface_name, fvs) @@ -732,12 +695,13 @@ def stp_interface_enable(ctx, interface_name): @spanning_tree_interface.command('disable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_disable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_disable(_db, interface_name): """Disable STP for interface""" - check_if_global_stp_enabled(ctx) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_global_stp_enabled(db, ctx) + check_if_interface_is_valid(ctx, db, interface_name) db.mod_entry('STP_PORT', interface_name, {'enabled': 'false'}) @@ -755,13 +719,14 @@ def is_valid_interface_priority(ctx, intf_priority): @spanning_tree_interface.command('priority') @click.argument('interface_name', metavar='', required=True) @click.argument('priority', metavar='<0-240>', required=True, type=int) -@click.pass_context -def stp_interface_priority(ctx, interface_name, priority): +@clicommon.pass_db +def stp_interface_priority(_db, interface_name, priority): """Configure STP port priority for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) is_valid_interface_priority(ctx, priority) - db = ctx.obj['db'] curr_intf_proirty = db.get_entry('STP_PORT', interface_name).get('priority') db.mod_entry('STP_PORT', interface_name, {'priority': priority}) # update interface priority in all stp_vlan_intf entries if entry exists @@ -789,13 +754,14 @@ def is_valid_interface_path_cost(ctx, intf_path_cost): @spanning_tree_interface.command('cost') @click.argument('interface_name', metavar='', required=True) @click.argument('cost', metavar='<1-200000000>', required=True, type=int) -@click.pass_context -def stp_interface_path_cost(ctx, interface_name, cost): +@clicommon.pass_db +def stp_interface_path_cost(_db, interface_name, cost): """Configure STP path cost for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) is_valid_interface_path_cost(ctx, cost) - db = ctx.obj['db'] curr_intf_cost = db.get_entry('STP_PORT', interface_name).get('path_cost') db.mod_entry('STP_PORT', interface_name, {'path_cost': cost}) # update interface path_cost in all stp_vlan_intf entries if entry exists @@ -812,38 +778,40 @@ def stp_interface_path_cost(ctx, interface_name, cost): # STP interface root guard @spanning_tree_interface.group('root_guard') -@click.pass_context -def spanning_tree_interface_root_guard(ctx): +@clicommon.pass_db +def spanning_tree_interface_root_guard(_db): """Configure STP root guard for interface""" pass @spanning_tree_interface_root_guard.command('enable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_root_guard_enable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_root_guard_enable(_db, interface_name): """Enable STP root guard for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) db.mod_entry('STP_PORT', interface_name, {'root_guard': 'true'}) @spanning_tree_interface_root_guard.command('disable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_root_guard_disable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_root_guard_disable(_db, interface_name): """Disable STP root guard for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) db.mod_entry('STP_PORT', interface_name, {'root_guard': 'false'}) # STP interface bpdu guard @spanning_tree_interface.group('bpdu_guard') -@click.pass_context -def spanning_tree_interface_bpdu_guard(ctx): +@clicommon.pass_db +def spanning_tree_interface_bpdu_guard(_db): """Configure STP bpdu guard for interface""" pass @@ -851,12 +819,13 @@ def spanning_tree_interface_bpdu_guard(ctx): @spanning_tree_interface_bpdu_guard.command('enable') @click.argument('interface_name', metavar='', required=True) @click.option('-s', '--shutdown', is_flag=True) -@click.pass_context -def stp_interface_bpdu_guard_enable(ctx, interface_name, shutdown): +@clicommon.pass_db +def stp_interface_bpdu_guard_enable(_db, interface_name, shutdown): """Enable STP bpdu guard for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) if shutdown is True: bpdu_guard_do_disable = 'true' else: @@ -869,72 +838,77 @@ def stp_interface_bpdu_guard_enable(ctx, interface_name, shutdown): @spanning_tree_interface_bpdu_guard.command('disable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_bpdu_guard_disable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_bpdu_guard_disable(_db, interface_name): """Disable STP bpdu guard for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) db.mod_entry('STP_PORT', interface_name, {'bpdu_guard': 'false'}) # STP interface portfast @spanning_tree_interface.group('portfast') -@click.pass_context -def spanning_tree_interface_portfast(ctx): +@clicommon.pass_db +def spanning_tree_interface_portfast(_db): """Configure STP portfast for interface""" pass @spanning_tree_interface_portfast.command('enable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_portfast_enable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_portfast_enable(_db, interface_name): """Enable STP portfast for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) db.mod_entry('STP_PORT', interface_name, {'portfast': 'true'}) @spanning_tree_interface_portfast.command('disable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_portfast_disable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_portfast_disable(_db, interface_name): """Disable STP portfast for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) db.mod_entry('STP_PORT', interface_name, {'portfast': 'false'}) # STP interface root uplink_fast @spanning_tree_interface.group('uplink_fast') -@click.pass_context -def spanning_tree_interface_uplink_fast(ctx): +@clicommon.pass_db +def spanning_tree_interface_uplink_fast(_db): """Configure STP uplink fast for interface""" pass @spanning_tree_interface_uplink_fast.command('enable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_uplink_fast_enable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_uplink_fast_enable(_db, interface_name): """Enable STP uplink fast for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) db.mod_entry('STP_PORT', interface_name, {'uplink_fast': 'true'}) @spanning_tree_interface_uplink_fast.command('disable') @click.argument('interface_name', metavar='', required=True) -@click.pass_context -def stp_interface_uplink_fast_disable(ctx, interface_name): +@clicommon.pass_db +def stp_interface_uplink_fast_disable(_db, interface_name): """Disable STP uplink fast for interface""" - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_interface_is_valid(ctx, interface_name) - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_interface_is_valid(ctx, db, interface_name) db.mod_entry('STP_PORT', interface_name, {'uplink_fast': 'false'}) @@ -942,8 +916,8 @@ def stp_interface_uplink_fast_disable(ctx, interface_name): # STP interface per VLAN commands implementation ############################################### @spanning_tree_vlan.group('interface') -@click.pass_context -def spanning_tree_vlan_interface(ctx): +@clicommon.pass_db +def spanning_tree_vlan_interface(_db): """Configure STP parameters for interface per VLAN""" pass @@ -958,15 +932,16 @@ def is_valid_vlan_interface_priority(ctx, priority): @click.argument('vid', metavar='', required=True, type=int) @click.argument('interface_name', metavar='', required=True) @click.argument('priority', metavar='<0-240>', required=True, type=int) -@click.pass_context -def stp_vlan_interface_priority(ctx, vid, interface_name, priority): +@clicommon.pass_db +def stp_vlan_interface_priority(_db, vid, interface_name, priority): """Configure STP per vlan port priority for interface""" - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb vlan_name = 'Vlan{}'.format(vid) - check_if_stp_enabled_for_vlan(ctx, vlan_name) - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_vlan_exist_in_db(ctx, vid) - is_interface_vlan_member(ctx, vlan_name, interface_name) + check_if_stp_enabled_for_vlan(ctx, db, vlan_name) + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_vlan_exist_in_db(db, ctx, vid) + is_interface_vlan_member(db, vlan_name, interface_name) is_valid_vlan_interface_priority(ctx, priority) vlan_interface = str(vlan_name) + "|" + interface_name db.mod_entry('STP_VLAN_PORT', vlan_interface, {'priority': priority}) @@ -976,20 +951,21 @@ def stp_vlan_interface_priority(ctx, vid, interface_name, priority): @click.argument('vid', metavar='', required=True, type=int) @click.argument('interface_name', metavar='', required=True) @click.argument('cost', metavar='<1-200000000>', required=True, type=int) -@click.pass_context -def stp_vlan_interface_cost(ctx, vid, interface_name, cost): +@clicommon.pass_db +def stp_vlan_interface_cost(_db, vid, interface_name, cost): """Configure STP per vlan path cost for interface""" - db = ctx.obj['db'] + ctx = click.get_current_context() + db = _db.cfgdb vlan_name = 'Vlan{}'.format(vid) - check_if_stp_enabled_for_vlan(ctx, vlan_name) - check_if_stp_enabled_for_interface(ctx, interface_name) - check_if_vlan_exist_in_db(ctx, vid) - is_interface_vlan_member(ctx, vlan_name, interface_name) + check_if_stp_enabled_for_vlan(ctx, db, vlan_name) + check_if_stp_enabled_for_interface(ctx, db, interface_name) + check_if_vlan_exist_in_db(db, ctx, vid) + is_interface_vlan_member(db, vlan_name, interface_name) is_valid_interface_path_cost(ctx, cost) vlan_interface = str(vlan_name) + "|" + interface_name db.mod_entry('STP_VLAN_PORT', vlan_interface, {'path_cost': cost}) # Invoke main() -if __name__ == '__main__': - spanning_tree() +#if __name__ == '__main__': +# spanning_tree() diff --git a/config/vlan.py b/config/vlan.py index 36ef3da0ac..6e42f4db52 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -3,6 +3,7 @@ from time import sleep from .utils import log +from . import stp # # 'vlan' group ('config vlan ...') @@ -28,6 +29,9 @@ def add_vlan(db, vid): ctx.fail("{} already exists".format(vlan)) db.cfgdb.set_entry('VLAN', vlan, {'vlanid': vid}) + # Enable STP on VLAN if PVST is enabled globally + if stp.is_global_stp_enabled(db.cfgdb): + stp.vlan_enable_stp(db.cfgdb, vlan) @vlan.command('del') @click.argument('vid', metavar='', required=True, type=int) @@ -58,6 +62,12 @@ def del_vlan(db, vid): ctx.fail("VLAN ID {} can not be removed. First remove all members assigned to this VLAN.".format(vid)) db.cfgdb.set_entry('VLAN', 'Vlan{}'.format(vid), None) + # Delete STP_VLAN & STP_VLAN_PORT entries when VLAN is deleted. + db.cfgdb.set_entry('STP_VLAN', 'Vlan{}'.format(vid), None) + stp_intf_list = stp.get_intf_list_from_stp_vlan_intf_table(db.cfgdb, 'Vlan{}'.format(vid)) + for intf_name in stp_intf_list: + key = 'Vlan{}'.format(vid) + "|" + intf_name + db.cfgdb.set_entry('STP_VLAN_PORT', key, None) def restart_ndppd(): verify_swss_running_cmd = "docker container inspect -f '{{.State.Status}}' swss" @@ -153,6 +163,12 @@ def add_vlan_member(db, vid, port, untagged): if (clicommon.interface_is_untagged_member(db.cfgdb, port) and untagged): ctx.fail("{} is already untagged member!".format(port)) + # If port is being made L2 port, enable STP + if stp.is_global_stp_enabled(db.cfgdb) is True: + vlan_list_for_intf = stp.get_vlan_list_for_interface(db.cfgdb, port) + if len(vlan_list_for_intf) == 0: + stp.interface_enable_stp(db.cfgdb, port) + db.cfgdb.set_entry('VLAN_MEMBER', (vlan, port), {'tagging_mode': "untagged" if untagged else "tagged" }) @vlan_member.command('del') @@ -184,6 +200,13 @@ def del_vlan_member(db, vid, port): ctx.fail("{} is not a member of {}".format(port, vlan)) db.cfgdb.set_entry('VLAN_MEMBER', (vlan, port), None) + # If port is being made non-L2 port, disable STP + if stp.is_global_stp_enabled(db.cfgdb) is True: + vlan_interface = str(vlan) + "|" + port + db.cfgdb.set_entry('STP_VLAN_PORT', vlan_interface, None) + vlan_list_for_intf = stp.get_vlan_list_for_interface(db.cfgdb, port) + if len(vlan_list_for_intf) == 0: + db.cfgdb.set_entry('STP_PORT', port, None) @vlan.group(cls=clicommon.AbbreviationGroup, name='dhcp_relay') def vlan_dhcp_relay(): diff --git a/show/main.py b/show/main.py index f2ae5eab1d..07fee8127a 100755 --- a/show/main.py +++ b/show/main.py @@ -36,6 +36,7 @@ from . import system_health from . import warm_restart from . import plugins +from . import stp # Global Variables @@ -177,9 +178,8 @@ def cli(ctx): if is_gearbox_configured(): cli.add_command(gearbox.gearbox) -from .stp import spanning_tree -cli.add_command(spanning_tree) +cli.add_command(stp.spanning_tree) # # 'vrf' command ("show vrf") @@ -1166,7 +1166,7 @@ def spanning_tree(verbose): for key in stp_list: table = config_db.get_table(key) if len(table): - print "\"" + key + "\":" + print("\"" + key + "\":") cmd = 'sudo sonic-cfggen -d --var-json ' + key run_command(cmd, display_cmd=verbose) diff --git a/show/stp.py b/show/stp.py index d3a5437c8b..ee43cd826f 100644 --- a/show/stp.py +++ b/show/stp.py @@ -3,9 +3,8 @@ import sys import click import subprocess -from show.main import AliasedGroup,cli -from swsssdk import SonicV2Connector -from swsssdk import ConfigDBConnector +import utilities_common.cli as clicommon +from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector ############################################################################## @@ -26,6 +25,7 @@ # Utility API's # def is_stp_docker_running(): + return True running_docker = subprocess.check_output('docker ps', shell=True) if running_docker.find('docker-stp') == -1: return False @@ -33,7 +33,7 @@ def is_stp_docker_running(): return True def connect_to_cfg_db(): - config_db = ConfigDBConnector(host="127.0.0.1") + config_db = ConfigDBConnector() config_db.connect() return config_db @@ -149,7 +149,7 @@ def stp_get_entry_from_vlan_intf_tb(db, vlanid, ifname): # # This group houses Spanning_tree commands and subgroups # -@cli.group(cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@click.group(cls=clicommon.AliasedGroup, invoke_without_command=True) @click.pass_context def spanning_tree(ctx): """Show spanning_tree commands""" @@ -189,7 +189,7 @@ def spanning_tree(ctx): pass -@spanning_tree.group('vlan', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@spanning_tree.group('vlan', cls=clicommon.AliasedGroup, invoke_without_command=True) @click.argument('vlanid', metavar='', required=True, type=int) @click.pass_context def show_stp_vlan(ctx, vlanid): @@ -363,7 +363,7 @@ def show_stp_root_guard(ctx): pass -@spanning_tree.group('statistics', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@spanning_tree.group('statistics', cls=clicommon.AliasedGroup, invoke_without_command=True) @click.pass_context def show_stp_statistics(ctx): """Show spanning_tree statistics""" @@ -391,7 +391,7 @@ def show_stp_statistics(ctx): def show_stp_vlan_statistics(ctx, vlanid): """Show spanning_tree statistics vlan""" - stp_inst_entry = stp_get_all_from_pattern(g_stp_appl_db, g_stp_appl_db.APPL_DB, "*STP_VLAN_INSTANCE_TABLE:Vlan{}".format(vlanid)) + stp_inst_entry = stp_get_all_from_pattern(g_stp_appl_db, g_stp_appl_db.APPL_DB, "*STP_VLAN_TABLE:Vlan{}".format(vlanid)) if not stp_inst_entry: return diff --git a/sonic_installer/main.py b/sonic_installer/main.py index 92ad7677f4..bb4f6e065a 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -458,6 +458,7 @@ def cleanup(): "swss", "syncd", "teamd", + "stp", "telemetry" ] diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index a73fbb0e55..eb59cbe37c 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -190,5 +190,39 @@ }, "VXLAN_REMOTE_VNI_TABLE:Vlan200:25.25.25.27": { "vni": "200" - } + }, + "_STP_VLAN_TABLE:Vlan100": { + "bridge_id": "8064b86a97e24e9c", + "max_age": "20", + "hello_time": "2", + "forward_delay": "15", + "hold_time": "1", + "root_bridge_id": "0064b86a97e24e9c", + "root_path_cost": "600", + "desig_bridge_id": "806480a235f281ec", + "root_port": "Root", + "root_max_age": "20", + "root_hello_time": "2", + "root_forward_delay": "15", + "stp_instance": "0", + "topology_change_count": "1", + "last_topology_change": "0" + }, + "_STP_VLAN_PORT_TABLE:Vlan100:Ethernet4": { + "port_num": "4", + "priority": "128", + "path_cost": "200", + "port_state": "FORWARDING", + "desig_cost": "400", + "desig_root": "0064b86a97e24e9c", + "desig_bridge": "806480a235f281ec", + "desig_port": "4", + "bpdu_sent": "10", + "bpdu_received": "15", + "config_bpdu_sent": "10", + "config_bpdu_received": "2", + "tc_sent": "15", + "tc_received": "5", + "root_guard_timer": "0" + } } diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index f8ceebffbf..9b0e5ae1f6 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -1515,5 +1515,28 @@ "holdtime": "10", "asn": "65200", "keepalive": "3" + }, + "STP|GLOBAL": { + "forward_delay": "15", + "hello_time": "2", + "max_age": "20", + "mode": "pvst", + "priority": "32768", + "rootguard_timeout": "30" + }, + "STP_PORT|Ethernet4": { + "bpdu_guard": "true", + "bpdu_guard_do_disable": "false", + "enabled": "true", + "portfast": "true", + "root_guard": "true", + "uplink_fast": "false" + }, + "STP_VLAN|Vlan100": { + "enabled": "true", + "forward_delay": "15", + "hello_time": "2", + "max_age": "20", + "priority": "32768" } } diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index 45057b5cf4..498a75e4e1 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -705,5 +705,8 @@ "non_fatal|Undefined": "0", "non_fatal|UnsupReq": "0", "non_fatal|UnxCmplt": "0" + }, + "STP_TABLE|GLOBAL": { + "max_stp_inst": "510" } } From 76386b1ad9fc8295096ee3931571686c5d6afdc1 Mon Sep 17 00:00:00 2001 From: Praveen Madhusudhana Date: Thu, 22 Apr 2021 23:48:26 -0700 Subject: [PATCH 4/6] STP PR - Fixed lgtm warnings --- clear/main.py | 4 +- clear/stp.py | 19 +- config/stp.py | 43 +--- debug/main.py | 6 +- debug/stp.py | 11 - show/stp.py | 24 +- tests/stp_test.py | 618 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 645 insertions(+), 80 deletions(-) create mode 100644 tests/stp_test.py diff --git a/clear/main.py b/clear/main.py index 8cfa6bd7af..17aaed60d4 100755 --- a/clear/main.py +++ b/clear/main.py @@ -5,6 +5,7 @@ import click +from . import stp from utilities_common import util_base from . import plugins @@ -127,8 +128,7 @@ def cli(): # # 'STP' # -from .stp import spanning_tree -cli.add_command(spanning_tree) +cli.add_command(stp.spanning_tree) # # 'ip' group ### diff --git a/clear/stp.py b/clear/stp.py index 79d4e5d85c..15bae8ee41 100644 --- a/clear/stp.py +++ b/clear/stp.py @@ -1,39 +1,35 @@ import click -from clear.main import AliasedGroup,cli -from clear.main import run_command +import utilities_common.cli as clicommon # # This group houses Spanning_tree commands and subgroups # -@cli.group(cls=AliasedGroup) +@click.group(cls=clicommon.AliasedGroup) @click.pass_context def spanning_tree(ctx): '''Clear Spanning-tree counters''' pass -@spanning_tree.group('statistics', cls=AliasedGroup, invoke_without_command=True) +@spanning_tree.group('statistics', cls=clicommon.AliasedGroup, invoke_without_command=True) @click.pass_context def stp_clr_stats(ctx): if ctx.invoked_subcommand is None: command = 'sudo stpctl clrstsall' - run_command(command) - pass + clicommon.run_command(command) @stp_clr_stats.command('interface') @click.argument('interface_name', metavar='', required=True) @click.pass_context def stp_clr_stats_intf(ctx, interface_name): command = 'sudo stpctl clrstsintf ' + interface_name - run_command(command) - pass + clicommon.run_command(command) @stp_clr_stats.command('vlan') @click.argument('vlan_id', metavar='', required=True) @click.pass_context def stp_clr_stats_vlan(ctx, vlan_id): command = 'sudo stpctl clrstsvlan ' + vlan_id - run_command(command) - pass + clicommon.run_command(command) @stp_clr_stats.command('vlan-interface') @click.argument('vlan_id', metavar='', required=True) @@ -41,6 +37,5 @@ def stp_clr_stats_vlan(ctx, vlan_id): @click.pass_context def stp_clr_stats_vlan_intf(ctx, vlan_id, interface_name): command = 'sudo stpctl clrstsvlanintf ' + vlan_id + ' ' + interface_name - run_command(command) - pass + clicommon.run_command(command) diff --git a/config/stp.py b/config/stp.py index 97d89f598a..47094379f1 100644 --- a/config/stp.py +++ b/config/stp.py @@ -5,8 +5,7 @@ import click import utilities_common.cli as clicommon -import netaddr -from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector +from swsscommon.swsscommon import SonicV2Connector from natsort import natsorted import logging @@ -53,25 +52,21 @@ def get_intf_list_in_vlan_member_table(config_db): def is_valid_root_guard_timeout(ctx, root_guard_timeout): if root_guard_timeout not in range(STP_MIN_ROOT_GUARD_TIMEOUT, STP_MAX_ROOT_GUARD_TIMEOUT + 1): ctx.fail("STP root guard timeout must be in range 5-600") - pass def is_valid_forward_delay(ctx, forward_delay): if forward_delay not in range(STP_MIN_FORWARD_DELAY, STP_MAX_FORWARD_DELAY + 1): ctx.fail("STP forward delay value must be in range 4-30") - pass def is_valid_hello_interval(ctx, hello_interval): if hello_interval not in range(STP_MIN_HELLO_INTERVAL, STP_MAX_HELLO_INTERVAL + 1): ctx.fail("STP hello timer must be in range 1-10") - pass def is_valid_max_age(ctx, max_age): if max_age not in range(STP_MIN_MAX_AGE, STP_MAX_MAX_AGE + 1): ctx.fail("STP max age value must be in range 6-40") - pass def is_valid_bridge_priority(ctx, priority): @@ -79,7 +74,6 @@ def is_valid_bridge_priority(ctx, priority): ctx.fail("STP bridge priority must be in range 0-61440") if priority % 4096 != 0: ctx.fail("STP bridge priority must be multiple of 4096") - pass def validate_params(forward_delay, max_age, hello_time): @@ -129,15 +123,16 @@ def is_valid_stp_global_parameters(ctx, db, param_type, new_value): parameter_bridge_priority = 4 def get_max_stp_instances(): - state_db = SonicV2Connector(host='127.0.0.1') - state_db.connect(state_db.STATE_DB, False) - max_inst = state_db.get(state_db.STATE_DB, "STP_TABLE|GLOBAL", "max_stp_inst") - #if max_inst == "": return PVST_MAX_INSTANCES - if max_inst != None and max_inst != 0 and max_inst < PVST_MAX_INSTANCES: - return max_inst - else: - return PVST_MAX_INSTANCES + #state_db = SonicV2Connector(host='127.0.0.1') + #state_db.connect(state_db.STATE_DB, False) + #max_inst = state_db.get(state_db.STATE_DB, "STP_TABLE|GLOBAL", "max_stp_inst") + #if max_inst == "": + # return PVST_MAX_INSTANCES + #if max_inst != None and max_inst != 0 and max_inst < PVST_MAX_INSTANCES: + # return max_inst + #else: + # return PVST_MAX_INSTANCES def update_stp_vlan_parameter(db, param_type, new_value): stp_global_entry = db.get_entry('STP', "GLOBAL") @@ -170,7 +165,6 @@ def update_stp_vlan_parameter(db, param_type, new_value): current_vlan_value = vlan_entry.get("priority") if current_global_value == current_vlan_value: db.mod_entry('STP_VLAN', vlan, {'priority': new_value}) - pass def check_if_vlan_exist_in_db(db, ctx, vid): @@ -178,7 +172,6 @@ def check_if_vlan_exist_in_db(db, ctx, vid): vlan = db.get_entry('VLAN', vlan_name) if len(vlan) == 0: ctx.fail("{} doesn't exist".format(vlan_name)) - pass def enable_stp_for_vlans(db): @@ -197,7 +190,6 @@ def enable_stp_for_vlans(db): break db.set_entry('STP_VLAN', vlan_key, fvs) vlan_count += 1 - pass def get_stp_enabled_vlan_count(db): @@ -312,7 +304,6 @@ def enable_stp_for_interfaces(db): for po_ch_key in po_ch_dict: if po_ch_key in intf_list_in_vlan_member_table: db.set_entry('STP_PORT', po_ch_key, fvs) - pass def is_global_stp_enabled(db): @@ -391,7 +382,6 @@ def spanning_tree_enable(_db, mode): # Enable STP for VLAN by default enable_stp_for_interfaces(db) enable_stp_for_vlans(db) - pass # cmd: STP disable @@ -406,7 +396,6 @@ def stp_disable(_db, mode): db.delete_table('STP_VLAN') db.delete_table('STP_PORT') db.delete_table('STP_VLAN_PORT') - pass # cmd: STP global root guard timeout @@ -420,7 +409,6 @@ def stp_global_root_guard_timeout(_db, root_guard_timeout): check_if_global_stp_enabled(db, ctx) is_valid_root_guard_timeout(ctx, root_guard_timeout) db.mod_entry('STP', "GLOBAL", {'rootguard_timeout': root_guard_timeout}) - pass # cmd: STP global forward delay @@ -436,7 +424,6 @@ def stp_global_forward_delay(_db, forward_delay): is_valid_stp_global_parameters(ctx, db, parameter_forward_delay, forward_delay) update_stp_vlan_parameter(db, parameter_forward_delay, forward_delay) db.mod_entry('STP', "GLOBAL", {'forward_delay': forward_delay}) - pass # cmd: STP global hello interval @@ -452,7 +439,6 @@ def stp_global_hello_interval(_db, hello_interval): is_valid_stp_global_parameters(ctx, db, parameter_hello_time, hello_interval) update_stp_vlan_parameter(db, parameter_hello_time, hello_interval) db.mod_entry('STP', "GLOBAL", {'hello_time': hello_interval}) - pass # cmd: STP global max age @@ -468,7 +454,6 @@ def stp_global_max_age(_db, max_age): is_valid_stp_global_parameters(ctx, db, parameter_max_age, max_age) update_stp_vlan_parameter(db, parameter_max_age, max_age) db.mod_entry('STP', "GLOBAL", {'max_age': max_age}) - pass # cmd: STP global bridge priority @@ -483,7 +468,6 @@ def stp_global_priority(_db, priority): is_valid_bridge_priority(ctx, priority) update_stp_vlan_parameter(db, parameter_bridge_priority, priority) db.mod_entry('STP', "GLOBAL", {'priority': priority}) - pass ############################################### @@ -543,7 +527,6 @@ def stp_vlan_enable(_db, vid): vlan_intf_key = "{}|{}".format(vlan_name, intf) vlan_intf_entry = db.get_entry('STP_VLAN_PORT', vlan_intf_key) db.mod_entry('STP_VLAN_PORT', vlan_intf_key, vlan_intf_entry) - pass @spanning_tree_vlan.command('disable') @@ -556,7 +539,6 @@ def stp_vlan_disable(_db, vid): check_if_vlan_exist_in_db(db, ctx, vid) vlan_name = 'Vlan{}'.format(vid) db.mod_entry('STP_VLAN', vlan_name, {'enabled': 'false'}) - pass @spanning_tree_vlan.command('forward_delay') @@ -573,7 +555,6 @@ def stp_vlan_forward_delay(_db, vid, forward_delay): is_valid_forward_delay(ctx, forward_delay) is_valid_stp_vlan_parameters(ctx, db, vlan_name, parameter_forward_delay, forward_delay) db.mod_entry('STP_VLAN', vlan_name, {'forward_delay': forward_delay}) - pass @spanning_tree_vlan.command('hello') @@ -590,7 +571,6 @@ def stp_vlan_hello_interval(_db, vid, hello_interval): is_valid_hello_interval(ctx, hello_interval) is_valid_stp_vlan_parameters(ctx, db, vlan_name, parameter_hello_time, hello_interval) db.mod_entry('STP_VLAN', vlan_name, {'hello_time': hello_interval}) - pass @spanning_tree_vlan.command('max_age') @@ -607,7 +587,6 @@ def stp_vlan_max_age(_db, vid, max_age): is_valid_max_age(ctx, max_age) is_valid_stp_vlan_parameters(ctx, db, vlan_name, parameter_max_age, max_age) db.mod_entry('STP_VLAN', vlan_name, {'max_age': max_age}) - pass @spanning_tree_vlan.command('priority') @@ -623,7 +602,6 @@ def stp_vlan_priority(_db, vid, priority): check_if_stp_enabled_for_vlan(ctx, db, vlan_name) is_valid_bridge_priority(ctx, priority) db.mod_entry('STP_VLAN', vlan_name, {'priority': priority}) - pass ############################################### @@ -658,7 +636,6 @@ def check_if_interface_is_valid(ctx, db, interface_name): ctx.fail(" {} is a portchannel member port - STP can't be configured".format(interface_name)) if not is_vlan_configured_interface(db, interface_name): ctx.fail(" {} has no VLAN configured - It's not a L2 interface".format(interface_name)) - pass @spanning_tree.group('interface') diff --git a/debug/main.py b/debug/main.py index 86f51f123f..44757599bc 100755 --- a/debug/main.py +++ b/debug/main.py @@ -1,6 +1,8 @@ import click import subprocess +from . import stp + def run_command(command, pager=False): click.echo(click.style("Command: ", fg='cyan') + click.style(command, fg='green')) p = subprocess.Popen(command, shell=True, text=True, stdout=subprocess.PIPE) @@ -24,9 +26,7 @@ def cli(): # # STP # -from .stp import spanning_tree -cli.add_command(spanning_tree) - +cli.add_command(stp.spanning_tree) p = subprocess.check_output(["sudo vtysh -c 'show version'"], shell=True, text=True) if 'FRRouting' in p: diff --git a/debug/stp.py b/debug/stp.py index 431ff1494d..43c8608656 100644 --- a/debug/stp.py +++ b/debug/stp.py @@ -12,7 +12,6 @@ def spanning_tree(ctx): if ctx.invoked_subcommand is None: command = 'sudo stpctl dbg enable' run_command(command) - pass @spanning_tree.group('dump', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) def stp_debug_dump(): @@ -22,14 +21,12 @@ def stp_debug_dump(): def stp_debug_dump_global(): command = 'sudo stpctl global' run_command(command) - pass @stp_debug_dump.command('vlan') @click.argument('vlan_id', metavar='', required=True) def stp_debug_dump_vlan(vlan_id): command = 'sudo stpctl vlan ' + vlan_id run_command(command) - pass @stp_debug_dump.command('interface') @click.argument('vlan_id', metavar='', required=True) @@ -37,19 +34,16 @@ def stp_debug_dump_vlan(vlan_id): def stp_debug_dump_vlan_intf(vlan_id, interface_name): command = 'sudo stpctl port ' + vlan_id + " " + interface_name run_command(command) - pass @spanning_tree.command('show') def stp_debug_show(): command = 'sudo stpctl dbg show' run_command(command) - pass @spanning_tree.command('reset') def stp_debug_reset(): command = 'sudo stpctl dbg disable' run_command(command) - pass @spanning_tree.command('bpdu') @click.argument('mode', metavar='{rx|tx}', required=False) @@ -70,7 +64,6 @@ def stp_debug_bpdu(mode, disable): else: command = 'sudo stpctl dbg bpdu on' run_command(command) - pass @spanning_tree.command('verbose') @click.option('-d', '--disable', is_flag=True) @@ -80,7 +73,6 @@ def stp_debug_verbose(disable): else: command = 'sudo stpctl dbg verbose on' run_command(command) - pass @spanning_tree.command('event') @click.option('-d', '--disable', is_flag=True) @@ -90,7 +82,6 @@ def stp_debug_event(disable): else: command = 'sudo stpctl dbg event on' run_command(command) - pass @spanning_tree.command('vlan') @click.argument('vlan_id', metavar='', required=True) @@ -101,7 +92,6 @@ def stp_debug_vlan(vlan_id, disable): else: command = 'sudo stpctl dbg vlan ' + vlan_id + ' on' run_command(command) - pass @spanning_tree.command('interface') @click.argument('interface_name', metavar='', required=True) @@ -112,6 +102,5 @@ def stp_debug_intf(interface_name, disable): else: command = 'sudo stpctl dbg port ' + interface_name + ' on' run_command(command) - pass diff --git a/show/stp.py b/show/stp.py index ee43cd826f..77af4015f6 100644 --- a/show/stp.py +++ b/show/stp.py @@ -1,6 +1,4 @@ -import time import re -import sys import click import subprocess import utilities_common.cli as clicommon @@ -26,11 +24,11 @@ # def is_stp_docker_running(): return True - running_docker = subprocess.check_output('docker ps', shell=True) - if running_docker.find('docker-stp') == -1: - return False - else: - return True + #running_docker = subprocess.check_output('docker ps', shell=True) + #if running_docker.find('docker-stp') == -1: + # return False + #else: + # return True def connect_to_cfg_db(): config_db = ConfigDBConnector() @@ -186,8 +184,6 @@ def spanning_tree(ctx): for vlanid in vlan_list: ctx.invoke(show_stp_vlan, vlanid=vlanid) - pass - @spanning_tree.group('vlan', cls=clicommon.AliasedGroup, invoke_without_command=True) @click.argument('vlanid', metavar='', required=True, type=int) @@ -287,9 +283,6 @@ def show_stp_interface(ctx, ifname): )) - pass - - @spanning_tree.command('bpdu_guard') @click.pass_context def show_stp_bpdu_guard(ctx): @@ -319,8 +312,6 @@ def show_stp_bpdu_guard(ctx): else: click.echo("{:17}{:13}{}".format(ifname,"No","NA")) - pass - @spanning_tree.command('root_guard') @click.pass_context @@ -360,8 +351,6 @@ def show_stp_root_guard(ctx): - pass - @spanning_tree.group('statistics', cls=clicommon.AliasedGroup, invoke_without_command=True) @click.pass_context @@ -382,7 +371,6 @@ def show_stp_statistics(ctx): vlan_list.sort() for vlanid in vlan_list: ctx.invoke(show_stp_vlan_statistics, vlanid=vlanid) - pass @show_stp_statistics.command('vlan') @@ -416,6 +404,4 @@ def show_stp_vlan_statistics(ctx, vlanid): click.echo("{:17}{:15}{:15}{:15}{:15}".format(ifname, entry['bpdu_sent'], entry['bpdu_received'], entry['tc_sent'], entry['tc_received'])) - pass - diff --git a/tests/stp_test.py b/tests/stp_test.py new file mode 100644 index 0000000000..f60b9fb94a --- /dev/null +++ b/tests/stp_test.py @@ -0,0 +1,618 @@ +import os +import traceback +from unittest import mock +from click.testing import CliRunner + +import config.main as config +import show.main as show +from utilities_common.db import Db + +show_spanning_tree="""\ +Spanning-tree Mode: PVST + +VLAN 100 - STP instance 0 +-------------------------------------------------------------------- +STP Bridge Parameters: +Bridge Bridge Bridge Bridge Hold LastTopology Topology +Identifier MaxAge Hello FwdDly Time Change Change +hex sec sec sec sec sec cnt +8064b86a97e24e9c 20 2 15 1 0 1 + +RootBridge RootPath DesignatedBridge RootPort Max Hel Fwd +Identifier Cost Identifier Age lo Dly +hex hex sec sec sec +0064b86a97e24e9c 600 806480a235f281ec Root 20 2 15 + +STP Port Parameters: +Port Prio Path Port Uplink State Designated Designated Designated +Name rity Cost Fast Fast Cost Root Bridge +Ethernet4 128 200 N N FORWARDING 400 0064b86a97e24e9c 806480a235f281ec +""" + +show_spanning_tree_vlan="""\ + +VLAN 100 - STP instance 0 +-------------------------------------------------------------------- +STP Bridge Parameters: +Bridge Bridge Bridge Bridge Hold LastTopology Topology +Identifier MaxAge Hello FwdDly Time Change Change +hex sec sec sec sec sec cnt +8064b86a97e24e9c 20 2 15 1 0 1 + +RootBridge RootPath DesignatedBridge RootPort Max Hel Fwd +Identifier Cost Identifier Age lo Dly +hex hex sec sec sec +0064b86a97e24e9c 600 806480a235f281ec Root 20 2 15 + +STP Port Parameters: +Port Prio Path Port Uplink State Designated Designated Designated +Name rity Cost Fast Fast Cost Root Bridge +Ethernet4 128 200 N N FORWARDING 400 0064b86a97e24e9c 806480a235f281ec +""" + +show_spanning_tree_statistics="""\ +VLAN 100 - STP instance 0 +-------------------------------------------------------------------- +PortNum BPDU Tx BPDU Rx TCN Tx TCN Rx +Ethernet4 10 15 15 5 +""" + +show_spanning_tree_bpdu_guard="""\ +PortNum Shutdown Port Shut + Configured due to BPDU guard +------------------------------------------- +Ethernet4 No NA +""" + +show_spanning_tree_root_guard="""\ +Root guard timeout: 30 secs + +Port VLAN Current State +------------------------------------------- +Ethernet4 100 Consistent state +""" + +class TestStp(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "1" + print("SETUP") + + def test_show_spanning_tree(self): + runner = CliRunner() + db = Db() + result = runner.invoke(show.cli.commands["spanning-tree"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_spanning_tree + + def test_show_spanning_tree_vlan(self): + runner = CliRunner() + db = Db() + result = runner.invoke(show.cli.commands["spanning-tree"].commands["vlan"], ["100"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_spanning_tree_vlan + + def test_show_spanning_tree_statistics(self): + runner = CliRunner() + db = Db() + result = runner.invoke(show.cli.commands["spanning-tree"].commands["statistics"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_spanning_tree_statistics + + def test_show_spanning_tree_statistics_vlan(self): + runner = CliRunner() + db = Db() + result = runner.invoke(show.cli.commands["spanning-tree"].commands["statistics"].commands["vlan"], ["100"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_spanning_tree_statistics + + def test_show_spanning_tree_bpdu_guard(self): + runner = CliRunner() + db = Db() + result = runner.invoke(show.cli.commands["spanning-tree"].commands["bpdu_guard"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_spanning_tree_bpdu_guard + + def test_show_spanning_tree_root_guard(self): + runner = CliRunner() + db = Db() + result = runner.invoke(show.cli.commands["spanning-tree"].commands["root_guard"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_spanning_tree_root_guard + + def test_disable_enable_global_pvst(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["spanning-tree"].commands["disable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["enable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["100"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"],["100", "Ethernet4"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["enable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "PVST is already configured" in result.output + + def test_stp_validate_interface_params(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["spanning-tree"].commands["disable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["enable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "Global STP is not enabled" in result.output + + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["100"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"],["100", "Ethernet4"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["enable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["priority"], ["Ethernet4", "16"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["cost"], ["Ethernet4", "100"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["disable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["enable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["portfast"].commands["disable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["portfast"].commands["enable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["uplink_fast"].commands["disable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["uplink_fast"].commands["enable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["bpdu_guard"].commands["enable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["bpdu_guard"].commands["disable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["bpdu_guard"].commands["enable"], ["Ethernet4", "--shutdown"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["bpdu_guard"].commands["disable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["root_guard"].commands["enable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["root_guard"].commands["disable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["cost"], ["Ethernet4", "0"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP interface path cost must be in range 1-200000000" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["cost"], ["Ethernet4", "2000000000"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP interface path cost must be in range 1-200000000" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["priority"], ["Ethernet4", "1000"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP interface priority must be in range 0-240" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["enable"], ["Ethernet4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP is already enabled for" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["enable"], ["Ethernet0"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "has ip address" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["enable"], ["Ethernet120"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "is a portchannel member port" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["interface"].commands["enable"], ["Ethernet20"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "has no VLAN configured" in result.output + + def test_stp_validate_vlan_interface_params(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["spanning-tree"].commands["disable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["100"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"],["100", "Ethernet4"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["enable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["interface"].commands["cost"], ["100", "Ethernet4", "100"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["interface"].commands["priority"], ["100", "Ethernet4", "32"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["interface"].commands["cost"], ["100", "Ethernet4", "0"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP interface path cost must be in range 1-200000000" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["interface"].commands["cost"], ["100", "Ethernet4", "2000000000"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP interface path cost must be in range 1-200000000" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["interface"].commands["priority"], ["100", "Ethernet4", "1000"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP per vlan port priority must be in range 0-240" in result.output + + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["101"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["interface"].commands["priority"], ["101", "Ethernet4", "16"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "is not member of" in result.output + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"],["100", "Ethernet4"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["100"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + def test_stp_validate_vlan_timer_and_priority_params(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["spanning-tree"].commands["disable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["enable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["100"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"],["100", "Ethernet4"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["hello"], ["100", "3"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["max_age"], ["100", "21"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["forward_delay"], ["100", "16"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["priority"], ["100", "4096"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["hello"], ["100", "0"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP hello timer must be in range 1-10" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["hello"], ["100", "20"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP hello timer must be in range 1-10" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["forward_delay"], ["100", "2"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP forward delay value must be in range 4-30" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["forward_delay"], ["100", "42"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP forward delay value must be in range 4-30" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["max_age"], ["100", "4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP max age value must be in range 6-40" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["max_age"], ["100", "45"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP max age value must be in range 6-40" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["forward_delay"], ["100", "4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "2*(forward_delay-1) >= max_age >= 2*(hello_time +1 )" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["priority"], ["100", "70000"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP bridge priority must be in range 0-61440" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["priority"], ["100", "8000"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP bridge priority must be multiple of 4096" in result.output + + + def test_add_vlan_enable_pvst(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["spanning-tree"].commands["disable"], ["pvst"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["100"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"],["100", "Ethernet4"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["enable"], ["pvst"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["200"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["disable"], ["200"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["enable"], ["200"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["200"], obj=db) + print(result.exit_code) + assert result.exit_code == 0 + + #Enable/Disable on non-existing VLAN + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["enable"], ["101"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "doesn't exist" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["vlan"].commands["disable"], ["101"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "doesn't exist" in result.output + + def test_stp_validate_global_timer_and_priority_params(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["spanning-tree"].commands["hello"], ["3"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["forward_delay"], ["16"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["max_age"], ["22"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["priority"], ["8192"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["root_guard_timeout"], ["100"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["spanning-tree"].commands["hello"], ["0"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP hello timer must be in range 1-10" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["hello"], ["20"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP hello timer must be in range 1-10" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["forward_delay"], ["2"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP forward delay value must be in range 4-30" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["forward_delay"], ["50"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP forward delay value must be in range 4-30" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["max_age"], ["5"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP max age value must be in range 6-40" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["max_age"], ["45"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP max age value must be in range 6-40" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["forward_delay"], ["4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "2*(forward_delay-1) >= max_age >= 2*(hello_time +1 )" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["root_guard_timeout"], ["4"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP root guard timeout must be in range 5-600" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["root_guard_timeout"], ["700"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP root guard timeout must be in range 5-600" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["priority"], ["70000"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP bridge priority must be in range 0-61440" in result.output + + result = runner.invoke(config.config.commands["spanning-tree"].commands["priority"], ["8000"], obj=db) + print("exit code {}".format(result.exit_code)) + print("result code {}".format(result.output)) + assert result.exit_code != 0 + assert "STP bridge priority must be multiple of 4096" in result.output + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + print("TEARDOWN") From 2de4355a59956197e8c7cf04f44893eb7d87a70a Mon Sep 17 00:00:00 2001 From: Praveen Madhusudhana Date: Fri, 23 Apr 2021 01:14:59 -0700 Subject: [PATCH 5/6] STP PR - Fixed lgtm warnings --- debug/stp.py | 29 ++++++++++++++--------------- show/stp.py | 1 - 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/debug/stp.py b/debug/stp.py index 43c8608656..c436016091 100644 --- a/debug/stp.py +++ b/debug/stp.py @@ -1,49 +1,48 @@ import click -from debug.main import AliasedGroup,cli -from debug.main import run_command +import utilities_common.cli as clicommon # # This group houses Spanning_tree commands and subgroups # -@cli.group(cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@click.group(cls=clicommon.AliasedGroup, default_if_no_args=False, invoke_without_command=True) @click.pass_context def spanning_tree(ctx): '''debug spanning_tree commands''' if ctx.invoked_subcommand is None: command = 'sudo stpctl dbg enable' - run_command(command) + clicommon.run_command(command) -@spanning_tree.group('dump', cls=AliasedGroup, default_if_no_args=False, invoke_without_command=True) +@spanning_tree.group('dump', cls=clicommon.AliasedGroup, default_if_no_args=False, invoke_without_command=True) def stp_debug_dump(): pass @stp_debug_dump.command('global') def stp_debug_dump_global(): command = 'sudo stpctl global' - run_command(command) + clicommon.run_command(command) @stp_debug_dump.command('vlan') @click.argument('vlan_id', metavar='', required=True) def stp_debug_dump_vlan(vlan_id): command = 'sudo stpctl vlan ' + vlan_id - run_command(command) + clicommon.run_command(command) @stp_debug_dump.command('interface') @click.argument('vlan_id', metavar='', required=True) @click.argument('interface_name', metavar='', required=True) def stp_debug_dump_vlan_intf(vlan_id, interface_name): command = 'sudo stpctl port ' + vlan_id + " " + interface_name - run_command(command) + clicommon.run_command(command) @spanning_tree.command('show') def stp_debug_show(): command = 'sudo stpctl dbg show' - run_command(command) + clicommon.run_command(command) @spanning_tree.command('reset') def stp_debug_reset(): command = 'sudo stpctl dbg disable' - run_command(command) + clicommon.run_command(command) @spanning_tree.command('bpdu') @click.argument('mode', metavar='{rx|tx}', required=False) @@ -63,7 +62,7 @@ def stp_debug_bpdu(mode, disable): command = 'sudo stpctl dbg bpdu tx-on' else: command = 'sudo stpctl dbg bpdu on' - run_command(command) + clicommon.run_command(command) @spanning_tree.command('verbose') @click.option('-d', '--disable', is_flag=True) @@ -72,7 +71,7 @@ def stp_debug_verbose(disable): command = 'sudo stpctl dbg verbose off' else: command = 'sudo stpctl dbg verbose on' - run_command(command) + clicommon.run_command(command) @spanning_tree.command('event') @click.option('-d', '--disable', is_flag=True) @@ -81,7 +80,7 @@ def stp_debug_event(disable): command = 'sudo stpctl dbg event off' else: command = 'sudo stpctl dbg event on' - run_command(command) + clicommon.run_command(command) @spanning_tree.command('vlan') @click.argument('vlan_id', metavar='', required=True) @@ -91,7 +90,7 @@ def stp_debug_vlan(vlan_id, disable): command = 'sudo stpctl dbg vlan ' + vlan_id + ' off' else: command = 'sudo stpctl dbg vlan ' + vlan_id + ' on' - run_command(command) + clicommon.run_command(command) @spanning_tree.command('interface') @click.argument('interface_name', metavar='', required=True) @@ -101,6 +100,6 @@ def stp_debug_intf(interface_name, disable): command = 'sudo stpctl dbg port ' + interface_name + ' off' else: command = 'sudo stpctl dbg port ' + interface_name + ' on' - run_command(command) + clicommon.run_command(command) diff --git a/show/stp.py b/show/stp.py index 77af4015f6..5cf47cf116 100644 --- a/show/stp.py +++ b/show/stp.py @@ -1,6 +1,5 @@ import re import click -import subprocess import utilities_common.cli as clicommon from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector From e9c26656dd38c953154419f460886721d291ad60 Mon Sep 17 00:00:00 2001 From: Praveen Madhusudhana Date: Fri, 23 Apr 2021 02:36:24 -0700 Subject: [PATCH 6/6] STP PR - Fixed lgtm warnings --- config/stp.py | 1 - tests/stp_test.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/config/stp.py b/config/stp.py index 47094379f1..76e951e7a5 100644 --- a/config/stp.py +++ b/config/stp.py @@ -5,7 +5,6 @@ import click import utilities_common.cli as clicommon -from swsscommon.swsscommon import SonicV2Connector from natsort import natsorted import logging diff --git a/tests/stp_test.py b/tests/stp_test.py index f60b9fb94a..dce5b5297e 100644 --- a/tests/stp_test.py +++ b/tests/stp_test.py @@ -1,6 +1,4 @@ import os -import traceback -from unittest import mock from click.testing import CliRunner import config.main as config