diff --git a/config/fabric.py b/config/fabric.py new file mode 100644 index 0000000000..a3870589ae --- /dev/null +++ b/config/fabric.py @@ -0,0 +1,247 @@ +import click +import utilities_common.cli as clicommon +import utilities_common.multi_asic as multi_asic_util +from sonic_py_common import multi_asic +from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector + +# +# 'config fabric ...' +# +@click.group(cls=clicommon.AbbreviationGroup) +def fabric(): + """FABRIC-related configuration tasks""" + pass + +# +# 'config fabric port ...' +# +@fabric.group(cls=clicommon.AbbreviationGroup) +def port(): + """FABRIC PORT configuration tasks""" + pass + +# +# 'config fabric port isolate [ -n ]' +# +@port.command() +@click.argument('portid', metavar='', required=True) +@multi_asic_util.multi_asic_click_option_namespace +def isolate(portid, namespace): + """FABRIC PORT isolate """ + + ctx = click.get_current_context() + + if not portid.isdigit(): + ctx.fail("Invalid portid") + + n_asics = multi_asic.get_num_asics() + if n_asics > 1 and namespace is None: + ctx.fail('Must specify asic') + + # Connect to config database + config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) + config_db.connect() + + # Connect to state database + state_db = SonicV2Connector(use_unix_socket_path=True, namespace=namespace) + state_db.connect(state_db.STATE_DB, False) + + # check if the port is actually in use + portName = f'PORT{portid}' + portStateData = state_db.get_all(state_db.STATE_DB, "FABRIC_PORT_TABLE|" + portName) + if "REMOTE_PORT" not in portStateData: + ctx.fail(f"Port {portid} is not in use") + + # Make sure configuration data exists + portName = f'Fabric{portid}' + portConfigData = config_db.get_all(config_db.CONFIG_DB, "FABRIC_PORT|" + portName) + if not bool(portConfigData): + ctx.fail("Fabric monitor configuration data not present") + + # Update entry + config_db.mod_entry("FABRIC_PORT", portName, {'isolateStatus': True}) + +# +# 'config fabric port unisolate [ -n ]' +# +@port.command() +@click.argument('portid', metavar='', required=True) +@multi_asic_util.multi_asic_click_option_namespace +def unisolate(portid, namespace): + """FABRIC PORT unisolate """ + + ctx = click.get_current_context() + + if not portid.isdigit(): + ctx.fail("Invalid portid") + + n_asics = multi_asic.get_num_asics() + if n_asics > 1 and namespace is None: + ctx.fail('Must specify asic') + + # Connect to config database + config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) + config_db.connect() + + # Connect to state database + state_db = SonicV2Connector(use_unix_socket_path=True, namespace=namespace) + state_db.connect(state_db.STATE_DB, False) + + # check if the port is actually in use + portName = f'PORT{portid}' + portStateData = state_db.get_all(state_db.STATE_DB, "FABRIC_PORT_TABLE|" + portName) + if "REMOTE_PORT" not in portStateData: + ctx.fail(f"Port {portid} is not in use") + + # Make sure configuration data exists + portName = f'Fabric{portid}' + portConfigData = config_db.get_all(config_db.CONFIG_DB, "FABRIC_PORT|" + portName) + if not bool(portConfigData): + ctx.fail("Fabric monitor configuration data not present") + + # Update entry + config_db.mod_entry("FABRIC_PORT", portName, {'isolateStatus': False}) + +# +# 'config fabric port monitor ...' +# +@port.group(cls=clicommon.AbbreviationGroup) +def monitor(): + """FABRIC PORT MONITOR configuration tasks""" + pass + +# +# 'config fabric port monitor error ...' +# +@monitor.group(cls=clicommon.AbbreviationGroup) +def error(): + """FABRIC PORT MONITOR ERROR configuration tasks""" + pass + +# +# 'config fabric port monitor error threshold ' +# +@error.command('threshold') +@click.argument('crcCells', metavar='', required=True, type=int) +@click.argument('rxcells', metavar='', required=True, type=int) +@multi_asic_util.multi_asic_click_option_namespace +def error_threshold(crccells, rxcells, namespace): + """FABRIC PORT MONITOR ERROR THRESHOLD configuration tasks""" + + ctx = click.get_current_context() + + n_asics = multi_asic.get_num_asics() + if n_asics > 1 and namespace is None: + ctx.fail('Must specify asic') + + # Check the values + if crccells < 1 or crccells > 1000: + ctx.fail("crcCells must be in range 1...1000") + if rxcells < 10000 or rxcells > 100000000: + ctx.fail("rxCells must be in range 10000...100000000") + + # Connect to config database + config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) + config_db.connect() + + # Connect to state database + state_db = SonicV2Connector(use_unix_socket_path=True, namespace=namespace) + state_db.connect(state_db.STATE_DB, False) + + # Make sure configuration data exists + monitorData = config_db.get_all(config_db.CONFIG_DB, "FABRIC_MONITOR|FABRIC_MONITOR_DATA") + if not bool(monitorData): + ctx.fail("Fabric monitor configuration data not present") + + # Update entry + config_db.mod_entry("FABRIC_MONITOR", "FABRIC_MONITOR_DATA", + {'monErrThreshCrcCells': crccells, 'monErrThreshRxCells': rxcells}) + +# +# 'config fabric port monitor poll ...' +# +@monitor.group(cls=clicommon.AbbreviationGroup) +def poll(): + """FABRIC PORT MONITOR POLL configuration tasks""" + pass + +# +# 'config fabric port monitor poll threshold ...' +# +@poll.group(cls=clicommon.AbbreviationGroup, name='threshold') +def poll_threshold(): + """FABRIC PORT MONITOR POLL THRESHOLD configuration tasks""" + pass + +# +# 'config fabric port monitor poll threshold isolation ' +# +@poll_threshold.command() +@click.argument('pollcount', metavar='', required=True, type=int) +@multi_asic_util.multi_asic_click_option_namespace +def isolation(pollcount, namespace): + """FABRIC PORT MONITOR POLL THRESHOLD configuration tasks""" + + ctx = click.get_current_context() + + n_asics = multi_asic.get_num_asics() + if n_asics > 1 and namespace is None: + ctx.fail('Must specify asic') + + if pollcount < 1 or pollcount > 10: + ctx.fail("pollCount must be in range 1...10") + + # Connect to config database + config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) + config_db.connect() + + # Connect to state database + state_db = SonicV2Connector(use_unix_socket_path=True, namespace=namespace) + state_db.connect(state_db.STATE_DB, False) + + # Make sure configuration data exists + monitorData = config_db.get_all(config_db.CONFIG_DB, "FABRIC_MONITOR|FABRIC_MONITOR_DATA") + if not bool(monitorData): + ctx.fail("Fabric monitor configuration data not present") + + # Update entry + config_db.mod_entry("FABRIC_MONITOR", "FABRIC_MONITOR_DATA", + {"monPollThreshIsolation": pollcount}) + + +# +# 'config fabric port monitor poll threshold recovery ' +# +@poll_threshold.command() +@click.argument('pollcount', metavar='', required=True, type=int) +@multi_asic_util.multi_asic_click_option_namespace +def recovery(pollcount, namespace): + """FABRIC PORT MONITOR POLL THRESHOLD configuration tasks""" + + ctx = click.get_current_context() + + n_asics = multi_asic.get_num_asics() + if n_asics > 1 and namespace is None: + ctx.fail('Must specify asic') + + if pollcount < 1 or pollcount > 10: + ctx.fail("pollCount must be in range 1...10") + + # Connect to config database + config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) + config_db.connect() + + # Connect to state database + state_db = SonicV2Connector(use_unix_socket_path=True, namespace=namespace) + state_db.connect(state_db.STATE_DB, False) + + # Make sure configuration data exists + monitorData = config_db.get_all(config_db.CONFIG_DB, "FABRIC_MONITOR|FABRIC_MONITOR_DATA") + if not bool(monitorData): + ctx.fail("Fabric monitor configuration data not present") + + # Update entry + config_db.mod_entry("FABRIC_MONITOR", "FABRIC_MONITOR_DATA", + {"monPollThreshRecovery": pollcount}) + + diff --git a/config/main.py b/config/main.py index 384e6f9f68..099e3e74f0 100644 --- a/config/main.py +++ b/config/main.py @@ -41,6 +41,7 @@ from . import chassis_modules from . import console from . import feature +from . import fabric from . import flow_counters from . import kdump from . import kube @@ -1229,6 +1230,7 @@ def config(ctx): config.add_command(aaa.radius) config.add_command(chassis_modules.chassis) config.add_command(console.console) +config.add_command(fabric.fabric) config.add_command(feature.feature) config.add_command(flow_counters.flowcnt_route) config.add_command(kdump.kdump) diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 69f282ccbb..560d237cd9 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -53,6 +53,8 @@ * [ECN](#ecn) * [ECN show commands](#ecn-show-commands) * [ECN config commands](#ecn-config-commands) +* [Fabric](#fabric) + * [Fabric config commands](#fabric-config-commands) * [Feature](#feature) * [Feature show commands](#feature-show-commands) * [Feature config commands](#feature-config-commands) @@ -3257,6 +3259,71 @@ The list of the WRED profile fields that are configurable is listed in the below Go Back To [Beginning of the document](#) or [Beginning of this section](#ecn) +## Fabric + +This section explains all Fabric commands that are supported in SONiC. + +### Fabric config commands + +**config fabric port isolate ** +**config fabric port unisolate ** + +The above two commands can be used to manually isolate and unisolate a fabric link. + +- Usage: + ``` + config fabric port isolate [OPTIONS] + config fabric port unisolate [OPTIONS] + ``` + +- Example: + ``` + admin@sonic:~$ config fabric port isolate 0 -n asic0 + admin@sonic:~$ config fabric port unisolate 0 -n asic0 + ``` + +**config fabric port monitor error threshold ** + +This command sets a fabric link monitoring error threshold + +- Usage: + ``` + config fabric port monitor error threshold [OPTIONS] + ``` + +- Example: + ``` + admin@sonic:~$ config fabric port monitor error threshold 2 61035156 -n asic0 + ``` + +**config fabric port monitor poll threshold isolation ** + +This command sets the number of consecutive polls in which the threshold needs to be detected to isolate a link + +- Usage: + ``` + config fabric port monitor poll threshold isolation [OPTIONS] + ``` + +- Example: + ``` + admin@sonic:~$ config fabric port monitor poll threshold isolation 2 -n asic0 + ``` + +**config fabric port monitor poll threshold recovery ** + +This command sets the number of consecutive polls in which no error is detected to unisolate a link + +- Usage: + ``` + config fabric port monitor poll threshold recovery [OPTIONS] + ``` + +- Example: + ``` + admin@sonic:~$ config fabric port monitor poll threshold recovery 5 -n asic0 + ``` + ## Feature SONiC includes a capability in which Feature state can be enabled/disabled diff --git a/tests/config_fabric_test.py b/tests/config_fabric_test.py new file mode 100644 index 0000000000..1f56ea416a --- /dev/null +++ b/tests/config_fabric_test.py @@ -0,0 +1,95 @@ +import click +import config.main as config +import operator +import os +import pytest +import sys + +from click.testing import CliRunner +from utilities_common.db import Db + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +scripts_path = os.path.join(modules_path, "scripts") +sys.path.insert(0, modules_path) + +@pytest.fixture(scope='module') +def ctx(scope='module'): + db = Db() + obj = {'config_db':db.cfgdb, 'namespace': ''} + yield obj + +class TestConfigFabric(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + def basic_check(self, command_name, para_list, ctx): + # This function issues command of "config fabric xxxx", + # and returns the result of the command. + runner = CliRunner() + result = runner.invoke(config.config.commands["fabric"].commands[command_name], para_list, obj = ctx) + print(result.output) + return result + + def test_config_isolation(self, ctx): + # Issue command "config fabric port isolate 0", + # check if the result is expected. + result = self.basic_check("port", ["isolate", "0"], ctx) + expect_result = 0 + assert operator.eq(result.exit_code, expect_result) + + # Issue command "config fabric port isolate 1", + # check if the result has the error message as port 1 is not in use. + result = self.basic_check("port", ["isolate", "1"], ctx) + assert "Port 1 is not in use" in result.output + + # Issue command "config fabric port unisolate 0", + # check if the result is expected. + result = self.basic_check("port", ["unisolate", "0"], ctx) + expect_result = 0 + assert operator.eq(result.exit_code, expect_result) + + # Issue command "config fabric port unisolate 1", + # check if the result has the error message as port 1 is not in use. + result = self.basic_check("port", ["unisolate", "1"], ctx) + assert "Port 1 is not in use" in result.output + + def test_config_fabric_monitor_threshold(self, ctx): + # Issue command "config fabric port monitor error threshold <#> <#>" + # with an out of range number, check if the result has the error message. + result = self.basic_check("port", ["monitor", "error", "threshold", "1", "2000"], ctx) + assert "rxCells must be in range 10000...100000000" in result.output + + result = self.basic_check("port", ["monitor", "error", "threshold", "10000", "20000"], ctx) + assert "crcCells must be in range 1...1000" in result.output + + # Issue command "config fabric port monitor error threshold <#> <#>" + # with a number in the range, check if the result is expected. + result = self.basic_check("port", ["monitor", "error", "threshold", "1", "20000"], ctx) + expect_result = 0 + assert operator.eq(result.exit_code, expect_result) + + # Issue command "config fabric port monitor poll threshold isolation <#>" + # with an out of range number, check if the result has the error message. + result = self.basic_check("port", ["monitor", "poll", "threshold", "isolation", "15"], ctx) + assert "pollCount must be in range 1...10" in result.output + + # Issue command "config fabric port monitor poll threshold isolation <#>" + # with a number in the range, check if the result is expected. + result = self.basic_check("port", ["monitor", "poll", "threshold", "isolation", "3"], ctx) + expect_result = 0 + assert operator.eq(result.exit_code, expect_result) + + # Issue command "config fabric port monitor poll threshold recovery <#>" + # with an out of range number, check if the result has the error message. + result = self.basic_check("port", ["monitor", "poll", "threshold", "recovery", "15"], ctx) + assert "pollCount must be in range 1...10" in result.output + + # Issue command "config fabric port monitor poll threshold recovery <#>" + # with a number in the range, check if the result is expected. + result = self.basic_check("port", ["monitor", "poll", "threshold", "recovery", "8"], ctx) + expect_result = 0 + assert operator.eq(result.exit_code, expect_result) diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 3a2b681a6e..636d9abcbd 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -2629,5 +2629,26 @@ "dst_port": "Ethernet44", "src_port": "Ethernet40,Ethernet48", "direction": "RX" + }, + "FABRIC_MONITOR|FABRIC_MONITOR_DATA": { + "monErrThreshCrcCells": "1", + "monErrThreshRxCells": "61035156", + "monPollThreshIsolation": "1", + "monPollThreshRecovery": "8" + }, + "FABRIC_PORT|Fabric0": { + "alias": "Fabric0", + "isolateStatus": "False", + "lanes": "0" + }, + "FABRIC_PORT|Fabric1": { + "alias": "Fabric1", + "isolateStatus": "False", + "lanes": "1" + }, + "FABRIC_PORT|Fabric2": { + "alias": "Fabric2", + "isolateStatus": "False", + "lanes": "2" } }