From 310b5f68c60e5ebe7e3dd675accb7e00d129a601 Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Sat, 7 May 2022 04:19:17 +0000 Subject: [PATCH 1/9] Yang helper initial changes --- src/sonic-yang-mgmt/sonic-cfg-help | 192 ++++++++++++++++++++++ src/sonic-yang-mgmt/tests/test_cfghelp.py | 166 +++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100755 src/sonic-yang-mgmt/sonic-cfg-help create mode 100644 src/sonic-yang-mgmt/tests/test_cfghelp.py diff --git a/src/sonic-yang-mgmt/sonic-cfg-help b/src/sonic-yang-mgmt/sonic-cfg-help new file mode 100755 index 000000000000..556c951f1470 --- /dev/null +++ b/src/sonic-yang-mgmt/sonic-cfg-help @@ -0,0 +1,192 @@ +#!/usr/bin/python3 + +import sys +import argparse +from tabulate import tabulate +import re +import textwrap + +from collections import OrderedDict +import sonic_yang + +YANG_MODELS_DIR = "/usr/local/yang-models" + +class SonicCfgDescriber: + + def __init__(self, table_name, field, print_format, yang_models_dir=YANG_MODELS_DIR): + self.yang_models_dir = yang_models_dir + self.yang_parser = sonic_yang.SonicYang(self.yang_models_dir) + self.yang_parser.loadYangModel() + self.table_descr = {} + self.print_format = print_format + + for j in self.yang_parser.yJson: + toplevel = j['module'].get('container') + if toplevel is not None: + container = toplevel.get('container') + if isinstance(container, list): + for c in container: + if c.get('@name') == table_name or not table_name: + self.print_table(c, field) + elif isinstance(container, dict): + if container.get('@name') == table_name or not table_name: + self.print_table(container, field) + + def print_table(self, table, field): + if table is None: + return + print("\n" + table.get('@name')) + if table.get('description') is not None and table.get('description').get('text') is not None: + print("Description: " + table.get('description').get('text')) + print () + + if table.get('list') is not None: + if (isinstance(table['list'], list)): + for schema in table['list']: + self.print_field_desc(schema, field) + print() + else: + self.print_field_desc(table['list'], field) + print() + elif table.get('container') is not None: + self.print_field_desc(table.get('container'), field) + print() + + + def get_referenced_table_field(self , ref): + if 'LIST' in ref.split('/')[-2]: + table = ref.split('/')[-3].split(':')[-1] + else: + table = ref.split('/')[-2].split(':')[-1] + field = ref.split('/')[-1].split(':')[-1] + return(table + ":" + field) + + def parse_when_condition(self, table): + condition = table['@condition'] + desc = "" + if "boolean" in condition: + values = re.findall("\'(.*?)\'", condition, re.DOTALL) + field = re.search("boolean\((.*?)\[", condition) + desc = "when " + field.group(1) + " in " + ",".join(values) + elif condition.startswith("(/"): + field = re.search("/(.*)\:(.*) \=", condition) + ref_table = condition.split("/")[2].split(':')[-1] + values = re.findall("\'(.*?)\'", condition, re.DOTALL) + desc = "when " + ref_table + ":" + field.group(2) + " in " + ",".join(values) + + return desc + + def parse_choice(self, table, field): + out = [] + for keys in table['case']: + desc = "Mutually exclusive in group " + table['@name'] + if 'when' in keys: + desc += "\n" + self.parse_when_condition(keys['when']) + out += self.validate_and_parse_leaf(keys, field, desc) + return out + + def parse_leaf(self, key, field, desc = ""): + mandatory = '' + default = '' + out = [] + reference = '' + name = key.get('@name') + if field and name != field: + return [] + if isinstance(key, dict): + if key.get('description') is not None and key.get('description').get('text') is not None: + desc += "\n".join(textwrap.wrap(re.sub(r"\s+", " ", key['description']['text']), width=50)) + if key.get('mandatory') is not None: + mandatory = key.get('mandatory').get('@value') + if key.get('default') is not None: + default = key.get('default').get('@value') + if key.get('type') is not None: + if key['type'].get('@name') == 'leafref': + reference = self.get_referenced_table_field(key['type']['path'].get('@value')) + elif key['type'].get('@name') == 'union': + for types in key['type']['type']: + if 'path' in types: + val = self.get_referenced_table_field(types['path'].get('@value')) + if not reference: + reference = val + else: + reference += "\n" + val + out.append([name,desc, mandatory, default, reference]) + return out + + def validate_and_parse_leaf(self, table, field, desc=""): + out = [] + if 'leaf' in table: + if isinstance(table['leaf'], list): + for key in table['leaf']: + ret = self.parse_leaf(key, field, desc) + out = out + ret + elif isinstance(table['leaf'], dict): + ret = self.parse_leaf(table['leaf'], field, desc) + out = out + ret + + if 'leaf-list' in table: + if desc: + desc = desc + "\n" + desc = desc + "The field contains list of unique members" + if isinstance(table['leaf-list'], list): + for key in table['leaf-list']: + ret = self.parse_leaf(key, field, desc) + out = out + ret + elif isinstance(table['leaf-list'], dict): + ret = self.parse_leaf(table['leaf-list'], field, desc) + out = out + ret + return out + + def print_field_desc(self, table, field): + if table is None: + return + + header = ['Field', 'Description', 'Mandatory', 'Default', 'Reference'] + out = [] + if 'key' in table: + print("key - " + ":".join(table['key']['@value'].split())) + + out += self.validate_and_parse_leaf(table, field) + + if 'choice' in table: + if isinstance(table['choice'], list): + for key in table['choice']: + out += self.parse_choice(key, field) + elif isinstance(table['choice'], dict): + out += self.parse_choice(table['choice'], field) + + if 'list' in table: + out += self.validate_and_parse_leaf(table['list'], field, "This field is for storing mapping between two fields") + + print(tabulate(out,header,tablefmt=self.print_format)) + +def main(): + parser=argparse.ArgumentParser(description="Description of table name") + parser.add_argument("-t", "--table", help="Table name", default = '') + parser.add_argument("-f", "--field", help="Field", default = '') + parser.add_argument("-p", "--print_format", help="Print format", default = 'grid') + #parser.add_argument("-a", "--all", action='store_true', defalut = False, help="Print all tables") + parser.add_argument('-a', "--all", action="store_true", default=False, help= "Print all tables") + args = parser.parse_args() + if not (args.table or args.all): + print("Error: Table or all option is required") + parser.print_help() + return -1 + + if args.table and args.all: + print("Cannot have table and all option together") + parser.print_help() + return -1 + + if args.field and not args.table: + print("Error: Filter by field requires table to be specified") + parser.print_help() + return -1 + + + yang_cfg = SonicCfgDescriber(args.table, args.field, args.print_format) + + +if __name__ == "__main__": + main() diff --git a/src/sonic-yang-mgmt/tests/test_cfghelp.py b/src/sonic-yang-mgmt/tests/test_cfghelp.py new file mode 100644 index 000000000000..34b2de6f496e --- /dev/null +++ b/src/sonic-yang-mgmt/tests/test_cfghelp.py @@ -0,0 +1,166 @@ +import json +import subprocess +import os + +from unittest import TestCase + +output1="""\ +Error: Table or all option is required +usage: sonic-cfg-help [-h] [-t TABLE] [-f FIELD] [-p PRINT_FORMAT] [-a] + +Description of table name + +optional arguments: + -h, --help show this help message and exit + -t TABLE, --table TABLE + Table name + -f FIELD, --field FIELD + Field + -p PRINT_FORMAT, --print_format PRINT_FORMAT + Print format + -a, --all Print all tables +""" + +techsupport_table_output="""\ + +AUTO_TECHSUPPORT +Description: AUTO_TECHSUPPORT part of config_db.json + ++-----------------------+----------------------------------------------------+-------------+-----------+-------------+ +| Field | Description | Mandatory | Default | Reference | ++=======================+====================================================+=============+===========+=============+ +| state | Knob to make techsupport invocation event-driven | | | | +| | based on core-dump generation | | | | ++-----------------------+----------------------------------------------------+-------------+-----------+-------------+ +| rate_limit_interval | Minimum time in seconds between two successive | | | | +| | techsupport invocations. Configure 0 to explicitly | | | | +| | disable | | | | ++-----------------------+----------------------------------------------------+-------------+-----------+-------------+ +| max_techsupport_limit | Max Limit in percentage for the cummulative size | | | | +| | of ts dumps. No cleanup is performed if the value | | | | +| | isn't configured or is 0.0 | | | | ++-----------------------+----------------------------------------------------+-------------+-----------+-------------+ +| max_core_limit | Max Limit in percentage for the cummulative size | | | | +| | of core dumps. No cleanup is performed if the | | | | +| | value isn't congiured or is 0.0 | | | | ++-----------------------+----------------------------------------------------+-------------+-----------+-------------+ +| since | Only collect the logs & core-dumps generated since | | | | +| | the time provided. A default value of '2 days ago' | | | | +| | is used if this value is not set explicitly or a | | | | +| | non-valid string is provided | | | | ++-----------------------+----------------------------------------------------+-------------+-----------+-------------+ + +""" + +techsupport_table_field_output="""\ + +AUTO_TECHSUPPORT +Description: AUTO_TECHSUPPORT part of config_db.json + ++---------+--------------------------------------------------+-------------+-----------+-------------+ +| Field | Description | Mandatory | Default | Reference | ++=========+==================================================+=============+===========+=============+ +| state | Knob to make techsupport invocation event-driven | | | | +| | based on core-dump generation | | | | ++---------+--------------------------------------------------+-------------+-----------+-------------+ + +""" + +portchannel_table_field_output="""\ + +PORTCHANNEL +Description: PORTCHANNEL part of config_db.json + +key - name ++---------+-------------------------------------------+-------------+-----------+-------------+ +| Field | Description | Mandatory | Default | Reference | ++=========+===========================================+=============+===========+=============+ +| members | The field contains list of unique members | | | PORT:name | ++---------+-------------------------------------------+-------------+-----------+-------------+ + +""" + +dscp_to_tc_table_field_output="""\ + +DSCP_TO_TC_MAP +Description: DSCP_TO_TC_MAP part of config_db.json + +key - name ++---------+------------------------------------------------------+-------------+-----------+-------------+ +| Field | Description | Mandatory | Default | Reference | ++=========+======================================================+=============+===========+=============+ +| name | | | | | ++---------+------------------------------------------------------+-------------+-----------+-------------+ +| dscp | This field is for storing mapping between two fields | | | | ++---------+------------------------------------------------------+-------------+-----------+-------------+ +| tc | This field is for storing mapping between two fields | | | | ++---------+------------------------------------------------------+-------------+-----------+-------------+ + +""" + +acl_rule_table_field_output="""\ + +ACL_RULE +Description: ACL_RULE part of config_db.json + +key - ACL_TABLE_NAME:RULE_NAME ++-----------+-----------------------------------------+-------------+-----------+-------------+ +| Field | Description | Mandatory | Default | Reference | ++===========+=========================================+=============+===========+=============+ +| ICMP_TYPE | Mutually exclusive in group icmp | | | | +| | when IP_TYPE in ANY,IP,IPV4,IPv4ANY,ARP | | | | ++-----------+-----------------------------------------+-------------+-----------+-------------+ + +""" + +class TestCfgHelp(TestCase): + + def setUp(self): + self.test_dir = os.path.dirname(os.path.realpath(__file__)) + self.script_file = 'python3 ' + os.path.join(self.test_dir, '..', 'sonic-cfg-help') + + def run_script(self, argument): + print('\n Running sonic-cfg-help ' + argument) + output = subprocess.check_output(self.script_file + ' ' + argument, shell=True) + + output = output.decode() + + linecount = output.strip().count('\n') + if linecount <= 0: + print(' Output: ' + output.strip()) + else: + print(' Output: ({0} lines, {1} bytes)'.format(linecount + 1, len(output))) + return output + + def test_dummy_run(self): + argument = '' + output = self.run_script(argument) + self.assertEqual(output, output1) + + def test_single_table(self): + argument = '-t AUTO_TECHSUPPORT' + output = self.run_script(argument) + self.assertEqual(output, techsupport_table_output) + + def test_single_field(self): + argument = '-t AUTO_TECHSUPPORT -f state' + output = self.run_script(argument) + self.assertEqual(output, techsupport_table_field_output) + + def test_leaf_list(self): + argument = '-t PORTCHANNEL -f members' + output = self.run_script(argument) + print(output) + self.assertEqual(output, portchannel_table_field_output) + + def test_leaf_list_map(self): + argument = '-t DSCP_TO_TC_MAP' + output = self.run_script(argument) + print(output) + self.assertEqual(output, dscp_to_tc_table_field_output) + + def test_when_condition(self): + argument = '-t ACL_RULE -f ICMP_TYPE' + output = self.run_script(argument) + print(output) + self.assertEqual(output, acl_rule_table_field_output) From c131a418e0c913c1464d3063c8fdb8cbe7a9823f Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Tue, 7 Jun 2022 20:50:25 +0000 Subject: [PATCH 2/9] Adding dependecies in setup.py --- src/sonic-yang-mgmt/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py index e97343ac0e0a..c334e2e394f3 100644 --- a/src/sonic-yang-mgmt/setup.py +++ b/src/sonic-yang-mgmt/setup.py @@ -28,6 +28,7 @@ 'xmltodict==0.12.0', 'ijson==2.6.1', 'jsondiff>=1.2.0', + 'tabulate==0.8.2' ], tests_require = [ 'pytest>3', From 5bd8cc44e7f8ffbab0609ada07483e2e0edab9fc Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Tue, 7 Jun 2022 21:22:42 +0000 Subject: [PATCH 3/9] Adding sonic-cfg-help in setup.py --- src/sonic-yang-mgmt/setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sonic-yang-mgmt/setup.py b/src/sonic-yang-mgmt/setup.py index c334e2e394f3..d513507dd17c 100644 --- a/src/sonic-yang-mgmt/setup.py +++ b/src/sonic-yang-mgmt/setup.py @@ -24,6 +24,9 @@ description="Package contains Python Library for YANG for sonic.", license="GNU General Public License v3", long_description=readme + '\n\n', + scripts = [ + 'sonic-cfg-help', + ], install_requires = [ 'xmltodict==0.12.0', 'ijson==2.6.1', From 3c1d4aefbcf103ae7327882dbc6af71ef2dae74b Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Wed, 29 Jun 2022 02:20:16 +0000 Subject: [PATCH 4/9] Addressing code review comments --- src/sonic-yang-mgmt/sonic-cfg-help | 49 +++++++++++++---------- src/sonic-yang-mgmt/tests/test_cfghelp.py | 2 +- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/sonic-yang-mgmt/sonic-cfg-help b/src/sonic-yang-mgmt/sonic-cfg-help index 556c951f1470..38066d9d81df 100755 --- a/src/sonic-yang-mgmt/sonic-cfg-help +++ b/src/sonic-yang-mgmt/sonic-cfg-help @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 import sys import argparse @@ -11,9 +11,11 @@ import sonic_yang YANG_MODELS_DIR = "/usr/local/yang-models" + class SonicCfgDescriber: - def __init__(self, table_name, field, print_format, yang_models_dir=YANG_MODELS_DIR): + def __init__(self, table_name, field, print_format, + yang_models_dir=YANG_MODELS_DIR): self.yang_models_dir = yang_models_dir self.yang_parser = sonic_yang.SonicYang(self.yang_models_dir) self.yang_parser.loadYangModel() @@ -36,7 +38,7 @@ class SonicCfgDescriber: if table is None: return print("\n" + table.get('@name')) - if table.get('description') is not None and table.get('description').get('text') is not None: + if table.get('description', {}).get('text') is not None: print("Description: " + table.get('description').get('text')) print () @@ -51,9 +53,8 @@ class SonicCfgDescriber: elif table.get('container') is not None: self.print_field_desc(table.get('container'), field) print() - - def get_referenced_table_field(self , ref): + def get_referenced_table_field(self, ref): if 'LIST' in ref.split('/')[-2]: table = ref.split('/')[-3].split(':')[-1] else: @@ -72,7 +73,8 @@ class SonicCfgDescriber: field = re.search("/(.*)\:(.*) \=", condition) ref_table = condition.split("/")[2].split(':')[-1] values = re.findall("\'(.*?)\'", condition, re.DOTALL) - desc = "when " + ref_table + ":" + field.group(2) + " in " + ",".join(values) + desc = "when " + ref_table + ":" + field.group(2) + " in " + + ",".join(values) return desc @@ -85,7 +87,7 @@ class SonicCfgDescriber: out += self.validate_and_parse_leaf(keys, field, desc) return out - def parse_leaf(self, key, field, desc = ""): + def parse_leaf(self, key, field, desc=""): mandatory = '' default = '' out = [] @@ -94,24 +96,27 @@ class SonicCfgDescriber: if field and name != field: return [] if isinstance(key, dict): - if key.get('description') is not None and key.get('description').get('text') is not None: - desc += "\n".join(textwrap.wrap(re.sub(r"\s+", " ", key['description']['text']), width=50)) + if key.get('description', {}).get('text') is not None: + desc += "\n".join(textwrap.wrap(re.sub(r"\s+", " ", + key['description']['text']), width=50)) if key.get('mandatory') is not None: mandatory = key.get('mandatory').get('@value') if key.get('default') is not None: default = key.get('default').get('@value') if key.get('type') is not None: if key['type'].get('@name') == 'leafref': - reference = self.get_referenced_table_field(key['type']['path'].get('@value')) + reference = self.get_referenced_table_field( + key['type']['path'].get('@value')) elif key['type'].get('@name') == 'union': for types in key['type']['type']: if 'path' in types: - val = self.get_referenced_table_field(types['path'].get('@value')) + val = self.get_referenced_table_field( + types['path'].get('@value')) if not reference: reference = val else: reference += "\n" + val - out.append([name,desc, mandatory, default, reference]) + out.append([name, desc, mandatory, default, reference]) return out def validate_and_parse_leaf(self, table, field, desc=""): @@ -157,17 +162,20 @@ class SonicCfgDescriber: out += self.parse_choice(table['choice'], field) if 'list' in table: - out += self.validate_and_parse_leaf(table['list'], field, "This field is for storing mapping between two fields") + out += self.validate_and_parse_leaf(table['list'], field, + "This field is for storing mapping between two fields") + + print(tabulate(out, header, tablefmt=self.print_format)) - print(tabulate(out,header,tablefmt=self.print_format)) def main(): parser=argparse.ArgumentParser(description="Description of table name") - parser.add_argument("-t", "--table", help="Table name", default = '') - parser.add_argument("-f", "--field", help="Field", default = '') - parser.add_argument("-p", "--print_format", help="Print format", default = 'grid') - #parser.add_argument("-a", "--all", action='store_true', defalut = False, help="Print all tables") - parser.add_argument('-a', "--all", action="store_true", default=False, help= "Print all tables") + parser.add_argument("-t", "--table", help="Table name", default ='') + parser.add_argument("-f", "--field", help="Field", default ='') + parser.add_argument("-p", "--print_format", help="Print format", + default ='grid') + parser.add_argument('-a', "--all", action="store_true", default=False, + help="Print all tables") args = parser.parse_args() if not (args.table or args.all): print("Error: Table or all option is required") @@ -184,9 +192,8 @@ def main(): parser.print_help() return -1 - yang_cfg = SonicCfgDescriber(args.table, args.field, args.print_format) - + if __name__ == "__main__": main() diff --git a/src/sonic-yang-mgmt/tests/test_cfghelp.py b/src/sonic-yang-mgmt/tests/test_cfghelp.py index 34b2de6f496e..4d525221e12b 100644 --- a/src/sonic-yang-mgmt/tests/test_cfghelp.py +++ b/src/sonic-yang-mgmt/tests/test_cfghelp.py @@ -117,7 +117,7 @@ class TestCfgHelp(TestCase): def setUp(self): self.test_dir = os.path.dirname(os.path.realpath(__file__)) - self.script_file = 'python3 ' + os.path.join(self.test_dir, '..', 'sonic-cfg-help') + self.script_file = 'python ' + os.path.join(self.test_dir, '..', 'sonic-cfg-help') def run_script(self, argument): print('\n Running sonic-cfg-help ' + argument) From b811833283020b4632c9143721b542b2d784bfe4 Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Wed, 29 Jun 2022 02:29:01 +0000 Subject: [PATCH 5/9] Pep8 compliance --- src/sonic-yang-mgmt/sonic-cfg-help | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/sonic-yang-mgmt/sonic-cfg-help b/src/sonic-yang-mgmt/sonic-cfg-help index 38066d9d81df..9427cdf663e8 100755 --- a/src/sonic-yang-mgmt/sonic-cfg-help +++ b/src/sonic-yang-mgmt/sonic-cfg-help @@ -14,7 +14,7 @@ YANG_MODELS_DIR = "/usr/local/yang-models" class SonicCfgDescriber: - def __init__(self, table_name, field, print_format, + def __init__(self, table_name, field, print_format, yang_models_dir=YANG_MODELS_DIR): self.yang_models_dir = yang_models_dir self.yang_parser = sonic_yang.SonicYang(self.yang_models_dir) @@ -74,7 +74,7 @@ class SonicCfgDescriber: ref_table = condition.split("/")[2].split(':')[-1] values = re.findall("\'(.*?)\'", condition, re.DOTALL) desc = "when " + ref_table + ":" + field.group(2) + " in " - + ",".join(values) + + ",".join(values) return desc @@ -97,7 +97,7 @@ class SonicCfgDescriber: return [] if isinstance(key, dict): if key.get('description', {}).get('text') is not None: - desc += "\n".join(textwrap.wrap(re.sub(r"\s+", " ", + desc += "\n".join(textwrap.wrap(re.sub(r"\s+", " ", key['description']['text']), width=50)) if key.get('mandatory') is not None: mandatory = key.get('mandatory').get('@value') @@ -163,18 +163,19 @@ class SonicCfgDescriber: if 'list' in table: out += self.validate_and_parse_leaf(table['list'], field, - "This field is for storing mapping between two fields") + "This field is for storing \ + mapping between two fields") print(tabulate(out, header, tablefmt=self.print_format)) def main(): - parser=argparse.ArgumentParser(description="Description of table name") - parser.add_argument("-t", "--table", help="Table name", default ='') - parser.add_argument("-f", "--field", help="Field", default ='') + parser = argparse.ArgumentParser(description="Description of table name") + parser.add_argument("-t", "--table", help="Table name", default='') + parser.add_argument("-f", "--field", help="Field", default='') parser.add_argument("-p", "--print_format", help="Print format", - default ='grid') - parser.add_argument('-a', "--all", action="store_true", default=False, + default='grid') + parser.add_argument('-a', "--all", action="store_true", default=False, help="Print all tables") args = parser.parse_args() if not (args.table or args.all): @@ -197,3 +198,4 @@ def main(): if __name__ == "__main__": main() + From 4bd547f7d88493c1b39d6bb5be70c4ec54c328e3 Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Wed, 29 Jun 2022 02:35:12 +0000 Subject: [PATCH 6/9] Pep8 compliance --- src/sonic-yang-mgmt/sonic-cfg-help | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sonic-yang-mgmt/sonic-cfg-help b/src/sonic-yang-mgmt/sonic-cfg-help index 9427cdf663e8..4840403fe5f0 100755 --- a/src/sonic-yang-mgmt/sonic-cfg-help +++ b/src/sonic-yang-mgmt/sonic-cfg-help @@ -73,8 +73,8 @@ class SonicCfgDescriber: field = re.search("/(.*)\:(.*) \=", condition) ref_table = condition.split("/")[2].split(':')[-1] values = re.findall("\'(.*?)\'", condition, re.DOTALL) - desc = "when " + ref_table + ":" + field.group(2) + " in " - + ",".join(values) + desc = "when " + ref_table + ":" + field.group(2) + \ + " in " + ",".join(values) return desc From 2a8a75a2f52f8015ce380ae3b0a334e16a616041 Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Wed, 29 Jun 2022 02:57:25 +0000 Subject: [PATCH 7/9] Fixing UT failure --- src/sonic-yang-mgmt/sonic-cfg-help | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sonic-yang-mgmt/sonic-cfg-help b/src/sonic-yang-mgmt/sonic-cfg-help index 4840403fe5f0..d4b200185eee 100755 --- a/src/sonic-yang-mgmt/sonic-cfg-help +++ b/src/sonic-yang-mgmt/sonic-cfg-help @@ -163,8 +163,8 @@ class SonicCfgDescriber: if 'list' in table: out += self.validate_and_parse_leaf(table['list'], field, - "This field is for storing \ - mapping between two fields") + "This field is for storing " + + "mapping between two fields") print(tabulate(out, header, tablefmt=self.print_format)) From c6d0814f43ee423d2075eaf1da33991e6ab7f271 Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Wed, 29 Jun 2022 20:13:34 +0000 Subject: [PATCH 8/9] Fixing test issues --- src/sonic-yang-mgmt/tests/test_cfghelp.py | 58 ++++++++++++----------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/sonic-yang-mgmt/tests/test_cfghelp.py b/src/sonic-yang-mgmt/tests/test_cfghelp.py index 4d525221e12b..2c09625a0e6a 100644 --- a/src/sonic-yang-mgmt/tests/test_cfghelp.py +++ b/src/sonic-yang-mgmt/tests/test_cfghelp.py @@ -26,29 +26,35 @@ AUTO_TECHSUPPORT Description: AUTO_TECHSUPPORT part of config_db.json -+-----------------------+----------------------------------------------------+-------------+-----------+-------------+ -| Field | Description | Mandatory | Default | Reference | -+=======================+====================================================+=============+===========+=============+ -| state | Knob to make techsupport invocation event-driven | | | | -| | based on core-dump generation | | | | -+-----------------------+----------------------------------------------------+-------------+-----------+-------------+ -| rate_limit_interval | Minimum time in seconds between two successive | | | | -| | techsupport invocations. Configure 0 to explicitly | | | | -| | disable | | | | -+-----------------------+----------------------------------------------------+-------------+-----------+-------------+ -| max_techsupport_limit | Max Limit in percentage for the cummulative size | | | | -| | of ts dumps. No cleanup is performed if the value | | | | -| | isn't configured or is 0.0 | | | | -+-----------------------+----------------------------------------------------+-------------+-----------+-------------+ -| max_core_limit | Max Limit in percentage for the cummulative size | | | | -| | of core dumps. No cleanup is performed if the | | | | -| | value isn't congiured or is 0.0 | | | | -+-----------------------+----------------------------------------------------+-------------+-----------+-------------+ -| since | Only collect the logs & core-dumps generated since | | | | -| | the time provided. A default value of '2 days ago' | | | | -| | is used if this value is not set explicitly or a | | | | -| | non-valid string is provided | | | | -+-----------------------+----------------------------------------------------+-------------+-----------+-------------+ ++-------------------------+----------------------------------------------------+-------------+-----------+-------------+ +| Field | Description | Mandatory | Default | Reference | ++=========================+====================================================+=============+===========+=============+ +| state | Knob to make techsupport invocation event-driven | | | | +| | based on core-dump generation | | | | ++-------------------------+----------------------------------------------------+-------------+-----------+-------------+ +| rate_limit_interval | Minimum time in seconds between two successive | | | | +| | techsupport invocations. Configure 0 to explicitly | | | | +| | disable | | | | ++-------------------------+----------------------------------------------------+-------------+-----------+-------------+ +| max_techsupport_limit | Max Limit in percentage for the cummulative size | | | | +| | of ts dumps. No cleanup is performed if the value | | | | +| | isn't configured or is 0.0 | | | | ++-------------------------+----------------------------------------------------+-------------+-----------+-------------+ +| max_core_limit | Max Limit in percentage for the cummulative size | | | | +| | of core dumps. No cleanup is performed if the | | | | +| | value isn't congiured or is 0.0 | | | | ++-------------------------+----------------------------------------------------+-------------+-----------+-------------+ +| available_mem_threshold | Memory threshold; 0 to disable techsupport | | 10.0 | | +| | invocation on memory usage threshold crossing | | | | ++-------------------------+----------------------------------------------------+-------------+-----------+-------------+ +| min_available_mem | Minimum Free memory (in MB) that should be | | 200 | | +| | available for the techsupport execution to start | | | | ++-------------------------+----------------------------------------------------+-------------+-----------+-------------+ +| since | Only collect the logs & core-dumps generated since | | | | +| | the time provided. A default value of '2 days ago' | | | | +| | is used if this value is not set explicitly or a | | | | +| | non-valid string is provided | | | | ++-------------------------+----------------------------------------------------+-------------+-----------+-------------+ """ @@ -117,7 +123,7 @@ class TestCfgHelp(TestCase): def setUp(self): self.test_dir = os.path.dirname(os.path.realpath(__file__)) - self.script_file = 'python ' + os.path.join(self.test_dir, '..', 'sonic-cfg-help') + self.script_file = os.path.join(self.test_dir, '..', 'sonic-cfg-help') def run_script(self, argument): print('\n Running sonic-cfg-help ' + argument) @@ -150,17 +156,15 @@ def test_single_field(self): def test_leaf_list(self): argument = '-t PORTCHANNEL -f members' output = self.run_script(argument) - print(output) self.assertEqual(output, portchannel_table_field_output) def test_leaf_list_map(self): argument = '-t DSCP_TO_TC_MAP' output = self.run_script(argument) - print(output) + self.maxDiff = None self.assertEqual(output, dscp_to_tc_table_field_output) def test_when_condition(self): argument = '-t ACL_RULE -f ICMP_TYPE' output = self.run_script(argument) - print(output) self.assertEqual(output, acl_rule_table_field_output) From 4960acb540549a3400d1e1c01be52038323eb572 Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Thu, 30 Jun 2022 04:30:06 +0000 Subject: [PATCH 9/9] Fixing LGTM warnings --- src/sonic-yang-mgmt/sonic-cfg-help | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/sonic-yang-mgmt/sonic-cfg-help b/src/sonic-yang-mgmt/sonic-cfg-help index d4b200185eee..4c14cebc44b8 100755 --- a/src/sonic-yang-mgmt/sonic-cfg-help +++ b/src/sonic-yang-mgmt/sonic-cfg-help @@ -1,12 +1,10 @@ #!/usr/bin/env python3 -import sys import argparse from tabulate import tabulate import re import textwrap -from collections import OrderedDict import sonic_yang YANG_MODELS_DIR = "/usr/local/yang-models" @@ -20,19 +18,24 @@ class SonicCfgDescriber: self.yang_parser = sonic_yang.SonicYang(self.yang_models_dir) self.yang_parser.loadYangModel() self.table_descr = {} + self.table_name = table_name + self.field = field self.print_format = print_format + def print_documentation(self): for j in self.yang_parser.yJson: toplevel = j['module'].get('container') if toplevel is not None: container = toplevel.get('container') if isinstance(container, list): for c in container: - if c.get('@name') == table_name or not table_name: - self.print_table(c, field) + if c.get('@name') == self.table_name or \ + not self.table_name: + self.print_table(c, self.field) elif isinstance(container, dict): - if container.get('@name') == table_name or not table_name: - self.print_table(container, field) + if container.get('@name') == self.table_name or \ + not self.table_name: + self.print_table(container, self.field) def print_table(self, table, field): if table is None: @@ -194,6 +197,7 @@ def main(): return -1 yang_cfg = SonicCfgDescriber(args.table, args.field, args.print_format) + yang_cfg.print_documentation() if __name__ == "__main__":