Skip to content

Commit 8f715ac

Browse files
authored
[config]Support single file reload for multiasic (sonic-net#3349)
ADO: 27595279 What I did Extend config reload to support single file reloading for multi-asic How I did it Add the single file reload support for mutli-asic How to verify it Unit test and manual test on multi-asic DUT
1 parent 31f5fa8 commit 8f715ac

File tree

2 files changed

+406
-109
lines changed

2 files changed

+406
-109
lines changed

config/main.py

+186-109
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from sonic_yang_cfg_generator import SonicYangCfgDbGenerator
3232
from utilities_common import util_base
3333
from swsscommon import swsscommon
34-
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector
34+
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector, ConfigDBPipeConnector
3535
from utilities_common.db import Db
3636
from utilities_common.intf_filter import parse_interface_in_filter
3737
from utilities_common import bgp_util
@@ -1197,7 +1197,7 @@ def validate_gre_type(ctx, _, value):
11971197
raise click.UsageError("{} is not a valid GRE type".format(value))
11981198

11991199

1200-
def multi_asic_save_config(db, filename):
1200+
def multiasic_save_to_singlefile(db, filename):
12011201
"""A function to save all asic's config to single file
12021202
"""
12031203
all_current_config = {}
@@ -1264,6 +1264,96 @@ def validate_patch(patch):
12641264
except Exception as e:
12651265
raise GenericConfigUpdaterError(f"Validate json patch: {patch} failed due to:{e}")
12661266

1267+
1268+
def multiasic_validate_single_file(filename):
1269+
ns_list = [DEFAULT_NAMESPACE, *multi_asic.get_namespace_list()]
1270+
file_input = read_json_file(filename)
1271+
file_ns_list = [DEFAULT_NAMESPACE if key == HOST_NAMESPACE else key for key in file_input]
1272+
if set(ns_list) != set(file_ns_list):
1273+
click.echo(
1274+
"Input file {} must contain all asics config. ns_list: {} file ns_list: {}".format(
1275+
filename, ns_list, file_ns_list)
1276+
)
1277+
raise click.Abort()
1278+
1279+
1280+
def load_sysinfo_if_missing(asic_config):
1281+
device_metadata = asic_config.get('DEVICE_METADATA', {})
1282+
platform = device_metadata.get("localhost", {}).get("platform")
1283+
mac = device_metadata.get("localhost", {}).get("mac")
1284+
if not platform:
1285+
log.log_warning("platform is missing from Input file")
1286+
return True
1287+
elif not mac:
1288+
log.log_warning("mac is missing from Input file")
1289+
return True
1290+
else:
1291+
return False
1292+
1293+
1294+
def flush_configdb(namespace=DEFAULT_NAMESPACE):
1295+
if namespace is DEFAULT_NAMESPACE:
1296+
config_db = ConfigDBConnector()
1297+
else:
1298+
config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace)
1299+
1300+
config_db.connect()
1301+
client = config_db.get_redis_client(config_db.CONFIG_DB)
1302+
client.flushdb()
1303+
return client, config_db
1304+
1305+
1306+
def migrate_db_to_lastest(namespace=DEFAULT_NAMESPACE):
1307+
# Migrate DB contents to latest version
1308+
db_migrator = '/usr/local/bin/db_migrator.py'
1309+
if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK):
1310+
if namespace is DEFAULT_NAMESPACE:
1311+
command = [db_migrator, '-o', 'migrate']
1312+
else:
1313+
command = [db_migrator, '-o', 'migrate', '-n', namespace]
1314+
clicommon.run_command(command, display_cmd=True)
1315+
1316+
1317+
def multiasic_write_to_db(filename, load_sysinfo):
1318+
file_input = read_json_file(filename)
1319+
for ns in [DEFAULT_NAMESPACE, *multi_asic.get_namespace_list()]:
1320+
asic_name = HOST_NAMESPACE if ns == DEFAULT_NAMESPACE else ns
1321+
asic_config = file_input[asic_name]
1322+
1323+
asic_load_sysinfo = True if load_sysinfo else False
1324+
if not asic_load_sysinfo:
1325+
asic_load_sysinfo = load_sysinfo_if_missing(asic_config)
1326+
1327+
if asic_load_sysinfo:
1328+
cfg_hwsku = asic_config.get("DEVICE_METADATA", {}).\
1329+
get("localhost", {}).get("hwsku")
1330+
if not cfg_hwsku:
1331+
click.secho("Could not get the HWSKU from config file, Exiting!", fg='magenta')
1332+
sys.exit(1)
1333+
1334+
client, _ = flush_configdb(ns)
1335+
1336+
if asic_load_sysinfo:
1337+
if ns is DEFAULT_NAMESPACE:
1338+
command = [str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '--write-to-db']
1339+
else:
1340+
command = [str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '-n', str(ns), '--write-to-db']
1341+
clicommon.run_command(command, display_cmd=True)
1342+
1343+
if ns is DEFAULT_NAMESPACE:
1344+
config_db = ConfigDBPipeConnector(use_unix_socket_path=True)
1345+
else:
1346+
config_db = ConfigDBPipeConnector(use_unix_socket_path=True, namespace=ns)
1347+
1348+
config_db.connect(False)
1349+
sonic_cfggen.FormatConverter.to_deserialized(asic_config)
1350+
data = sonic_cfggen.FormatConverter.output_to_db(asic_config)
1351+
config_db.mod_config(sonic_cfggen.FormatConverter.output_to_db(data))
1352+
client.set(config_db.INIT_INDICATOR, 1)
1353+
1354+
migrate_db_to_lastest(ns)
1355+
1356+
12671357
# This is our main entrypoint - the main 'config' command
12681358
@click.group(cls=clicommon.AbbreviationGroup, context_settings=CONTEXT_SETTINGS)
12691359
@click.pass_context
@@ -1351,7 +1441,7 @@ def save(db, filename):
13511441
# save all ASIC configurations to that single file.
13521442
if len(cfg_files) == 1 and multi_asic.is_multi_asic():
13531443
filename = cfg_files[0]
1354-
multi_asic_save_config(db, filename)
1444+
multiasic_save_to_singlefile(db, filename)
13551445
return
13561446
elif len(cfg_files) != num_cfg_file:
13571447
click.echo("Input {} config file(s) separated by comma for multiple files ".format(num_cfg_file))
@@ -1669,11 +1759,15 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, force, file_form
16691759
if multi_asic.is_multi_asic() and file_format == 'config_db':
16701760
num_cfg_file += num_asic
16711761

