Skip to content

Commit

Permalink
Fix multi-asic support to PFC config/show (sonic-net#3521)
Browse files Browse the repository at this point in the history
* Add multi-asic support to pfc

- Add namespace arg for show and config cmds for pfc
- Replace test DB with JSON to support verification of multiple namespaces in unit test
- Add unit tests for multi-asic behaviour
- Added a test vector file for better test organization

* Fix linter errors
  • Loading branch information
arista-hpandya authored and mssonicbld committed Sep 20, 2024
1 parent 2fd5f15 commit 567d47b
Show file tree
Hide file tree
Showing 6 changed files with 554 additions and 73 deletions.
18 changes: 14 additions & 4 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6446,8 +6446,9 @@ def pfc(ctx):
@pfc.command()
@click.argument('interface_name', metavar='<interface_name>', required=True)
@click.argument('status', type=click.Choice(['on', 'off']))
@multi_asic_util.multi_asic_click_option_namespace
@click.pass_context
def asymmetric(ctx, interface_name, status):
def asymmetric(ctx, interface_name, status, namespace):
"""Set asymmetric PFC configuration."""
# Get the config_db connector
config_db = ctx.obj['config_db']
Expand All @@ -6457,7 +6458,11 @@ def asymmetric(ctx, interface_name, status):
if interface_name is None:
ctx.fail("'interface_name' is None!")

clicommon.run_command(['pfc', 'config', 'asymmetric', str(status), str(interface_name)])
cmd = ['pfc', 'config', 'asymmetric', str(status), str(interface_name)]
if namespace is not None:
cmd += ['-n', str(namespace)]

clicommon.run_command(cmd)

#
# 'pfc priority' command ('config interface pfc priority ...')
Expand All @@ -6467,8 +6472,9 @@ def asymmetric(ctx, interface_name, status):
@click.argument('interface_name', metavar='<interface_name>', required=True)
@click.argument('priority', type=click.Choice([str(x) for x in range(8)]))
@click.argument('status', type=click.Choice(['on', 'off']))
@multi_asic_util.multi_asic_click_option_namespace
@click.pass_context
def priority(ctx, interface_name, priority, status):
def priority(ctx, interface_name, priority, status, namespace):
"""Set PFC priority configuration."""
# Get the config_db connector
config_db = ctx.obj['config_db']
Expand All @@ -6478,7 +6484,11 @@ def priority(ctx, interface_name, priority, status):
if interface_name is None:
ctx.fail("'interface_name' is None!")

clicommon.run_command(['pfc', 'config', 'priority', str(status), str(interface_name), str(priority)])
cmd = ['pfc', 'config', 'priority', str(status), str(interface_name), str(priority)]
if namespace is not None:
cmd += ['-n', str(namespace)]

clicommon.run_command(cmd)

#
# 'buffer' group ('config buffer ...')
Expand Down
132 changes: 78 additions & 54 deletions pfc/main.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,63 @@
#!/usr/bin/env python3
import os
import click
from swsscommon.swsscommon import ConfigDBConnector
import json
from sonic_py_common import multi_asic
from tabulate import tabulate
from natsort import natsorted
from utilities_common import multi_asic as multi_asic_util

# Constants
ALL_PRIORITIES = [str(x) for x in range(8)]
PRIORITY_STATUS = ['on', 'off']
PORT_TABLE_NAME = "PORT"
PORT_QOS_MAP_TABLE_NAME = "PORT_QOS_MAP"


class Pfc(object):
def __init__(self, cfgdb=None):
self.cfgdb = cfgdb
def __init__(self, namespace=None):
self.multi_asic = multi_asic_util.MultiAsic(namespace_option=namespace)
self.config_db = None

# For unit testing
self.updated_port_tables = {}
self.test_filename = '/tmp/pfc_testdata.json'

def dump_config_to_json(self, table_name, namespace):
"""
This function dumps the current config in a JSON file for unit testing.
"""
# Only dump files in unit testing mode
if os.environ["UTILITIES_UNIT_TESTING"] != "2":
return

if namespace not in self.updated_port_tables.keys():
self.updated_port_tables[namespace] = {}

self.updated_port_tables[namespace][table_name] = self.config_db.get_table(table_name)
with open(self.test_filename, "w") as fd:
json.dump(self.updated_port_tables, fd)

@multi_asic_util.run_on_multi_asic
def configPfcAsym(self, interface, pfc_asym):
"""
PFC handler to configure asymmetric PFC.
"""
configdb = ConfigDBConnector() if self.cfgdb is None else self.cfgdb
configdb.connect()

configdb.mod_entry("PORT", interface, {'pfc_asym': pfc_asym})
self.config_db.mod_entry(PORT_TABLE_NAME, interface, {'pfc_asym': pfc_asym})
self.dump_config_to_json(PORT_TABLE_NAME, self.multi_asic.current_namespace)

@multi_asic_util.run_on_multi_asic
def showPfcAsym(self, interface):
"""
PFC handler to display asymmetric PFC information.
"""
namespace_str = f"Namespace {self.multi_asic.current_namespace}" if multi_asic.is_multi_asic() else ''
header = ('Interface', 'Asymmetric')

configdb = ConfigDBConnector() if self.cfgdb is None else self.cfgdb
configdb.connect()

if interface:
db_keys = configdb.keys(configdb.CONFIG_DB, 'PORT|{0}'.format(interface))
db_keys = self.config_db.keys(self.config_db.CONFIG_DB, 'PORT|{0}'.format(interface))
else:
db_keys = configdb.keys(configdb.CONFIG_DB, 'PORT|*')
db_keys = self.config_db.keys(self.config_db.CONFIG_DB, 'PORT|*')

table = []

Expand All @@ -43,36 +67,35 @@ def showPfcAsym(self, interface):
key = i.split('|')[-1]

if key and key.startswith('Ethernet'):
entry = configdb.get_entry('PORT', key)
entry = self.config_db.get_entry(PORT_TABLE_NAME, key)
table.append([key, entry.get('pfc_asym', 'N/A')])

sorted_table = natsorted(table)

click.echo()
click.echo(namespace_str)
click.echo(tabulate(sorted_table, headers=header, tablefmt="simple", missingval=""))
click.echo()

@multi_asic_util.run_on_multi_asic
def configPfcPrio(self, status, interface, priority):
configdb = ConfigDBConnector() if self.cfgdb is None else self.cfgdb
configdb.connect()

if interface not in configdb.get_keys('PORT_QOS_MAP'):
if interface not in self.config_db.get_keys(PORT_QOS_MAP_TABLE_NAME):
click.echo('Cannot find interface {0}'.format(interface))
return

"""Current lossless priorities on the interface"""
entry = configdb.get_entry('PORT_QOS_MAP', interface)
entry = self.config_db.get_entry('PORT_QOS_MAP', interface)
enable_prio = entry.get('pfc_enable').split(',')

"""Avoid '' in enable_prio"""
enable_prio = [x.strip() for x in enable_prio if x.strip()]

namespace_str = f" for namespace {self.multi_asic.current_namespace}" if multi_asic.is_multi_asic() else ''
if status == 'on' and priority in enable_prio:
click.echo('Priority {0} has already been enabled on {1}'.format(priority, interface))
click.echo('Priority {0} has already been enabled on {1}{2}'.format(priority, interface, namespace_str))
return

if status == 'off' and priority not in enable_prio:
click.echo('Priority {0} is not enabled on {1}'.format(priority, interface))
click.echo('Priority {0} is not enabled on {1}{2}'.format(priority, interface, namespace_str))
return

if status == 'on':
Expand All @@ -82,92 +105,93 @@ def configPfcPrio(self, status, interface, priority):
enable_prio.remove(priority)

enable_prio.sort()
configdb.mod_entry("PORT_QOS_MAP", interface, {'pfc_enable': ','.join(enable_prio)})
self.config_db.mod_entry(PORT_QOS_MAP_TABLE_NAME, interface, {'pfc_enable': ','.join(enable_prio)})
self.dump_config_to_json(PORT_QOS_MAP_TABLE_NAME, self.multi_asic.current_namespace)

"""Show the latest PFC configuration"""
self.showPfcPrio(interface)

@multi_asic_util.run_on_multi_asic
def showPfcPrio(self, interface):
"""
PFC handler to display PFC enabled priority information.
"""
header = ('Interface', 'Lossless priorities')
table = []

configdb = ConfigDBConnector() if self.cfgdb is None else self.cfgdb
configdb.connect()

"""Get all the interfaces with QoS map information"""
intfs = configdb.get_keys('PORT_QOS_MAP')
intfs = self.config_db.get_keys('PORT_QOS_MAP')

"""The user specifies an interface but we cannot find it"""
namespace_str = f"Namespace {self.multi_asic.current_namespace}" if multi_asic.is_multi_asic() else ''
if interface and interface not in intfs:
click.echo('Cannot find interface {0}'.format(interface))
if multi_asic.is_multi_asic():
click.echo('Cannot find interface {0} for {1}'.format(interface, namespace_str))
else:
click.echo('Cannot find interface {0}'.format(interface))
return

if interface:
intfs = [interface]

for intf in intfs:
entry = configdb.get_entry('PORT_QOS_MAP', intf)
entry = self.config_db.get_entry('PORT_QOS_MAP', intf)
table.append([intf, entry.get('pfc_enable', 'N/A')])

sorted_table = natsorted(table)
click.echo()
click.echo(namespace_str)
click.echo(tabulate(sorted_table, headers=header, tablefmt="simple", missingval=""))
click.echo()



@click.group()
@click.pass_context
def cli(ctx):
def cli():
"""PFC Command Line"""
# Use the cfgdb object if given as input.
cfgdb = None if ctx.obj is None else ctx.obj.cfgdb

ctx.obj = {'pfc': Pfc(cfgdb)}

@cli.group()
@click.pass_context
def config(ctx):
def config():
"""Config PFC"""
pass


@cli.group()
@click.pass_context
def show(ctx):
def show():
"""Show PFC information"""
pass


@click.command()
@click.argument('status', type=click.Choice(PRIORITY_STATUS))
@click.argument('interface', type=click.STRING)
@click.pass_context
def configAsym(ctx, status, interface):
@multi_asic_util.multi_asic_click_option_namespace
def configAsym(status, interface, namespace):
"""Configure asymmetric PFC on a given port."""
ctx.obj['pfc'].configPfcAsym(interface, status)
Pfc(namespace).configPfcAsym(interface, status)


@click.command()
@click.argument('status', type=click.Choice(PRIORITY_STATUS))
@click.argument('interface', type=click.STRING)
@click.argument('priority', type=click.Choice(ALL_PRIORITIES))
@click.pass_context
def configPrio(ctx, status, interface, priority):
@multi_asic_util.multi_asic_click_option_namespace
def configPrio(status, interface, priority, namespace):
"""Configure PFC on a given priority."""
ctx.obj['pfc'].configPfcPrio(status, interface, priority)
Pfc(namespace).configPfcPrio(status, interface, priority)


@click.command()
@click.argument('interface', type=click.STRING, required=False)
@click.pass_context
def showAsym(ctx, interface):
@multi_asic_util.multi_asic_click_option_namespace
def showAsym(interface, namespace):
"""Show asymmetric PFC information"""
ctx.obj['pfc'].showPfcAsym(interface)
Pfc(namespace).showPfcAsym(interface)


@click.command()
@click.argument('interface', type=click.STRING, required=False)
@click.pass_context
def showPrio(ctx, interface):
@multi_asic_util.multi_asic_click_option_namespace
def showPrio(interface, namespace):
"""Show PFC priority information"""
ctx.obj['pfc'].showPfcPrio(interface)
Pfc(namespace).showPfcPrio(interface)


config.add_command(configAsym, "asymmetric")
config.add_command(configPrio, "priority")
Expand Down
10 changes: 8 additions & 2 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,27 +645,33 @@ def counters(namespace, display, verbose):

@pfc.command()
@click.argument('interface', type=click.STRING, required=False)
def priority(interface):
@multi_asic_util.multi_asic_click_option_namespace
def priority(interface, namespace):
"""Show pfc priority"""
cmd = ['pfc', 'show', 'priority']
if interface is not None and clicommon.get_interface_naming_mode() == "alias":
interface = iface_alias_converter.alias_to_name(interface)

if interface is not None:
cmd += [str(interface)]
if namespace is not None:
cmd += ['-n', str(namespace)]

run_command(cmd)

@pfc.command()
@click.argument('interface', type=click.STRING, required=False)
def asymmetric(interface):
@multi_asic_util.multi_asic_click_option_namespace
def asymmetric(interface, namespace):
"""Show asymmetric pfc"""
cmd = ['pfc', 'show', 'asymmetric']
if interface is not None and clicommon.get_interface_naming_mode() == "alias":
interface = iface_alias_converter.alias_to_name(interface)

if interface is not None:
cmd += [str(interface)]
if namespace is not None:
cmd += ['-n', str(namespace)]

run_command(cmd)

Expand Down
Loading

0 comments on commit 567d47b

Please sign in to comment.