From 890951712be3a4c1c5566592899500288eb1c174 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 10 May 2022 10:55:19 +0000 Subject: [PATCH] Add portchannel YANG validation --- config/main.py | 18 +++++++---- config/yang_validation_service.py | 51 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 config/yang_validation_service.py diff --git a/config/main.py b/config/main.py index 3d549b991c..b48a5c21a3 100644 --- a/config/main.py +++ b/config/main.py @@ -30,6 +30,7 @@ from utilities_common.general import load_db_config from .utils import log +from .yang_validation_service import YangValidationService from . import aaa from . import chassis_modules @@ -1740,10 +1741,6 @@ def portchannel(ctx, namespace): @click.pass_context def add_portchannel(ctx, portchannel_name, min_links, fallback): """Add port channel""" - if is_portchannel_name_valid(portchannel_name) != True: - ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" - .format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO)) - db = ctx.obj['db'] if is_portchannel_present_in_db(db, portchannel_name): @@ -1756,7 +1753,12 @@ def add_portchannel(ctx, portchannel_name, min_links, fallback): fvs['min_links'] = str(min_links) if fallback != 'false': fvs['fallback'] = 'true' - db.set_entry('PORTCHANNEL', portchannel_name, fvs) + + yvs = YangValidationService() + if not yvs.validate_set_entry('PORTCHANNEL', portchannel_name, fvs): + ctx.fail("Invalid configuration based on PortChannel YANG model") + else: + db.set_entry('PORTCHANNEL', portchannel_name, fvs) @portchannel.command('del') @click.argument('portchannel_name', metavar='', required=True) @@ -1776,7 +1778,11 @@ def remove_portchannel(ctx, portchannel_name): if len([(k, v) for k, v in db.get_table('PORTCHANNEL_MEMBER') if k == portchannel_name]) != 0: click.echo("Error: Portchannel {} contains members. Remove members before deleting Portchannel!".format(portchannel_name)) else: - db.set_entry('PORTCHANNEL', portchannel_name, None) + yvs = YangValidationService() + if not yvs.validate_set_entry('PORTCHANNEL', portchannel_name, None): + ctx.fail("Invalid configuration based on PortChannel YANG model") + else: + db.set_entry('PORTCHANNEL', portchannel_name, None) @portchannel.group(cls=clicommon.AbbreviationGroup, name='member') @click.pass_context diff --git a/config/yang_validation_service.py b/config/yang_validation_service.py new file mode 100644 index 0000000000..37d3bd2bc7 --- /dev/null +++ b/config/yang_validation_service.py @@ -0,0 +1,51 @@ +import json +import sonic_yang +import subprocess +import copy + +YANG_DIR = "/usr/local/yang-models" + +class YangValidationService: + def __init__(self, yang_dir = YANG_DIR): + self.yang_dir = YANG_DIR + self.sonic_yang_with_loaded_models = None + + def get_config_db_as_json(self): + cmd = "show runningconfiguration all" + result = subprocess.Popen(cmd, shell=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + text, err = result.communicate() + return_code = result.returncode + if return_code: + raise RuntimeError("Failed to get running config, Return Code: {}, Error: {}".format(return_code, err)) + return json.loads(text) + + def create_sonic_yang_with_loaded_models(self): + if self.sonic_yang_with_loaded_models is None: + loaded_models_sy = sonic_yang.SonicYang(self.yang_dir) + loaded_models_sy.loadYangModel() + self.sonic_yang_with_loaded_models = loaded_models_sy + + return copy.copy(self.sonic_yang_with_loaded_models) + + def validate_config_db_config(self, config_json): + sy = self.create_sonic_yang_with_loaded_models() + try: + tmp_config_json = copy.deepcopy(config_json) + sy.loadData(tmp_config_json) + sy.validate_data_tree() + return True + except sonic_yang.SonicYangException as ex: + return False + return False + + def validate_set_entry(self, table, key, data): + config_json = self.get_config_db_as_json() + if not self.validate_config_db_config(config_json): + return False + if data is not None: + config_json[table][key] = data + if not self.validate_config_db_config(config_json): + return False + return True + +