1762+
multiasic_single_file_mode = False
16721763
# If the user give the filename[s], extract the file names.
16731764
if filename is not None:
16741765
cfg_files = filename.split(',')
16751766

1676-
if len(cfg_files) != num_cfg_file:
1767+
if len(cfg_files) == 1 and multi_asic.is_multi_asic():
1768+
multiasic_validate_single_file(cfg_files[0])
1769+
multiasic_single_file_mode = True
1770+
elif len(cfg_files) != num_cfg_file:
16771771
click.echo("Input {} config file(s) separated by comma for multiple files ".format(num_cfg_file))
16781772
return
16791773

@@ -1682,127 +1776,109 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, force, file_form
16821776
log.log_notice("'reload' stopping services...")
16831777
_stop_services()
16841778

1685-
# In Single ASIC platforms we have single DB service. In multi-ASIC platforms we have a global DB
1686-
# service running in the host + DB services running in each ASIC namespace created per ASIC.
1687-
# In the below logic, we get all namespaces in this platform and add an empty namespace ''
1688-
# denoting the current namespace which we are in ( the linux host )
1689-
for inst in range(-1, num_cfg_file-1):
1690-
# Get the namespace name, for linux host it is None
1691-
if inst == -1:
1692-
namespace = None
1693-
else:
1694-
namespace = "{}{}".format(NAMESPACE_PREFIX, inst)
1695-
1696-
# Get the file from user input, else take the default file /etc/sonic/config_db{NS_id}.json
1697-
if cfg_files:
1698-
file = cfg_files[inst+1]
1699-
# Save to tmpfile in case of stdin input which can only be read once
1700-
if file == "/dev/stdin":
1701-
file_input = read_json_file(file)
1702-
(_, tmpfname) = tempfile.mkstemp(dir="/tmp", suffix="_configReloadStdin")
1703-
write_json_file(file_input, tmpfname)
1704-
file = tmpfname
1705-
else:
1706-
if file_format == 'config_db':
1707-
if namespace is None:
1708-
file = DEFAULT_CONFIG_DB_FILE
1709-
else:
1710-
file = "/etc/sonic/config_db{}.json".format(inst)
1779+
if multiasic_single_file_mode:
1780+
multiasic_write_to_db(cfg_files[0], load_sysinfo)
1781+
else:
1782+
# In Single ASIC platforms we have single DB service. In multi-ASIC platforms we have a global DB
1783+
# service running in the host + DB services running in each ASIC namespace created per ASIC.
1784+
# In the below logic, we get all namespaces in this platform and add an empty namespace ''
1785+
# denoting the current namespace which we are in ( the linux host )
1786+
for inst in range(-1, num_cfg_file-1):
1787+
# Get the namespace name, for linux host it is DEFAULT_NAMESPACE
1788+
if inst == -1:
1789+
namespace = DEFAULT_NAMESPACE
17111790
else:
1712-
file = DEFAULT_CONFIG_YANG_FILE
1713-
1714-
1715-
# Check the file exists before proceeding.
1716-
if not os.path.exists(file):
1717-
click.echo("The config file {} doesn't exist".format(file))
1718-
continue
1719-
1720-
if file_format == 'config_db':
1721-
file_input = read_json_file(file)
1791+
namespace = "{}{}".format(NAMESPACE_PREFIX, inst)
1792+
1793+
# Get the file from user input, else take the default file /etc/sonic/config_db{NS_id}.json
1794+
if cfg_files:
1795+
file = cfg_files[inst+1]
1796+
# Save to tmpfile in case of stdin input which can only be read once
1797+
if file == "/dev/stdin":
1798+
file_input = read_json_file(file)
1799+
(_, tmpfname) = tempfile.mkstemp(dir="/tmp", suffix="_configReloadStdin")
1800+
write_json_file(file_input, tmpfname)
1801+
file = tmpfname
1802+
else:
1803+
if file_format == 'config_db':
1804+
if namespace is DEFAULT_NAMESPACE:
1805+
file = DEFAULT_CONFIG_DB_FILE
1806+
else:
1807+
file = "/etc/sonic/config_db{}.json".format(inst)
1808+
else:
1809+
file = DEFAULT_CONFIG_YANG_FILE
17221810

