diff --git a/config/main.py b/config/main.py index 80459b9eef90..1b2678a4efe6 100644 --- a/config/main.py +++ b/config/main.py @@ -608,6 +608,27 @@ def _change_hostname(hostname): clicommon.run_command(r'sed -i "/\s{}$/d" /etc/hosts'.format(current_hostname), display_cmd=True) clicommon.run_command('echo "127.0.0.1 {}" >> /etc/hosts'.format(hostname), display_cmd=True) +def _clear_cbf(): + CBF_TABLE_NAMES = [ + 'DSCP_TO_FC_MAP', + 'EXP_TO_FC_MAP'] + + namespace_list = [DEFAULT_NAMESPACE] + if multi_asic.get_num_asics() > 1: + namespace_list = multi_asic.get_namespaces_from_linux() + + for ns in namespace_list: + if ns is DEFAULT_NAMESPACE: + config_db = ConfigDBConnector() + else: + config_db = ConfigDBConnector( + use_unix_socket_path=True, namespace=ns + ) + config_db.connect() + for cbf_table in CBF_TABLE_NAMES: + config_db.delete_table(cbf_table) + + def _clear_qos(): QOS_TABLE_NAMES = [ 'TC_TO_PRIORITY_GROUP_MAP', @@ -2061,6 +2082,83 @@ def start_default(verbose): clicommon.run_command(cmd, display_cmd=verbose) +# +# 'cbf' group ('config cbf ...') +# +@config.group(cls=clicommon.AbbreviationGroup) +@click.pass_context +def cbf(ctx): + """CBF-related configuration tasks""" + pass + +@cbf.command('clear') +def clear(): + """Clear CBF configuration""" + log.log_info("'cbf clear' executing...") + _clear_cbf() + +@cbf.command('reload') +@click.pass_context +@click.option( + '--json-data', type=click.STRING, + help="json string with additional data, valid with --dry-run option" +) +@click.option( + '--dry_run', type=click.STRING, + help="Dry run, writes config to the given file" +) +def reload(ctx, dry_run, json_data): + """Reload CBF configuration""" + log.log_info("'cbf reload' executing...") + _clear_cbf() + + _, hwsku_path = device_info.get_paths_to_platform_and_hwsku_dirs() + sonic_version_file = device_info.get_sonic_version_file() + from_db = "-d --write-to-db" + if dry_run: + from_db = "--additional-data \'{}\'".format(json_data) if json_data else "" + + namespace_list = [DEFAULT_NAMESPACE] + if multi_asic.get_num_asics() > 1: + namespace_list = multi_asic.get_namespaces_from_linux() + + for ns in namespace_list: + if ns is DEFAULT_NAMESPACE: + asic_id_suffix = "" + config_db = ConfigDBConnector() + else: + asic_id = multi_asic.get_asic_id_from_name(ns) + if asic_id is None: + click.secho( + "Command 'cbf reload' failed with invalid namespace '{}'". + format(ns), + fg="yellow" + ) + raise click.Abort() + asic_id_suffix = str(asic_id) + + config_db = ConfigDBConnector( + use_unix_socket_path=True, namespace=ns + ) + + config_db.connect() + + cbf_template_file = os.path.join(hwsku_path, asic_id_suffix, "cbf.json.j2") + if os.path.isfile(cbf_template_file): + cmd_ns = "" if ns is DEFAULT_NAMESPACE else "-n {}".format(ns) + fname = "{}{}".format(dry_run, asic_id_suffix) if dry_run else "config-db" + command = "{} {} {} -t {},{} -y {}".format( + SONIC_CFGGEN_PATH, cmd_ns, from_db, + cbf_template_file, fname, sonic_version_file + ) + + # Apply the configuration + clicommon.run_command(command, display_cmd=True) + else: + click.secho("CBF definition template not found at {}".format( + cbf_template_file + ), fg="yellow") + # # 'qos' group ('config qos ...') # diff --git a/tests/cbf_config_input/0/cbf.json.j2 b/tests/cbf_config_input/0/cbf.json.j2 new file mode 100644 index 000000000000..1ccf88f98c61 --- /dev/null +++ b/tests/cbf_config_input/0/cbf.json.j2 @@ -0,0 +1 @@ +{%- include 'cbf_config.j2' %} \ No newline at end of file diff --git a/tests/cbf_config_input/0/cbf_config.j2 b/tests/cbf_config_input/0/cbf_config.j2 new file mode 100644 index 000000000000..894729f66f2c --- /dev/null +++ b/tests/cbf_config_input/0/cbf_config.j2 @@ -0,0 +1,82 @@ +{ + "DSCP_TO_FC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "EXP_TO_FC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + } +} \ No newline at end of file diff --git a/tests/cbf_config_input/0/config_cbf.json b/tests/cbf_config_input/0/config_cbf.json new file mode 100644 index 000000000000..ff465f1ca29d --- /dev/null +++ b/tests/cbf_config_input/0/config_cbf.json @@ -0,0 +1,82 @@ +{ + "DSCP_TO_FC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "EXP_TO_FC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + } +} diff --git a/tests/cbf_config_input/1/cbf.json.j2 b/tests/cbf_config_input/1/cbf.json.j2 new file mode 100644 index 000000000000..1ccf88f98c61 --- /dev/null +++ b/tests/cbf_config_input/1/cbf.json.j2 @@ -0,0 +1 @@ +{%- include 'cbf_config.j2' %} \ No newline at end of file diff --git a/tests/cbf_config_input/1/cbf_config.j2 b/tests/cbf_config_input/1/cbf_config.j2 new file mode 100644 index 000000000000..894729f66f2c --- /dev/null +++ b/tests/cbf_config_input/1/cbf_config.j2 @@ -0,0 +1,82 @@ +{ + "DSCP_TO_FC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "EXP_TO_FC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + } +} \ No newline at end of file diff --git a/tests/cbf_config_input/1/config_cbf.json b/tests/cbf_config_input/1/config_cbf.json new file mode 100644 index 000000000000..ff465f1ca29d --- /dev/null +++ b/tests/cbf_config_input/1/config_cbf.json @@ -0,0 +1,82 @@ +{ + "DSCP_TO_FC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "EXP_TO_FC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + } +} diff --git a/tests/cbf_config_input/cbf.json.j2 b/tests/cbf_config_input/cbf.json.j2 new file mode 100644 index 000000000000..1ccf88f98c61 --- /dev/null +++ b/tests/cbf_config_input/cbf.json.j2 @@ -0,0 +1 @@ +{%- include 'cbf_config.j2' %} \ No newline at end of file diff --git a/tests/cbf_config_input/cbf_config.j2 b/tests/cbf_config_input/cbf_config.j2 new file mode 100644 index 000000000000..894729f66f2c --- /dev/null +++ b/tests/cbf_config_input/cbf_config.j2 @@ -0,0 +1,82 @@ +{ + "DSCP_TO_FC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "EXP_TO_FC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + } +} \ No newline at end of file diff --git a/tests/cbf_config_input/config_cbf.json b/tests/cbf_config_input/config_cbf.json new file mode 100644 index 000000000000..ff465f1ca29d --- /dev/null +++ b/tests/cbf_config_input/config_cbf.json @@ -0,0 +1,82 @@ +{ + "DSCP_TO_FC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "EXP_TO_FC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + } +} diff --git a/tests/config_test.py b/tests/config_test.py index d875ad7ea303..9ea891687e27 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -155,7 +155,6 @@ def teardown_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "0" print("TEARDOWN") - class TestReloadConfig(object): dummy_cfg_file = os.path.join(os.sep, "tmp", "config.json") @@ -228,13 +227,111 @@ def test_reload_yang_config(self, get_cmd_module, assert "\n".join([l.rstrip() for l in result.output.split('\n')]) \ == RELOAD_YANG_CFG_OUTPUT - @classmethod def teardown_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "0" os.remove(cls.dummy_cfg_file) print("TEARDOWN") + +class TestConfigCbf(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ['UTILITIES_UNIT_TESTING'] = "2" + import config.main + importlib.reload(config.main) + + def test_cbf_reload_single( + self, get_cmd_module, setup_cbf_mock_apis, + setup_single_broadcom_asic + ): + (config, show) = get_cmd_module + runner = CliRunner() + output_file = os.path.join(os.sep, "tmp", "cbf_config_output.json") + print("Saving output in {}".format(output_file)) + try: + os.remove(output_file) + except OSError: + pass + result = runner.invoke( + config.config.commands["cbf"], + ["reload", "--dry_run", output_file] + ) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + cwd = os.path.dirname(os.path.realpath(__file__)) + expected_result = os.path.join( + cwd, "cbf_config_input", "config_cbf.json" + ) + assert filecmp.cmp(output_file, expected_result, shallow=False) + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ['UTILITIES_UNIT_TESTING'] = "0" + + +class TestConfigCbfMasic(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ['UTILITIES_UNIT_TESTING'] = "2" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + import config.main + importlib.reload(config.main) + # change to multi asic config + from .mock_tables import dbconnector + from .mock_tables import mock_multi_asic + importlib.reload(mock_multi_asic) + dbconnector.load_namespace_config() + + def test_cbf_reload_masic( + self, get_cmd_module, setup_cbf_mock_apis, + setup_multi_broadcom_masic + ): + (config, show) = get_cmd_module + runner = CliRunner() + output_file = os.path.join(os.sep, "tmp", "cbf_config_output.json") + print("Saving output in {}<0,1,2..>".format(output_file)) + num_asic = device_info.get_num_npus() + print(num_asic) + for asic in range(num_asic): + try: + file = "{}{}".format(output_file, asic) + os.remove(file) + except OSError: + pass + result = runner.invoke( + config.config.commands["cbf"], + ["reload", "--dry_run", output_file] + ) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + cwd = os.path.dirname(os.path.realpath(__file__)) + + for asic in range(num_asic): + expected_result = os.path.join( + cwd, "cbf_config_input", str(asic), "config_cbf.json" + ) + file = "{}{}".format(output_file, asic) + assert filecmp.cmp(file, expected_result, shallow=False) + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ['UTILITIES_UNIT_TESTING'] = "0" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + # change back to single asic config + from .mock_tables import dbconnector + from .mock_tables import mock_single_asic + importlib.reload(mock_single_asic) + dbconnector.load_namespace_config() + class TestConfigQos(object): @classmethod @@ -285,6 +382,11 @@ def setup_class(cls): os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" import config.main importlib.reload(config.main) + # change to multi asic config + from .mock_tables import dbconnector + from .mock_tables import mock_multi_asic + importlib.reload(mock_multi_asic) + dbconnector.load_namespace_config() def test_qos_reload_masic( self, get_cmd_module, setup_qos_mock_apis, diff --git a/tests/conftest.py b/tests/conftest.py index 66586181138c..68bf6cbc4395 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -74,6 +74,18 @@ def set_mock_apis(): config.asic_type = mock.MagicMock(return_value="broadcom") config._get_device_type = mock.MagicMock(return_value="ToRRouter") +@pytest.fixture +def setup_cbf_mock_apis(): + cwd = os.path.dirname(os.path.realpath(__file__)) + device_info.get_paths_to_platform_and_hwsku_dirs = mock.MagicMock( + return_value=( + os.path.join(cwd, "."), os.path.join(cwd, "cbf_config_input") + ) + ) + device_info.get_sonic_version_file = mock.MagicMock( + return_value=os.path.join(cwd, "qos_config_input/sonic_version.yml") + ) + @pytest.fixture def setup_qos_mock_apis(): cwd = os.path.dirname(os.path.realpath(__file__))