Skip to content
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

Generic_upater: Apply JSON change #1856

Merged
merged 11 commits into from
Oct 25, 2021
103 changes: 103 additions & 0 deletions generic_config_updater/change_applier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import copy
import json
import importlib
import os
import syslog
import tempfile
from collections import defaultdict
from swsscommon.swsscommon import ConfigDBConnector
from .gu_common import log_error, log_debug, log_info


def get_config_db():
config_db = ConfigDBConnector()
config_db.connect()
return config_db


def set_config(config_db, tbl, key, data):
config_db.set_entry(tbl, key, data)


UPDATER_CONF_FILE = "/etc/sonic/generic_config_updater.conf"
updater_data = None

class ChangeApplier:
def __init__(self):
global updater_data, log_level
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved

self.config_db = get_config_db()
if updater_data == None:
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
with open(UPDATER_CONF_FILE, "r") as s:
updater_data = json.load(s)


def _invoke_cmd(cmd, old_cfg, upd_cfg, keys):
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
method_name = cmd.split(".")[-1]
module_name = ".".join(cmd.split(".")[0:-1])

module = importlib.import_module(module_name, package=None)
method_to_call = getattr(module, method_name)

return method_to_call(old_cfg, upd_cfg, keys)


def _services_validate(old_cfg, upd_cfg, keys):
lst_svcs = set()
lst_cmds = set()
if not keys:
keys[""] = {}
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
for tbl in keys:
lst_svcs.update(updater_data.get(tbl, {}).get("services_to_validate", []))
for svc in lst_svcs:
lst_cmds.update(updater_data.get(svc, {}).get("validate_commands", []))

for cmd in lst_cmds:
ret = _invoke_cmd(cmd, old_cfg, upd_cfg, keys)
if ret:
return ret
return 0


def _upd_data(self, tbl, run_tbl, upd_tbl, upd_keys):
for key in set(run_tbl.keys()).union(set(upd_tbl.keys())):
run_data = run_tbl.get(key, None)
upd_data = upd_tbl.get(key, None)

if run_data != upd_data:
set_config(self.config_db, tbl, key, upd_data)
upd_keys[tbl][key] = {}


def apply(self, change):
run_data = self._get_running_config()
upd_data = change.apply(copy.deepcopy(run_data))
upd_keys = defaultdict(dict)

for tbl in set(run_data.keys()).union(set(upd_data.keys())):
self._upd_data(tbl, run_data.get(tbl, {}),
upd_data.get(tbl, {}), upd_keys)

ret = _services_validate(run_data, upd_data, upd_keys)
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
if not ret:
run_data = self._get_running_config()
if upd_data != run_data:
report_mismatch(run_data, upd_data)
ret = -1
return ret


def _get_running_config(self):
(_, fname) = tempfile.mkstemp(suffix="_changeApplier")
os.system("sonic-cfggen -d --print-data > {}".format(fname))
run_data = {}
with open(fname, "r") as s:
run_data = json.load(s)
if os.path.isfile(fname):
os.remove(fname)
return run_data





renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
13 changes: 7 additions & 6 deletions generic_config_updater/generic_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
import os
from enum import Enum
from .gu_common import GenericConfigUpdaterError, ConfigWrapper, \
DryRunConfigWrapper, PatchWrapper
DryRunConfigWrapper, PatchWrapper, \
set_log_level
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved

from .patch_sorter import PatchSorter
from .change_applier import ChangeApplier


CHECKPOINTS_DIR = "/etc/sonic/checkpoints"
CHECKPOINT_EXT = ".cp.json"
Expand All @@ -17,15 +21,12 @@ def release_lock(self):
# TODO: Implement ConfigLock
pass

class ChangeApplier:
def apply(self, change):
# TODO: Implement change applier
raise NotImplementedError("ChangeApplier.apply(change) is not implemented yet")

class ConfigFormat(Enum):
CONFIGDB = 1
SONICYANG = 2


class PatchApplier:
def __init__(self,
patchsorter=None,
Expand Down Expand Up @@ -297,7 +298,7 @@ def init_verbose_logging(self, verbose):
# Usually logs have levels such as: error, warning, info, debug.
# By default all log levels should show up to the user, except debug.
# By allowing verbose logging, debug msgs will also be shown to the user.
pass
set_log_level(syslog.LOG_ERR if not verbose else syslog.LOG_DEBUG)

def get_config_wrapper(self, dry_run):
if dry_run:
Expand Down
44 changes: 44 additions & 0 deletions generic_config_updater/generic_updater_config.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"tables": {
"": {
"services_to_validate": [ "system_health" ]
},
"PORT": {
"services_to_validate": [ "port_service" ]
}
},
"README": [
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
'Validate_commands provides, module & method name as ',
' <module name>.<method name>',
'NOTE: module name could have "."',
' ',
'The last element separated by "." is considered as ',
'method name',
'',
'e.g. "show.acl.test_acl"',
'',
'Here we load "show.acl" and call "test_acl" method on it.',
'',
'called as:',
' <module>.<method>>(<config before change>, ',
' <config after change>, <affected keys>)',
' config is in JSON format as in config_db.json',
' affected_keys in same format, but w/o value',
' { "ACL_TABLE": { "SNMP_ACL": {} ... }, ...}',
' The affected keys has "added", "updated" & "deleted"',
'',
'Multiple validate commands may be provided.',',
'',
'Note: The commands may be called in any order',
''
],
"services": {
"system_health": {
"validate_commands": [ ]
},
"port_service": {
"validate_commands": [ ]
}
}
}

30 changes: 30 additions & 0 deletions generic_config_updater/gu_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,36 @@
class GenericConfigUpdaterError(Exception):
pass

log_level = syslog.LOG_ERR

def _log_msg(lvl, m):
if lvl <= log_level:
syslog.syslog(lvl, m)
if log_level == syslog.LOG_DEBUG:
print(m)

def log_error(m):
_log_msg(syslog.LOG_ERR, m)


def log_info(m):
_log_msg(syslog.LOG_INFO, m)


def log_debug(m):
_log_msg(syslog.LOG_DEBUG, m)


def run_cmd(cmd):
proc = subprocess.run(cmd, shell=True, capture_output=True)
if proc.returncode:
log_error("Failed to run: ret={} cmd: {}".format(
proc.returncode, proc.args))
log_error(f"Failed to run: stdout: {proc.stdout}")
log_error(f"Failed to run: stderr: {proc.stderr}")
return proc.returncode


class JsonChange:
"""
A class that describes a partial change to a JSON object.
Expand Down
Loading