1723-
platform = file_input.get("DEVICE_METADATA", {}).\
1724-
get(HOST_NAMESPACE, {}).get("platform")
1725-
mac = file_input.get("DEVICE_METADATA", {}).\
1726-
get(HOST_NAMESPACE, {}).get("mac")
1811+
# Check the file exists before proceeding.
1812+
if not os.path.exists(file):
1813+
click.echo("The config file {} doesn't exist".format(file))
1814+
continue
17271815

1728-
if not platform or not mac:
1729-
log.log_warning("Input file does't have platform or mac. platform: {}, mac: {}"
1730-
.format(None if platform is None else platform, None if mac is None else mac))
1731-
load_sysinfo = True
1816+
if file_format == 'config_db':
1817+
file_input = read_json_file(file)
1818+
if not load_sysinfo:
1819+
load_sysinfo = load_sysinfo_if_missing(file_input)
1820+
1821+
if load_sysinfo:
1822+
try:
1823+
command = [SONIC_CFGGEN_PATH, "-j", file, '-v', "DEVICE_METADATA.localhost.hwsku"]
1824+
proc = subprocess.Popen(command, text=True, stdout=subprocess.PIPE)
1825+
output, err = proc.communicate()
1826+
1827+
except FileNotFoundError as e:
1828+
click.echo("{}".format(str(e)), err=True)
1829+
raise click.Abort()
1830+
except Exception as e:
1831+
click.echo("{}\n{}".format(type(e), str(e)), err=True)
1832+
raise click.Abort()
1833+
1834+
if not output:
1835+
click.secho("Could not get the HWSKU from config file, Exiting!!!", fg='magenta')
1836+
sys.exit(1)
17321837

1733-
if load_sysinfo:
1734-
try:
1735-
command = [SONIC_CFGGEN_PATH, "-j", file, '-v', "DEVICE_METADATA.localhost.hwsku"]
1736-
proc = subprocess.Popen(command, text=True, stdout=subprocess.PIPE)
1737-
output, err = proc.communicate()
1838+
cfg_hwsku = output.strip()
17381839

1739-
except FileNotFoundError as e:
1740-
click.echo("{}".format(str(e)), err=True)
1741-
raise click.Abort()
1742-
except Exception as e:
1743-
click.echo("{}\n{}".format(type(e), str(e)), err=True)
1744-
raise click.Abort()
1840+
client, config_db = flush_configdb(namespace)
17451841

