From 0a4365aaa0adadcb4c3fa448e05f6d2e217bda5f Mon Sep 17 00:00:00 2001 From: Ying Xie Date: Thu, 7 Jan 2021 19:35:20 -0800 Subject: [PATCH 01/12] [storyteller] adding a grep wrapper with predefined scenarios (#1349) * [storyteller] adding a grep wrapper with predefined scenarios Introduce storyteller tool to grep syslog (or any other logs) for a story line of a certain scenario. Defined scenarios are: - reboot : device reboot related events. - service : service start/stop events. - link : link up/down events. - lag : LAG up/down events. - bgp : BGP config change events. - crash : process/service crash events. Unmatched cateory is used as grepping regex directly. Signed-off-by: Ying Xie --- scripts/storyteller | 85 +++++++++++++++++++++++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 86 insertions(+) create mode 100755 scripts/storyteller diff --git a/scripts/storyteller b/scripts/storyteller new file mode 100755 index 0000000000..01e18174a3 --- /dev/null +++ b/scripts/storyteller @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +''' +Story Teller: + Utility to help analyze log for certain sequence of events. + e.g.: reboot (including warm/fast reboot), interface flapping, etc. +''' + +import argparse +import subprocess + +regex_dict = { + 'bgp' : 'bgpcfgd', + 'crash' : 'what\|unexpected exception\|notify_OA_about_syncd_exception\|SIG\|not expected', + 'interface' : 'updatePortOperStatus\|Configure .* to', + 'lag' : 'link becomes\|addLag', + 'reboot' : 'BOOT\|rc.local\|old_config\|minigraph.xml\|Rebooting\|reboot\|executeOperationsOnAsic\|getAsicView\|dumpVidToAsicOperatioId', + 'service' : 'Starting\|Stopping\|Started\|Stopped', + } + + +def exec_cmd(cmd): + out = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, text=True) + stdout, stderr = out.communicate() + return out.returncode, stdout, stderr + + +def build_options(after=0, before=0, context=0): + options = [] + if after: + options.append('-A {}'.format(after)) + if before: + options.append('-B {}'.format(before)) + if context: + options.append('-C {}'.format(context)) + + return ' '.join(x for x in options) + + +def find_log(log, regex, after=0, before=0, context=0): + options = build_options(after, before, context) + cmd = 'ls -rt /var/log/{}* | xargs zgrep -a {} "{}"'.format(log, options, regex) + _, out, _ = exec_cmd(cmd) + ''' + Opportunity to improve: + output (out) can be split to lines and send to a filter to + decide if a line should be printed out or not. + e.g. limited to a certain time span. + ''' + print(out) + + +def build_regex(category): + regex = [] + for c in category.split(','): + # if c is not found, add c to grep list directly + regex.append(regex_dict[c] if c in regex_dict else c) + + return '\|'.join(x for x in regex) + + +def main(): + parser = argparse.ArgumentParser(description='Story Teller') + + parser.add_argument('-l', '--log', help='log file prefix, e.g. syslog; default: syslog', + type=str, required=False, default='syslog') + parser.add_argument('-c', '--category', help='Categories: bgp, crash, interface, lag, reboot, service Specify multiple categories as c1,c2,c3; default: reboot', + type=str, required=False, default='reboot') + parser.add_argument('-A', '--after', help='Show N lines after match', + type=int, required=False, default=0) + parser.add_argument('-B', '--before', help='Show N lines before match', + type=int, required=False, default=0) + parser.add_argument('-C', '--context', help='Show N lines before and after match', + type=int, required=False, default=0) + + args = parser.parse_args() + + log = args.log + reg = build_regex(args.category) + + find_log(log, reg, args.after, args.before, args.context) + + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py index a6cdaf8f59..adb725727b 100644 --- a/setup.py +++ b/setup.py @@ -106,6 +106,7 @@ 'scripts/route_check.py', 'scripts/route_check_test.sh', 'scripts/sfpshow', + 'scripts/storyteller', 'scripts/syseeprom-to-json', 'scripts/tempershow', 'scripts/update_json.py', From b18ef5a73f17f8e7a80c1cd5547f37cea4eb1615 Mon Sep 17 00:00:00 2001 From: Renuka Manavalan <47282725+renukamanavalan@users.noreply.github.com> Date: Mon, 11 Jan 2021 09:18:10 -0800 Subject: [PATCH 02/12] [route_check.py] - update includes checks on subscriptions (#1344) Summary: Improve the tool to handle the possible latency between APPL-DB & ASIC-DB by looking at subscription messages. - What I did The routes flow from APPL-DB to ASIC-DB, via orchagent. This tool's job is to verify that all routes added to APPL-DB do get into ASIC-DB and all routes removed from APPL-DB are deleted from ASIC-DB. - How I did it NOTE: The flow from APPL-DB to ASIC-DB takes non zero milliseconds. 1) Initiate subscribe for ASIC-DB updates. 2) Read APPL-DB & ASIC-DB 3) Get the diff. 4) If any diff, 4.1) Collect subscribe messages for a second 4.2) check diff against the subscribe messages 5) Rule out local interfaces & default routes 6) If still outstanding diffs, report failure. - How to verify it Run this tool in SONiC switch and watch the result. In case of failure checkout the result to validate the failure. To simulate failure: Stop Orchagent. Run this tool, and likely you would see some failures. You could potentially remove / add routes in APPL / ASIC DBs with orchagent down to ensure failure. Analyze the reported failures to match expected. You may use the exit code to verify the result as success or not. --- scripts/route_check.py | 282 +++++++++++++++++++++---- tests/route_check_test.py | 431 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 669 insertions(+), 44 deletions(-) create mode 100644 tests/route_check_test.py diff --git a/scripts/route_check.py b/scripts/route_check.py index 3bb47a2643..3970964ab0 100755 --- a/scripts/route_check.py +++ b/scripts/route_check.py @@ -1,16 +1,59 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +What it is: + The routes flow from APPL-DB to ASIC-DB, via orchagent. + This tool's job is to verify that all routes added to APPL-DB do + get into ASIC-DB. + + +How: + NOTE: The flow from APPL-DB to ASIC-DB takes non zero milliseconds. + 1) Initiate subscribe for ASIC-DB updates. + 2) Read APPL-DB & ASIC-DB + 3) Get the diff. + 4) If any diff, + 4.1) Collect subscribe messages for a second + 4.2) check diff against the subscribe messages + 5) Rule out local interfaces & default routes + 6) If still outstanding diffs, report failure. + +To verify: + Run this tool in SONiC switch and watch the result. In case of failure + checkout the result to validate the failure. + To simulate failure: + Stop Orchagent. + Run this tool, and likely you would see some failures. + You could potentially remove / add routes in APPL / ASIC DBs with orchagent + down to ensure failure. + Analyze the reported failures to match expected. + You may use the exit code to verify the result as success or not. + + + +""" + +import argparse +from enum import Enum +import ipaddress +import json import os import re import sys -import argparse -import ipaddress import syslog -import json import time -from enum import Enum -from swsssdk import ConfigDBConnector + +from swsscommon import swsscommon + +APPL_DB_NAME = 'APPL_DB' +ASIC_DB_NAME = 'ASIC_DB' +ASIC_TABLE_NAME = 'ASIC_STATE' +ASIC_KEY_PREFIX = 'SAI_OBJECT_TYPE_ROUTE_ENTRY:' + +SUBSCRIBE_WAIT_SECS = 1 + +UNIT_TESTING = 0 os.environ['PYTHONUNBUFFERED']='True' @@ -33,6 +76,12 @@ def __str__(self): write_to_syslog = False def set_level(lvl, log_to_syslog): + """ + Sets the log level + :param lvl: Log level as ERR/INFO/DEBUG; default: syslog.LOG_ERR + :param log_to_syslog; True - write into syslog. False: skip + :return None + """ global report_level global write_to_syslog @@ -45,6 +94,12 @@ def set_level(lvl, log_to_syslog): def print_message(lvl, *args): + """ + print and log the message for given level. + :param lvl: Log level for this message as ERR/INFO/DEBUG + :param args: message as list of strings or convertible to string + :return None + """ if (lvl <= report_level): msg = "" for arg in args: @@ -55,6 +110,11 @@ def print_message(lvl, *args): def add_prefix(ip): + """ + helper add static prefix based on IP type + :param ip: IP to add prefix as string. + :return ip + "/32 or /128" + """ if ip.find(IPV6_SEPARATOR) == -1: ip = ip + PREFIX_SEPARATOR + "32" else: @@ -63,20 +123,41 @@ def add_prefix(ip): def add_prefix_ifnot(ip): + """ + helper add static prefix if absent + :param ip: IP to add prefix as string. + :return ip with prefix + """ return ip if ip.find(PREFIX_SEPARATOR) != -1 else add_prefix(ip) def is_local(ip): + """ + helper to check if this IP qualify as link local + :param ip: IP to check as string + :return True if link local, else False + """ t = ipaddress.ip_address(ip.split("/")[0]) return t.is_link_local def is_default_route(ip): + """ + helper to check if this IP is default route + :param ip: IP to check as string + :return True if default, else False + """ t = ipaddress.ip_address(ip.split("/")[0]) return t.is_unspecified and ip.split("/")[1] == "0" def cmps(s1, s2): + """ + helper to compare two strings + :param s1: left string + :param s2: right string + :return comparison result as -1/0/1 + """ if (s1 == s2): return 0 if (s1 < s2): @@ -84,7 +165,13 @@ def cmps(s1, s2): return 1 -def do_diff(t1, t2): +def diff_sorted_lists(t1, t2): + """ + helper to compare two sorted lists. + :param t1: list 1 + :param t2: list 2 + :return (, ) + """ t1_x = t2_x = 0 t1_miss = [] t2_miss = [] @@ -112,11 +199,59 @@ def do_diff(t1, t2): return t1_miss, t2_miss +def checkout_rt_entry(k): + """ + helper to filter out correct keys and strip out IP alone. + :param ip: key to check as string + :return (True, ip) or (False, None) + """ + if k.startswith(ASIC_KEY_PREFIX): + e = k.lower().split("\"", -1)[3] + if not is_local(e): + return True, e + return False, None + + +def get_subscribe_updates(selector, subs): + """ + helper to collect subscribe messages for a period + :param selector: Selector object to wait + :param subs: Subscription object to pop messages + :return (add, del) messages as sorted + """ + adds = [] + deletes = [] + t_end = time.time() + SUBSCRIBE_WAIT_SECS + t_wait = SUBSCRIBE_WAIT_SECS + + while t_wait > 0: + selector.select(t_wait) + t_wait = int(t_end - time.time()) + while True: + key, op, val = subs.pop() + if not key: + break + res, e = checkout_rt_entry(key) + if res: + if op == "SET": + adds.append(e) + elif op == "DEL": + deletes.append(e) + + print_message(syslog.LOG_DEBUG, "adds={}".format(adds)) + print_message(syslog.LOG_DEBUG, "dels={}".format(deletes)) + return (sorted(adds), sorted(deletes)) + + def get_routes(): - db = ConfigDBConnector() - db.db_connect('APPL_DB') + """ + helper to read route table from APPL-DB. + :return list of sorted routes with prefix ensured + """ + db = swsscommon.DBConnector(APPL_DB_NAME, 0) print_message(syslog.LOG_DEBUG, "APPL DB connected for routes") - keys = db.get_keys('ROUTE_TABLE') + tbl = swsscommon.Table(db, 'ROUTE_TABLE') + keys = tbl.getKeys() valid_rt = [] for k in keys: @@ -128,28 +263,42 @@ def get_routes(): def get_route_entries(): - db = ConfigDBConnector() - db.db_connect('ASIC_DB') + """ + helper to read present route entries from ASIC-DB and + as well initiate selector for ASIC-DB:ASIC-state updates. + :return (selector, subscriber, ) + """ + db = swsscommon.DBConnector(ASIC_DB_NAME, 0) + subs = swsscommon.SubscriberStateTable(db, ASIC_TABLE_NAME) print_message(syslog.LOG_DEBUG, "ASIC DB connected") - keys = db.get_keys('ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY', False) rt = [] - for k in keys: - e = k.lower().split("\"", -1)[3] - if not is_local(e): + while True: + k, _, _ = subs.pop() + if not k: + break + res, e = checkout_rt_entry(k) + if res: rt.append(e) + print_message(syslog.LOG_DEBUG, json.dumps({"ASIC_ROUTE_ENTRY": sorted(rt)}, indent=4)) - return sorted(rt) + + selector = swsscommon.Select() + selector.addSelectable(subs) + return (selector, subs, sorted(rt)) def get_interfaces(): - db = ConfigDBConnector() - db.db_connect('APPL_DB') + """ + helper to read interface table from APPL-DB. + :return sorted list of IP addresses with added prefix + """ + db = swsscommon.DBConnector(APPL_DB_NAME, 0) print_message(syslog.LOG_DEBUG, "APPL DB connected for interfaces") + tbl = swsscommon.Table(db, 'INTF_TABLE') + keys = tbl.getKeys() intf = [] - keys = db.get_keys('INTF_TABLE') - for k in keys: lst = re.split(':', k.lower(), maxsplit=1) if len(lst) == 1: @@ -165,17 +314,22 @@ def get_interfaces(): def filter_out_local_interfaces(keys): + """ + helper to filter out local interfaces + :param keys: APPL-DB:ROUTE_TABLE Routes to check. + :return keys filtered out of local + """ rt = [] local_if_re = ['eth0', 'lo', 'docker0', 'Loopback\d+'] - db = ConfigDBConnector() - db.db_connect('APPL_DB') + db = swsscommon.DBConnector(APPL_DB_NAME, 0) + tbl = swsscommon.Table(db, 'ROUTE_TABLE') for k in keys: - e = db.get_entry('ROUTE_TABLE', k) + e = dict(tbl.get(k)[1]) if not e: # Prefix might have been added. So try w/o it. - e = db.get_entry('ROUTE_TABLE', k.split("/")[0]) + e = dict(tbl.get(k.split("/"))[1]) if not e or all([not re.match(x, e['ifname']) for x in local_if_re]): rt.append(k) @@ -183,6 +337,11 @@ def filter_out_local_interfaces(keys): def filter_out_default_routes(lst): + """ + helper to filter out default routes + :param lst: list to filter + :return filtered list. + """ upd = [] for rt in lst: @@ -193,52 +352,82 @@ def filter_out_default_routes(lst): def check_routes(): + """ + The heart of this script which runs the checks. + Read APPL-DB & ASIC-DB, the relevant tables for route checking. + Checkout routes in ASIC-DB to match APPL-DB, discounting local & + default routes. In case of missed / unexpected entries in ASIC, + it might be due to update latency between APPL & ASIC DBs. So collect + ASIC-DB subscribe updates for a second, and checkout if you see SET + command for missing ones & DEL command for unexpectes ones in ASIC. + + If there are still some unjustifiable diffs, between APPL & ASIC DB, + related to routes report failure, else all good. + + :return (0, None) on sucess, else (-1, results) where results holds + the unjustifiable entries. + """ intf_appl_miss = [] rt_appl_miss = [] rt_asic_miss = [] results = {} - err_present = False + + selector, subs, rt_asic = get_route_entries() rt_appl = get_routes() - rt_asic = get_route_entries() intf_appl = get_interfaces() # Diff APPL-DB routes & ASIC-DB routes - rt_appl_miss, rt_asic_miss = do_diff(rt_appl, rt_asic) + rt_appl_miss, rt_asic_miss = diff_sorted_lists(rt_appl, rt_asic) # Check missed ASIC routes against APPL-DB INTF_TABLE - _, rt_asic_miss = do_diff(intf_appl, rt_asic_miss) + _, rt_asic_miss = diff_sorted_lists(intf_appl, rt_asic_miss) rt_asic_miss = filter_out_default_routes(rt_asic_miss) # Check APPL-DB INTF_TABLE with ASIC table route entries - intf_appl_miss, _ = do_diff(intf_appl, rt_asic) + intf_appl_miss, _ = diff_sorted_lists(intf_appl, rt_asic) - if (len(rt_appl_miss) != 0): + if rt_appl_miss: rt_appl_miss = filter_out_local_interfaces(rt_appl_miss) - if (len(rt_appl_miss) != 0): + if rt_appl_miss or rt_asic_miss: + # Look for subscribe updates for a second + adds, deletes = get_subscribe_updates(selector, subs) + + # Drop all those for which SET received + rt_appl_miss, _ = diff_sorted_lists(rt_appl_miss, adds) + + # Drop all those for which DEL received + rt_asic_miss, _ = diff_sorted_lists(rt_asic_miss, deletes) + + if rt_appl_miss: results["missed_ROUTE_TABLE_routes"] = rt_appl_miss - err_present = True - if (len(intf_appl_miss) != 0): + if intf_appl_miss: results["missed_INTF_TABLE_entries"] = intf_appl_miss - err_present = True - if (len(rt_asic_miss) != 0): + if rt_asic_miss: results["Unaccounted_ROUTE_ENTRY_TABLE_entries"] = rt_asic_miss - err_present = True - if err_present: - print_message(syslog.LOG_ERR, "results: {", json.dumps(results, indent=4), "}") + if results: + print_message(syslog.LOG_ERR, "Failure results: {", json.dumps(results, indent=4), "}") print_message(syslog.LOG_ERR, "Failed. Look at reported mismatches above") - return -1 + print_message(syslog.LOG_ERR, "add: {", json.dumps(adds, indent=4), "}") + print_message(syslog.LOG_ERR, "del: {", json.dumps(deletes, indent=4), "}") + return -1, results else: print_message(syslog.LOG_INFO, "All good!") - return 0 + return 0, None -def main(argv): +def main(): + """ + main entry point, which mainly parses the args and call check_routes + In case of single run, it returns on one call or stays in forever loop + with given interval in-between calls to check_route + :return Same return value as returned by check_route. + """ interval = 0 parser=argparse.ArgumentParser(description="Verify routes between APPL-DB & ASIC-DB are in sync") parser.add_argument('-m', "--mode", type=Level, choices=list(Level), default='ERR') @@ -255,15 +444,20 @@ def main(argv): interval = MAX_SCAN_INTERVAL else: interval = args.interval + if UNIT_TESTING: + interval = 1 while True: - ret = check_routes() + ret, res= check_routes() if interval: time.sleep(interval) + if UNIT_TESTING: + return ret, res else: - sys.exit(ret) + return ret, res + if __name__ == "__main__": - main(sys.argv[1:]) + sys.exit(main()[0]) diff --git a/tests/route_check_test.py b/tests/route_check_test.py new file mode 100644 index 0000000000..c7c0d47b88 --- /dev/null +++ b/tests/route_check_test.py @@ -0,0 +1,431 @@ +import copy +import json +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest + +sys.path.append("scripts") +import route_check + +DESCR = "Description" +ARGS = "args" +RET = "return" +APPL_DB = 0 +ASIC_DB = 1 +PRE = "pre-value" +UPD = "update" +RESULT = "res" + +OP_SET = "SET" +OP_DEL = "DEL" + +ROUTE_TABLE = 'ROUTE_TABLE' +INTF_TABLE = 'INTF_TABLE' +RT_ENTRY_TABLE = 'ASIC_STATE' +SEPARATOR = ":" + +RT_ENTRY_KEY_PREFIX = 'SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest":\"' +RT_ENTRY_KEY_SUFFIX = '\",\"switch_id\":\"oid:0x21000000000000\",\"vr\":\"oid:0x3000000000023\"}' + +current_test_name = None +current_test_no = None +current_test_data = None + +tables_returned = {} + +selector_returned = None +subscribers_returned = {} + +test_data = { + "0": { + DESCR: "basic good one", + ARGS: "route_check -m INFO -i 1000", + PRE: { + APPL_DB: { + ROUTE_TABLE: { + "0.0.0.0/0" : { "ifname": "portchannel0" }, + "10.10.196.12/31" : { "ifname": "portchannel0" }, + "10.10.196.20/31" : { "ifname": "portchannel0" }, + "10.10.196.30/31" : { "ifname": "lo" } + }, + INTF_TABLE: { + "PortChannel1013:10.10.196.24/31": {}, + "PortChannel1023:2603:10b0:503:df4::5d/126": {}, + "PortChannel1024": {} + } + }, + ASIC_DB: { + RT_ENTRY_TABLE: { + RT_ENTRY_KEY_PREFIX + "10.10.196.12/31" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "10.10.196.20/31" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "10.10.196.24/32" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "2603:10b0:503:df4::5d/128" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "0.0.0.0/0" + RT_ENTRY_KEY_SUFFIX: {} + } + } + } + }, + "1": { + DESCR: "With updates", + ARGS: "route_check -m DEBUG -i 1", + PRE: { + APPL_DB: { + ROUTE_TABLE: { + "0.0.0.0/0" : { "ifname": "portchannel0" }, + "10.10.196.12/31" : { "ifname": "portchannel0" }, + "10.10.196.20/31" : { "ifname": "portchannel0" }, + "10.10.196.30/31" : { "ifname": "lo" } + }, + INTF_TABLE: { + "PortChannel1013:10.10.196.24/31": {}, + "PortChannel1023:2603:10b0:503:df4::5d/126": {} + } + }, + ASIC_DB: { + RT_ENTRY_TABLE: { + RT_ENTRY_KEY_PREFIX + "10.10.196.20/31" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "10.10.196.24/32" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "2603:10b0:503:df4::5d/128" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "0.0.0.0/0" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "10.10.10.10/32" + RT_ENTRY_KEY_SUFFIX: {} + } + } + }, + UPD: { + ASIC_DB: { + RT_ENTRY_TABLE: { + OP_SET: { + RT_ENTRY_KEY_PREFIX + "10.10.196.12/31" + RT_ENTRY_KEY_SUFFIX: {}, + }, + OP_DEL: { + RT_ENTRY_KEY_PREFIX + "10.10.10.10/32" + RT_ENTRY_KEY_SUFFIX: {} + } + } + } + } + }, + "2": { + DESCR: "basic failure one", + ARGS: "route_check -i 15", + RET: -1, + PRE: { + APPL_DB: { + ROUTE_TABLE: { + "0.0.0.0/0" : { "ifname": "portchannel0" }, + "10.10.196.12/31" : { "ifname": "portchannel0" }, + "10.10.196.20/31" : { "ifname": "portchannel0" }, + "10.10.196.30/31" : { "ifname": "lo" } + }, + INTF_TABLE: { + "PortChannel1013:90.10.196.24/31": {}, + "PortChannel1023:9603:10b0:503:df4::5d/126": {}, + "PortChannel1024": {} + } + }, + ASIC_DB: { + RT_ENTRY_TABLE: { + RT_ENTRY_KEY_PREFIX + "20.10.196.12/31" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "20.10.196.20/31" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "20.10.196.24/32" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "3603:10b0:503:df4::5d/128" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "0.0.0.0/0" + RT_ENTRY_KEY_SUFFIX: {} + } + } + }, + RESULT: { + "missed_ROUTE_TABLE_routes": [ + "10.10.196.12/31", + "10.10.196.20/31" + ], + "missed_INTF_TABLE_entries": [ + "90.10.196.24/32", + "9603:10b0:503:df4::5d/128" + ], + "Unaccounted_ROUTE_ENTRY_TABLE_entries": [ + "20.10.196.12/31", + "20.10.196.20/31", + "20.10.196.24/32", + "3603:10b0:503:df4::5d/128" + ] + } + }, + "3": { + DESCR: "basic good one with no args", + ARGS: "route_check", + PRE: { + APPL_DB: { + ROUTE_TABLE: { + "0.0.0.0/0" : { "ifname": "portchannel0" }, + "10.10.196.12/31" : { "ifname": "portchannel0" }, + "10.10.196.20/31" : { "ifname": "portchannel0" }, + "10.10.196.30/31" : { "ifname": "lo" } + }, + INTF_TABLE: { + "PortChannel1013:10.10.196.24/31": {}, + "PortChannel1023:2603:10b0:503:df4::5d/126": {}, + "PortChannel1024": {} + } + }, + ASIC_DB: { + RT_ENTRY_TABLE: { + RT_ENTRY_KEY_PREFIX + "10.10.196.12/31" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "10.10.196.20/31" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "10.10.196.24/32" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "2603:10b0:503:df4::5d/128" + RT_ENTRY_KEY_SUFFIX: {}, + RT_ENTRY_KEY_PREFIX + "0.0.0.0/0" + RT_ENTRY_KEY_SUFFIX: {} + } + } + } + } +} + +def do_start_test(tname, tno, ctdata): + global current_test_name, current_test_no, current_test_data + global tables_returned, selector_returned, subscribers_returned + + current_test_name = tname + current_test_no = tno + current_test_data = ctdata + tables_returned = {} + + selector_returned = None + subscribers_returned = {} + + print("Starting test case {} number={}".format(tname, tno)) + + +def check_subset(d_sub, d_all): + if type(d_sub) != type(d_all): + return -1 + if not type(d_sub) is dict: + ret = 0 if d_sub == d_all else -2 + return ret + + for (k, v) in d_sub.items(): + if not k in d_all: + return -3 + ret = check_subset(v, d_all[k]) + if ret != 0: + return ret + return 0 + + +def recursive_update(d, t): + assert (type(t) is dict) + for k in t.keys(): + if type(t[k]) is not dict: + d.update(t) + return + + if k not in d: + d[k] = {} + recursive_update(d[k], t[k]) + + +class Table: + + def __init__(self, db, tbl): + self.db = db + self.tbl = tbl + self.data = copy.deepcopy(self.get_val(current_test_data[PRE], [db, tbl])) + # print("Table:init: db={} tbl={} data={}".format(db, tbl, json.dumps(self.data, indent=4))) + + + def update(self): + t = copy.deepcopy(self.get_val(current_test_data.get(UPD, {}), + [self.db, self.tbl, OP_SET])) + drop = copy.deepcopy(self.get_val(current_test_data.get(UPD, {}), + [self.db, self.tbl, OP_DEL])) + if t: + recursive_update(self.data, t) + + for k in drop: + self.data.pop(k, None) + return (list(t.keys()), list(drop.keys())) + + + def get_val(self, d, keys): + for k in keys: + d = d[k] if k in d else {} + return d + + + def getKeys(self): + return list(self.data.keys()) + + + def get(self, key): + ret = copy.deepcopy(self.data.get(key, {})) + return (True, ret) + + +db_conns = {"APPL_DB": APPL_DB, "ASIC_DB": ASIC_DB} +def conn_side_effect(arg, _): + return db_conns[arg] + + +def table_side_effect(db, tbl): + if not db in tables_returned: + tables_returned[db] = {} + if not tbl in tables_returned[db]: + tables_returned[db][tbl] = Table(db, tbl) + return tables_returned[db][tbl] + + +class mock_selector: + TIMEOUT = 1 + + def __init__(self): + self.select_state = 0 + self.select_cnt = 0 + self.subs = None + # print("Mock Selector constructed") + + + def addSelectable(self, subs): + self.subs = subs + return 0 + + + def select(self, timeout): + # Toggle between good & timeout + # + state = self.select_state + self.subs.update() + + if self.select_state == 0: + self.select_state = self.TIMEOUT + else: + time.sleep(timeout) + + return (state, None) + + +class mock_db_conn: + def __init__(self, db): + self.db_name = None + for (k, v) in db_conns.items(): + if v == db: + self.db_name = k + assert self.db_name != None + + def getDbName(self): + return self.db_name + + +class mock_subscriber: + def __init__(self, db, tbl): + self.state = PRE + self.db = db + self.tbl = tbl + self.dbconn = mock_db_conn(db) + self.mock_tbl = table_side_effect(self.db, self.tbl) + self.set_keys = list(self.mock_tbl.data.keys()) + self.del_keys = [] + + + def update(self): + if self.state == PRE: + s_keys, d_keys = self.mock_tbl.update() + self.set_keys += s_keys + self.del_keys += d_keys + self.state = UPD + + + def pop(self): + v = None + if self.set_keys: + op = OP_SET + k = self.set_keys.pop(0) + v = self.mock_tbl.get(k)[1] + elif self.del_keys: + op = OP_DEL + k = self.del_keys.pop(0) + else: + k = "" + op = "" + + print("state={} k={} op={} v={}".format(self.state, k, op, str(v))) + return (k, op, v) + + + def getDbConnector(self): + return self.dbconn + + + def getTableName(self): + return self.tbl + + +def subscriber_side_effect(db, tbl): + global subscribers_returned + + key = "db_{}_tbl_{}".format(db, tbl) + if not key in subscribers_returned: + subscribers_returned[key] = mock_subscriber(db, tbl) + return subscribers_returned[key] + + +def select_side_effect(): + global selector_returned + + if not selector_returned: + selector_returned = mock_selector() + return selector_returned + + +def table_side_effect(db, tbl): + if not db in tables_returned: + tables_returned[db] = {} + if not tbl in tables_returned[db]: + tables_returned[db][tbl] = Table(db, tbl) + return tables_returned[db][tbl] + + +def set_mock(mock_table, mock_conn, mock_sel, mock_subs): + mock_conn.side_effect = conn_side_effect + mock_table.side_effect = table_side_effect + mock_sel.side_effect = select_side_effect + mock_subs.side_effect = subscriber_side_effect + + +class TestRouteCheck(object): + def setup(self): + pass + + def init(self): + route_check.UNIT_TESTING = 1 + + + @patch("route_check.swsscommon.DBConnector") + @patch("route_check.swsscommon.Table") + @patch("route_check.swsscommon.Select") + @patch("route_check.swsscommon.SubscriberStateTable") + def test_server(self, mock_subs, mock_sel, mock_table, mock_conn): + self.init() + ret = 0 + + set_mock(mock_table, mock_conn, mock_sel, mock_subs) + for (i, ct_data) in test_data.items(): + do_start_test("route_test", i, ct_data) + + with patch('sys.argv', ct_data[ARGS].split()): + ret, res = route_check.main() + expect_ret = ct_data[RET] if RET in ct_data else 0 + expect_res = ct_data[RESULT] if RESULT in ct_data else None + if res: + print("res={}".format(json.dumps(res, indent=4))) + if expect_res: + print("expect_res={}".format(json.dumps(expect_res, indent=4))) + assert ret == expect_ret + assert res == expect_res + + + + + + + + From dba8fcb54b88d36c63e2e481e370c942fc0781fe Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni <47657796+AkhileshSamineni@users.noreply.github.com> Date: Tue, 12 Jan 2021 20:46:01 +0530 Subject: [PATCH 03/12] Validations checks while adding a member to PortChannel and removing a member from a Portchannel (#1328) * Validations checks while adding a member to PortChannel and removing a member from a Portchannel Added below validation checks when user the configures a port as member a portchannel: 1. Check if the given portchannel name is valid or not 2. Check if the given portchannel name exists in CONFIG_DB 3. Check if the given port is configured with an IP address or not 4. Check if the given port is configured with with VLAN membership or not 5. Check if the given port is already member of a portchannel 6. Check if the given port speed matches the speed of existing members 7. Check if the given port MTU matches the speed of existing members Added below validation checks when user the removes member port from a portchannel: 1. Check if the given portchannel name is valid or not 2. Check if the given portchannel name exists in exists in CONFIG_DB or not 3. Check if the given port is member of the given portchannel or not Signed-off-by: Akhilesh Samineni --- config/main.py | 121 ++++++++++++++++++++++++++++++- tests/mock_tables/config_db.json | 6 ++ tests/portchannel_test.py | 115 +++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 tests/portchannel_test.py diff --git a/config/main.py b/config/main.py index 7f82deec8c..f07d7a7b4b 100644 --- a/config/main.py +++ b/config/main.py @@ -60,6 +60,14 @@ CFG_LOOPBACK_ID_MAX_VAL = 999 CFG_LOOPBACK_NO="<0-999>" +CFG_PORTCHANNEL_PREFIX = "PortChannel" +CFG_PORTCHANNEL_PREFIX_LEN = 11 +CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX = 15 +CFG_PORTCHANNEL_MAX_VAL = 9999 +CFG_PORTCHANNEL_NO="<0-9999>" + +PORT_MTU = "mtu" +PORT_SPEED = "speed" asic_type = None @@ -390,6 +398,45 @@ def is_interface_bind_to_vrf(config_db, interface_name): return True return False +def is_portchannel_name_valid(portchannel_name): + """Port channel name validation + """ + + # Return True if Portchannel name is PortChannelXXXX (XXXX can be 0-9999) + if portchannel_name[:CFG_PORTCHANNEL_PREFIX_LEN] != CFG_PORTCHANNEL_PREFIX : + return False + if (portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:].isdigit() is False or + int(portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:]) > CFG_PORTCHANNEL_MAX_VAL) : + return False + if len(portchannel_name) > CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX: + return False + return True + +def is_portchannel_present_in_db(db, portchannel_name): + """Check if Portchannel is present in Config DB + """ + + # Return True if Portchannel name exists in the CONFIG_DB + portchannel_list = db.get_table(CFG_PORTCHANNEL_PREFIX) + if portchannel_list is None: + return False + if portchannel_name in portchannel_list: + return True + return False + +def is_port_member_of_this_portchannel(db, port_name, portchannel_name): + """Check if a port is member of given portchannel + """ + portchannel_list = db.get_table(CFG_PORTCHANNEL_PREFIX) + if portchannel_list is None: + return False + + for k,v in db.get_table('PORTCHANNEL_MEMBER'): + if (k == portchannel_name) and (v == port_name): + return True + + return False + # Return the namespace where an interface belongs # The port name input could be in default mode or in alias mode. def get_port_namespace(port): @@ -1330,9 +1377,64 @@ def add_portchannel_member(ctx, portchannel_name, port_name): ctx.fail("{} is configured as mirror destination port".format(port_name)) # Check if the member interface given by user is valid in the namespace. - if interface_name_is_valid(db, port_name) is False: + if port_name.startswith("Ethernet") is False or interface_name_is_valid(db, port_name) is False: ctx.fail("Interface name is invalid. Please enter a valid interface name!!") + # Dont proceed if the port channel name is not valid + if is_portchannel_name_valid(portchannel_name) is False: + ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" + .format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO)) + + # Dont proceed if the port channel does not exist + if is_portchannel_present_in_db(db, portchannel_name) is False: + ctx.fail("{} is not present.".format(portchannel_name)) + + # Dont allow a port to be member of port channel if it is configured with an IP address + for key in db.get_table('INTERFACE').keys(): + if type(key) != tuple: + continue + if key[0] == port_name: + ctx.fail(" {} has ip address {} configured".format(port_name, key[1])) + return + + # Dont allow a port to be member of port channel if it is configured as a VLAN member + for k,v in db.get_table('VLAN_MEMBER'): + if v == port_name: + ctx.fail("%s Interface configured as VLAN_MEMBER under vlan : %s" %(port_name,str(k))) + return + + # Dont allow a port to be member of port channel if it is already member of a port channel + for k,v in db.get_table('PORTCHANNEL_MEMBER'): + if v == port_name: + ctx.fail("{} Interface is already member of {} ".format(v,k)) + + # Dont allow a port to be member of port channel if its speed does not match with existing members + for k,v in db.get_table('PORTCHANNEL_MEMBER'): + if k == portchannel_name: + member_port_entry = db.get_entry('PORT', v) + port_entry = db.get_entry('PORT', port_name) + + if member_port_entry is not None and port_entry is not None: + member_port_speed = member_port_entry.get(PORT_SPEED) + + port_speed = port_entry.get(PORT_SPEED) + if member_port_speed != port_speed: + ctx.fail("Port speed of {} is different than the other members of the portchannel {}" + .format(port_name, portchannel_name)) + + # Dont allow a port to be member of port channel if its MTU does not match with portchannel + portchannel_entry = db.get_entry('PORTCHANNEL', portchannel_name) + if portchannel_entry and portchannel_entry.get(PORT_MTU) is not None : + port_entry = db.get_entry('PORT', port_name) + + if port_entry and port_entry.get(PORT_MTU) is not None: + port_mtu = port_entry.get(PORT_MTU) + + portchannel_mtu = portchannel_entry.get(PORT_MTU) + if portchannel_mtu != port_mtu: + ctx.fail("Port MTU of {} is different than the {} MTU size" + .format(port_name, portchannel_name)) + db.set_entry('PORTCHANNEL_MEMBER', (portchannel_name, port_name), {'NULL': 'NULL'}) @@ -1342,12 +1444,25 @@ def add_portchannel_member(ctx, portchannel_name, port_name): @click.pass_context def del_portchannel_member(ctx, portchannel_name, port_name): """Remove member from portchannel""" + # Dont proceed if the port channel name is not valid + if is_portchannel_name_valid(portchannel_name) is False: + ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" + .format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO)) + db = ctx.obj['db'] # Check if the member interface given by user is valid in the namespace. if interface_name_is_valid(db, port_name) is False: ctx.fail("Interface name is invalid. Please enter a valid interface name!!") + # Dont proceed if the port channel does not exist + if is_portchannel_present_in_db(db, portchannel_name) is False: + ctx.fail("{} is not present.".format(portchannel_name)) + + # Dont proceed if the the port is not an existing member of the port channel + if not is_port_member_of_this_portchannel(db, port_name, portchannel_name): + ctx.fail("{} is not a member of portchannel {}".format(port_name, portchannel_name)) + db.set_entry('PORTCHANNEL_MEMBER', (portchannel_name, port_name), None) db.set_entry('PORTCHANNEL_MEMBER', portchannel_name + '|' + port_name, None) @@ -2410,6 +2525,10 @@ def mtu(ctx, interface_name, interface_mtu, verbose): if interface_name is None: ctx.fail("'interface_name' is None!") + portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER') + if interface_is_in_portchannel(portchannel_member_table, interface_name): + ctx.fail("'interface_name' is in portchannel!") + if ctx.obj['namespace'] is DEFAULT_NAMESPACE: command = "portconfig -p {} -m {}".format(interface_name, interface_mtu) else: diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index fde2f7dc47..21676feb57 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -546,6 +546,12 @@ "PORTCHANNEL_INTERFACE|PortChannel0004|FC00::7D/126": { "NULL": "NULL" }, + "INTERFACE|Ethernet0": { + "NULL": "NULL" + }, + "INTERFACE|Ethernet0|14.14.0.1/24": { + "NULL": "NULL" + }, "DEBUG_COUNTER|DEBUG_0": { "type": "PORT_INGRESS_DROPS" }, diff --git a/tests/portchannel_test.py b/tests/portchannel_test.py new file mode 100644 index 0000000000..a07e205392 --- /dev/null +++ b/tests/portchannel_test.py @@ -0,0 +1,115 @@ +import os +import traceback + +from click.testing import CliRunner + +import config.main as config +import show.main as show +from utilities_common.db import Db + +class TestPortChannel(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "1" + print("SETUP") + + def test_add_portchannel_member_with_invalid_name(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel member with invalid portchannel name + result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChan005", "Ethernet0"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output + + def test_delete_portchannel_member_with_invalid_name(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # delete a portchannel member with invalid portchannel name + result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["del"], ["PortChan005", "Ethernet0"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output + + def test_add_non_existing_portchannel_member(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel member with portchannel is not yet created + result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel0005", "Ethernet0"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel0005 is not present." in result.output + + def test_delete_non_existing_portchannel_member(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # delete a portchannel member with portchannel is not yet created + result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["del"], ["PortChannel0005", "Ethernet0"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel0005 is not present." in result.output + + def test_add_portchannel_member_which_has_ipaddress(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel member with port which has ip-address + result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel1001", "Ethernet0"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet0 has ip address 14.14.0.1/24 configured" in result.output + + def test_add_portchannel_member_which_is_member_of_vlan(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel member with port which is member of Vlan + result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel1001", "Ethernet24"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet24 Interface configured as VLAN_MEMBER under vlan : Vlan2000" in result.output + + def test_add_portchannel_member_which_is_member_of_another_po(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel member with port which is member of another PO + result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel1001", "Ethernet116"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet116 Interface is already member of PortChannel0002 " in result.output + + def test_delete_portchannel_member_which_is_member_of_another_po(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # delete a portchannel member with port which is member of another PO + result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["del"], ["PortChannel1001", "Ethernet116"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet116 is not a member of portchannel PortChannel1001" in result.output + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + print("TEARDOWN") From be7cac59f4f928e7da3a9df7376b8ade16478f53 Mon Sep 17 00:00:00 2001 From: mprabhu-nokia <66807480+mprabhu-nokia@users.noreply.github.com> Date: Tue, 12 Jan 2021 12:23:57 -0500 Subject: [PATCH 04/12] [show] Add subcommand to show midplane status for modular chassis (#1267) Add subcommand to show midplane status for modular chassis and related unit tests --- show/chassis_modules.py | 47 +++++++++++++++++++++-- tests/chassis_modules_test.py | 67 +++++++++++++++++++++++++++++++++ tests/mock_tables/state_db.json | 12 ++++++ 3 files changed, 123 insertions(+), 3 deletions(-) diff --git a/show/chassis_modules.py b/show/chassis_modules.py index 5a13a32419..8a9d22ae35 100644 --- a/show/chassis_modules.py +++ b/show/chassis_modules.py @@ -1,7 +1,7 @@ import click from natsort import natsorted from tabulate import tabulate -from swsssdk import SonicV2Connector +from swsscommon.swsscommon import SonicV2Connector import utilities_common.cli as clicommon @@ -12,6 +12,10 @@ CHASSIS_MODULE_INFO_OPERSTATUS_FIELD = 'oper_status' CHASSIS_MODULE_INFO_ADMINSTATUS_FIELD = 'admin_status' +CHASSIS_MIDPLANE_INFO_TABLE = 'CHASSIS_MIDPLANE_TABLE' +CHASSIS_MIDPLANE_INFO_IP_FIELD = 'ip_address' +CHASSIS_MIDPLANE_INFO_ACCESS_FIELD = 'access' + @click.group(cls=clicommon.AliasedGroup) def chassis_modules(): """Show chassis-modules information""" @@ -31,7 +35,7 @@ def status(db, chassis_module_name): key_pattern = '*' if chassis_module_name: - key_pattern = '|'+chassis_module_name + key_pattern = '|' + chassis_module_name keys = state_db.keys(state_db.STATE_DB, CHASSIS_MODULE_INFO_TABLE + key_pattern) if not keys: @@ -41,7 +45,7 @@ def status(db, chassis_module_name): table = [] for key in natsorted(keys): key_list = key.split('|') - if len(key_list) != 2: # error data in DB, log it and ignore + if len(key_list) != 2: # error data in DB, log it and ignore print('Warn: Invalid Key {} in {} table'.format(key, CHASSIS_MODULE_INFO_TABLE)) continue @@ -61,3 +65,40 @@ def status(db, chassis_module_name): click.echo(tabulate(table, header, tablefmt='simple', stralign='right')) else: click.echo('No data available in CHASSIS_MODULE_TABLE\n') + +@chassis_modules.command() +@click.argument('chassis_module_name', metavar='', required=False) +def midplane_status(chassis_module_name): + """Show chassis-modules midplane-status""" + + header = ['Name', 'IP-Address', 'Reachability'] + + state_db = SonicV2Connector(host="127.0.0.1") + state_db.connect(state_db.STATE_DB) + + key_pattern = '*' + if chassis_module_name: + key_pattern = '|' + chassis_module_name + + keys = state_db.keys(state_db.STATE_DB, CHASSIS_MIDPLANE_INFO_TABLE + key_pattern) + if not keys: + print('Key {} not found in {} table'.format(key_pattern, CHASSIS_MIDPLANE_INFO_TABLE)) + return + + table = [] + for key in natsorted(keys): + key_list = key.split('|') + if len(key_list) != 2: # error data in DB, log it and ignore + print('Warn: Invalid Key {} in {} table'.format(key, CHASSIS_MIDPLANE_INFO_TABLE)) + continue + + data_dict = state_db.get_all(state_db.STATE_DB, key) + ip = data_dict[CHASSIS_MIDPLANE_INFO_IP_FIELD] + access = data_dict[CHASSIS_MIDPLANE_INFO_ACCESS_FIELD] + + table.append((key_list[1], ip, access)) + + if table: + click.echo(tabulate(table, header, tablefmt='simple', stralign='right')) + else: + click.echo('No data available in CHASSIS_MIDPLANE_TABLE\n') diff --git a/tests/chassis_modules_test.py b/tests/chassis_modules_test.py index 754845eba2..fb63488ecd 100644 --- a/tests/chassis_modules_test.py +++ b/tests/chassis_modules_test.py @@ -21,12 +21,36 @@ header_lines = 2 warning_lines = 0 +show_chassis_modules_output="""\ + Name Description Physical-Slot Oper-Status Admin-Status +------------ --------------- --------------- ------------- -------------- +FABRIC-CARD0 fabric-card 17 Online up +FABRIC-CARD1 fabric-card 18 Offline up + LINE-CARD0 line-card 1 Empty up + LINE-CARD1 line-card 2 Online down + SUPERVISOR0 supervisor-card 16 Online up +""" + +show_chassis_midplane_output="""\ + Name IP-Address Reachability +----------- ------------- -------------- + LINE-CARD0 192.168.1.1 True + LINE-CARD1 192.168.1.2 False +SUPERVISOR0 192.168.1.100 True +""" + class TestChassisModules(object): @classmethod def setup_class(cls): print("SETUP") os.environ["UTILITIES_UNIT_TESTING"] = "1" + def test_show_and_verify_output(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["status"], []) + print(result.output) + assert(result.output == show_chassis_modules_output) + def test_show_all_count_lines(self): runner = CliRunner() result = runner.invoke(show.cli.commands["chassis-modules"].commands["status"], []) @@ -112,6 +136,49 @@ def test_config_incorrect_module(self): print(result.output) assert result.exit_code != 0 + def test_show_and_verify_midplane_output(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["midplane-status"], []) + print(result.output) + assert(result.output == show_chassis_midplane_output) + + def test_midplane_show_all_count_lines(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["midplane-status"], []) + print(result.output) + result_lines = result.output.strip('\n').split('\n') + modules = ["LINE-CARD0", "LINE-CARD1", "SUPERVISOR0"] + for i, module in enumerate(modules): + assert module in result_lines[i + warning_lines + header_lines] + assert len(result_lines) == warning_lines + header_lines + len(modules) + + def test_midplane_show_single_count_lines(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["midplane-status"], ["LINE-CARD0"]) + print(result.output) + result_lines = result.output.strip('\n').split('\n') + modules = ["LINE-CARD0"] + for i, module in enumerate(modules): + assert module in result_lines[i+header_lines] + assert len(result_lines) == header_lines + len(modules) + + def test_midplane_show_module_down(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["midplane-status"], ["LINE-CARD1"]) + print(result.output) + result_lines = result.output.strip('\n').split('\n') + assert result.exit_code == 0 + result_out = (result_lines[header_lines]).split() + print(result_out) + assert result_out[2] == 'False' + + def test_midplane_show_incorrect_module(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["midplane-status"], ["TEST-CARD1"]) + print(result.output) + print(result.exit_code) + assert result.exit_code == 0 + @classmethod def teardown_class(cls): print("TEARDOWN") diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index 7f563d8842..c7d56da133 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -377,5 +377,17 @@ "xon": "18432", "xoff": "32768", "size": "51200" + }, + "CHASSIS_MIDPLANE_TABLE|SUPERVISOR0": { + "ip_address": "192.168.1.100", + "access": "True" + }, + "CHASSIS_MIDPLANE_TABLE|LINE-CARD0": { + "ip_address": "192.168.1.1", + "access": "True" + }, + "CHASSIS_MIDPLANE_TABLE|LINE-CARD1": { + "ip_address": "192.168.1.2", + "access": "False" } } From aa4b2d59ff2f99648aaca175363f52b40105c40c Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala <10645050+smaheshm@users.noreply.github.com> Date: Thu, 14 Jan 2021 15:48:22 -0800 Subject: [PATCH 05/12] [pytest][qos][config] Added pytests for "config qos reload" commands" (#1346) Added pytest to verify QoS config generation on multi ASIC platform. --- config/main.py | 45 ++- pfcwd/main.py | 2 + scripts/intfutil | 4 +- tests/config_test.py | 105 ++++++- tests/conftest.py | 35 ++- tests/crm_test.py | 5 +- tests/mock_tables/mock_multi_asic.py | 2 + tests/qos_config_input/0/buffers.json.j2 | 3 + tests/qos_config_input/0/buffers_config.j2 | 172 ++++++++++++ .../qos_config_input/0/buffers_defaults_t0.j2 | 45 +++ .../qos_config_input/0/buffers_defaults_t1.j2 | 45 +++ tests/qos_config_input/0/config_qos.json | 137 ++++++++++ tests/qos_config_input/0/qos.json.j2 | 1 + tests/qos_config_input/0/qos_config.j2 | 257 ++++++++++++++++++ tests/qos_config_input/1/buffers.json.j2 | 3 + tests/qos_config_input/1/buffers_config.j2 | 172 ++++++++++++ .../qos_config_input/1/buffers_defaults_t0.j2 | 45 +++ .../qos_config_input/1/buffers_defaults_t1.j2 | 45 +++ tests/qos_config_input/1/config_qos.json | 137 ++++++++++ tests/qos_config_input/1/qos.json.j2 | 1 + tests/qos_config_input/1/qos_config.j2 | 257 ++++++++++++++++++ tests/qos_config_input/buffers.json.j2 | 3 + tests/qos_config_input/buffers_config.j2 | 172 ++++++++++++ tests/qos_config_input/buffers_defaults_t1.j2 | 45 +++ tests/qos_config_input/config_qos.json | 137 ++++++++++ tests/qos_config_input/qos.json.j2 | 1 + tests/qos_config_input/qos_config.j2 | 257 ++++++++++++++++++ tests/qos_config_input/sonic_version.yml | 8 + 28 files changed, 2124 insertions(+), 17 deletions(-) create mode 100644 tests/qos_config_input/0/buffers.json.j2 create mode 100644 tests/qos_config_input/0/buffers_config.j2 create mode 100644 tests/qos_config_input/0/buffers_defaults_t0.j2 create mode 100644 tests/qos_config_input/0/buffers_defaults_t1.j2 create mode 100644 tests/qos_config_input/0/config_qos.json create mode 100644 tests/qos_config_input/0/qos.json.j2 create mode 100644 tests/qos_config_input/0/qos_config.j2 create mode 100644 tests/qos_config_input/1/buffers.json.j2 create mode 100644 tests/qos_config_input/1/buffers_config.j2 create mode 100644 tests/qos_config_input/1/buffers_defaults_t0.j2 create mode 100644 tests/qos_config_input/1/buffers_defaults_t1.j2 create mode 100644 tests/qos_config_input/1/config_qos.json create mode 100644 tests/qos_config_input/1/qos.json.j2 create mode 100644 tests/qos_config_input/1/qos_config.j2 create mode 100644 tests/qos_config_input/buffers.json.j2 create mode 100644 tests/qos_config_input/buffers_config.j2 create mode 100644 tests/qos_config_input/buffers_defaults_t1.j2 create mode 100644 tests/qos_config_input/config_qos.json create mode 100644 tests/qos_config_input/qos.json.j2 create mode 100644 tests/qos_config_input/qos_config.j2 create mode 100644 tests/qos_config_input/sonic_version.yml diff --git a/config/main.py b/config/main.py index f07d7a7b4b..a4d3e2b107 100644 --- a/config/main.py +++ b/config/main.py @@ -37,6 +37,21 @@ from . import vxlan from .config_mgmt import ConfigMgmtDPB +# mock masic APIs for unit test +try: + if os.environ["UTILITIES_UNIT_TESTING"] == "1" or os.environ["UTILITIES_UNIT_TESTING"] == "2": + modules_path = os.path.join(os.path.dirname(__file__), "..") + tests_path = os.path.join(modules_path, "tests") + sys.path.insert(0, modules_path) + sys.path.insert(0, tests_path) + import mock_tables.dbconnector + if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic": + import mock_tables.mock_multi_asic + mock_tables.dbconnector.load_namespace_config() +except KeyError: + pass + + CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) SONIC_GENERATED_SERVICE_PATH = '/etc/sonic/generated_services.conf' @@ -1756,12 +1771,24 @@ def _update_buffer_calculation_model(config_db, model): @qos.command('reload') @click.pass_context @click.option('--no-dynamic-buffer', is_flag=True, help="Disable dynamic buffer calculation") -def reload(ctx, no_dynamic_buffer): +@click.option( + '--json-data', type=click.STRING, + help="json string with additional data, valid with --dry-run option" +) +@click.option( + '--dry_run', type=click.STRING, + help="Dry run, writes config to the given file" +) +def reload(ctx, no_dynamic_buffer, dry_run, json_data): """Reload QoS configuration""" log.log_info("'qos reload' executing...") _clear_qos() _, hwsku_path = device_info.get_paths_to_platform_and_hwsku_dirs() + sonic_version_file = device_info.get_sonic_version_file() + from_db = "-d --write-to-db" + if dry_run: + from_db = "--additional-data \'{}\'".format(json_data) if json_data else "" namespace_list = [DEFAULT_NAMESPACE] if multi_asic.get_num_asics() > 1: @@ -1798,17 +1825,17 @@ def reload(ctx, no_dynamic_buffer): buffer_template_file = os.path.join(hwsku_path, asic_id_suffix, "buffers.json.j2") if asic_type in vendors_supporting_dynamic_buffer: buffer_model_updated |= _update_buffer_calculation_model(config_db, "traditional") + if os.path.isfile(buffer_template_file): - qos_template_file = os.path.join(hwsku_path, asic_id_suffix, "qos.json.j2") + qos_template_file = os.path.join( + hwsku_path, asic_id_suffix, "qos.json.j2" + ) if os.path.isfile(qos_template_file): cmd_ns = "" if ns is DEFAULT_NAMESPACE else "-n {}".format(ns) - sonic_version_file = os.path.join('/', "etc", "sonic", "sonic_version.yml") - command = "{} {} -d -t {},config-db -t {},config-db -y {} --write-to-db".format( - SONIC_CFGGEN_PATH, - cmd_ns, - buffer_template_file, - qos_template_file, - sonic_version_file + fname = "{}{}".format(dry_run, asic_id_suffix) if dry_run else "config-db" + command = "{} {} {} -t {},{} -t {},{} -y {}".format( + SONIC_CFGGEN_PATH, cmd_ns, from_db, buffer_template_file, + fname, qos_template_file, fname, sonic_version_file ) # Apply the configurations only when both buffer and qos # configuration files are present diff --git a/pfcwd/main.py b/pfcwd/main.py index c55038b9b2..bc5211191b 100644 --- a/pfcwd/main.py +++ b/pfcwd/main.py @@ -1,4 +1,5 @@ import os +import imp import sys import click @@ -21,6 +22,7 @@ import mock_tables.dbconnector if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic": import mock_tables.mock_multi_asic + imp.reload(mock_tables.mock_multi_asic) mock_tables.dbconnector.load_namespace_config() except KeyError: diff --git a/scripts/intfutil b/scripts/intfutil index 2f4baa71ab..b943b7c543 100755 --- a/scripts/intfutil +++ b/scripts/intfutil @@ -22,7 +22,7 @@ try: if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic": import mock_tables.mock_multi_asic mock_tables.dbconnector.load_namespace_config() - + except KeyError: pass @@ -461,7 +461,7 @@ class IntfDescription(object): self.intf_name = intf_name def display_intf_description(self): - + self.get_intf_description() # Sorting and tabulating the result table. diff --git a/tests/config_test.py b/tests/config_test.py index 1ca37fb7a9..30a1f10ec8 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -1,10 +1,14 @@ +import filecmp +import imp import os import traceback +import json from unittest import mock import click from click.testing import CliRunner +from sonic_py_common import device_info from utilities_common.db import Db load_minigraph_command_output="""\ @@ -61,8 +65,10 @@ class TestLoadMinigraph(object): def setup_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "1" print("SETUP") + import config.main + imp.reload(config.main) - def test_load_minigraph(self, get_cmd_module, setup_single_broacom_asic): + def test_load_minigraph(self, get_cmd_module, setup_single_broadcom_asic): with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command: (config, show) = get_cmd_module runner = CliRunner() @@ -74,7 +80,7 @@ def test_load_minigraph(self, get_cmd_module, setup_single_broacom_asic): assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == load_minigraph_command_output assert mock_run_command.call_count == 38 - def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_single_broacom_asic): + def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_single_broadcom_asic): with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command: (config, show) = get_cmd_module db = Db() @@ -95,3 +101,98 @@ def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_sing def teardown_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "0" print("TEARDOWN") + + +class TestConfigQos(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ['UTILITIES_UNIT_TESTING'] = "2" + import config.main + imp.reload(config.main) + + def test_qos_reload_single( + self, get_cmd_module, setup_qos_mock_apis, + setup_single_broadcom_asic + ): + (config, show) = get_cmd_module + runner = CliRunner() + output_file = os.path.join(os.sep, "tmp", "qos_config_output.json") + print("Saving output in {}".format(output_file)) + try: + os.remove(output_file) + except OSError: + pass + json_data = '{"DEVICE_METADATA": {"localhost": {}}}' + result = runner.invoke( + config.config.commands["qos"], + ["reload", "--dry_run", output_file, "--json-data", json_data] + ) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + cwd = os.path.dirname(os.path.realpath(__file__)) + expected_result = os.path.join( + cwd, "qos_config_input", "config_qos.json" + ) + assert filecmp.cmp(output_file, expected_result, shallow=False) + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ['UTILITIES_UNIT_TESTING'] = "0" + + +class TestConfigQosMasic(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ['UTILITIES_UNIT_TESTING'] = "2" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + import config.main + imp.reload(config.main) + + def test_qos_reload_masic( + self, get_cmd_module, setup_qos_mock_apis, + setup_multi_broadcom_masic + ): + (config, show) = get_cmd_module + runner = CliRunner() + output_file = os.path.join(os.sep, "tmp", "qos_config_output.json") + print("Saving output in {}<0,1,2..>".format(output_file)) + num_asic = device_info.get_num_npus() + for asic in range(num_asic): + try: + file = "{}{}".format(output_file, asic) + os.remove(file) + except OSError: + pass + json_data = '{"DEVICE_METADATA": {"localhost": {}}}' + result = runner.invoke( + config.config.commands["qos"], + ["reload", "--dry_run", output_file, "--json-data", json_data] + ) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + cwd = os.path.dirname(os.path.realpath(__file__)) + + for asic in range(num_asic): + expected_result = os.path.join( + cwd, "qos_config_input", str(asic), "config_qos.json" + ) + file = "{}{}".format(output_file, asic) + assert filecmp.cmp(file, expected_result, shallow=False) + + @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 + imp.reload(mock_single_asic) + dbconnector.load_namespace_config() diff --git a/tests/conftest.py b/tests/conftest.py index c4ec7bb61d..309e65f055 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -56,18 +56,47 @@ def get_cmd_module(): return (config, show) +def set_mock_apis(): + import config.main as config + cwd = os.path.dirname(os.path.realpath(__file__)) + config.asic_type = mock.MagicMock(return_value="broadcom") + config._get_device_type = mock.MagicMock(return_value="ToRRouter") + +@pytest.fixture +def setup_qos_mock_apis(): + cwd = os.path.dirname(os.path.realpath(__file__)) + device_info.get_paths_to_platform_and_hwsku_dirs = mock.MagicMock( + return_value=( + os.path.join(cwd, "."), os.path.join(cwd, "qos_config_input") + ) + ) + device_info.get_sonic_version_file = mock.MagicMock( + return_value=os.path.join(cwd, "qos_config_input/sonic_version.yml") + ) @pytest.fixture -def setup_single_broacom_asic(): +def setup_single_broadcom_asic(): import config.main as config import show.main as show + set_mock_apis() device_info.get_num_npus = mock.MagicMock(return_value=1) config._get_sonic_generated_services = \ mock.MagicMock(return_value=(generated_services_list, [])) - config.asic_type = mock.MagicMock(return_value="broadcom") - config._get_device_type = mock.MagicMock(return_value="ToRRouter") + +@pytest.fixture +def setup_multi_broadcom_masic(): + import config.main as config + import show.main as show + + set_mock_apis() + device_info.get_num_npus = mock.MagicMock(return_value=2) + + yield + + device_info.get_num_npus = mock.MagicMock(return_value=1) + @pytest.fixture def setup_t1_topo(): diff --git a/tests/crm_test.py b/tests/crm_test.py index f4fdcece10..0c42bf17fc 100644 --- a/tests/crm_test.py +++ b/tests/crm_test.py @@ -1,3 +1,4 @@ +import imp import os import sys from importlib import reload @@ -1574,5 +1575,7 @@ def teardown_class(cls): print("TEARDOWN") os.environ["UTILITIES_UNIT_TESTING"] = "0" os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + from .mock_tables import dbconnector from .mock_tables import mock_single_asic - reload(mock_single_asic) + imp.reload(mock_single_asic) + dbconnector.load_namespace_config() diff --git a/tests/mock_tables/mock_multi_asic.py b/tests/mock_tables/mock_multi_asic.py index ad694003f7..3e875af36a 100644 --- a/tests/mock_tables/mock_multi_asic.py +++ b/tests/mock_tables/mock_multi_asic.py @@ -14,6 +14,8 @@ def mock_is_multi_asic(): def mock_get_namespace_list(namespace=None): return ['asic0', 'asic1'] + multi_asic.get_num_asics = mock_get_num_asics multi_asic.is_multi_asic = mock_is_multi_asic multi_asic.get_namespace_list = mock_get_namespace_list +multi_asic.get_namespaces_from_linux = mock_get_namespace_list diff --git a/tests/qos_config_input/0/buffers.json.j2 b/tests/qos_config_input/0/buffers.json.j2 new file mode 100644 index 0000000000..b67cf577ab --- /dev/null +++ b/tests/qos_config_input/0/buffers.json.j2 @@ -0,0 +1,3 @@ +{%- set default_topo = 't1' %} +{%- include 'buffers_config.j2' %} + diff --git a/tests/qos_config_input/0/buffers_config.j2 b/tests/qos_config_input/0/buffers_config.j2 new file mode 100644 index 0000000000..f5dbb9784f --- /dev/null +++ b/tests/qos_config_input/0/buffers_config.j2 @@ -0,0 +1,172 @@ +{%- macro set_default_topology() %} +{%- if default_topo is defined %} +{{ default_topo }} +{%- else %} +def +{%- endif %} +{%- endmacro -%} + +{# Determine device topology and filename postfix #} +{%- if DEVICE_METADATA is defined and DEVICE_METADATA['localhost']['type'] is defined %} +{%- set switch_role = DEVICE_METADATA['localhost']['type'] %} +{%- if 'torrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} +{%- set filename_postfix = 't0' %} +{%- elif 'leafrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} +{%- set filename_postfix = 't1' %} +{%- else %} +{%- set filename_postfix = set_default_topology() %} +{%- endif %} +{%- else %} +{%- set filename_postfix = set_default_topology() %} +{%- set switch_role = '' %} +{%- endif -%} + +{# Import default values from device HWSKU folder #} +{%- import 'buffers_defaults_%s.j2' % filename_postfix as defs %} + +{%- set default_cable = defs.default_cable -%} + +{# Port configuration to cable length look-up table #} +{# Each record describes mapping of DUT (DUT port) role and neighbor role to cable length #} +{# Roles described in the minigraph #} +{%- if defs.ports2cable is defined %} + {%- set ports2cable = defs.ports2cable %} +{%- else %} + {%- set ports2cable = { + 'torrouter_server' : '5m', + 'leafrouter_torrouter' : '40m', + 'spinerouter_leafrouter' : '300m' + } + -%} +{%- endif %} + +{%- macro cable_length(port_name) %} + {%- set cable_len = [] %} + {%- for local_port in DEVICE_NEIGHBOR %} + {%- if local_port == port_name %} + {%- if DEVICE_NEIGHBOR_METADATA is defined and DEVICE_NEIGHBOR_METADATA[DEVICE_NEIGHBOR[local_port].name] %} + {%- set neighbor = DEVICE_NEIGHBOR_METADATA[DEVICE_NEIGHBOR[local_port].name] %} + {%- set neighbor_role = neighbor.type %} + {%- if 'asic' == neighbor_role | lower %} + {%- set roles1 = 'internal' %} + {%- if 'internal' not in ports2cable %} + {%- set _ = ports2cable.update({'internal': '5m'}) %} + {%- endif -%} + {%- else %} + {%- set roles1 = switch_role + '_' + neighbor_role %} + {%- set roles2 = neighbor_role + '_' + switch_role %} + {%- set roles1 = roles1 | lower %} + {%- set roles2 = roles2 | lower %} + {%- endif %} + {%- if roles1 in ports2cable %} + {%- if cable_len.append(ports2cable[roles1]) %}{% endif %} + {%- elif roles2 in ports2cable %} + {%- if cable_len.append(ports2cable[roles2]) %}{% endif %} + {%- endif %} + {%- endif %} + {%- endif %} + {%- endfor %} + {%- if cable_len -%} + {{ cable_len.0 }} + {%- else %} + {%- if 'torrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} + {%- for local_port in VLAN_MEMBER %} + {%- if local_port[1] == port_name %} + {%- set roles3 = switch_role + '_' + 'server' %} + {%- set roles3 = roles3 | lower %} + {%- if roles3 in ports2cable %} + {%- if cable_len.append(ports2cable[roles3]) %}{% endif %} + {%- endif %} + {%- endif %} + {%- endfor %} + {%- if cable_len -%} + {{ cable_len.0 }} + {%- else -%} + {{ default_cable }} + {%- endif %} + {%- else -%} + {{ default_cable }} + {%- endif %} + {%- endif %} +{%- endmacro %} + +{%- set PORT_ALL = [] %} + +{%- if PORT is not defined %} + {%- if defs.generate_port_lists(PORT_ALL) %} {% endif %} +{%- else %} + {%- for port in PORT %} + {%- if PORT_ALL.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} + +{%- set PORT_ACTIVE = [] %} +{%- if DEVICE_NEIGHBOR is not defined %} + {%- set PORT_ACTIVE = PORT_ALL %} +{%- else %} + {%- for port in DEVICE_NEIGHBOR.keys() %} + {%- if PORT_ACTIVE.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} + +{%- set port_names_list_active = [] %} +{%- for port in PORT_ACTIVE %} + {%- if port_names_list_active.append(port) %}{%- endif %} +{%- endfor %} +{%- set port_names_active = port_names_list_active | join(',') %} + +{ + "CABLE_LENGTH": { + "AZURE": { + {% for port in PORT_ALL %} + {%- set cable = cable_length(port) %} + "{{ port }}": "{{ cable }}"{%- if not loop.last %},{% endif %} + + {% endfor %} + } + }, + +{% if defs.generate_buffer_pool_and_profiles is defined %} +{{ defs.generate_buffer_pool_and_profiles() }} +{% endif %} + +{%- if defs.generate_profile_lists is defined %} +{{ defs.generate_profile_lists(port_names_active) }}, +{% endif %} + +{%- if defs.generate_pg_profils is defined %} +{{ defs.generate_pg_profils(port_names_active) }} +{% else %} + "BUFFER_PG": { +{% for port in PORT_ACTIVE %} + "{{ port }}|0": { + "profile" : "[BUFFER_PROFILE|ingress_lossy_profile]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + }, +{% endif %} + +{% if defs.generate_queue_buffers is defined %} +{{ defs.generate_queue_buffers(port_names_active) }} +{% else %} + "BUFFER_QUEUE": { +{% for port in PORT_ACTIVE %} + "{{ port }}|3-4": { + "profile" : "[BUFFER_PROFILE|egress_lossless_profile]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|0-2": { + "profile" : "[BUFFER_PROFILE|egress_lossy_profile]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|5-6": { + "profile" : "[BUFFER_PROFILE|egress_lossy_profile]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + } +{% endif %} +} diff --git a/tests/qos_config_input/0/buffers_defaults_t0.j2 b/tests/qos_config_input/0/buffers_defaults_t0.j2 new file mode 100644 index 0000000000..38e34eb571 --- /dev/null +++ b/tests/qos_config_input/0/buffers_defaults_t0.j2 @@ -0,0 +1,45 @@ +{%- set default_cable = '300m' %} + +{%- macro generate_port_lists(PORT_ALL) %} + {# Generate list of ports #} + {% for port_idx in range(0,32) %} + {% if PORT_ALL.append("Ethernet%d" % (port_idx * 4)) %}{% endif %} + {% endfor %} +{%- endmacro %} + +{%- macro generate_buffer_pool_and_profiles() %} + "BUFFER_POOL": { + "ingress_lossless_pool": { + "size": "12766208", + "type": "ingress", + "mode": "dynamic" + }, + "egress_lossless_pool": { + "size": "12766208", + "type": "egress", + "mode": "static" + }, + "egress_lossy_pool": { + "size": "7326924", + "type": "egress", + "mode": "dynamic" + } + }, + "BUFFER_PROFILE": { + "ingress_lossy_profile": { + "pool":"[BUFFER_POOL|ingress_lossless_pool]", + "size":"0", + "dynamic_th":"3" + }, + "egress_lossless_profile": { + "pool":"[BUFFER_POOL|egress_lossless_pool]", + "size":"0", + "static_th":"12766208" + }, + "egress_lossy_profile": { + "pool":"[BUFFER_POOL|egress_lossy_pool]", + "size":"1518", + "dynamic_th":"3" + } + }, +{%- endmacro %} diff --git a/tests/qos_config_input/0/buffers_defaults_t1.j2 b/tests/qos_config_input/0/buffers_defaults_t1.j2 new file mode 100644 index 0000000000..38e34eb571 --- /dev/null +++ b/tests/qos_config_input/0/buffers_defaults_t1.j2 @@ -0,0 +1,45 @@ +{%- set default_cable = '300m' %} + +{%- macro generate_port_lists(PORT_ALL) %} + {# Generate list of ports #} + {% for port_idx in range(0,32) %} + {% if PORT_ALL.append("Ethernet%d" % (port_idx * 4)) %}{% endif %} + {% endfor %} +{%- endmacro %} + +{%- macro generate_buffer_pool_and_profiles() %} + "BUFFER_POOL": { + "ingress_lossless_pool": { + "size": "12766208", + "type": "ingress", + "mode": "dynamic" + }, + "egress_lossless_pool": { + "size": "12766208", + "type": "egress", + "mode": "static" + }, + "egress_lossy_pool": { + "size": "7326924", + "type": "egress", + "mode": "dynamic" + } + }, + "BUFFER_PROFILE": { + "ingress_lossy_profile": { + "pool":"[BUFFER_POOL|ingress_lossless_pool]", + "size":"0", + "dynamic_th":"3" + }, + "egress_lossless_profile": { + "pool":"[BUFFER_POOL|egress_lossless_pool]", + "size":"0", + "static_th":"12766208" + }, + "egress_lossy_profile": { + "pool":"[BUFFER_POOL|egress_lossy_pool]", + "size":"1518", + "dynamic_th":"3" + } + }, +{%- endmacro %} diff --git a/tests/qos_config_input/0/config_qos.json b/tests/qos_config_input/0/config_qos.json new file mode 100644 index 0000000000..40c1903a06 --- /dev/null +++ b/tests/qos_config_input/0/config_qos.json @@ -0,0 +1,137 @@ +{ + "TC_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "0": "0", + "1": "0", + "2": "0", + "3": "3", + "4": "4", + "5": "0", + "6": "0", + "7": "7" + } + }, + "MAP_PFC_PRIORITY_TO_QUEUE": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "DSCP_TO_TC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type" : "DWRR", + "weight": "14" + }, + "scheduler.1": { + "type" : "DWRR", + "weight": "15" + } + }, + "PORT_QOS_MAP": { + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS" : { + "wred_green_enable" : "true", + "wred_yellow_enable" : "true", + "wred_red_enable" : "true", + "ecn" : "ecn_all", + "green_max_threshold" : "2097152", + "green_min_threshold" : "1048576", + "yellow_max_threshold" : "2097152", + "yellow_min_threshold" : "1048576", + "red_max_threshold" : "2097152", + "red_min_threshold" : "1048576", + "green_drop_probability" : "5", + "yellow_drop_probability": "5", + "red_drop_probability" : "5" + } + }, + "QUEUE": { + } +} diff --git a/tests/qos_config_input/0/qos.json.j2 b/tests/qos_config_input/0/qos.json.j2 new file mode 100644 index 0000000000..3e548325ea --- /dev/null +++ b/tests/qos_config_input/0/qos.json.j2 @@ -0,0 +1 @@ +{%- include 'qos_config.j2' %} diff --git a/tests/qos_config_input/0/qos_config.j2 b/tests/qos_config_input/0/qos_config.j2 new file mode 100644 index 0000000000..a7c361d69f --- /dev/null +++ b/tests/qos_config_input/0/qos_config.j2 @@ -0,0 +1,257 @@ +{%- set PORT_ALL = [] %} +{%- for port in PORT %} + {%- if PORT_ALL.append(port) %}{% endif %} +{%- endfor %} +{%- if PORT_ALL | sort_by_port_index %}{% endif %} + +{%- set port_names_list_all = [] %} +{%- for port in PORT_ALL %} + {%- if port_names_list_all.append(port) %}{% endif %} +{%- endfor %} +{%- set port_names_all = port_names_list_all | join(',') -%} + + +{%- set PORT_ACTIVE = [] %} +{%- if DEVICE_NEIGHBOR is not defined %} + {%- set PORT_ACTIVE = PORT_ALL %} +{%- else %} + {%- for port in DEVICE_NEIGHBOR.keys() %} + {%- if PORT_ACTIVE.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} +{%- if PORT_ACTIVE | sort_by_port_index %}{% endif %} + +{%- set port_names_list_active = [] %} +{%- for port in PORT_ACTIVE %} + {%- if port_names_list_active.append(port) %}{%- endif %} +{%- endfor %} +{%- set port_names_active = port_names_list_active | join(',') -%} + + +{%- set pfc_to_pg_map_supported_asics = ['mellanox', 'barefoot', 'marvell'] -%} +{%- set backend_device_types = ['BackEndToRRouter', 'BackEndLeafRouter'] -%} + + +{ +{% if generate_tc_to_pg_map is defined %} + {{- generate_tc_to_pg_map() }} +{% else %} + "TC_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "0": "0", + "1": "0", + "2": "0", + "3": "3", + "4": "4", + "5": "0", + "6": "0", + "7": "7" + } + }, +{% endif %} + "MAP_PFC_PRIORITY_TO_QUEUE": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, +{% if 'type' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['type'] in backend_device_types and 'storage_device' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['storage_device'] == 'true' %} + "DOT1P_TO_TC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, +{% else %} + "DSCP_TO_TC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, +{% endif %} + "SCHEDULER": { + "scheduler.0": { + "type" : "DWRR", + "weight": "14" + }, + "scheduler.1": { + "type" : "DWRR", + "weight": "15" + } + }, +{% if asic_type in pfc_to_pg_map_supported_asics %} + "PFC_PRIORITY_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "3": "3", + "4": "4" + } + }, +{% endif %} + "PORT_QOS_MAP": { +{% for port in PORT_ACTIVE %} + "{{ port }}": { +{% if 'type' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['type'] in backend_device_types and 'storage_device' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['storage_device'] == 'true' %} + "dot1p_to_tc_map" : "[DOT1P_TO_TC_MAP|AZURE]", +{% else %} + "dscp_to_tc_map" : "[DSCP_TO_TC_MAP|AZURE]", +{% endif %} + "tc_to_queue_map" : "[TC_TO_QUEUE_MAP|AZURE]", + "tc_to_pg_map" : "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", + "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", +{% if asic_type in pfc_to_pg_map_supported_asics %} + "pfc_to_pg_map" : "[PFC_PRIORITY_TO_PRIORITY_GROUP_MAP|AZURE]", +{% endif %} + "pfc_enable" : "3,4" + }{% if not loop.last %},{% endif %} + +{% endfor %} + }, +{% if generate_wred_profiles is defined %} + {{- generate_wred_profiles() }} +{% else %} + "WRED_PROFILE": { + "AZURE_LOSSLESS" : { + "wred_green_enable" : "true", + "wred_yellow_enable" : "true", + "wred_red_enable" : "true", + "ecn" : "ecn_all", + "green_max_threshold" : "2097152", + "green_min_threshold" : "1048576", + "yellow_max_threshold" : "2097152", + "yellow_min_threshold" : "1048576", + "red_max_threshold" : "2097152", + "red_min_threshold" : "1048576", + "green_drop_probability" : "5", + "yellow_drop_probability": "5", + "red_drop_probability" : "5" + } + }, +{% endif %} + "QUEUE": { +{% for port in PORT_ACTIVE %} + "{{ port }}|3": { + "scheduler" : "[SCHEDULER|scheduler.1]", + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|4": { + "scheduler" : "[SCHEDULER|scheduler.1]", + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|0": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|1": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|2": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|5": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|6": { + "scheduler": "[SCHEDULER|scheduler.0]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + } +} diff --git a/tests/qos_config_input/1/buffers.json.j2 b/tests/qos_config_input/1/buffers.json.j2 new file mode 100644 index 0000000000..b67cf577ab --- /dev/null +++ b/tests/qos_config_input/1/buffers.json.j2 @@ -0,0 +1,3 @@ +{%- set default_topo = 't1' %} +{%- include 'buffers_config.j2' %} + diff --git a/tests/qos_config_input/1/buffers_config.j2 b/tests/qos_config_input/1/buffers_config.j2 new file mode 100644 index 0000000000..f5dbb9784f --- /dev/null +++ b/tests/qos_config_input/1/buffers_config.j2 @@ -0,0 +1,172 @@ +{%- macro set_default_topology() %} +{%- if default_topo is defined %} +{{ default_topo }} +{%- else %} +def +{%- endif %} +{%- endmacro -%} + +{# Determine device topology and filename postfix #} +{%- if DEVICE_METADATA is defined and DEVICE_METADATA['localhost']['type'] is defined %} +{%- set switch_role = DEVICE_METADATA['localhost']['type'] %} +{%- if 'torrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} +{%- set filename_postfix = 't0' %} +{%- elif 'leafrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} +{%- set filename_postfix = 't1' %} +{%- else %} +{%- set filename_postfix = set_default_topology() %} +{%- endif %} +{%- else %} +{%- set filename_postfix = set_default_topology() %} +{%- set switch_role = '' %} +{%- endif -%} + +{# Import default values from device HWSKU folder #} +{%- import 'buffers_defaults_%s.j2' % filename_postfix as defs %} + +{%- set default_cable = defs.default_cable -%} + +{# Port configuration to cable length look-up table #} +{# Each record describes mapping of DUT (DUT port) role and neighbor role to cable length #} +{# Roles described in the minigraph #} +{%- if defs.ports2cable is defined %} + {%- set ports2cable = defs.ports2cable %} +{%- else %} + {%- set ports2cable = { + 'torrouter_server' : '5m', + 'leafrouter_torrouter' : '40m', + 'spinerouter_leafrouter' : '300m' + } + -%} +{%- endif %} + +{%- macro cable_length(port_name) %} + {%- set cable_len = [] %} + {%- for local_port in DEVICE_NEIGHBOR %} + {%- if local_port == port_name %} + {%- if DEVICE_NEIGHBOR_METADATA is defined and DEVICE_NEIGHBOR_METADATA[DEVICE_NEIGHBOR[local_port].name] %} + {%- set neighbor = DEVICE_NEIGHBOR_METADATA[DEVICE_NEIGHBOR[local_port].name] %} + {%- set neighbor_role = neighbor.type %} + {%- if 'asic' == neighbor_role | lower %} + {%- set roles1 = 'internal' %} + {%- if 'internal' not in ports2cable %} + {%- set _ = ports2cable.update({'internal': '5m'}) %} + {%- endif -%} + {%- else %} + {%- set roles1 = switch_role + '_' + neighbor_role %} + {%- set roles2 = neighbor_role + '_' + switch_role %} + {%- set roles1 = roles1 | lower %} + {%- set roles2 = roles2 | lower %} + {%- endif %} + {%- if roles1 in ports2cable %} + {%- if cable_len.append(ports2cable[roles1]) %}{% endif %} + {%- elif roles2 in ports2cable %} + {%- if cable_len.append(ports2cable[roles2]) %}{% endif %} + {%- endif %} + {%- endif %} + {%- endif %} + {%- endfor %} + {%- if cable_len -%} + {{ cable_len.0 }} + {%- else %} + {%- if 'torrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} + {%- for local_port in VLAN_MEMBER %} + {%- if local_port[1] == port_name %} + {%- set roles3 = switch_role + '_' + 'server' %} + {%- set roles3 = roles3 | lower %} + {%- if roles3 in ports2cable %} + {%- if cable_len.append(ports2cable[roles3]) %}{% endif %} + {%- endif %} + {%- endif %} + {%- endfor %} + {%- if cable_len -%} + {{ cable_len.0 }} + {%- else -%} + {{ default_cable }} + {%- endif %} + {%- else -%} + {{ default_cable }} + {%- endif %} + {%- endif %} +{%- endmacro %} + +{%- set PORT_ALL = [] %} + +{%- if PORT is not defined %} + {%- if defs.generate_port_lists(PORT_ALL) %} {% endif %} +{%- else %} + {%- for port in PORT %} + {%- if PORT_ALL.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} + +{%- set PORT_ACTIVE = [] %} +{%- if DEVICE_NEIGHBOR is not defined %} + {%- set PORT_ACTIVE = PORT_ALL %} +{%- else %} + {%- for port in DEVICE_NEIGHBOR.keys() %} + {%- if PORT_ACTIVE.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} + +{%- set port_names_list_active = [] %} +{%- for port in PORT_ACTIVE %} + {%- if port_names_list_active.append(port) %}{%- endif %} +{%- endfor %} +{%- set port_names_active = port_names_list_active | join(',') %} + +{ + "CABLE_LENGTH": { + "AZURE": { + {% for port in PORT_ALL %} + {%- set cable = cable_length(port) %} + "{{ port }}": "{{ cable }}"{%- if not loop.last %},{% endif %} + + {% endfor %} + } + }, + +{% if defs.generate_buffer_pool_and_profiles is defined %} +{{ defs.generate_buffer_pool_and_profiles() }} +{% endif %} + +{%- if defs.generate_profile_lists is defined %} +{{ defs.generate_profile_lists(port_names_active) }}, +{% endif %} + +{%- if defs.generate_pg_profils is defined %} +{{ defs.generate_pg_profils(port_names_active) }} +{% else %} + "BUFFER_PG": { +{% for port in PORT_ACTIVE %} + "{{ port }}|0": { + "profile" : "[BUFFER_PROFILE|ingress_lossy_profile]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + }, +{% endif %} + +{% if defs.generate_queue_buffers is defined %} +{{ defs.generate_queue_buffers(port_names_active) }} +{% else %} + "BUFFER_QUEUE": { +{% for port in PORT_ACTIVE %} + "{{ port }}|3-4": { + "profile" : "[BUFFER_PROFILE|egress_lossless_profile]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|0-2": { + "profile" : "[BUFFER_PROFILE|egress_lossy_profile]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|5-6": { + "profile" : "[BUFFER_PROFILE|egress_lossy_profile]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + } +{% endif %} +} diff --git a/tests/qos_config_input/1/buffers_defaults_t0.j2 b/tests/qos_config_input/1/buffers_defaults_t0.j2 new file mode 100644 index 0000000000..38e34eb571 --- /dev/null +++ b/tests/qos_config_input/1/buffers_defaults_t0.j2 @@ -0,0 +1,45 @@ +{%- set default_cable = '300m' %} + +{%- macro generate_port_lists(PORT_ALL) %} + {# Generate list of ports #} + {% for port_idx in range(0,32) %} + {% if PORT_ALL.append("Ethernet%d" % (port_idx * 4)) %}{% endif %} + {% endfor %} +{%- endmacro %} + +{%- macro generate_buffer_pool_and_profiles() %} + "BUFFER_POOL": { + "ingress_lossless_pool": { + "size": "12766208", + "type": "ingress", + "mode": "dynamic" + }, + "egress_lossless_pool": { + "size": "12766208", + "type": "egress", + "mode": "static" + }, + "egress_lossy_pool": { + "size": "7326924", + "type": "egress", + "mode": "dynamic" + } + }, + "BUFFER_PROFILE": { + "ingress_lossy_profile": { + "pool":"[BUFFER_POOL|ingress_lossless_pool]", + "size":"0", + "dynamic_th":"3" + }, + "egress_lossless_profile": { + "pool":"[BUFFER_POOL|egress_lossless_pool]", + "size":"0", + "static_th":"12766208" + }, + "egress_lossy_profile": { + "pool":"[BUFFER_POOL|egress_lossy_pool]", + "size":"1518", + "dynamic_th":"3" + } + }, +{%- endmacro %} diff --git a/tests/qos_config_input/1/buffers_defaults_t1.j2 b/tests/qos_config_input/1/buffers_defaults_t1.j2 new file mode 100644 index 0000000000..38e34eb571 --- /dev/null +++ b/tests/qos_config_input/1/buffers_defaults_t1.j2 @@ -0,0 +1,45 @@ +{%- set default_cable = '300m' %} + +{%- macro generate_port_lists(PORT_ALL) %} + {# Generate list of ports #} + {% for port_idx in range(0,32) %} + {% if PORT_ALL.append("Ethernet%d" % (port_idx * 4)) %}{% endif %} + {% endfor %} +{%- endmacro %} + +{%- macro generate_buffer_pool_and_profiles() %} + "BUFFER_POOL": { + "ingress_lossless_pool": { + "size": "12766208", + "type": "ingress", + "mode": "dynamic" + }, + "egress_lossless_pool": { + "size": "12766208", + "type": "egress", + "mode": "static" + }, + "egress_lossy_pool": { + "size": "7326924", + "type": "egress", + "mode": "dynamic" + } + }, + "BUFFER_PROFILE": { + "ingress_lossy_profile": { + "pool":"[BUFFER_POOL|ingress_lossless_pool]", + "size":"0", + "dynamic_th":"3" + }, + "egress_lossless_profile": { + "pool":"[BUFFER_POOL|egress_lossless_pool]", + "size":"0", + "static_th":"12766208" + }, + "egress_lossy_profile": { + "pool":"[BUFFER_POOL|egress_lossy_pool]", + "size":"1518", + "dynamic_th":"3" + } + }, +{%- endmacro %} diff --git a/tests/qos_config_input/1/config_qos.json b/tests/qos_config_input/1/config_qos.json new file mode 100644 index 0000000000..40c1903a06 --- /dev/null +++ b/tests/qos_config_input/1/config_qos.json @@ -0,0 +1,137 @@ +{ + "TC_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "0": "0", + "1": "0", + "2": "0", + "3": "3", + "4": "4", + "5": "0", + "6": "0", + "7": "7" + } + }, + "MAP_PFC_PRIORITY_TO_QUEUE": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "DSCP_TO_TC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type" : "DWRR", + "weight": "14" + }, + "scheduler.1": { + "type" : "DWRR", + "weight": "15" + } + }, + "PORT_QOS_MAP": { + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS" : { + "wred_green_enable" : "true", + "wred_yellow_enable" : "true", + "wred_red_enable" : "true", + "ecn" : "ecn_all", + "green_max_threshold" : "2097152", + "green_min_threshold" : "1048576", + "yellow_max_threshold" : "2097152", + "yellow_min_threshold" : "1048576", + "red_max_threshold" : "2097152", + "red_min_threshold" : "1048576", + "green_drop_probability" : "5", + "yellow_drop_probability": "5", + "red_drop_probability" : "5" + } + }, + "QUEUE": { + } +} diff --git a/tests/qos_config_input/1/qos.json.j2 b/tests/qos_config_input/1/qos.json.j2 new file mode 100644 index 0000000000..3e548325ea --- /dev/null +++ b/tests/qos_config_input/1/qos.json.j2 @@ -0,0 +1 @@ +{%- include 'qos_config.j2' %} diff --git a/tests/qos_config_input/1/qos_config.j2 b/tests/qos_config_input/1/qos_config.j2 new file mode 100644 index 0000000000..a7c361d69f --- /dev/null +++ b/tests/qos_config_input/1/qos_config.j2 @@ -0,0 +1,257 @@ +{%- set PORT_ALL = [] %} +{%- for port in PORT %} + {%- if PORT_ALL.append(port) %}{% endif %} +{%- endfor %} +{%- if PORT_ALL | sort_by_port_index %}{% endif %} + +{%- set port_names_list_all = [] %} +{%- for port in PORT_ALL %} + {%- if port_names_list_all.append(port) %}{% endif %} +{%- endfor %} +{%- set port_names_all = port_names_list_all | join(',') -%} + + +{%- set PORT_ACTIVE = [] %} +{%- if DEVICE_NEIGHBOR is not defined %} + {%- set PORT_ACTIVE = PORT_ALL %} +{%- else %} + {%- for port in DEVICE_NEIGHBOR.keys() %} + {%- if PORT_ACTIVE.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} +{%- if PORT_ACTIVE | sort_by_port_index %}{% endif %} + +{%- set port_names_list_active = [] %} +{%- for port in PORT_ACTIVE %} + {%- if port_names_list_active.append(port) %}{%- endif %} +{%- endfor %} +{%- set port_names_active = port_names_list_active | join(',') -%} + + +{%- set pfc_to_pg_map_supported_asics = ['mellanox', 'barefoot', 'marvell'] -%} +{%- set backend_device_types = ['BackEndToRRouter', 'BackEndLeafRouter'] -%} + + +{ +{% if generate_tc_to_pg_map is defined %} + {{- generate_tc_to_pg_map() }} +{% else %} + "TC_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "0": "0", + "1": "0", + "2": "0", + "3": "3", + "4": "4", + "5": "0", + "6": "0", + "7": "7" + } + }, +{% endif %} + "MAP_PFC_PRIORITY_TO_QUEUE": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, +{% if 'type' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['type'] in backend_device_types and 'storage_device' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['storage_device'] == 'true' %} + "DOT1P_TO_TC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, +{% else %} + "DSCP_TO_TC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, +{% endif %} + "SCHEDULER": { + "scheduler.0": { + "type" : "DWRR", + "weight": "14" + }, + "scheduler.1": { + "type" : "DWRR", + "weight": "15" + } + }, +{% if asic_type in pfc_to_pg_map_supported_asics %} + "PFC_PRIORITY_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "3": "3", + "4": "4" + } + }, +{% endif %} + "PORT_QOS_MAP": { +{% for port in PORT_ACTIVE %} + "{{ port }}": { +{% if 'type' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['type'] in backend_device_types and 'storage_device' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['storage_device'] == 'true' %} + "dot1p_to_tc_map" : "[DOT1P_TO_TC_MAP|AZURE]", +{% else %} + "dscp_to_tc_map" : "[DSCP_TO_TC_MAP|AZURE]", +{% endif %} + "tc_to_queue_map" : "[TC_TO_QUEUE_MAP|AZURE]", + "tc_to_pg_map" : "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", + "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", +{% if asic_type in pfc_to_pg_map_supported_asics %} + "pfc_to_pg_map" : "[PFC_PRIORITY_TO_PRIORITY_GROUP_MAP|AZURE]", +{% endif %} + "pfc_enable" : "3,4" + }{% if not loop.last %},{% endif %} + +{% endfor %} + }, +{% if generate_wred_profiles is defined %} + {{- generate_wred_profiles() }} +{% else %} + "WRED_PROFILE": { + "AZURE_LOSSLESS" : { + "wred_green_enable" : "true", + "wred_yellow_enable" : "true", + "wred_red_enable" : "true", + "ecn" : "ecn_all", + "green_max_threshold" : "2097152", + "green_min_threshold" : "1048576", + "yellow_max_threshold" : "2097152", + "yellow_min_threshold" : "1048576", + "red_max_threshold" : "2097152", + "red_min_threshold" : "1048576", + "green_drop_probability" : "5", + "yellow_drop_probability": "5", + "red_drop_probability" : "5" + } + }, +{% endif %} + "QUEUE": { +{% for port in PORT_ACTIVE %} + "{{ port }}|3": { + "scheduler" : "[SCHEDULER|scheduler.1]", + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|4": { + "scheduler" : "[SCHEDULER|scheduler.1]", + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|0": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|1": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|2": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|5": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|6": { + "scheduler": "[SCHEDULER|scheduler.0]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + } +} diff --git a/tests/qos_config_input/buffers.json.j2 b/tests/qos_config_input/buffers.json.j2 new file mode 100644 index 0000000000..b67cf577ab --- /dev/null +++ b/tests/qos_config_input/buffers.json.j2 @@ -0,0 +1,3 @@ +{%- set default_topo = 't1' %} +{%- include 'buffers_config.j2' %} + diff --git a/tests/qos_config_input/buffers_config.j2 b/tests/qos_config_input/buffers_config.j2 new file mode 100644 index 0000000000..f5dbb9784f --- /dev/null +++ b/tests/qos_config_input/buffers_config.j2 @@ -0,0 +1,172 @@ +{%- macro set_default_topology() %} +{%- if default_topo is defined %} +{{ default_topo }} +{%- else %} +def +{%- endif %} +{%- endmacro -%} + +{# Determine device topology and filename postfix #} +{%- if DEVICE_METADATA is defined and DEVICE_METADATA['localhost']['type'] is defined %} +{%- set switch_role = DEVICE_METADATA['localhost']['type'] %} +{%- if 'torrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} +{%- set filename_postfix = 't0' %} +{%- elif 'leafrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} +{%- set filename_postfix = 't1' %} +{%- else %} +{%- set filename_postfix = set_default_topology() %} +{%- endif %} +{%- else %} +{%- set filename_postfix = set_default_topology() %} +{%- set switch_role = '' %} +{%- endif -%} + +{# Import default values from device HWSKU folder #} +{%- import 'buffers_defaults_%s.j2' % filename_postfix as defs %} + +{%- set default_cable = defs.default_cable -%} + +{# Port configuration to cable length look-up table #} +{# Each record describes mapping of DUT (DUT port) role and neighbor role to cable length #} +{# Roles described in the minigraph #} +{%- if defs.ports2cable is defined %} + {%- set ports2cable = defs.ports2cable %} +{%- else %} + {%- set ports2cable = { + 'torrouter_server' : '5m', + 'leafrouter_torrouter' : '40m', + 'spinerouter_leafrouter' : '300m' + } + -%} +{%- endif %} + +{%- macro cable_length(port_name) %} + {%- set cable_len = [] %} + {%- for local_port in DEVICE_NEIGHBOR %} + {%- if local_port == port_name %} + {%- if DEVICE_NEIGHBOR_METADATA is defined and DEVICE_NEIGHBOR_METADATA[DEVICE_NEIGHBOR[local_port].name] %} + {%- set neighbor = DEVICE_NEIGHBOR_METADATA[DEVICE_NEIGHBOR[local_port].name] %} + {%- set neighbor_role = neighbor.type %} + {%- if 'asic' == neighbor_role | lower %} + {%- set roles1 = 'internal' %} + {%- if 'internal' not in ports2cable %} + {%- set _ = ports2cable.update({'internal': '5m'}) %} + {%- endif -%} + {%- else %} + {%- set roles1 = switch_role + '_' + neighbor_role %} + {%- set roles2 = neighbor_role + '_' + switch_role %} + {%- set roles1 = roles1 | lower %} + {%- set roles2 = roles2 | lower %} + {%- endif %} + {%- if roles1 in ports2cable %} + {%- if cable_len.append(ports2cable[roles1]) %}{% endif %} + {%- elif roles2 in ports2cable %} + {%- if cable_len.append(ports2cable[roles2]) %}{% endif %} + {%- endif %} + {%- endif %} + {%- endif %} + {%- endfor %} + {%- if cable_len -%} + {{ cable_len.0 }} + {%- else %} + {%- if 'torrouter' in switch_role.lower() and 'mgmt' not in switch_role.lower()%} + {%- for local_port in VLAN_MEMBER %} + {%- if local_port[1] == port_name %} + {%- set roles3 = switch_role + '_' + 'server' %} + {%- set roles3 = roles3 | lower %} + {%- if roles3 in ports2cable %} + {%- if cable_len.append(ports2cable[roles3]) %}{% endif %} + {%- endif %} + {%- endif %} + {%- endfor %} + {%- if cable_len -%} + {{ cable_len.0 }} + {%- else -%} + {{ default_cable }} + {%- endif %} + {%- else -%} + {{ default_cable }} + {%- endif %} + {%- endif %} +{%- endmacro %} + +{%- set PORT_ALL = [] %} + +{%- if PORT is not defined %} + {%- if defs.generate_port_lists(PORT_ALL) %} {% endif %} +{%- else %} + {%- for port in PORT %} + {%- if PORT_ALL.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} + +{%- set PORT_ACTIVE = [] %} +{%- if DEVICE_NEIGHBOR is not defined %} + {%- set PORT_ACTIVE = PORT_ALL %} +{%- else %} + {%- for port in DEVICE_NEIGHBOR.keys() %} + {%- if PORT_ACTIVE.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} + +{%- set port_names_list_active = [] %} +{%- for port in PORT_ACTIVE %} + {%- if port_names_list_active.append(port) %}{%- endif %} +{%- endfor %} +{%- set port_names_active = port_names_list_active | join(',') %} + +{ + "CABLE_LENGTH": { + "AZURE": { + {% for port in PORT_ALL %} + {%- set cable = cable_length(port) %} + "{{ port }}": "{{ cable }}"{%- if not loop.last %},{% endif %} + + {% endfor %} + } + }, + +{% if defs.generate_buffer_pool_and_profiles is defined %} +{{ defs.generate_buffer_pool_and_profiles() }} +{% endif %} + +{%- if defs.generate_profile_lists is defined %} +{{ defs.generate_profile_lists(port_names_active) }}, +{% endif %} + +{%- if defs.generate_pg_profils is defined %} +{{ defs.generate_pg_profils(port_names_active) }} +{% else %} + "BUFFER_PG": { +{% for port in PORT_ACTIVE %} + "{{ port }}|0": { + "profile" : "[BUFFER_PROFILE|ingress_lossy_profile]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + }, +{% endif %} + +{% if defs.generate_queue_buffers is defined %} +{{ defs.generate_queue_buffers(port_names_active) }} +{% else %} + "BUFFER_QUEUE": { +{% for port in PORT_ACTIVE %} + "{{ port }}|3-4": { + "profile" : "[BUFFER_PROFILE|egress_lossless_profile]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|0-2": { + "profile" : "[BUFFER_PROFILE|egress_lossy_profile]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|5-6": { + "profile" : "[BUFFER_PROFILE|egress_lossy_profile]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + } +{% endif %} +} diff --git a/tests/qos_config_input/buffers_defaults_t1.j2 b/tests/qos_config_input/buffers_defaults_t1.j2 new file mode 100644 index 0000000000..38e34eb571 --- /dev/null +++ b/tests/qos_config_input/buffers_defaults_t1.j2 @@ -0,0 +1,45 @@ +{%- set default_cable = '300m' %} + +{%- macro generate_port_lists(PORT_ALL) %} + {# Generate list of ports #} + {% for port_idx in range(0,32) %} + {% if PORT_ALL.append("Ethernet%d" % (port_idx * 4)) %}{% endif %} + {% endfor %} +{%- endmacro %} + +{%- macro generate_buffer_pool_and_profiles() %} + "BUFFER_POOL": { + "ingress_lossless_pool": { + "size": "12766208", + "type": "ingress", + "mode": "dynamic" + }, + "egress_lossless_pool": { + "size": "12766208", + "type": "egress", + "mode": "static" + }, + "egress_lossy_pool": { + "size": "7326924", + "type": "egress", + "mode": "dynamic" + } + }, + "BUFFER_PROFILE": { + "ingress_lossy_profile": { + "pool":"[BUFFER_POOL|ingress_lossless_pool]", + "size":"0", + "dynamic_th":"3" + }, + "egress_lossless_profile": { + "pool":"[BUFFER_POOL|egress_lossless_pool]", + "size":"0", + "static_th":"12766208" + }, + "egress_lossy_profile": { + "pool":"[BUFFER_POOL|egress_lossy_pool]", + "size":"1518", + "dynamic_th":"3" + } + }, +{%- endmacro %} diff --git a/tests/qos_config_input/config_qos.json b/tests/qos_config_input/config_qos.json new file mode 100644 index 0000000000..40c1903a06 --- /dev/null +++ b/tests/qos_config_input/config_qos.json @@ -0,0 +1,137 @@ +{ + "TC_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "0": "0", + "1": "0", + "2": "0", + "3": "3", + "4": "4", + "5": "0", + "6": "0", + "7": "7" + } + }, + "MAP_PFC_PRIORITY_TO_QUEUE": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "DSCP_TO_TC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type" : "DWRR", + "weight": "14" + }, + "scheduler.1": { + "type" : "DWRR", + "weight": "15" + } + }, + "PORT_QOS_MAP": { + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS" : { + "wred_green_enable" : "true", + "wred_yellow_enable" : "true", + "wred_red_enable" : "true", + "ecn" : "ecn_all", + "green_max_threshold" : "2097152", + "green_min_threshold" : "1048576", + "yellow_max_threshold" : "2097152", + "yellow_min_threshold" : "1048576", + "red_max_threshold" : "2097152", + "red_min_threshold" : "1048576", + "green_drop_probability" : "5", + "yellow_drop_probability": "5", + "red_drop_probability" : "5" + } + }, + "QUEUE": { + } +} diff --git a/tests/qos_config_input/qos.json.j2 b/tests/qos_config_input/qos.json.j2 new file mode 100644 index 0000000000..3e548325ea --- /dev/null +++ b/tests/qos_config_input/qos.json.j2 @@ -0,0 +1 @@ +{%- include 'qos_config.j2' %} diff --git a/tests/qos_config_input/qos_config.j2 b/tests/qos_config_input/qos_config.j2 new file mode 100644 index 0000000000..a7c361d69f --- /dev/null +++ b/tests/qos_config_input/qos_config.j2 @@ -0,0 +1,257 @@ +{%- set PORT_ALL = [] %} +{%- for port in PORT %} + {%- if PORT_ALL.append(port) %}{% endif %} +{%- endfor %} +{%- if PORT_ALL | sort_by_port_index %}{% endif %} + +{%- set port_names_list_all = [] %} +{%- for port in PORT_ALL %} + {%- if port_names_list_all.append(port) %}{% endif %} +{%- endfor %} +{%- set port_names_all = port_names_list_all | join(',') -%} + + +{%- set PORT_ACTIVE = [] %} +{%- if DEVICE_NEIGHBOR is not defined %} + {%- set PORT_ACTIVE = PORT_ALL %} +{%- else %} + {%- for port in DEVICE_NEIGHBOR.keys() %} + {%- if PORT_ACTIVE.append(port) %}{%- endif %} + {%- endfor %} +{%- endif %} +{%- if PORT_ACTIVE | sort_by_port_index %}{% endif %} + +{%- set port_names_list_active = [] %} +{%- for port in PORT_ACTIVE %} + {%- if port_names_list_active.append(port) %}{%- endif %} +{%- endfor %} +{%- set port_names_active = port_names_list_active | join(',') -%} + + +{%- set pfc_to_pg_map_supported_asics = ['mellanox', 'barefoot', 'marvell'] -%} +{%- set backend_device_types = ['BackEndToRRouter', 'BackEndLeafRouter'] -%} + + +{ +{% if generate_tc_to_pg_map is defined %} + {{- generate_tc_to_pg_map() }} +{% else %} + "TC_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "0": "0", + "1": "0", + "2": "0", + "3": "3", + "4": "4", + "5": "0", + "6": "0", + "7": "7" + } + }, +{% endif %} + "MAP_PFC_PRIORITY_TO_QUEUE": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, +{% if 'type' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['type'] in backend_device_types and 'storage_device' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['storage_device'] == 'true' %} + "DOT1P_TO_TC_MAP": { + "AZURE": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7" + } + }, +{% else %} + "DSCP_TO_TC_MAP": { + "AZURE": { + "0" : "1", + "1" : "1", + "2" : "1", + "3" : "3", + "4" : "4", + "5" : "2", + "6" : "1", + "7" : "1", + "8" : "0", + "9" : "1", + "10": "1", + "11": "1", + "12": "1", + "13": "1", + "14": "1", + "15": "1", + "16": "1", + "17": "1", + "18": "1", + "19": "1", + "20": "1", + "21": "1", + "22": "1", + "23": "1", + "24": "1", + "25": "1", + "26": "1", + "27": "1", + "28": "1", + "29": "1", + "30": "1", + "31": "1", + "32": "1", + "33": "1", + "34": "1", + "35": "1", + "36": "1", + "37": "1", + "38": "1", + "39": "1", + "40": "1", + "41": "1", + "42": "1", + "43": "1", + "44": "1", + "45": "1", + "46": "5", + "47": "1", + "48": "6", + "49": "1", + "50": "1", + "51": "1", + "52": "1", + "53": "1", + "54": "1", + "55": "1", + "56": "1", + "57": "1", + "58": "1", + "59": "1", + "60": "1", + "61": "1", + "62": "1", + "63": "1" + } + }, +{% endif %} + "SCHEDULER": { + "scheduler.0": { + "type" : "DWRR", + "weight": "14" + }, + "scheduler.1": { + "type" : "DWRR", + "weight": "15" + } + }, +{% if asic_type in pfc_to_pg_map_supported_asics %} + "PFC_PRIORITY_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "3": "3", + "4": "4" + } + }, +{% endif %} + "PORT_QOS_MAP": { +{% for port in PORT_ACTIVE %} + "{{ port }}": { +{% if 'type' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['type'] in backend_device_types and 'storage_device' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['storage_device'] == 'true' %} + "dot1p_to_tc_map" : "[DOT1P_TO_TC_MAP|AZURE]", +{% else %} + "dscp_to_tc_map" : "[DSCP_TO_TC_MAP|AZURE]", +{% endif %} + "tc_to_queue_map" : "[TC_TO_QUEUE_MAP|AZURE]", + "tc_to_pg_map" : "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", + "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", +{% if asic_type in pfc_to_pg_map_supported_asics %} + "pfc_to_pg_map" : "[PFC_PRIORITY_TO_PRIORITY_GROUP_MAP|AZURE]", +{% endif %} + "pfc_enable" : "3,4" + }{% if not loop.last %},{% endif %} + +{% endfor %} + }, +{% if generate_wred_profiles is defined %} + {{- generate_wred_profiles() }} +{% else %} + "WRED_PROFILE": { + "AZURE_LOSSLESS" : { + "wred_green_enable" : "true", + "wred_yellow_enable" : "true", + "wred_red_enable" : "true", + "ecn" : "ecn_all", + "green_max_threshold" : "2097152", + "green_min_threshold" : "1048576", + "yellow_max_threshold" : "2097152", + "yellow_min_threshold" : "1048576", + "red_max_threshold" : "2097152", + "red_min_threshold" : "1048576", + "green_drop_probability" : "5", + "yellow_drop_probability": "5", + "red_drop_probability" : "5" + } + }, +{% endif %} + "QUEUE": { +{% for port in PORT_ACTIVE %} + "{{ port }}|3": { + "scheduler" : "[SCHEDULER|scheduler.1]", + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|4": { + "scheduler" : "[SCHEDULER|scheduler.1]", + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|0": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|1": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|2": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|5": { + "scheduler": "[SCHEDULER|scheduler.0]" + }, +{% endfor %} +{% for port in PORT_ACTIVE %} + "{{ port }}|6": { + "scheduler": "[SCHEDULER|scheduler.0]" + }{% if not loop.last %},{% endif %} + +{% endfor %} + } +} diff --git a/tests/qos_config_input/sonic_version.yml b/tests/qos_config_input/sonic_version.yml new file mode 100644 index 0000000000..4791f627e7 --- /dev/null +++ b/tests/qos_config_input/sonic_version.yml @@ -0,0 +1,8 @@ +build_version: 'master.487-a98cf221' +debian_version: '10.6' +kernel_version: '4.19.0-9-2-amd64' +asic_type: broadcom +commit_id: 'a98cf221' +build_date: Thu Nov 12 12:21:45 UTC 2020 +build_number: 487 +built_by: johnar@jenkins-worker-8 From 69e2cc33049a5d6f2095ee86c8f1a38fdd0857f7 Mon Sep 17 00:00:00 2001 From: Renuka Manavalan <47282725+renukamanavalan@users.noreply.github.com> Date: Fri, 15 Jan 2021 11:39:49 -0800 Subject: [PATCH 06/12] Drop explict 3 seconds pause between two object updates/deletes. (#1359) - What I did Drop explicitly introduced 3 seconds pause between object updates using configlet. - How I did it Remove the added time.sleep(3) - How to verify it Add 10 objects using old code and see it takes 30+ seconds. Repeat the same with this updated code which would complete in small fraction of a second. Verified ASIC-DB contents to the extent possible in both scenarios of applying objects using old code with 3 seconds pause and new code w/o any explicit pause, to be the same. --- scripts/configlet | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/configlet b/scripts/configlet index 44b0047a49..4d519d3943 100755 --- a/scripts/configlet +++ b/scripts/configlet @@ -77,7 +77,6 @@ A sample for update: import argparse import json -import time from swsssdk import ConfigDBConnector @@ -204,11 +203,6 @@ def main(): if parse_only == False: for i in data: process_entry (do_update, i) - # Artificial sleep to give a pause between two entries - # so as to ensure that all internal daemons have digested the - # previous update, before the next one arrives. - # - time.sleep(3) else: print("Parsed:") print(data) From d1e260a6d3e80c0c5df125cbadf4b51037eae7d0 Mon Sep 17 00:00:00 2001 From: vdahiya12 <67608553+vdahiya12@users.noreply.github.com> Date: Fri, 15 Jan 2021 14:45:39 -0800 Subject: [PATCH 07/12] [show]fix for show muxcable status by replacing "hostname" to "peer_switch" for deriving tor ipv4_address (#1360) This PR provides the support for fixing the show muxcable config. There was a change in the Config_DB schema where there was an addition of key-value pair "peer_switch:tor_name" in DEVICE_METADATA|localhost table which actually gives the other TOR's name (hostname). This TOR name is then utilized in PEER_SWITCH|switchname table which has "address_ipv4: IPv4 address" key-value pair, which correctly gives the ip address for displaying on the show mux config What is the motivation for this PR? To add the fix the working for show muxcable config Signed-off-by: vaibhav-dahiya --- show/muxcable.py | 2 +- tests/mock_tables/config_db.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/show/muxcable.py b/show/muxcable.py index 217ab23635..3b65e4b638 100644 --- a/show/muxcable.py +++ b/show/muxcable.py @@ -63,7 +63,7 @@ def get_switch_name(config_db): info_dict = config_db.get_entry("DEVICE_METADATA", "localhost") #click.echo("{} ".format(info_dict)) - switch_name = get_value_for_key_in_dict(info_dict, "localhost", "hostname", "DEVICE_METADATA") + switch_name = get_value_for_key_in_dict(info_dict, "localhost", "peer_switch", "DEVICE_METADATA") if switch_name is not None: return switch_name else: diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 21676feb57..a8da97f3eb 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -666,6 +666,7 @@ "hwsku": "Mellanox-SN3800-D112C8", "mac": "1d:34:db:16:a6:00", "platform": "x86_64-mlnx_msn3800-r0", + "peer_switch": "sonic-switch", "type": "ToRRouter" }, "DEVICE_NEIGHBOR|Ethernet4": { From 2cbeccbc520b2e767ba8dab9a30657ebc3806002 Mon Sep 17 00:00:00 2001 From: shlomibitton <60430976+shlomibitton@users.noreply.github.com> Date: Sun, 17 Jan 2021 13:52:42 +0200 Subject: [PATCH 08/12] [PFCWD] Fix 'start' pfcwd command (#1345) - What I did The 'config' method is getting arguments from the user CLI and build a proper command for the pfcwd script in a wrong way, causing this issue. - How I did it Fix the command structure to align with pfcwd.main script. - How to verify it Run "config pfcwd start --action drop ports all detection-time 400 --restoration-time 400" - Previous command output (if the output of a command-line utility has changed) config pfcwd start --action drop ports all detection-time 400 --restoration-time 400 Failed to run command, invalid options: ports detection-time --- config/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/main.py b/config/main.py index a4d3e2b107..ea798a1a2e 100644 --- a/config/main.py +++ b/config/main.py @@ -1685,10 +1685,10 @@ def start(action, restoration_time, ports, detection_time, verbose): if ports: ports = set(ports) - set(['ports', 'detection-time']) - cmd += " ports {}".format(' '.join(ports)) + cmd += " {}".format(' '.join(ports)) if detection_time: - cmd += " detection-time {}".format(detection_time) + cmd += " {}".format(detection_time) if restoration_time: cmd += " --restoration-time {}".format(restoration_time) From bc2d27edc2923f5096b37a727200a8427f93463e Mon Sep 17 00:00:00 2001 From: Guohan Lu Date: Mon, 18 Jan 2021 18:39:18 -0800 Subject: [PATCH 09/12] [generate_dump]: fix syntax error syntax error introduced in commit 22d79f387728f40e9994d8dcd3397f42dda8a578 Signed-off-by: Guohan Lu --- scripts/generate_dump | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/generate_dump b/scripts/generate_dump index 4cb192647c..92d95dd246 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -82,7 +82,7 @@ save_bcmcmd() { fi fi fi - if $do_gzip + if $do_gzip; then gzip ${filepath} 2>/dev/null tarpath="${tarpath}.gz" filepath="${filepath}.gz" @@ -302,7 +302,7 @@ save_bridge() { } ############################################################################### -# Dump the bridge L2 information +# Dump the bridge L2 information # Globals: # None # Arguments: @@ -509,8 +509,8 @@ save_proc() { ############################################################################### # Dumps all fields and values from given Redis DB. # Arguments: -# DB name: DB name -# Filename: Destination filename, if not given then filename would be DB name +# DB name: DB name +# Filename: Destination filename, if not given then filename would be DB name # Returns: # None ############################################################################### From 9bd709b6564e3df1b04f0279c502efdc7524ca5a Mon Sep 17 00:00:00 2001 From: maksymbelei95 <75987222+maksymbelei95@users.noreply.github.com> Date: Tue, 19 Jan 2021 07:55:39 +0200 Subject: [PATCH 10/12] [show] Fix show arp in case with FDB entries, linked to default VLAN (#1357) * Adding condition to check result of getting of Vlan id, using bvid. If the vlan id is None, then skip the record to avoid exception raising on int(NoneType) Signed-off-by: Maksym Belei --- scripts/nbrshow | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/nbrshow b/scripts/nbrshow index 694e64242d..b59acf0287 100644 --- a/scripts/nbrshow +++ b/scripts/nbrshow @@ -95,6 +95,10 @@ class NbrBase(object): elif 'bvid' in fdb: try: vlan_id = port_util.get_vlan_id_from_bvid(self.db, fdb["bvid"]) + if vlan_id is None: + # the case could be happened if the FDB entry has created with linking to + # default VLAN 1, which is not present in the system + continue except Exception: vlan_id = fdb["bvid"] print("Failed to get Vlan id for bvid {}\n".format(fdb["bvid"])) From 3df267e252bfd669c52f3221b0a29412a129153d Mon Sep 17 00:00:00 2001 From: Sangita Maity Date: Tue, 19 Jan 2021 16:40:52 -0800 Subject: [PATCH 11/12] [config] Fix Breakout mode option and BREAKOUT_CFG table check method (#1270) Made a robust check for below cases: - when the interface is wrong and the mode is correct. - BREAKOUT_CFG table is NOT present in CONFIG DB Also, Fixed Breakout mode option when options are not available for easy readability. Signed-off-by: Sangita Maity --- config/main.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/config/main.py b/config/main.py index ea798a1a2e..5816f02f96 100644 --- a/config/main.py +++ b/config/main.py @@ -116,7 +116,7 @@ def _get_breakout_options(ctx, args, incomplete): for i in breakout_mode_list.split(','): breakout_mode_options.append(i) all_mode_options = [str(c) for c in breakout_mode_options if incomplete in c] - return all_mode_options + return all_mode_options def shutdown_interfaces(ctx, del_intf_dict): """ shut down all the interfaces before deletion """ @@ -2422,6 +2422,14 @@ def breakout(ctx, interface_name, mode, verbose, force_remove_dependencies, load # Get current breakout mode cur_brkout_dict = config_db.get_table('BREAKOUT_CFG') + if len(cur_brkout_dict) == 0: + click.secho("[ERROR] BREAKOUT_CFG table is NOT present in CONFIG DB", fg='red') + raise click.Abort() + + if interface_name not in cur_brkout_dict.keys(): + click.secho("[ERROR] {} interface is NOT present in BREAKOUT_CFG table of CONFIG DB".format(interface_name), fg='red') + raise click.Abort() + cur_brkout_mode = cur_brkout_dict[interface_name]["brkout_mode"] # Validate Interface and Breakout mode From 8119ba25b7453d685a475694142e293c2a3a5341 Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni <47657796+AkhileshSamineni@users.noreply.github.com> Date: Wed, 20 Jan 2021 10:28:14 +0530 Subject: [PATCH 12/12] Validations checks while creating and deleting a Portchannel (#1326) Added below validation checks when a portchannel is created by the user: 1 if a given portchannel name is valid or not 2 if a given portchannel name is already created by user (exists in CONFIG_DB) Added below validation checks when a portchannel is attempted for deletion by the user: 1 if a given portchannel name is valid or not 2 if a given portchannel name exists in exists in CONFIG_DB or not (throw an error if it does not exist) Signed-off-by: Akhilesh Samineni --- config/main.py | 17 ++++++++++++++ tests/portchannel_test.py | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/config/main.py b/config/main.py index 5816f02f96..57d15a75c7 100644 --- a/config/main.py +++ b/config/main.py @@ -1356,7 +1356,15 @@ 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): + ctx.fail("{} already exists!".format(portchannel_name)) + fvs = {'admin_status': 'up', 'mtu': '9100'} if min_links != 0: @@ -1370,7 +1378,16 @@ def add_portchannel(ctx, portchannel_name, min_links, fallback): @click.pass_context def remove_portchannel(ctx, portchannel_name): """Remove 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'] + + # Dont proceed if the port channel does not exist + if is_portchannel_present_in_db(db, portchannel_name) is False: + ctx.fail("{} is not present.".format(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: diff --git a/tests/portchannel_test.py b/tests/portchannel_test.py index a07e205392..1ccf19495f 100644 --- a/tests/portchannel_test.py +++ b/tests/portchannel_test.py @@ -13,6 +13,54 @@ def setup_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "1" print("SETUP") + def test_add_portchannel_with_invalid_name(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel with invalid name + result = runner.invoke(config.config.commands["portchannel"].commands["add"], ["PortChan005"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output + + def test_delete_portchannel_with_invalid_name(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # delete a portchannel with invalid name + result = runner.invoke(config.config.commands["portchannel"].commands["del"], ["PortChan005"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output + + def test_add_existing_portchannel_again(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel which is already created + result = runner.invoke(config.config.commands["portchannel"].commands["add"], ["PortChannel0001"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel0001 already exists!" in result.output + + def test_delete_non_existing_portchannel(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # delete a portchannel which is not present + result = runner.invoke(config.config.commands["portchannel"].commands["del"], ["PortChannel0005"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel0005 is not present." in result.output + def test_add_portchannel_member_with_invalid_name(self): runner = CliRunner() db = Db()