-
Notifications
You must be signed in to change notification settings - Fork 815
[config]Support single file reload for multiasic #3349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
53b3341
[cnfig]Support single file reload for multiasic
wen587 a9533ea
style
wen587 0c1f86b
unit
wen587 37e1823
Merge branch 'master' into refactor_reload
wen587 48185be
comment
wen587 c8605c0
Merge branch 'refactor_reload' of https://github.com/wen587/sonic-uti…
wen587 ecc2e4f
comment
wen587 d748e0f
comment
wen587 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,7 +31,7 @@ | |
| from sonic_yang_cfg_generator import SonicYangCfgDbGenerator | ||
| from utilities_common import util_base | ||
| from swsscommon import swsscommon | ||
| from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector | ||
| from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector, ConfigDBPipeConnector | ||
| from utilities_common.db import Db | ||
| from utilities_common.intf_filter import parse_interface_in_filter | ||
| from utilities_common import bgp_util | ||
|
|
@@ -1160,7 +1160,7 @@ def validate_gre_type(ctx, _, value): | |
| raise click.UsageError("{} is not a valid GRE type".format(value)) | ||
|
|
||
|
|
||
| def multi_asic_save_config(db, filename): | ||
| def multiasic_save_to_singlefile(db, filename): | ||
| """A function to save all asic's config to single file | ||
| """ | ||
| all_current_config = {} | ||
|
|
@@ -1227,6 +1227,96 @@ def validate_patch(patch): | |
| except Exception as e: | ||
| raise GenericConfigUpdaterError(f"Validate json patch: {patch} failed due to:{e}") | ||
|
|
||
|
|
||
| def multiasic_validate_single_file(filename): | ||
| ns_list = [DEFAULT_NAMESPACE, *multi_asic.get_namespace_list()] | ||
| file_input = read_json_file(filename) | ||
| file_ns_list = [DEFAULT_NAMESPACE if key == HOST_NAMESPACE else key for key in file_input] | ||
wen587 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if set(ns_list) != set(file_ns_list): | ||
wen587 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| click.echo( | ||
| "Input file {} must contain all asics config. ns_list: {} file ns_list: {}".format( | ||
| filename, ns_list, file_ns_list) | ||
| ) | ||
| raise click.Abort() | ||
|
|
||
|
|
||
| def load_sysinfo_if_missing(asic_config): | ||
| device_metadata = asic_config.get('DEVICE_METADATA', {}) | ||
| platform = device_metadata.get("localhost", {}).get("platform") | ||
| mac = device_metadata.get("localhost", {}).get("mac") | ||
| if not platform: | ||
| log.log_warning("platform is missing from Input file") | ||
| return True | ||
| elif not mac: | ||
| log.log_warning("mac is missing from Input file") | ||
| return True | ||
| else: | ||
| return False | ||
|
|
||
|
|
||
| def flush_configdb(namespace=DEFAULT_NAMESPACE): | ||
| if namespace is DEFAULT_NAMESPACE: | ||
| config_db = ConfigDBConnector() | ||
wen587 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| else: | ||
| config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) | ||
wen587 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| config_db.connect() | ||
| client = config_db.get_redis_client(config_db.CONFIG_DB) | ||
| client.flushdb() | ||
| return client, config_db | ||
|
|
||
|
|
||
| def migrate_db_to_lastest(namespace=DEFAULT_NAMESPACE): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vaibhavhd Could you help review? |
||
| # Migrate DB contents to latest version | ||
| db_migrator = '/usr/local/bin/db_migrator.py' | ||
| if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK): | ||
| if namespace is DEFAULT_NAMESPACE: | ||
| command = [db_migrator, '-o', 'migrate'] | ||
| else: | ||
| command = [db_migrator, '-o', 'migrate', '-n', namespace] | ||
| clicommon.run_command(command, display_cmd=True) | ||
|
|
||
|
|
||
| def multiasic_write_to_db(filename, load_sysinfo): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rlhui @abdosi @judyjoseph Could you help review? |
||
| file_input = read_json_file(filename) | ||
| for ns in [DEFAULT_NAMESPACE, *multi_asic.get_namespace_list()]: | ||
| asic_name = HOST_NAMESPACE if ns == DEFAULT_NAMESPACE else ns | ||
| asic_config = file_input[asic_name] | ||
|
|
||
| asic_load_sysinfo = True if load_sysinfo else False | ||
| if not asic_load_sysinfo: | ||
| asic_load_sysinfo = load_sysinfo_if_missing(asic_config) | ||
wen587 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if asic_load_sysinfo: | ||
| cfg_hwsku = asic_config.get("DEVICE_METADATA", {}).\ | ||
| get("localhost", {}).get("hwsku") | ||
| if not cfg_hwsku: | ||
| click.secho("Could not get the HWSKU from config file, Exiting!", fg='magenta') | ||
| sys.exit(1) | ||
|
|
||
| client, _ = flush_configdb(ns) | ||
|
|
||
| if asic_load_sysinfo: | ||
wen587 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if ns is DEFAULT_NAMESPACE: | ||
| command = [str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '--write-to-db'] | ||
| else: | ||
| command = [str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '-n', str(ns), '--write-to-db'] | ||
| clicommon.run_command(command, display_cmd=True) | ||
|
|
||
| if ns is DEFAULT_NAMESPACE: | ||
| config_db = ConfigDBPipeConnector(use_unix_socket_path=True) | ||
| else: | ||
| config_db = ConfigDBPipeConnector(use_unix_socket_path=True, namespace=ns) | ||
|
|
||
| config_db.connect(False) | ||
| sonic_cfggen.FormatConverter.to_deserialized(asic_config) | ||
| data = sonic_cfggen.FormatConverter.output_to_db(asic_config) | ||
| config_db.mod_config(sonic_cfggen.FormatConverter.output_to_db(data)) | ||
| client.set(config_db.INIT_INDICATOR, 1) | ||
|
|
||
| migrate_db_to_lastest(ns) | ||
wen587 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| # This is our main entrypoint - the main 'config' command | ||
| @click.group(cls=clicommon.AbbreviationGroup, context_settings=CONTEXT_SETTINGS) | ||
| @click.pass_context | ||
|
|
@@ -1314,7 +1404,7 @@ def save(db, filename): | |
| # save all ASIC configurations to that single file. | ||
| if len(cfg_files) == 1 and multi_asic.is_multi_asic(): | ||
| filename = cfg_files[0] | ||
| multi_asic_save_config(db, filename) | ||
| multiasic_save_to_singlefile(db, filename) | ||
| return | ||
| elif len(cfg_files) != num_cfg_file: | ||
| click.echo("Input {} config file(s) separated by comma for multiple files ".format(num_cfg_file)) | ||
|
|
@@ -1632,11 +1722,15 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, force, file_form | |
| if multi_asic.is_multi_asic() and file_format == 'config_db': | ||
| num_cfg_file += num_asic | ||
|
|
||
| multiasic_single_file_mode = False | ||
| # If the user give the filename[s], extract the file names. | ||
| if filename is not None: | ||
| cfg_files = filename.split(',') | ||
|
|
||
| if len(cfg_files) != num_cfg_file: | ||
| if len(cfg_files) == 1 and multi_asic.is_multi_asic(): | ||
| multiasic_validate_single_file(cfg_files[0]) | ||
| multiasic_single_file_mode = True | ||
| elif len(cfg_files) != num_cfg_file: | ||
| click.echo("Input {} config file(s) separated by comma for multiple files ".format(num_cfg_file)) | ||
| return | ||
|
|
||
|
|
@@ -1645,127 +1739,109 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, force, file_form | |
| log.log_notice("'reload' stopping services...") | ||
| _stop_services() | ||
|
|
||
| # In Single ASIC platforms we have single DB service. In multi-ASIC platforms we have a global DB | ||
| # service running in the host + DB services running in each ASIC namespace created per ASIC. | ||
| # In the below logic, we get all namespaces in this platform and add an empty namespace '' | ||
| # denoting the current namespace which we are in ( the linux host ) | ||
| for inst in range(-1, num_cfg_file-1): | ||
| # Get the namespace name, for linux host it is None | ||
| if inst == -1: | ||
| namespace = None | ||
| else: | ||
| namespace = "{}{}".format(NAMESPACE_PREFIX, inst) | ||
|
|
||
| # Get the file from user input, else take the default file /etc/sonic/config_db{NS_id}.json | ||
| if cfg_files: | ||
| file = cfg_files[inst+1] | ||
| # Save to tmpfile in case of stdin input which can only be read once | ||
| if file == "/dev/stdin": | ||
| file_input = read_json_file(file) | ||
| (_, tmpfname) = tempfile.mkstemp(dir="/tmp", suffix="_configReloadStdin") | ||
| write_json_file(file_input, tmpfname) | ||
| file = tmpfname | ||
| else: | ||
| if file_format == 'config_db': | ||
| if namespace is None: | ||
| file = DEFAULT_CONFIG_DB_FILE | ||
| else: | ||
| file = "/etc/sonic/config_db{}.json".format(inst) | ||
| if multiasic_single_file_mode: | ||
| multiasic_write_to_db(cfg_files[0], load_sysinfo) | ||
| else: | ||
| # In Single ASIC platforms we have single DB service. In multi-ASIC platforms we have a global DB | ||
| # service running in the host + DB services running in each ASIC namespace created per ASIC. | ||
| # In the below logic, we get all namespaces in this platform and add an empty namespace '' | ||
| # denoting the current namespace which we are in ( the linux host ) | ||
| for inst in range(-1, num_cfg_file-1): | ||
| # Get the namespace name, for linux host it is DEFAULT_NAMESPACE | ||
| if inst == -1: | ||
| namespace = DEFAULT_NAMESPACE | ||
| else: | ||
| file = DEFAULT_CONFIG_YANG_FILE | ||
|
|
||
|
|
||
| # Check the file exists before proceeding. | ||
| if not os.path.exists(file): | ||
| click.echo("The config file {} doesn't exist".format(file)) | ||
| continue | ||
|
|
||
| if file_format == 'config_db': | ||
| file_input = read_json_file(file) | ||
| namespace = "{}{}".format(NAMESPACE_PREFIX, inst) | ||
|
|
||
| # Get the file from user input, else take the default file /etc/sonic/config_db{NS_id}.json | ||
| if cfg_files: | ||
| file = cfg_files[inst+1] | ||
| # Save to tmpfile in case of stdin input which can only be read once | ||
| if file == "/dev/stdin": | ||
| file_input = read_json_file(file) | ||
| (_, tmpfname) = tempfile.mkstemp(dir="/tmp", suffix="_configReloadStdin") | ||
| write_json_file(file_input, tmpfname) | ||
| file = tmpfname | ||
| else: | ||
| if file_format == 'config_db': | ||
| if namespace is DEFAULT_NAMESPACE: | ||
| file = DEFAULT_CONFIG_DB_FILE | ||
| else: | ||
| file = "/etc/sonic/config_db{}.json".format(inst) | ||
| else: | ||
| file = DEFAULT_CONFIG_YANG_FILE | ||
|
|
||
| platform = file_input.get("DEVICE_METADATA", {}).\ | ||
| get(HOST_NAMESPACE, {}).get("platform") | ||
| mac = file_input.get("DEVICE_METADATA", {}).\ | ||
| get(HOST_NAMESPACE, {}).get("mac") | ||
| # Check the file exists before proceeding. | ||
| if not os.path.exists(file): | ||
| click.echo("The config file {} doesn't exist".format(file)) | ||
| continue | ||
|
|
||
| if not platform or not mac: | ||
| log.log_warning("Input file does't have platform or mac. platform: {}, mac: {}" | ||
| .format(None if platform is None else platform, None if mac is None else mac)) | ||
| load_sysinfo = True | ||
| if file_format == 'config_db': | ||
| file_input = read_json_file(file) | ||
| if not load_sysinfo: | ||
| load_sysinfo = load_sysinfo_if_missing(file_input) | ||
|
|
||
| if load_sysinfo: | ||
| try: | ||
| command = [SONIC_CFGGEN_PATH, "-j", file, '-v', "DEVICE_METADATA.localhost.hwsku"] | ||
| proc = subprocess.Popen(command, text=True, stdout=subprocess.PIPE) | ||
| output, err = proc.communicate() | ||
|
|
||
| except FileNotFoundError as e: | ||
| click.echo("{}".format(str(e)), err=True) | ||
| raise click.Abort() | ||
| except Exception as e: | ||
| click.echo("{}\n{}".format(type(e), str(e)), err=True) | ||
| raise click.Abort() | ||
|
|
||
| if not output: | ||
| click.secho("Could not get the HWSKU from config file, Exiting!!!", fg='magenta') | ||
| sys.exit(1) | ||
|
|
||
| if load_sysinfo: | ||
| try: | ||
| command = [SONIC_CFGGEN_PATH, "-j", file, '-v', "DEVICE_METADATA.localhost.hwsku"] | ||
| proc = subprocess.Popen(command, text=True, stdout=subprocess.PIPE) | ||
| output, err = proc.communicate() | ||
| cfg_hwsku = output.strip() | ||
|
|
||
| except FileNotFoundError as e: | ||
| click.echo("{}".format(str(e)), err=True) | ||
| raise click.Abort() | ||
| except Exception as e: | ||
| click.echo("{}\n{}".format(type(e), str(e)), err=True) | ||
| raise click.Abort() | ||
| client, config_db = flush_configdb(namespace) | ||
|
|
||
| if not output: | ||
| click.secho("Could not get the HWSKU from config file, Exiting!!!", fg='magenta') | ||
| sys.exit(1) | ||
| if load_sysinfo: | ||
| if namespace is DEFAULT_NAMESPACE: | ||
| command = [ | ||
| str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '--write-to-db'] | ||
| else: | ||
| command = [ | ||
| str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '-n', str(namespace), '--write-to-db'] | ||
| clicommon.run_command(command, display_cmd=True) | ||
|
|
||
| cfg_hwsku = output.strip() | ||
| # For the database service running in linux host we use the file user gives as input | ||
| # or by default DEFAULT_CONFIG_DB_FILE. In the case of database service running in namespace, | ||
| # the default config_db<namespaceID>.json format is used. | ||
|
|
||
| if namespace is None: | ||
| config_db = ConfigDBConnector() | ||
| else: | ||
| config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) | ||
| config_gen_opts = [] | ||
|
|
||
| config_db.connect() | ||
| client = config_db.get_redis_client(config_db.CONFIG_DB) | ||
| client.flushdb() | ||
| if os.path.isfile(INIT_CFG_FILE): | ||
| config_gen_opts += ['-j', str(INIT_CFG_FILE)] | ||
|
|
||
| if load_sysinfo: | ||
| if namespace is None: | ||
| command = [str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '--write-to-db'] | ||
| if file_format == 'config_db': | ||
| config_gen_opts += ['-j', str(file)] | ||
| else: | ||
| command = [str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '-n', str(namespace), '--write-to-db'] | ||
| clicommon.run_command(command, display_cmd=True) | ||
|
|
||
| # For the database service running in linux host we use the file user gives as input | ||
| # or by default DEFAULT_CONFIG_DB_FILE. In the case of database service running in namespace, | ||
| # the default config_db<namespaceID>.json format is used. | ||
|
|
||
| config_gen_opts += ['-Y', str(file)] | ||
|
|
||
| config_gen_opts = [] | ||
| if namespace is not DEFAULT_NAMESPACE: | ||
| config_gen_opts += ['-n', str(namespace)] | ||
|
|
||
| if os.path.isfile(INIT_CFG_FILE): | ||
| config_gen_opts += ['-j', str(INIT_CFG_FILE)] | ||
| command = [SONIC_CFGGEN_PATH] + config_gen_opts + ['--write-to-db'] | ||
|
|
||
| if file_format == 'config_db': | ||
| config_gen_opts += ['-j', str(file)] | ||
| else: | ||
| config_gen_opts += ['-Y', str(file)] | ||
|
|
||
| if namespace is not None: | ||
| config_gen_opts += ['-n', str(namespace)] | ||
|
|
||
| command = [SONIC_CFGGEN_PATH] + config_gen_opts + ['--write-to-db'] | ||
|
|
||
| clicommon.run_command(command, display_cmd=True) | ||
| client.set(config_db.INIT_INDICATOR, 1) | ||
| clicommon.run_command(command, display_cmd=True) | ||
| client.set(config_db.INIT_INDICATOR, 1) | ||
|
|
||
| if os.path.exists(file) and file.endswith("_configReloadStdin"): | ||
| # Remove tmpfile | ||
| try: | ||
| os.remove(file) | ||
| except OSError as e: | ||
| click.echo("An error occurred while removing the temporary file: {}".format(str(e)), err=True) | ||
| if os.path.exists(file) and file.endswith("_configReloadStdin"): | ||
| # Remove tmpfile | ||
| try: | ||
| os.remove(file) | ||
| except OSError as e: | ||
| click.echo("An error occurred while removing the temporary file: {}".format(str(e)), err=True) | ||
|
|
||
| # Migrate DB contents to latest version | ||
| db_migrator='/usr/local/bin/db_migrator.py' | ||
| if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK): | ||
| if namespace is None: | ||
| command = [db_migrator, '-o', 'migrate'] | ||
| else: | ||
| command = [db_migrator, '-o', 'migrate', '-n', str(namespace)] | ||
| clicommon.run_command(command, display_cmd=True) | ||
| # Migrate DB contents to latest version | ||
| migrate_db_to_lastest(namespace) | ||
|
|
||
| # Re-generate the environment variable in case config_db.json was edited | ||
| update_sonic_environment() | ||
|
|
@@ -4049,6 +4125,7 @@ def bgp(): | |
| pass | ||
|
|
||
|
|
||
|
|
||
| # BGP module extensions | ||
| config.commands['bgp'].add_command(bgp_cli.DEVICE_GLOBAL) | ||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.