Skip to content

Commit

Permalink
[202205][config]Support multi-asic Golden Config override with fix (s…
Browse files Browse the repository at this point in the history
…onic-net#2825)

ADO: 17746282

Support multi-asic Golden Config Override with fix based on sonic-net#2738
Add ConfigMgmt support for ASIC validation. Modify override config cli to support multi-asic.
Unit test:
```
tests/config_override_test.py::TestConfigOverrideMultiasic::test_macsec_override PASSED [  8%]
tests/config_override_test.py::TestConfigOverrideMultiasic::test_device_metadata_table_rm PASSED [  8%]
```
  • Loading branch information
wen587 committed Jun 5, 2023
1 parent ec47214 commit 31973ee
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 34 deletions.
19 changes: 14 additions & 5 deletions config/config_mgmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class ConfigMgmt():
to verify config for the commands which are capable of change in config DB.
'''

def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True):
def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True,
configdb=None):
'''
Initialise the class, --read the config, --load in data tree.
Expand All @@ -44,6 +45,7 @@ def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True):
debug (bool): verbose mode.
allowTablesWithoutYang (bool): allow tables without yang model in
config or not.
configdb: configdb to work on.
Returns:
void
Expand All @@ -53,6 +55,7 @@ def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True):
self.configdbJsonOut = None
self.source = source
self.allowTablesWithoutYang = allowTablesWithoutYang
self.configdb = configdb

