diff --git a/config/main.py b/config/main.py index 042b410e67..c0a4c500fd 100644 --- a/config/main.py +++ b/config/main.py @@ -1441,6 +1441,12 @@ def load_minigraph(db, no_service_restart): if os.path.isfile('/etc/sonic/acl.json'): clicommon.run_command("acl-loader update full /etc/sonic/acl.json", display_cmd=True) + # Load port_config.json + try: + load_port_config(db.cfgdb, '/etc/sonic/port_config.json') + except Exception as e: + click.secho("Failed to load port_config.json, Error: {}".format(str(e)), fg='magenta') + # generate QoS and Buffer configs clicommon.run_command("config qos reload --no-dynamic-buffer", display_cmd=True) @@ -1463,6 +1469,44 @@ def load_minigraph(db, no_service_restart): _restart_services() click.echo("Please note setting loaded from minigraph will be lost after system reboot. To preserve setting, run `config save`.") +def load_port_config(config_db, port_config_path): + if not os.path.isfile(port_config_path): + return + + try: + # Load port_config.json + port_config_input = read_json_file(port_config_path) + except Exception: + raise Exception("Bad format: json file broken") + + # Validate if the input is an array + if not isinstance(port_config_input, list): + raise Exception("Bad format: port_config is not an array") + + if len(port_config_input) == 0 or 'PORT' not in port_config_input[0]: + raise Exception("Bad format: PORT table not exists") + + port_config = port_config_input[0]['PORT'] + + # Ensure all ports are exist + port_table = {} + for port_name in port_config.keys(): + port_entry = config_db.get_entry('PORT', port_name) + if not port_entry: + raise Exception("Port {} is not defined in current device".format(port_name)) + port_table[port_name] = port_entry + + # Update port state + for port_name in port_config.keys(): + if 'admin_status' not in port_config[port_name]: + continue + if 'admin_status' in port_table[port_name]: + if port_table[port_name]['admin_status'] == port_config[port_name]['admin_status']: + continue + clicommon.run_command('config interface {} {}'.format( + 'startup' if port_config[port_name]['admin_status'] == 'up' else 'shutdown', + port_name), display_cmd=True) + return # # 'hostname' command diff --git a/tests/config_test.py b/tests/config_test.py index 32ecc5bdef..4f71266d76 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -37,7 +37,6 @@ def mock_run_command_side_effect(*args, **kwargs): if kwargs.get('return_cmd'): return '' - class TestLoadMinigraph(object): @classmethod def setup_class(cls): @@ -58,6 +57,58 @@ def test_load_minigraph(self, get_cmd_module, setup_single_broadcom_asic): assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == load_minigraph_command_output assert mock_run_command.call_count == 7 + def test_load_minigraph_with_port_config_bad_format(self, setup_single_broadcom_asic): + with mock.patch( + "utilities_common.cli.run_command", + mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command: + + # Not in an array + port_config = {"PORT": {"Ethernet0": {"admin_status": "up"}}} + self.check_port_config(None, port_config, "Failed to load port_config.json, Error: Bad format: port_config is not an array") + + # No PORT table + port_config = [{}] + self.check_port_config(None, port_config, "Failed to load port_config.json, Error: Bad format: PORT table not exists") + + def test_load_minigraph_with_port_config_inconsistent_port(self, setup_single_broadcom_asic): + with mock.patch( + "utilities_common.cli.run_command", + mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command: + db = Db() + db.cfgdb.set_entry("PORT", "Ethernet1", {"admin_status": "up"}) + port_config = [{"PORT": {"Eth1": {"admin_status": "up"}}}] + self.check_port_config(db, port_config, "Failed to load port_config.json, Error: Port Eth1 is not defined in current device") + + def test_load_minigraph_with_port_config(self, setup_single_broadcom_asic): + with mock.patch( + "utilities_common.cli.run_command", + mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command: + db = Db() + + # From up to down + db.cfgdb.set_entry("PORT", "Ethernet0", {"admin_status": "up"}) + port_config = [{"PORT": {"Ethernet0": {"admin_status": "down"}}}] + self.check_port_config(db, port_config, "config interface shutdown Ethernet0") + + # From down to up + db.cfgdb.set_entry("PORT", "Ethernet0", {"admin_status": "down"}) + port_config = [{"PORT": {"Ethernet0": {"admin_status": "up"}}}] + self.check_port_config(db, port_config, "config interface startup Ethernet0") + + def check_port_config(self, db, port_config, expected_output): + def read_json_file_side_effect(filename): + return port_config + with mock.patch('config.main.read_json_file', mock.MagicMock(side_effect=read_json_file_side_effect)): + def is_file_side_effect(filename): + return True + with mock.patch('os.path.isfile', mock.MagicMock(side_effect=is_file_side_effect)): + runner = CliRunner() + result = runner.invoke(config.config.commands["load_minigraph"], ["-y"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert expected_output in result.output + @classmethod def teardown_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "0"