Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions clear/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from utilities_common import util_base
from show.plugins.pbh import read_pbh_counters
from config.plugins.pbh import serialize_pbh_counters
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector
from . import plugins
from . import stp
# This is from the aliases example:
Expand Down Expand Up @@ -763,5 +764,61 @@ def asic_sdk_health_event(db, namespace):
state_db.delete(state_db.STATE_DB, key);


#
# 'interfaces' group ("sonic-clear interfaces ...")
#

@cli.group(cls=AliasedGroup)
def interfaces():
"""Clear interface-related state"""
pass


@interfaces.command()
@click.argument('interface_name', metavar='<interface_name>', required=False)
def dampening(interface_name):
"""Clear link event dampening state (reset penalty to 0).

Without an interface name, clears dampening on all interfaces.
The interface is immediately unsuppressed if currently damped.
"""
config_db = ConfigDBConnector()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

host CONFIG_DB only — on multi-ASIC, PORT lives in per-namespace CONFIG_DB and that's where orchagent reads. Same issue with ctx.obj['config_db'] in the enable/disable commands. See how config interface ip add resolves the namespace via multi_asic.get_port_namespace(port) / db.cfgdb_clients[ns].

config_db.connect()
port_table = config_db.get_table("PORT")

if interface_name:
if clicommon.get_interface_naming_mode() == "alias":
alias = interface_name
interface_name = clicommon.InterfaceAliasConverter().alias_to_name(interface_name)
if interface_name == alias:
click.echo("Error: invalid interface alias {}".format(alias))
sys.exit(1)
if interface_name not in port_table:
click.echo("Error: Interface {} does not exist".format(interface_name))
sys.exit(1)
Comment on lines +794 to +798
ports_to_clear = [interface_name]
else:
ports_to_clear = []
for port_name, port_data in port_table.items():
algo = port_data.get("link_event_damping_algorithm", "disabled")
if algo != "disabled":
ports_to_clear.append(port_name)

if not ports_to_clear:
click.echo("No interfaces have dampening configured")
return

state_db = SonicV2Connector(host="127.0.0.1")
state_db.connect(state_db.STATE_DB)

Comment on lines +811 to +813
for port_name in ports_to_clear:
state_key = "CLEAR_DAMPENING|{}".format(port_name)
state_db.set(state_db.STATE_DB, state_key, "clear", "true")
click.echo("Cleared dampening on {}".format(port_name))

if not interface_name:
click.echo("Cleared dampening on {} interface(s)".format(len(ports_to_clear)))


if __name__ == '__main__':
cli()
118 changes: 118 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6349,6 +6349,124 @@ def cable_length(ctx, interface_name, length):
except ValueError as e:
ctx.fail("Invalid ConfigDB. Error: {}".format(e))


#
# 'dampening' subgroup ('config interface dampening ...')
#


@interface.group(cls=clicommon.AbbreviationGroup)
@click.pass_context
def dampening(ctx):
"""Configure link event dampening on an interface"""
pass


@dampening.command()
@click.argument('interface_name', metavar='<interface_name>', required=True)
@click.option('--half-life', type=click.IntRange(min=1, max=3600), default=5,
show_default=True,
help='Decay half-life in seconds (1-3600).')
@click.option('--reuse', type=click.IntRange(min=1, max=20000), default=1000,
show_default=True,
help='Reuse threshold (1-20000).')
@click.option('--suppress', type=click.IntRange(min=1, max=20000), default=2000,
show_default=True,
help='Suppress threshold (1-20000).')
@click.option('--max-suppress-time', type=click.IntRange(min=1, max=3600), default=20,
show_default=True,
help='Max suppress time in seconds (1-3600).')
@click.option('--flap-penalty', type=click.IntRange(min=1, max=20000), default=1000,
show_default=True,
help='Penalty per link-down event (1-20000).')
@click.option('--monitor', is_flag=True, default=False,
help='Monitor-only mode: calculate penalties and emit syslog '
'but do NOT suppress events. Use to safely tune parameters '
'in production before enabling full dampening.')
@click.pass_context
def enable(ctx, interface_name, half_life, reuse, suppress, max_suppress_time,
flap_penalty, monitor):
"""Enable link event dampening on an interface with AIED algorithm."""
config_db = ctx.obj['config_db']

if clicommon.get_interface_naming_mode() == "alias":
interface_name = interface_alias_to_name(config_db, interface_name)
if interface_name is None:
ctx.fail("'interface_name' is None!")

Comment on lines +6392 to +6396
# Validate interface exists in PORT table
port_table = config_db.get_table("PORT")
if interface_name not in port_table:
ctx.fail("Interface {} does not exist".format(interface_name))

# Validate parameter relationships
if reuse >= suppress:
ctx.fail("Reuse threshold ({}) must be less than suppress threshold ({})".format(
reuse, suppress))

if half_life > max_suppress_time:
ctx.fail("Half-life ({}) must not exceed max-suppress-time ({})".format(
half_life, max_suppress_time))

# Calculate ceiling and warn if very high
exponent = max_suppress_time / half_life
if exponent > 50:
click.echo("Warning: calculated penalty ceiling is extremely high "
"(exponent={:.0f}). Consider reducing max-suppress-time or "
"increasing half-life.".format(exponent))
else:
ceiling = reuse * (2 ** exponent)
if ceiling > 100000:
click.echo("Warning: calculated penalty ceiling is very high ({:.0f}). "
"Consider reducing max-suppress-time or increasing reuse "
"threshold.".format(ceiling))

algorithm = "aied-monitor" if monitor else "aied"