1746-
if not output:
1747-
click.secho("Could not get the HWSKU from config file, Exiting!!!", fg='magenta')
1748-
sys.exit(1)
1842+
if load_sysinfo:
1843+
if namespace is DEFAULT_NAMESPACE:
1844+
command = [
1845+
str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '--write-to-db']
1846+
else:
1847+
command = [
1848+
str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '-n', str(namespace), '--write-to-db']
1849+
clicommon.run_command(command, display_cmd=True)
17491850

1750-
cfg_hwsku = output.strip()
1851+
# For the database service running in linux host we use the file user gives as input
1852+
# or by default DEFAULT_CONFIG_DB_FILE. In the case of database service running in namespace,
1853+
# the default config_db<namespaceID>.json format is used.
17511854

1752-
if namespace is None:
1753-
config_db = ConfigDBConnector()
1754-
else:
1755-
config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace)
1855+
config_gen_opts = []
17561856

1757-
config_db.connect()
1758-
client = config_db.get_redis_client(config_db.CONFIG_DB)
1759-
client.flushdb()
1857+
if os.path.isfile(INIT_CFG_FILE):
1858+
config_gen_opts += ['-j', str(INIT_CFG_FILE)]
17601859

1761-
if load_sysinfo:
1762-
if namespace is None:
1763-
command = [str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '--write-to-db']
1860+
if file_format == 'config_db':
1861+
config_gen_opts += ['-j', str(file)]
17641862
else:
1765-
command = [str(SONIC_CFGGEN_PATH), '-H', '-k', str(cfg_hwsku), '-n', str(namespace), '--write-to-db']
1766-
clicommon.run_command(command, display_cmd=True)
1767-
1768-
# For the database service running in linux host we use the file user gives as input
1769-
# or by default DEFAULT_CONFIG_DB_FILE. In the case of database service running in namespace,
1770-
# the default config_db<namespaceID>.json format is used.
1771-
1863+
config_gen_opts += ['-Y', str(file)]
17721864

1773-
config_gen_opts = []
1865+
if namespace is not DEFAULT_NAMESPACE:
1866+
config_gen_opts += ['-n', str(namespace)]
17741867

1775-
if os.path.isfile(INIT_CFG_FILE):
1776-
config_gen_opts += ['-j', str(INIT_CFG_FILE)]
1868+
command = [SONIC_CFGGEN_PATH] + config_gen_opts + ['--write-to-db']
17771869

1778-
if file_format == 'config_db':
1779-
config_gen_opts += ['-j', str(file)]
1780-
else:
1781-
config_gen_opts += ['-Y', str(file)]
1782-
1783-
if namespace is not None:
1784-
config_gen_opts += ['-n', str(namespace)]
1785-
1786-
command = [SONIC_CFGGEN_PATH] + config_gen_opts + ['--write-to-db']
1787-
1788-
clicommon.run_command(command, display_cmd=True)
1789-
client.set(config_db.INIT_INDICATOR, 1)
1870+
clicommon.run_command(command, display_cmd=True)
1871+
client.set(config_db.INIT_INDICATOR, 1)
17901872

1791-
if os.path.exists(file) and file.endswith("_configReloadStdin"):
1792-
# Remove tmpfile
1793-
try:
1794-
os.remove(file)
1795-
except OSError as e:
1796-
click.echo("An error occurred while removing the temporary file: {}".format(str(e)), err=True)
1873+
if os.path.exists(file) and file.endswith("_configReloadStdin"):
1874+
# Remove tmpfile
1875+
try:
1876+
os.remove(file)
1877+
except OSError as e:
1878+
click.echo("An error occurred while removing the temporary file: {}".format(str(e)), err=True)
17971879

1798-
# Migrate DB contents to latest version
1799-
db_migrator='/usr/local/bin/db_migrator.py'
1800-
if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK):
1801-
if namespace is None:
1802-
command = [db_migrator, '-o', 'migrate']
1803-
else:
1804-
command = [db_migrator, '-o', 'migrate', '-n', str(namespace)]
1805-
clicommon.run_command(command, display_cmd=True)
1880+
# Migrate DB contents to latest version
1881+
migrate_db_to_lastest(namespace)
18061882

18071883
# Re-generate the environment variable in case config_db.json was edited
18081884
update_sonic_environment()
@@ -4086,6 +4162,7 @@ def bgp():
40864162
pass
40874163

40884164

4165+
40894166
# BGP module extensions
40904167
config.commands['bgp'].add_command(bgp_cli.DEVICE_GLOBAL)
40914168

0 commit comments

Comments
 (0)