# logging vars
self.SYSLOG_IDENTIFIER = "ConfigMgmt"
Expand Down Expand Up @@ -193,8 +196,11 @@ def readConfigDB(self):
self.sysLog(doPrint=True, msg='Reading data from Redis configDb')
# Read from config DB on sonic switch
data = dict()
configdb = ConfigDBConnector()
configdb.connect()
if self.configdb is None:
configdb = ConfigDBConnector()
configdb.connect()
else:
configdb = self.configdb
sonic_cfggen.deep_update(data, sonic_cfggen.FormatConverter.db_to_output(configdb.get_config()))
self.configdbJsonIn = sonic_cfggen.FormatConverter.to_serialized(data)
self.sysLog(syslog.LOG_DEBUG, 'Reading Input from ConfigDB {}'.\
Expand All @@ -214,8 +220,11 @@ def writeConfigDB(self, jDiff):
'''
self.sysLog(doPrint=True, msg='Writing in Config DB')
data = dict()
configdb = ConfigDBConnector()
configdb.connect(False)
if self.configdb is None:
configdb = ConfigDBConnector()
configdb.connect(False)
else:
configdb = self.configdb
sonic_cfggen.deep_update(data, sonic_cfggen.FormatConverter.to_deserialized(jDiff))
self.sysLog(msg="Write in DB: {}".format(data))
configdb.mod_config(sonic_cfggen.FormatConverter.output_to_db(data))
Expand Down
63 changes: 36 additions & 27 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1777,36 +1777,45 @@ def override_config_table(db, input_config_db, dry_run):
fg='magenta')
sys.exit(1)

config_db = db.cfgdb

# Read config from configDB
current_config = config_db.get_config()
# Serialize to the same format as json input
sonic_cfggen.FormatConverter.to_serialized(current_config)

updated_config = update_config(current_config, config_input)
cfgdb_clients = db.cfgdb_clients

for ns, config_db in cfgdb_clients.items():
# Read config from configDB
current_config = config_db.get_config()
# Serialize to the same format as json input
sonic_cfggen.FormatConverter.to_serialized(current_config)

if multi_asic.is_multi_asic():
# Golden Config will use "localhost" to represent host name
if ns == DEFAULT_NAMESPACE:
ns_config_input = config_input["localhost"]
else:
ns_config_input = config_input[ns]
else:
ns_config_input = config_input
updated_config = update_config(current_config, ns_config_input)

yang_enabled = device_info.is_yang_config_validation_enabled(config_db)
if yang_enabled:
# The ConfigMgmt will load YANG and running
# config during initialization.
try:
cm = ConfigMgmt()
cm.validateConfigData()
except Exception as ex:
click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta")
sys.exit(1)
yang_enabled = device_info.is_yang_config_validation_enabled(config_db)
if yang_enabled:
# The ConfigMgmt will load YANG and running
# config during initialization.
try:
cm = ConfigMgmt(configdb=config_db)
cm.validateConfigData()
except Exception as ex:
click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta")
sys.exit(1)

# Validate input config
validate_config_by_cm(cm, config_input, "config_input")
# Validate updated whole config
validate_config_by_cm(cm, updated_config, "updated_config")
# Validate input config
validate_config_by_cm(cm, ns_config_input, "config_input")
# Validate updated whole config
validate_config_by_cm(cm, updated_config, "updated_config")

if dry_run:
print(json.dumps(updated_config, sort_keys=True,
indent=4, cls=minigraph_encoder))
else:
override_config_db(config_db, config_input)
if dry_run:
print(json.dumps(updated_config, sort_keys=True,
indent=4, cls=minigraph_encoder))
else:
override_config_db(config_db, ns_config_input)


def validate_config_by_cm(cm, config_json, jname):
Expand Down
11 changes: 11 additions & 0 deletions tests/config_override_input/multi_asic_dm_rm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"localhost": {
"DEVICE_METADATA": {}
},
"asic0": {
"DEVICE_METADATA": {}
},
"asic1": {
"DEVICE_METADATA": {}
}
}
23 changes: 23 additions & 0 deletions tests/config_override_input/multi_asic_macsec_ov.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"localhost": {
"MACSEC_PROFILE": {
"profile": {
"key": "value"
}
}
},
"asic0": {
"MACSEC_PROFILE": {
"profile": {
"key": "value"
}
}
},
"asic1": {
"MACSEC_PROFILE": {
"profile": {
"key": "value"
}
}
}
}
77 changes: 75 additions & 2 deletions tests/config_override_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import json
import filecmp
import importlib
import config.main as config

from click.testing import CliRunner
Expand All @@ -20,6 +21,8 @@
RUNNING_CONFIG_YANG_FAILURE = os.path.join(DATA_DIR, "running_config_yang_failure.json")
GOLDEN_INPUT_YANG_FAILURE = os.path.join(DATA_DIR, "golden_input_yang_failure.json")
FINAL_CONFIG_YANG_FAILURE = os.path.join(DATA_DIR, "final_config_yang_failure.json")
MULTI_ASIC_MACSEC_OV = os.path.join(DATA_DIR, "multi_asic_macsec_ov.json")
MULTI_ASIC_DEVICE_METADATA_RM = os.path.join(DATA_DIR, "multi_asic_dm_rm.json")

# Load sonic-cfggen from source since /usr/local/bin/sonic-cfggen does not have .py extension.
sonic_cfggen = load_module_from_source('sonic_cfggen', '/usr/local/bin/sonic-cfggen')
Expand Down Expand Up @@ -173,7 +176,7 @@ def test_yang_verification_enabled(self):
def is_yang_config_validation_enabled_side_effect(filename):
return True

def config_mgmt_side_effect():
def config_mgmt_side_effect(configdb):
return config_mgmt.ConfigMgmt(source=CONFIG_DB_JSON_FILE)

db = Db()
Expand Down Expand Up @@ -232,7 +235,7 @@ def check_yang_verification_failure(self, db, config, running_config,
def read_json_file_side_effect(filename):
return golden_config

def config_mgmt_side_effect():
def config_mgmt_side_effect(configdb):
return config_mgmt.ConfigMgmt(source=CONFIG_DB_JSON_FILE)

# ConfigMgmt will call ConfigDBConnector to load default config_db.json.
Expand All @@ -257,3 +260,73 @@ def teardown_class(cls):
print("TEARDOWN")
os.environ["UTILITIES_UNIT_TESTING"] = "0"
return


class TestConfigOverrideMultiasic(object):
@classmethod
def setup_class(cls):
print("SETUP")
os.environ["UTILITIES_UNIT_TESTING"] = "1"
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic"
# 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()
return

def test_macsec_override(self):
def read_json_file_side_effect(filename):
with open(MULTI_ASIC_MACSEC_OV, "r") as f:
macsec_profile = json.load(f)
return macsec_profile
db = Db()
cfgdb_clients = db.cfgdb_clients

# The profile_content was copied from MULTI_ASIC_MACSEC_OV, where all
# ns sharing the same content: {"profile": {"key": "value"}}
profile_content = {"profile": {"key": "value"}}

with mock.patch('config.main.read_json_file',
mock.MagicMock(side_effect=read_json_file_side_effect)):
runner = CliRunner()
result = runner.invoke(config.config.commands["override-config-table"],
['golden_config_db.json'], obj=db)
assert result.exit_code == 0

for ns, config_db in cfgdb_clients.items():
assert config_db.get_config()['MACSEC_PROFILE'] == profile_content

def test_device_metadata_table_rm(self):
def read_json_file_side_effect(filename):
with open(MULTI_ASIC_DEVICE_METADATA_RM, "r") as f:
device_metadata = json.load(f)
return device_metadata
db = Db()
cfgdb_clients = db.cfgdb_clients

for ns, config_db in cfgdb_clients.items():
assert 'DEVICE_METADATA' in config_db.get_config()

with mock.patch('config.main.read_json_file',
mock.MagicMock(side_effect=read_json_file_side_effect)):
runner = CliRunner()
result = runner.invoke(config.config.commands["override-config-table"],
['golden_config_db.json'], obj=db)
assert result.exit_code == 0

for ns, config_db in cfgdb_clients.items():
assert 'DEVICE_METADATA' not in config_db.get_config()


@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()
return

0 comments on commit 31973ee

Please sign in to comment.