config_db.mod_entry("PORT", interface_name, {
"link_event_damping_algorithm": algorithm,
"decay_half_life": str(half_life),
"reuse_threshold": str(reuse),
"suppress_threshold": str(suppress),
"max_suppress_time": str(max_suppress_time),
"flap_penalty": str(flap_penalty),
})

mode_str = "monitor-only" if monitor else "active"
click.echo("Link event dampening enabled on {} (algorithm={}, mode={}, half-life={}s, "
"reuse={}, suppress={}, max-suppress={}s, penalty={})".format(
interface_name, algorithm, mode_str, half_life, reuse, suppress,
max_suppress_time, flap_penalty))


@dampening.command()
@click.argument('interface_name', metavar='<interface_name>', required=True)
@click.pass_context
def disable(ctx, interface_name):
"""Disable link event dampening on an interface."""
config_db = ctx.obj['config_db']

if clicommon.get_interface_naming_mode() == "alias":
interface_name = interface_alias_to_name(config_db, interface_name)
if interface_name is None:
ctx.fail("'interface_name' is None!")

Comment on lines +6449 to +6453
port_table = config_db.get_table("PORT")
if interface_name not in port_table:
ctx.fail("Interface {} does not exist".format(interface_name))

config_db.mod_entry("PORT", interface_name, {
"link_event_damping_algorithm": "disabled",
"decay_half_life": "0",
"reuse_threshold": "0",
"suppress_threshold": "0",
"max_suppress_time": "0",
"flap_penalty": "0",
})

click.echo("Link event dampening disabled on {}".format(interface_name))


#
# 'transceiver' subgroup ('config interface transceiver ...')
#
Expand Down
140 changes: 140 additions & 0 deletions show/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,146 @@ def display_phy_taps_attribute(attr_display_name, attr_json):
click.echo("")


#
# 'dampening' subcommand ("show interfaces dampening")
#
@interfaces.command()
@click.argument('interfacename', required=False)
@clicommon.pass_db
def dampening(db, interfacename):
"""Show link event dampening configuration and operational state"""

ctx = click.get_current_context()

if interfacename:
interfacename = try_convert_interfacename_from_alias(ctx, interfacename)

config_db = db.cfgdb
state_db = db.db

Comment on lines +1463 to +1465
port_table = config_db.get_table("PORT")

if interfacename:
if interfacename not in port_table:
ctx.fail("Interface {} does not exist".format(interfacename))
ports = {interfacename: port_table[interfacename]}
else:
ports = port_table

header = [
"Interface",
"Algorithm",
"Half-Life(s)",
"Reuse",
"Suppress",
"Max-Suppress(s)",
"Penalty",
"Flap-Penalty",
"Suppressed",
"Time-Left(s)",
]

rows = []
for port_name in natsorted(ports.keys()):
port_data = ports[port_name]
algorithm = port_data.get("link_event_damping_algorithm", "disabled")

if algorithm == "disabled":
# Only show this port if specifically requested
if interfacename:
rows.append([
port_name,
"disabled",
"-", "-", "-", "-", "-", "-", "-", "-"
])
continue

is_monitor = (algorithm == "aied-monitor")

half_life = port_data.get("decay_half_life", "0")
reuse = port_data.get("reuse_threshold", "0")
suppress = port_data.get("suppress_threshold", "0")
max_suppress = port_data.get("max_suppress_time", "0")
flap_penalty = port_data.get("flap_penalty", "1000")

# Read operational state from STATE_DB
state_key = "PORT_TABLE|{}".format(port_name)
current_penalty = "N/A"
suppressed = "N/A"
time_remaining = "N/A"

if state_db:
penalty_val = state_db.get(state_db.STATE_DB, state_key,
"damping_current_penalty")
suppressed_val = state_db.get(state_db.STATE_DB, state_key,
"damping_suppressed")
time_val = state_db.get(state_db.STATE_DB, state_key,
"damping_time_remaining")

if penalty_val:
current_penalty = penalty_val
if suppressed_val:
if is_monitor and suppressed_val == "true":
suppressed = "(Mon)"
else:
suppressed = "Yes" if suppressed_val == "true" else "No"
if time_val:
time_remaining = time_val if suppressed == "Yes" else "-"

rows.append([
port_name,
algorithm,
half_life,
reuse,
suppress,
max_suppress,
current_penalty,
flap_penalty,
suppressed,
time_remaining,
])

if not rows:
if interfacename:
click.echo("Link event dampening is not configured on {}".format(
interfacename))
else:
click.echo("Link event dampening is not configured on any interface")
return

click.echo(tabulate(rows, header, tablefmt="simple"))
click.echo("")

# Show counters if a specific interface is queried
if interfacename and state_db:
_show_dampening_counters(state_db, interfacename)


def _show_dampening_counters(state_db, interface_name):
"""Display per-interface dampening event counters."""
state_key = "PORT_TABLE|{}".format(interface_name)

counter_fields = [
("damping_pre_transitions", "Pre-damping transitions (total)"),
("damping_post_transitions", "Post-damping transitions (total)"),
("damping_pre_up_transitions", "Pre-damping UP transitions"),
("damping_pre_down_transitions", "Pre-damping DOWN transitions"),
("damping_post_up_transitions", "Post-damping UP transitions"),
("damping_post_down_transitions", "Post-damping DOWN transitions"),
]

counter_rows = []
for field, description in counter_fields:
value = state_db.get(state_db.STATE_DB, state_key, field)
if value:
counter_rows.append([description, value])

if counter_rows:
click.echo("Dampening Counters for {}:".format(interface_name))
click.echo(tabulate(counter_rows, ["Counter", "Value"], tablefmt="simple"))
click.echo("")


@interfaces.command('phy-serdes')
@click.argument('interfacename', required=True)
@multi_asic_util.multi_asic_click_options
Expand Down
Loading
Loading