diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 086dfbd517..0000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.bandit b/.bandit new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml new file mode 100644 index 0000000000..954292cc27 --- /dev/null +++ b/.github/workflows/bandit.yml @@ -0,0 +1,20 @@ +# This workflow is to do the bandit check +# + +name: bandit +on: + pull_request: + types: + - opened + - reopened + - synchronize + +jobs: + bendit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: bandit + uses: jpetrucciani/bandit-check@master + with: + path: '.' diff --git a/acl_loader/main.py b/acl_loader/main.py index ada7162154..dedaf0eb3e 100644 --- a/acl_loader/main.py +++ b/acl_loader/main.py @@ -4,6 +4,7 @@ import ipaddress import json import syslog +import operator import openconfig_acl import tabulate @@ -758,7 +759,7 @@ def incremental_update(self): namespace_configdb.mod_entry(self.ACL_RULE, key, None) for key in existing_controlplane_rules: - if cmp(self.rules_info[key], self.rules_db_info[key]) != 0: + if not operator.eq(self.rules_info[key], self.rules_db_info[key]): self.configdb.set_entry(self.ACL_RULE, key, self.rules_info[key]) # Program for per-asic namespace corresponding to front asic also if present. # For control plane ACL it's not needed but to keep all db in sync program everywhere diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6cbc9d4316..85f9171214 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,8 +10,8 @@ resources: repositories: - repository: sonic-swss type: github - name: Azure/sonic-swss - endpoint: build + name: sonic-net/sonic-swss + endpoint: sonic-net stages: - stage: Build diff --git a/config/main.py b/config/main.py index 56aeddc673..b6290e6a45 100644 --- a/config/main.py +++ b/config/main.py @@ -12,6 +12,7 @@ import sys import time import itertools +import copy from collections import OrderedDict from generic_config_updater.generic_updater import GenericUpdater, ConfigFormat @@ -22,6 +23,7 @@ from sonic_py_common import device_info, multi_asic from sonic_py_common.interface import get_interface_table_name, get_port_table_name, get_intf_longname from utilities_common import util_base +from swsscommon import swsscommon from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector from utilities_common.db import Db from utilities_common.intf_filter import parse_interface_in_filter @@ -45,7 +47,7 @@ from . import vlan from . import vxlan from . import plugins -from .config_mgmt import ConfigMgmtDPB +from .config_mgmt import ConfigMgmtDPB, ConfigMgmt from . import mclag from . import syslog @@ -368,6 +370,19 @@ def get_interface_ipaddresses(config_db, interface_name): return ipaddresses +def is_vrf_exists(config_db, vrf_name): + """Check if VRF exists + """ + keys = config_db.get_keys("VRF") + if vrf_name in keys: + return True + elif vrf_name == "mgmt": + entry = config_db.get_entry("MGMT_VRF_CONFIG", "vrf_global") + if entry and entry.get("mgmtVrfEnabled") == "true": + return True + + return False + def is_interface_bind_to_vrf(config_db, interface_name): """Get interface if bind to vrf or not """ @@ -985,6 +1000,7 @@ def cli_sroute_to_config(ctx, command_str, strict_nh = True): nexthop_str = None config_entry = {} vrf_name = "" + config_db = ctx.obj['config_db'] if "nexthop" in command_str: idx = command_str.index("nexthop") @@ -997,10 +1013,13 @@ def cli_sroute_to_config(ctx, command_str, strict_nh = True): if 'prefix' in prefix_str and 'vrf' in prefix_str: # prefix_str: ['prefix', 'vrf', Vrf-name, ip] vrf_name = prefix_str[2] + if not is_vrf_exists(config_db, vrf_name): + ctx.fail("VRF %s does not exist!"%(vrf_name)) ip_prefix = prefix_str[3] elif 'prefix' in prefix_str: # prefix_str: ['prefix', ip] ip_prefix = prefix_str[1] + vrf_name = "default" else: ctx.fail("prefix is not in pattern!") @@ -1008,6 +1027,8 @@ def cli_sroute_to_config(ctx, command_str, strict_nh = True): if 'nexthop' in nexthop_str and 'vrf' in nexthop_str: # nexthop_str: ['nexthop', 'vrf', Vrf-name, ip] config_entry["nexthop"] = nexthop_str[3] + if not is_vrf_exists(config_db, nexthop_str[2]): + ctx.fail("VRF %s does not exist!"%(nexthop_str[2])) config_entry["nexthop-vrf"] = nexthop_str[2] elif 'nexthop' in nexthop_str and 'dev' in nexthop_str: # nexthop_str: ['nexthop', 'dev', ifname] @@ -1361,6 +1382,20 @@ def apply_patch(ctx, patch_file_path, format, dry_run, ignore_non_yang_tables, i patch_as_json = json.loads(text) patch = jsonpatch.JsonPatch(patch_as_json) + # convert IPv6 addresses to lowercase + for patch_line in patch: + if 'remove' == patch_line['op']: + match = re.search(r"(?P/INTERFACE/\w+\|)(?P([a-fA-F0-9]{0,4}[:~]|::){1,7}[a-fA-F0-9]{0,4})" + "(?P.*)", str.format(patch_line['path'])) + if match: + prefix = match.group('prefix') + ipv6_address_str = match.group('ipv6_address') + suffix = match.group('suffix') + ipv6_address_str = ipv6_address_str.lower() + click.secho("converted ipv6 address to lowercase {} with prefix {} in value: {}" + .format(ipv6_address_str, prefix, patch_line['path'])) + patch_line['path'] = prefix + ipv6_address_str + suffix + config_format = ConfigFormat[format.upper()] GenericUpdater().apply_patch(patch, config_format, verbose, dry_run, ignore_non_yang_tables, ignore_path) @@ -1517,11 +1552,6 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, disable_arp_cach if multi_asic.is_multi_asic() and file_format == 'config_db': num_cfg_file += num_asic - # Remove cached PG drop counters data - dropstat_dir_prefix = '/tmp/dropstat' - command = "rm -rf {}-*".format(dropstat_dir_prefix) - clicommon.run_command(command, display_cmd=True) - # If the user give the filename[s], extract the file names. if filename is not None: cfg_files = filename.split(',') @@ -1857,27 +1887,66 @@ def override_config_table(db, input_config_db, dry_run): config_db = db.cfgdb + # Read config from configDB + current_config = config_db.get_config() + # Serialize to the same format as json input + sonic_cfggen.FormatConverter.to_serialized(current_config) + + updated_config = update_config(current_config, config_input) + + yang_enabled = device_info.is_yang_config_validation_enabled(config_db) + if yang_enabled: + # The ConfigMgmt will load YANG and running + # config during initialization. + try: + cm = ConfigMgmt() + cm.validateConfigData() + except Exception as ex: + click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta") + sys.exit(1) + + # Validate input config + validate_config_by_cm(cm, config_input, "config_input") + # Validate updated whole config + validate_config_by_cm(cm, updated_config, "updated_config") + if dry_run: - # Read config from configDB - current_config = config_db.get_config() - # Serialize to the same format as json input - sonic_cfggen.FormatConverter.to_serialized(current_config) - # Override current config with golden config - for table in config_input: - current_config[table] = config_input[table] - print(json.dumps(current_config, sort_keys=True, + print(json.dumps(updated_config, sort_keys=True, indent=4, cls=minigraph_encoder)) else: - # Deserialized golden config to DB recognized format - sonic_cfggen.FormatConverter.to_deserialized(config_input) - # Delete table from DB then mod_config to apply golden config - click.echo("Removing configDB overriden table first ...") - for table in config_input: - config_db.delete_table(table) - click.echo("Overriding input config to configDB ...") - data = sonic_cfggen.FormatConverter.output_to_db(config_input) - config_db.mod_config(data) - click.echo("Overriding completed. No service is restarted.") + override_config_db(config_db, config_input) + + +def validate_config_by_cm(cm, config_json, jname): + tmp_config_json = copy.deepcopy(config_json) + try: + cm.loadData(tmp_config_json) + cm.validateConfigData() + except Exception as ex: + click.secho("Failed to validate {}. Error: {}".format(jname, ex), fg="magenta") + sys.exit(1) + + +def update_config(current_config, config_input): + updated_config = copy.deepcopy(current_config) + # Override current config with golden config + for table in config_input: + updated_config[table] = config_input[table] + return updated_config + + +def override_config_db(config_db, config_input): + # Deserialized golden config to DB recognized format + sonic_cfggen.FormatConverter.to_deserialized(config_input) + # Delete table from DB then mod_config to apply golden config + click.echo("Removing configDB overriden table first ...") + for table in config_input: + config_db.delete_table(table) + click.echo("Overriding input config to configDB ...") + data = sonic_cfggen.FormatConverter.output_to_db(config_input) + config_db.mod_config(data) + click.echo("Overriding completed. No service is restarted.") + # # 'hostname' command @@ -1889,19 +1958,11 @@ def hostname(new_hostname): config_db = ConfigDBConnector() config_db.connect() - config_db.mod_entry('DEVICE_METADATA' , 'localhost', {"hostname" : new_hostname}) - try: - command = "service hostname-config restart" - clicommon.run_command(command, display_cmd=True) - except SystemExit as e: - click.echo("Restarting hostname-config service failed with error {}".format(e)) - raise - - # Reload Monit configuration to pick up new hostname in case it changed - click.echo("Reloading Monit configuration ...") - clicommon.run_command("sudo monit reload") + config_db.mod_entry(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, 'localhost', + {'hostname': new_hostname}) - click.echo("Please note loaded setting will be lost after system reboot. To preserve setting, run `config save`.") + click.echo('Please note loaded setting will be lost after system reboot. To' + ' preserve setting, run `config save`.') # # 'synchronous_mode' command ('config synchronous_mode ...') @@ -1928,6 +1989,21 @@ def synchronous_mode(sync_mode): else: raise click.BadParameter("Error: Invalid argument %s, expect either enable or disable" % sync_mode) +# +# 'yang_config_validation' command ('config yang_config_validation ...') +# +@config.command('yang_config_validation') +@click.argument('yang_config_validation', metavar='', required=True) +def yang_config_validation(yang_config_validation): + # Enable or disable YANG validation on updates to ConfigDB + if yang_config_validation == 'enable' or yang_config_validation == 'disable': + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry('DEVICE_METADATA', 'localhost', {"yang_config_validation": yang_config_validation}) + click.echo("""Wrote %s yang config validation into CONFIG_DB""" % yang_config_validation) + else: + raise click.BadParameter("Error: Invalid argument %s, expect either enable or disable" % yang_config_validation) + # # 'portchannel' group ('config portchannel ...') # @@ -1950,8 +2026,11 @@ def portchannel(db, ctx, namespace): @click.argument('portchannel_name', metavar='', required=True) @click.option('--min-links', default=1, type=click.IntRange(1,1024)) @click.option('--fallback', default='false') +@click.option('--fast-rate', default='false', + type=click.Choice(['true', 'false'], + case_sensitive=False)) @click.pass_context -def add_portchannel(ctx, portchannel_name, min_links, fallback): +def add_portchannel(ctx, portchannel_name, min_links, fallback, fast_rate): """Add port channel""" if is_portchannel_name_valid(portchannel_name) != True: ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" @@ -1962,9 +2041,12 @@ def add_portchannel(ctx, portchannel_name, min_links, fallback): if is_portchannel_present_in_db(db, portchannel_name): ctx.fail("{} already exists!".format(portchannel_name)) - fvs = {'admin_status': 'up', - 'mtu': '9100', - 'lacp_key': 'auto'} + fvs = { + 'admin_status': 'up', + 'mtu': '9100', + 'lacp_key': 'auto', + 'fast_rate': fast_rate.lower(), + } if min_links != 0: fvs['min_links'] = str(min_links) if fallback != 'false': @@ -2032,6 +2114,14 @@ def add_portchannel_member(ctx, portchannel_name, port_name): ctx.fail(" {} has ip address configured".format(port_name)) return + for key in db.get_keys('VLAN_SUB_INTERFACE'): + if type(key) == tuple: + continue + intf = key.split(VLAN_SUB_INTERFACE_SEPARATOR)[0] + parent_intf = get_intf_longname(intf) + if parent_intf == port_name: + ctx.fail(" {} has subinterfaces configured".format(port_name)) + # 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: @@ -2827,22 +2917,6 @@ def warm_restart_bgp_eoiu(ctx, enable): db = ctx.obj['db'] db.mod_entry('WARM_RESTART', 'bgp', {'bgp_eoiu': enable}) -def mvrf_restart_services(): - """Restart interfaces-config service and NTP service when mvrf is changed""" - """ - When mvrf is enabled, eth0 should be moved to mvrf; when it is disabled, - move it back to default vrf. Restarting the "interfaces-config" service - will recreate the /etc/network/interfaces file and restart the - "networking" service that takes care of the eth0 movement. - NTP service should also be restarted to rerun the NTP service with or - without "cgexec" accordingly. - """ - cmd="service ntp stop" - os.system (cmd) - cmd="systemctl restart interfaces-config" - os.system (cmd) - cmd="service ntp start" - os.system (cmd) def vrf_add_management_vrf(config_db): """Enable management vrf in config DB""" @@ -2852,22 +2926,7 @@ def vrf_add_management_vrf(config_db): click.echo("ManagementVRF is already Enabled.") return None config_db.mod_entry('MGMT_VRF_CONFIG', "vrf_global", {"mgmtVrfEnabled": "true"}) - mvrf_restart_services() - """ - The regular expression for grep in below cmd is to match eth0 line in /proc/net/route, sample file: - $ cat /proc/net/route - Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT - eth0 00000000 01803B0A 0003 0 0 202 00000000 0 0 0 - """ - cmd = r"cat /proc/net/route | grep -E \"eth0\s+00000000\s+[0-9A-Z]+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+202\" | wc -l" - proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) - output = proc.communicate() - if int(output[0]) >= 1: - cmd="ip -4 route del default dev eth0 metric 202" - proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) - proc.communicate() - if proc.returncode != 0: - click.echo("Could not delete eth0 route") + def vrf_delete_management_vrf(config_db): """Disable management vrf in config DB""" @@ -2877,7 +2936,7 @@ def vrf_delete_management_vrf(config_db): click.echo("ManagementVRF is already Disabled.") return None config_db.mod_entry('MGMT_VRF_CONFIG', "vrf_global", {"mgmtVrfEnabled": "false"}) - mvrf_restart_services() + @config.group(cls=clicommon.AbbreviationGroup) @click.pass_context @@ -4113,20 +4172,6 @@ def _get_all_mgmtinterface_keys(): config_db.connect() return list(config_db.get_table('MGMT_INTERFACE').keys()) -def mgmt_ip_restart_services(): - """Restart the required services when mgmt inteface IP address is changed""" - """ - Whenever the eth0 IP address is changed, restart the "interfaces-config" - service which regenerates the /etc/network/interfaces file and restarts - the networking service to make the new/null IP address effective for eth0. - "ntp-config" service should also be restarted based on the new - eth0 IP address since the ntp.conf (generated from ntp.conf.j2) is - made to listen on that particular eth0 IP address or reset it back. - """ - cmd="systemctl restart interfaces-config" - os.system (cmd) - cmd="systemctl restart ntp-config" - os.system (cmd) # # 'mtu' subcommand @@ -4197,8 +4242,6 @@ def fec(ctx, interface_name, interface_fec, verbose): # Get the config_db connector config_db = ctx.obj['config_db'] - if interface_fec not in ["rs", "fc", "none"]: - ctx.fail("'fec not in ['rs', 'fc', 'none']!") if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(config_db, interface_name) if interface_name is None: @@ -4274,7 +4317,6 @@ def add(ctx, interface_name, ip_addr, gw): config_db.set_entry("MGMT_INTERFACE", (interface_name, str(ip_address)), {"NULL": "NULL"}) else: config_db.set_entry("MGMT_INTERFACE", (interface_name, str(ip_address)), {"gwaddr": gw}) - mgmt_ip_restart_services() return @@ -4314,7 +4356,6 @@ def remove(ctx, interface_name, ip_addr): if interface_name == 'eth0': config_db.set_entry("MGMT_INTERFACE", (interface_name, str(ip_address)), None) - mgmt_ip_restart_services() return table_name = get_interface_table_name(interface_name) @@ -4909,6 +4950,9 @@ def bind(ctx, interface_name, vrf_name): if interface_name is None: ctx.fail("'interface_name' is None!") + if not is_vrf_exists(config_db, vrf_name): + ctx.fail("VRF %s does not exist!"%(vrf_name)) + table_name = get_interface_table_name(interface_name) if table_name == "": ctx.fail("'interface_name' is not valid. Valid names [Ethernet/PortChannel/Vlan/Loopback]") @@ -4919,6 +4963,11 @@ def bind(ctx, interface_name, vrf_name): interface_addresses = get_interface_ipaddresses(config_db, interface_name) for ipaddress in interface_addresses: remove_router_interface_ip_address(config_db, interface_name, ipaddress) + if table_name == "VLAN_SUB_INTERFACE": + subintf_entry = config_db.get_entry(table_name, interface_name) + if 'vrf_name' in subintf_entry: + subintf_entry.pop('vrf_name') + config_db.set_entry(table_name, interface_name, None) # When config_db del entry and then add entry with same key, the DEL will lost. if ctx.obj['namespace'] is DEFAULT_NAMESPACE: @@ -4930,7 +4979,11 @@ def bind(ctx, interface_name, vrf_name): while state_db.exists(state_db.STATE_DB, _hash): time.sleep(0.01) state_db.close(state_db.STATE_DB) - config_db.set_entry(table_name, interface_name, {"vrf_name": vrf_name}) + if table_name == "VLAN_SUB_INTERFACE": + subintf_entry['vrf_name'] = vrf_name + config_db.set_entry(table_name, interface_name, subintf_entry) + else: + config_db.set_entry(table_name, interface_name, {"vrf_name": vrf_name}) # # 'unbind' subcommand @@ -4952,12 +5005,21 @@ def unbind(ctx, interface_name): table_name = get_interface_table_name(interface_name) if table_name == "": ctx.fail("'interface_name' is not valid. Valid names [Ethernet/PortChannel/Vlan/Loopback]") + if is_interface_bind_to_vrf(config_db, interface_name) is False: return + if table_name == "VLAN_SUB_INTERFACE": + subintf_entry = config_db.get_entry(table_name, interface_name) + if 'vrf_name' in subintf_entry: + subintf_entry.pop('vrf_name') + interface_ipaddresses = get_interface_ipaddresses(config_db, interface_name) for ipaddress in interface_ipaddresses: remove_router_interface_ip_address(config_db, interface_name, ipaddress) - config_db.set_entry(table_name, interface_name, None) + if table_name == "VLAN_SUB_INTERFACE": + config_db.set_entry(table_name, interface_name, subintf_entry) + else: + config_db.set_entry(table_name, interface_name, None) # # 'ipv6' subgroup ('config interface ipv6 ...') @@ -5253,7 +5315,7 @@ def add_route(ctx, command_str): # Check if exist entry with key keys = config_db.get_keys('STATIC_ROUTE') - if key in keys: + if tuple(key.split("|")) in keys: # If exist update current entry current_entry = config_db.get_entry('STATIC_ROUTE', key) @@ -5278,7 +5340,7 @@ def del_route(ctx, command_str): key, route = cli_sroute_to_config(ctx, command_str, strict_nh=False) keys = config_db.get_keys('STATIC_ROUTE') prefix_tuple = tuple(key.split('|')) - if not key in keys and not prefix_tuple in keys: + if not tuple(key.split("|")) in keys and not prefix_tuple in keys: ctx.fail('Route {} doesnt exist'.format(key)) else: # If not defined nexthop or intf name remove entire route @@ -6682,6 +6744,13 @@ def subintf_vlan_check(config_db, parent_intf, vlan): return True return False +def is_subintf_shortname(intf): + if VLAN_SUB_INTERFACE_SEPARATOR in intf: + if intf.startswith("Ethernet") or intf.startswith("PortChannel"): + return False + return True + return False + @subinterface.command('add') @click.argument('subinterface_name', metavar='', required=True) @click.argument('vid', metavar='', required=False, type=click.IntRange(1,4094)) @@ -6702,23 +6771,24 @@ def add_subinterface(ctx, subinterface_name, vid): config_db = ctx.obj['db'] port_dict = config_db.get_table(intf_table_name) + parent_intf = get_intf_longname(interface_alias) if interface_alias is not None: if not port_dict: ctx.fail("{} parent interface not found. {} table none".format(interface_alias, intf_table_name)) - if get_intf_longname(interface_alias) not in port_dict.keys(): + if parent_intf not in port_dict.keys(): ctx.fail("{} parent interface not found".format(subinterface_name)) # Validate if parent is portchannel member portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER') - if interface_is_in_portchannel(portchannel_member_table, interface_alias): + if interface_is_in_portchannel(portchannel_member_table, parent_intf): ctx.fail("{} is configured as a member of portchannel. Cannot configure subinterface" - .format(interface_alias)) + .format(parent_intf)) # Validate if parent is vlan member vlan_member_table = config_db.get_table('VLAN_MEMBER') - if interface_is_in_vlan(vlan_member_table, interface_alias): + if interface_is_in_vlan(vlan_member_table, parent_intf): ctx.fail("{} is configured as a member of vlan. Cannot configure subinterface" - .format(interface_alias)) + .format(parent_intf)) sub_intfs = [k for k,v in config_db.get_table('VLAN_SUB_INTERFACE').items() if type(k) != tuple] if subinterface_name in sub_intfs: @@ -6727,6 +6797,8 @@ def add_subinterface(ctx, subinterface_name, vid): subintf_dict = {} if vid is not None: subintf_dict.update({"vlan" : vid}) + elif is_subintf_shortname(subinterface_name): + ctx.fail("{} Encap vlan is mandatory for short name subinterfaces".format(subinterface_name)) if subintf_vlan_check(config_db, get_intf_longname(interface_alias), vid) is True: ctx.fail("Vlan {} encap already configured on other subinterface on {}".format(vid, interface_alias)) diff --git a/config/muxcable.py b/config/muxcable.py index fec0f16b3b..071f578a7a 100644 --- a/config/muxcable.py +++ b/config/muxcable.py @@ -1200,3 +1200,46 @@ def set_fec(db, port, target, mode): else: click.echo("ERR: Unable to set fec enable/disable port {} to {}".format(port, mode)) sys.exit(CONFIG_FAIL) + +def update_configdb_ycable_telemetry_data(config_db, key, val): + log_verbosity = get_value_for_key_in_config_tbl(config_db, key, "log_verbosity", "XCVRD_LOG") + + config_db.set_entry("XCVRD_LOG", key, {"log_verbosity": log_verbosity, + "disable_telemetry": val}) + return 0 + +@muxcable.command() +@click.argument('state', metavar='', required=True, type=click.Choice(["enable", "disable"])) +@clicommon.pass_db +def telemetry(db, state): + """Enable/Disable Telemetry for ycabled """ + + per_npu_configdb = {} + xcvrd_log_cfg_db_tbl = {} + + if state == 'enable': + val = 'False' + elif state == 'disable': + val = 'True' + + + # Getting all front asic namespace and correspding config and state DB connector + + namespaces = multi_asic.get_front_end_namespaces() + for namespace in namespaces: + asic_id = multi_asic.get_asic_index_from_namespace(namespace) + # replace these with correct macros + per_npu_configdb[asic_id] = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) + per_npu_configdb[asic_id].connect() + + xcvrd_log_cfg_db_tbl[asic_id] = per_npu_configdb[asic_id].get_table("XCVRD_LOG") + + asic_index = multi_asic.get_asic_index_from_namespace(EMPTY_NAMESPACE) + rc = update_configdb_ycable_telemetry_data(per_npu_configdb[asic_index], "Y_CABLE", val) + + + if rc == 0: + click.echo("Success in ycabled telemetry state to {}".format(state)) + else: + click.echo("ERR: Unable to set ycabled telemetry state to {}".format(state)) + sys.exit(CONFIG_FAIL) diff --git a/config/plugins/pbh.py b/config/plugins/pbh.py index b6726aa154..ce9187a36d 100644 --- a/config/plugins/pbh.py +++ b/config/plugins/pbh.py @@ -6,13 +6,14 @@ CLI Auto-generation tool HLD - https://github.com/Azure/SONiC/pull/78 """ +import os import click import json import ipaddress import re import utilities_common.cli as clicommon -from show.plugins.pbh import deserialize_pbh_counters +from show.plugins.pbh import deserialize_pbh_counters, PBH_COUNTERS_CACHE_FILENAME GRE_KEY_RE = r"^(0x){1}[a-fA-F0-9]{1,8}/(0x){1}[a-fA-F0-9]{1,8}$" @@ -79,8 +80,6 @@ PBH_UPDATE = "UPDATE" PBH_REMOVE = "REMOVE" -PBH_COUNTERS_LOCATION = "/tmp/.pbh_counters.txt" - # # DB interface -------------------------------------------------------------------------------------------------------- # @@ -467,11 +466,14 @@ def serialize_pbh_counters(obj): obj: counters dict. """ + cache = clicommon.UserCache('pbh') + counters_cache_file = os.path.join(cache.get_directory(), PBH_COUNTERS_CACHE_FILENAME) + def remap_keys(obj): return [{'key': k, 'value': v} for k, v in obj.items()] try: - with open(PBH_COUNTERS_LOCATION, 'w') as f: + with open(counters_cache_file, 'w') as f: json.dump(remap_keys(obj), f) except IOError as err: pass diff --git a/config/plugins/sonic-passwh_yang.py b/config/plugins/sonic-passwh_yang.py new file mode 100644 index 0000000000..6cfe2acafe --- /dev/null +++ b/config/plugins/sonic-passwh_yang.py @@ -0,0 +1,380 @@ +import copy +import click +import utilities_common.cli as clicommon +import utilities_common.general as general +from config import config_mgmt + + +# Load sonic-cfggen from source since /usr/local/bin/sonic-cfggen does not have .py extension. +sonic_cfggen = general.load_module_from_source('sonic_cfggen', '/usr/local/bin/sonic-cfggen') + + +def exit_with_error(*args, **kwargs): + """ Print a message with click.secho and abort CLI. + + Args: + args: Positional arguments to pass to click.secho + kwargs: Keyword arguments to pass to click.secho + """ + + click.secho(*args, **kwargs) + raise click.Abort() + + +def validate_config_or_raise(cfg): + """ Validate config db data using ConfigMgmt. + + Args: + cfg (Dict): Config DB data to validate. + Raises: + Exception: when cfg does not satisfy YANG schema. + """ + + try: + cfg = sonic_cfggen.FormatConverter.to_serialized(copy.deepcopy(cfg)) + config_mgmt.ConfigMgmt().loadData(cfg) + except Exception as err: + raise Exception('Failed to validate configuration: {}'.format(err)) + + +def update_entry_validated(db, table, key, data, create_if_not_exists=False): + """ Update entry in table and validate configuration. + If attribute value in data is None, the attribute is deleted. + + Args: + db (swsscommon.ConfigDBConnector): Config DB connector obect. + table (str): Table name to add new entry to. + key (Union[str, Tuple]): Key name in the table. + data (Dict): Entry data. + create_if_not_exists (bool): + In case entry does not exists already a new entry + is not created if this flag is set to False and + creates a new entry if flag is set to True. + Raises: + Exception: when cfg does not satisfy YANG schema. + """ + + cfg = db.get_config() + cfg.setdefault(table, {}) + + if not data: + raise Exception(f"No field/values to update {key}") + + if create_if_not_exists: + cfg[table].setdefault(key, {}) + + if key not in cfg[table]: + raise Exception(f"{key} does not exist") + + entry_changed = False + for attr, value in data.items(): + if value == cfg[table][key].get(attr): + continue + entry_changed = True + if value is None: + cfg[table][key].pop(attr, None) + else: + cfg[table][key][attr] = value + + if not entry_changed: + return + + validate_config_or_raise(cfg) + db.set_entry(table, key, cfg[table][key]) + + +@click.group(name="passw-hardening", + cls=clicommon.AliasedGroup) +def PASSW_HARDENING(): + """ PASSWORD HARDENING part of config_db.json """ + + pass + + + + +@PASSW_HARDENING.group(name="policies", + cls=clicommon.AliasedGroup) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES(db): + """ """ + + pass + + + + +@PASSW_HARDENING_POLICIES.command(name="state") + +@click.argument( + "state", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_state(db, state): + """ state of the feature """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "state": state, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="expiration") + +@click.argument( + "expiration", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_expiration(db, expiration): + """ expiration time (days unit) """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "expiration": expiration, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="expiration-warning") + +@click.argument( + "expiration-warning", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_expiration_warning(db, expiration_warning): + """ expiration warning time (days unit) """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "expiration_warning": expiration_warning, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="history-cnt") + +@click.argument( + "history-cnt", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_history_cnt(db, history_cnt): + """ num of old password that the system will recorded """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "history_cnt": history_cnt, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="len-min") + +@click.argument( + "len-min", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_len_min(db, len_min): + """ password min length """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "len_min": len_min, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="reject-user-passw-match") + +@click.argument( + "reject-user-passw-match", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_reject_user_passw_match(db, reject_user_passw_match): + """ username password match """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "reject_user_passw_match": reject_user_passw_match, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="lower-class") + +@click.argument( + "lower-class", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_lower_class(db, lower_class): + """ password lower chars policy """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "lower_class": lower_class, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="upper-class") + +@click.argument( + "upper-class", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_upper_class(db, upper_class): + """ password upper chars policy """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "upper_class": upper_class, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="digits-class") + +@click.argument( + "digits-class", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_digits_class(db, digits_class): + """ password digits chars policy """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "digits_class": digits_class, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + +@PASSW_HARDENING_POLICIES.command(name="special-class") + +@click.argument( + "special-class", + nargs=1, + required=True, +) +@clicommon.pass_db +def PASSW_HARDENING_POLICIES_special_class(db, special_class): + """ password special chars policy """ + + table = "PASSW_HARDENING" + key = "POLICIES" + data = { + "special_class": special_class, + } + try: + update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) + except Exception as err: + exit_with_error(f"Error: {err}", fg="red") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +def register(cli): + """ Register new CLI nodes in root CLI. + + Args: + cli: Root CLI node. + Raises: + Exception: when root CLI already has a command + we are trying to register. + """ + cli_node = PASSW_HARDENING + if cli_node.name in cli.commands: + raise Exception(f"{cli_node.name} already exists in CLI") + cli.add_command(PASSW_HARDENING) diff --git a/counterpoll/main.py b/counterpoll/main.py index f3befe1311..ad15c8c248 100644 --- a/counterpoll/main.py +++ b/counterpoll/main.py @@ -419,9 +419,9 @@ def show(): if buffer_pool_wm_info: data.append(["BUFFER_POOL_WATERMARK_STAT", buffer_pool_wm_info.get("POLL_INTERVAL", DEFLT_60_SEC), buffer_pool_wm_info.get("FLEX_COUNTER_STATUS", DISABLE)]) if acl_info: - data.append([ACL, pg_drop_info.get("POLL_INTERVAL", DEFLT_10_SEC), acl_info.get("FLEX_COUNTER_STATUS", DISABLE)]) + data.append([ACL, acl_info.get("POLL_INTERVAL", DEFLT_10_SEC), acl_info.get("FLEX_COUNTER_STATUS", DISABLE)]) if tunnel_info: - data.append(["TUNNEL_STAT", rif_info.get("POLL_INTERVAL", DEFLT_10_SEC), rif_info.get("FLEX_COUNTER_STATUS", DISABLE)]) + data.append(["TUNNEL_STAT", tunnel_info.get("POLL_INTERVAL", DEFLT_10_SEC), tunnel_info.get("FLEX_COUNTER_STATUS", DISABLE)]) if trap_info: data.append(["FLOW_CNT_TRAP_STAT", trap_info.get("POLL_INTERVAL", DEFLT_10_SEC), trap_info.get("FLEX_COUNTER_STATUS", DISABLE)]) if route_info: diff --git a/crm/main.py b/crm/main.py index f728f87dd9..9b0d06e89a 100644 --- a/crm/main.py +++ b/crm/main.py @@ -237,9 +237,9 @@ def polling(ctx): @polling.command() @click.pass_context -@click.argument('interval', type=click.INT) +@click.argument('interval', type=click.IntRange(1, 9999)) def interval(ctx, interval): - """CRM polling interval configuration""" + """CRM polling interval configuration in seconds (range: 1-9999)""" ctx.obj["crm"].config('polling_interval', interval) @config.group() diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 5799e110ce..5f188e7a78 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -79,8 +79,8 @@ * [Kubernetes show commands](#Kubernetes-show-commands) * [Kubernetes config commands](#Kubernetes-config-commands) * [Linux Kernel Dump](#kdump) - * [Linux Kernel Dump show commands](#kdump-show-commands) - * [Linux Kernel Dump config commands](#kdump-config-commands) + * [Linux Kernel Dump show commands](#Linux-Kernel-Dump-show-commands) + * [Linux Kernel Dump config commands](#Linux-Kernel-Dump-config-command) * [LLDP](#lldp) * [LLDP show commands](#lldp-show-commands) * [Loading, Reloading And Saving Configuration](#loading-reloading-and-saving-configuration) @@ -134,6 +134,9 @@ * [Queue And Priority-Group](#queue-and-priority-group) * [Buffer Pool](#buffer-pool) * [QoS config commands](#qos-config-commands) +* [Radius](#radius) + * [radius show commands](#show-radius-commands) + * [radius config commands](#Radius-config-commands) * [sFlow](#sflow) * [sFlow Show commands](#sflow-show-commands) * [sFlow Config commands](#sflow-config-commands) @@ -183,6 +186,11 @@ * [ZTP Configuration And Show Commands](#ztp-configuration-and-show-commands) * [ ZTP show commands](#ztp-show-commands) * [ZTP configuration commands](#ztp-configuration-commands) +* [MACsec Commands](#macsec-commands) + * [MACsec config command](#macsec-config-command) + * [MACsec show command](#macsec-show-command) + * [MACsec clear command](#macsec-clear-command) + ## Document History @@ -1580,7 +1588,7 @@ This command displays the state and key parameters of all BFD sessions. - Usage: ``` - show bgp summary + show bfd summary ``` - Example: ``` @@ -4481,7 +4489,7 @@ This will move the interface to default vrf. - Usage: ``` - config interface vrf unbind + config interface vrf unbind ``` ### Interface vrf binding show commands @@ -5001,6 +5009,27 @@ last number of lines. [ 656.337476] gpio_ich(E) ahci(E) mlxsw_core(E) libahci(E) devlink(E) crc32c_intel(E) libata(E) i2c_i801(E) scsi_mod(E) lpc_ich(E) mfd_core(E) ehci_pci(E) ehci_hcd(E) usbcore(E) e1000e(E) usb_common(E) fan(E) thermal(E) [ 656.569590] CR2: 0000000000000000 ``` +### Linux Kernel Dump config command + +**config kdump** + +Administrative state of kdump is stored in ConfigDB. + +The variable USE_KDUMP in the file /etc/default/kdump-tools is set to 0 to disable kdump, and set to 1 to enable kdump. + +Since this command might require changing the kernel parameters to specify the amount of memory reserved for the capture kernel (the kernel parameters which are exported through /proc/cmdline), a reboot is necessary. The command displays a message showing that kdump functionality will be either enabled or disabled following the next reboot. + +- Usage: +``` + admin@sonic:~$ config kdump + +Commands: + disable Disable the KDUMP mechanism + enable Enable the KDUMP mechanism + memory Configure the memory for KDUMP mechanism + num_dumps Configure the maximum dump files of KDUMP mechanism + +``` Go Back To [Beginning of the document](#) or [Beginning of this section](#kdump) ## LLDP @@ -6901,12 +6930,13 @@ When any port is already member of any other portchannel and if user tries to ad Command takes two optional arguements given below. 1) min-links - minimum number of links required to bring up the portchannel 2) fallback - true/false. LACP fallback feature can be enabled / disabled. When it is set to true, only one member port will be selected as active per portchannel during fallback mode. Refer https://github.com/Azure/SONiC/blob/master/doc/lag/LACP%20Fallback%20Feature%20for%20SONiC_v0.5.md for more details about fallback feature. +3) fast-rate - true/false, default is false (slow). Option specifying the rate in which we'll ask our link partner to transmit LACPDU packets in 802.3ad mode. slow - request partner to transmit LACPDUs every 30 seconds, fast - request partner to transmit LACPDUs every 1 second. In slow mode 60-90 seconds needed to detect linkdown, in fast mode only 2-3 seconds. A port channel can be deleted only if it does not have any members or the members are already deleted. When a user tries to delete a port channel and the port channel still has one or more members that exist, the deletion of port channel is blocked. - Usage: ``` - config portchannel (add | del) [--min-links ] [--fallback (true | false)] + config portchannel (add | del) [--min-links ] [--fallback (true | false) [--fast-rate (true | false)] ``` - Example (Create the portchannel with name "PortChannel0011"): @@ -7777,6 +7807,51 @@ If there was QoS configuration in the above tables for the ports: Go Back To [Beginning of the document](#) or [Beginning of this section](#qos) +## Radius + +### show radius commands + +This command displays the global radius configuration that includes the auth_type, retransmit, timeout and passkey. + +- Usage: + ``` + show radius + ``` +- Example: + + ``` + admin@sonic:~$ show radius + RADIUS global auth_type pap (default) + RADIUS global retransmit 3 (default) + RADIUS global timeout 5 (default) + RADIUS global passkey (default) + + ``` + +### Radius config commands + +This command is to config the radius server for various parameter listed. + + - Usage: + ``` + config radius + ``` +- Example: + ``` + admin@sonic:~$ config radius + + add Specify a RADIUS server + authtype Specify RADIUS server global auth_type [chap | pap | mschapv2] + default set its default configuration + delete Delete a RADIUS server + nasip Specify RADIUS server global NAS-IP|IPV6-Address + passkey Specify RADIUS server global passkey + retransmit Specify RADIUS server global retry attempts <0 - 10> + sourceip Specify RADIUS server global source ip + statistics Specify RADIUS server global statistics [enable | disable |... + timeout Specify RADIUS server global timeout <1 - 60> + + ``` ## sFlow ### sFlow Show commands @@ -10953,3 +11028,248 @@ Running command: ztp run -y ``` Go Back To [Beginning of the document](#SONiC-COMMAND-LINE-INTERFACE-GUIDE) or [Beginning of this section](#ztp-configuration-and-show-commands) + +# MACsec Commands + +This sub-section explains the list of the configuration options available for MACsec. MACsec feature is as a plugin to SONiC, So please install MACsec package before using MACsec commands. + +## MACsec config command + +- Add MACsec profile +``` +admin@sonic:~$ sudo config macsec profile add --help +Usage: config macsec profile add [OPTIONS] + + Add MACsec profile + +Options: + --priority For Key server election. In 0-255 range with + 0 being the highest priority. [default: + 255] + --cipher_suite The cipher suite for MACsec. [default: GCM- + AES-128] + --primary_cak Primary Connectivity Association Key. + [required] + --primary_ckn Primary CAK Name. [required] + --policy MACsec policy. INTEGRITY_ONLY: All traffic, + except EAPOL, will be converted to MACsec + packets without encryption. SECURITY: All + traffic, except EAPOL, will be encrypted by + SecY. [default: security] + --enable_replay_protect / --disable_replay_protect + Whether enable replay protect. [default: + False] + --replay_window + Replay window size that is the number of + packets that could be out of order. This + field works only if ENABLE_REPLAY_PROTECT is + true. [default: 0] + --send_sci / --no_send_sci Send SCI in SecTAG field of MACsec header. + [default: True] + --rekey_period The period of proactively refresh (Unit + second). [default: 0] + -?, -h, --help Show this message and exit. +``` + +- Delete MACsec profile +``` +admin@sonic:~$ sudo config macsec profile del --help +Usage: config macsec profile del [OPTIONS] + + Delete MACsec profile + +Options: + -?, -h, --help Show this message and exit. +``` + +- Enable MACsec on the port +``` +admin@sonic:~$ sudo config macsec port add --help +Usage: config macsec port add [OPTIONS] + + Add MACsec port + +Options: + -?, -h, --help Show this message and exit. +``` + + +- Disable MACsec on the port +``` +admin@sonic:~$ sudo config macsec port del --help +Usage: config macsec port del [OPTIONS] + + Delete MACsec port + +Options: + -?, -h, --help Show this message and exit. + +``` + + +## MACsec show command + +- Show MACsec + +``` +admin@vlab-02:~$ show macsec --help +Usage: show macsec [OPTIONS] [INTERFACE_NAME] + +Options: + -d, --display [all] Show internal interfaces [default: all] + -n, --namespace [] Namespace name or all + -h, -?, --help Show this message and exit. + +``` + +``` +admin@vlab-02:~$ show macsec +MACsec port(Ethernet0) +--------------------- ----------- +cipher_suite GCM-AES-256 +enable true +enable_encrypt true +enable_protect true +enable_replay_protect false +replay_window 0 +send_sci true +--------------------- ----------- + MACsec Egress SC (5254008f4f1c0001) + ----------- - + encoding_an 2 + ----------- - + MACsec Egress SA (1) + ------------------------------------- ---------------------------------------------------------------- + auth_key 849B69D363E2B0AA154BEBBD7C1D9487 + next_pn 1 + sak AE8C9BB36EA44B60375E84BC8E778596289E79240FDFA6D7BA33D3518E705A5E + salt 000000000000000000000000 + ssci 0 + SAI_MACSEC_SA_ATTR_CURRENT_XPN 179 + SAI_MACSEC_SA_STAT_OCTETS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OCTETS_PROTECTED 0 + SAI_MACSEC_SA_STAT_OUT_PKTS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OUT_PKTS_PROTECTED 0 + ------------------------------------- ---------------------------------------------------------------- + MACsec Egress SA (2) + ------------------------------------- ---------------------------------------------------------------- + auth_key 5A8B8912139551D3678B43DD0F10FFA5 + next_pn 1 + sak 7F2651140F12C434F782EF9AD7791EE2CFE2BF315A568A48785E35FC803C9DB6 + salt 000000000000000000000000 + ssci 0 + SAI_MACSEC_SA_ATTR_CURRENT_XPN 87185 + SAI_MACSEC_SA_STAT_OCTETS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OCTETS_PROTECTED 0 + SAI_MACSEC_SA_STAT_OUT_PKTS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OUT_PKTS_PROTECTED 0 + ------------------------------------- ---------------------------------------------------------------- + MACsec Ingress SC (525400edac5b0001) + MACsec Ingress SA (1) + --------------------------------------- ---------------------------------------------------------------- + active true + auth_key 849B69D363E2B0AA154BEBBD7C1D9487 + lowest_acceptable_pn 1 + sak AE8C9BB36EA44B60375E84BC8E778596289E79240FDFA6D7BA33D3518E705A5E + salt 000000000000000000000000 + ssci 0 + SAI_MACSEC_SA_ATTR_CURRENT_XPN 103 + SAI_MACSEC_SA_STAT_IN_PKTS_DELAYED 0 + SAI_MACSEC_SA_STAT_IN_PKTS_INVALID 0 + SAI_MACSEC_SA_STAT_IN_PKTS_LATE 0 + SAI_MACSEC_SA_STAT_IN_PKTS_NOT_USING_SA 0 + SAI_MACSEC_SA_STAT_IN_PKTS_NOT_VALID 0 + SAI_MACSEC_SA_STAT_IN_PKTS_OK 0 + SAI_MACSEC_SA_STAT_IN_PKTS_UNCHECKED 0 + SAI_MACSEC_SA_STAT_IN_PKTS_UNUSED_SA 0 + SAI_MACSEC_SA_STAT_OCTETS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OCTETS_PROTECTED 0 + --------------------------------------- ---------------------------------------------------------------- + MACsec Ingress SA (2) + --------------------------------------- ---------------------------------------------------------------- + active true + auth_key 5A8B8912139551D3678B43DD0F10FFA5 + lowest_acceptable_pn 1 + sak 7F2651140F12C434F782EF9AD7791EE2CFE2BF315A568A48785E35FC803C9DB6 + salt 000000000000000000000000 + ssci 0 + SAI_MACSEC_SA_ATTR_CURRENT_XPN 91824 + SAI_MACSEC_SA_STAT_IN_PKTS_DELAYED 0 + SAI_MACSEC_SA_STAT_IN_PKTS_INVALID 0 + SAI_MACSEC_SA_STAT_IN_PKTS_LATE 0 + SAI_MACSEC_SA_STAT_IN_PKTS_NOT_USING_SA 0 + SAI_MACSEC_SA_STAT_IN_PKTS_NOT_VALID 0 + SAI_MACSEC_SA_STAT_IN_PKTS_OK 0 + SAI_MACSEC_SA_STAT_IN_PKTS_UNCHECKED 0 + SAI_MACSEC_SA_STAT_IN_PKTS_UNUSED_SA 0 + SAI_MACSEC_SA_STAT_OCTETS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OCTETS_PROTECTED 0 + --------------------------------------- ---------------------------------------------------------------- +MACsec port(Ethernet1) +--------------------- ----------- +cipher_suite GCM-AES-256 +enable true +enable_encrypt true +enable_protect true +enable_replay_protect false +replay_window 0 +send_sci true +--------------------- ----------- + MACsec Egress SC (5254008f4f1c0001) + ----------- - + encoding_an 1 + ----------- - + MACsec Egress SA (1) + ------------------------------------- ---------------------------------------------------------------- + auth_key 35FC8F2C81BCA28A95845A4D2A1EE6EF + next_pn 1 + sak 1EC8572B75A840BA6B3833DC550C620D2C65BBDDAD372D27A1DFEB0CD786671B + salt 000000000000000000000000 + ssci 0 + SAI_MACSEC_SA_ATTR_CURRENT_XPN 4809 + SAI_MACSEC_SA_STAT_OCTETS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OCTETS_PROTECTED 0 + SAI_MACSEC_SA_STAT_OUT_PKTS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OUT_PKTS_PROTECTED 0 + ------------------------------------- ---------------------------------------------------------------- + MACsec Ingress SC (525400edac5b0001) + MACsec Ingress SA (1) + --------------------------------------- ---------------------------------------------------------------- + active true + auth_key 35FC8F2C81BCA28A95845A4D2A1EE6EF + lowest_acceptable_pn 1 + sak 1EC8572B75A840BA6B3833DC550C620D2C65BBDDAD372D27A1DFEB0CD786671B + salt 000000000000000000000000 + ssci 0 + SAI_MACSEC_SA_ATTR_CURRENT_XPN 5033 + SAI_MACSEC_SA_STAT_IN_PKTS_DELAYED 0 + SAI_MACSEC_SA_STAT_IN_PKTS_INVALID 0 + SAI_MACSEC_SA_STAT_IN_PKTS_LATE 0 + SAI_MACSEC_SA_STAT_IN_PKTS_NOT_USING_SA 0 + SAI_MACSEC_SA_STAT_IN_PKTS_NOT_VALID 0 + SAI_MACSEC_SA_STAT_IN_PKTS_OK 0 + SAI_MACSEC_SA_STAT_IN_PKTS_UNCHECKED 0 + SAI_MACSEC_SA_STAT_IN_PKTS_UNUSED_SA 0 + SAI_MACSEC_SA_STAT_OCTETS_ENCRYPTED 0 + SAI_MACSEC_SA_STAT_OCTETS_PROTECTED 0 + --------------------------------------- ---------------------------------------------------------------- +``` + +## MACsec clear command + +Clear MACsec counters which is to reset all MACsec counters to ZERO. + +``` +admin@sonic:~$ sonic-clear macsec --help +Usage: sonic-clear macsec [OPTIONS] + + Clear MACsec counts. This clear command will generated a cache for next + show commands which will base on this cache as the zero baseline to show + the increment of counters. + +Options: + --clean-cache BOOLEAN If the option of clean cache is true, next show + commands will show the raw counters which based on + the service booted instead of the last clear command. + -h, -?, --help Show this message and exit. +``` diff --git a/generic_config_updater/change_applier.py b/generic_config_updater/change_applier.py index 63ca98049c..f5a365d59f 100644 --- a/generic_config_updater/change_applier.py +++ b/generic_config_updater/change_applier.py @@ -65,12 +65,21 @@ def apply(self, change): self.config_wrapper.apply_change_to_config_db(change) + def remove_backend_tables_from_config(self, data): + return data + + class ChangeApplier: updater_conf = None def __init__(self): self.config_db = get_config_db() + self.backend_tables = [ + "BUFFER_PG", + "BUFFER_PROFILE", + "FLEX_COUNTER_TABLE" + ] if (not ChangeApplier.updater_conf) and os.path.exists(UPDATER_CONF_FILE): with open(UPDATER_CONF_FILE, "r") as s: ChangeApplier.updater_conf = json.load(s) @@ -142,6 +151,8 @@ def apply(self, change): ret = self._services_validate(run_data, upd_data, upd_keys) if not ret: run_data = self._get_running_config() + self.remove_backend_tables_from_config(upd_data) + self.remove_backend_tables_from_config(run_data) if upd_data != run_data: self._report_mismatch(run_data, upd_data) ret = -1 @@ -150,6 +161,11 @@ def apply(self, change): return ret + def remove_backend_tables_from_config(self, data): + for key in self.backend_tables: + data.pop(key, None) + + def _get_running_config(self): (_, fname) = tempfile.mkstemp(suffix="_changeApplier") os.system("sonic-cfggen -d --print-data > {}".format(fname)) diff --git a/generic_config_updater/generic_updater.py b/generic_config_updater/generic_updater.py index ee7af65620..56297039aa 100644 --- a/generic_config_updater/generic_updater.py +++ b/generic_config_updater/generic_updater.py @@ -77,6 +77,8 @@ def apply(self, patch): # Validate config updated successfully self.logger.log_notice("Verifying patch updates are reflected on ConfigDB.") new_config = self.config_wrapper.get_config_db_as_json() + self.changeapplier.remove_backend_tables_from_config(target_config) + self.changeapplier.remove_backend_tables_from_config(new_config) if not(self.patch_wrapper.verify_same_json(target_config, new_config)): raise GenericConfigUpdaterError(f"After applying patch to config, there are still some parts not updated") diff --git a/scripts/aclshow b/scripts/aclshow index db0cc40ddf..db922a6cce 100755 --- a/scripts/aclshow +++ b/scripts/aclshow @@ -20,15 +20,13 @@ optional arguments: import argparse import json import os -from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector import sys +from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector +from utilities_common.cli import UserCache + from tabulate import tabulate -### temp file to save counter positions when doing clear counter action. -### if we could have a SAI command to clear counters will be better, so no need to maintain -### counters in temp loaction for clear conter action -COUNTER_POSITION = '/tmp/.counters_acl.p' COUNTERS = "COUNTERS" ACL_COUNTER_RULE_MAP = "ACL_COUNTER_RULE_MAP" @@ -38,6 +36,9 @@ ACL_HEADER = ["RULE NAME", "TABLE NAME", "PRIO", "PACKETS COUNT", "BYTES COUNT"] COUNTER_PACKETS_ATTR = "SAI_ACL_COUNTER_ATTR_PACKETS" COUNTER_BYTES_ATTR = "SAI_ACL_COUNTER_ATTR_BYTES" +USER_CACHE = UserCache() +COUNTERS_CACHE_DIR = USER_CACHE.get_directory() +COUNTERS_CACHE = os.path.join(COUNTERS_CACHE_DIR, 'aclstat') class AclStat(object): """ @@ -78,9 +79,9 @@ class AclStat(object): res[e['key'][0], e['key'][1]] = e['value'] return res - if os.path.isfile(COUNTER_POSITION): + if os.path.isfile(COUNTERS_CACHE): try: - with open(COUNTER_POSITION) as fp: + with open(COUNTERS_CACHE) as fp: self.saved_acl_counters = remap_keys(json.load(fp)) except Exception: pass @@ -207,7 +208,7 @@ class AclStat(object): def remap_keys(dict): return [{'key': k, 'value': v} for k, v in dict.items()] - with open(COUNTER_POSITION, 'w') as fp: + with open(COUNTERS_CACHE, 'w') as fp: json.dump(remap_keys(self.acl_counters), fp) def main(): diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index ae7437389a..04e0dbcb4b 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -485,6 +485,23 @@ def migrate_qos_fieldval_reference_format(self): self.migrate_qos_db_fieldval_reference_remove(qos_table_list, self.configDB, self.configDB.CONFIG_DB, '|') return True + def migrate_port_qos_map_global(self): + """ + Generate dscp_to_tc_map for switch. + """ + asics_require_global_dscp_to_tc_map = ["broadcom"] + if self.asic_type not in asics_require_global_dscp_to_tc_map: + return + dscp_to_tc_map_table_names = self.configDB.get_keys('DSCP_TO_TC_MAP') + if len(dscp_to_tc_map_table_names) == 0: + return + + qos_maps = self.configDB.get_table('PORT_QOS_MAP') + if 'global' not in qos_maps.keys(): + # We are unlikely to have more than 1 DSCP_TO_TC_MAP in previous versions + self.configDB.set_entry('PORT_QOS_MAP', 'global', {"dscp_to_tc_map": dscp_to_tc_map_table_names[0]}) + log.log_info("Created entry for global DSCP_TO_TC_MAP {}".format(dscp_to_tc_map_table_names[0])) + def version_unknown(self): """ version_unknown tracks all SONiC versions that doesn't have a version @@ -600,13 +617,13 @@ def version_1_0_6(self): abandon_method = self.mellanox_buffer_migrator.mlnx_abandon_pending_buffer_configuration append_method = self.mellanox_buffer_migrator.mlnx_append_item_on_pending_configuration_list - if self.mellanox_buffer_migrator.mlnx_migrate_buffer_pool_size('version_1_0_6', 'version_3_0_0') \ - and self.mellanox_buffer_migrator.mlnx_migrate_buffer_profile('version_1_0_6', 'version_3_0_0') \ + if self.mellanox_buffer_migrator.mlnx_migrate_buffer_pool_size('version_1_0_6', 'version_2_0_0') \ + and self.mellanox_buffer_migrator.mlnx_migrate_buffer_profile('version_1_0_6', 'version_2_0_0') \ and (not self.mellanox_buffer_migrator.mlnx_is_buffer_model_dynamic() or \ self.migrate_config_db_buffer_tables_for_dynamic_calculation(speed_list, cable_len_list, '0', abandon_method, append_method)) \ and self.mellanox_buffer_migrator.mlnx_flush_new_buffer_configuration() \ and self.prepare_dynamic_buffer_for_warm_reboot(buffer_pools, buffer_profiles, buffer_pgs): - self.set_version('version_3_0_0') + self.set_version('version_2_0_0') else: self.prepare_dynamic_buffer_for_warm_reboot() @@ -615,8 +632,26 @@ def version_1_0_6(self): self.configDB.set_entry('DEVICE_METADATA', 'localhost', metadata) log.log_notice('Setting buffer_model to traditional') - self.set_version('version_3_0_0') + self.set_version('version_2_0_0') + return 'version_2_0_0' + + def version_2_0_0(self): + """ + Version 2_0_0 + """ + log.log_info('Handling version_2_0_0') + self.migrate_port_qos_map_global() + self.set_version('version_2_0_1') + return 'version_2_0_1' + + def version_2_0_1(self): + """ + Version 2_0_1. + This is the latest version for 202012 branch + """ + log.log_info('Handling version_2_0_1') + self.set_version('version_3_0_0') return 'version_3_0_0' def version_3_0_0(self): @@ -677,6 +712,7 @@ def version_3_0_4(self): if 'pfc_enable' in v: v['pfcwd_sw_enable'] = v['pfc_enable'] self.configDB.set_entry('PORT_QOS_MAP', k, v) + self.set_version('version_3_0_5') return 'version_3_0_5' def version_3_0_5(self): diff --git a/scripts/decode-syseeprom b/scripts/decode-syseeprom index eeb38306b0..3d0b8d1db9 100755 --- a/scripts/decode-syseeprom +++ b/scripts/decode-syseeprom @@ -228,8 +228,6 @@ def main(): (opts, args) = get_cmdline_opts() - use_db = opts.db and support_eeprom_db - # Get platform name platform = device_info.get_platform() @@ -238,6 +236,8 @@ def main(): if any(re.match(p, platform) for p in platforms_without_eeprom_db): support_eeprom_db = False + use_db = opts.db and support_eeprom_db + if opts.mgmtmac: print_mgmt_mac(use_db) elif opts.serial: diff --git a/scripts/disk_check.py b/scripts/disk_check.py index 4fa8d69746..0f5f882400 100644 --- a/scripts/disk_check.py +++ b/scripts/disk_check.py @@ -33,11 +33,17 @@ import sys import syslog import subprocess +from swsscommon.swsscommon import events_init_publisher, events_deinit_publisher, event_publish +from swsscommon.swsscommon import FieldValueMap UPPER_DIR = "/run/mount/upper" WORK_DIR = "/run/mount/work" MOUNTS_FILE = "/proc/mounts" +EVENTS_PUBLISHER_SOURCE = "sonic-events-host" +EVENTS_PUBLISHER_TAG = "event-disk" +events_handle = None + chk_log_level = syslog.LOG_ERR def _log_msg(lvl, pfx, msg): @@ -45,6 +51,7 @@ def _log_msg(lvl, pfx, msg): print("{}: {}".format(pfx, msg)) syslog.syslog(lvl, msg) + def log_err(m): _log_msg(syslog.LOG_ERR, "Err", m) @@ -57,11 +64,18 @@ def log_debug(m): _log_msg(syslog.LOG_DEBUG, "Debug", m) +def event_pub(): + param_dict = FieldValueMap() + param_dict["fail_type"] = "read_only" + event_publish(events_handle, EVENTS_PUBLISHER_TAG, param_dict) + + def test_writable(dirs): for d in dirs: rw = os.access(d, os.W_OK) if not rw: log_err("{} is not read-write".format(d)) + event_pub() return False else: log_debug("{} is Read-Write".format(d)) @@ -145,12 +159,13 @@ def do_check(skip_mount, dirs): # Check if mounted if (not ret) and is_mounted(dirs): log_err("READ-ONLY: Mounted {} to make Read-Write".format(dirs)) + event_pub() return ret def main(): - global chk_log_level + global chk_log_level, events_handle parser=argparse.ArgumentParser( description="check disk for Read-Write and mount etc & home as Read-Write") @@ -163,7 +178,12 @@ def main(): args = parser.parse_args() chk_log_level = args.loglvl + + events_handle = events_init_publisher(EVENTS_PUBLISHER_SOURCE) + ret = do_check(args.skip_mount, args.dirs.split(",")) + + events_deinit_publisher(events_handle) return ret diff --git a/scripts/dropstat b/scripts/dropstat index 6766d2a2c1..f98fc29197 100755 --- a/scripts/dropstat +++ b/scripts/dropstat @@ -35,6 +35,7 @@ except KeyError: pass from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector +from utilities_common.cli import UserCache # COUNTERS_DB Tables @@ -80,8 +81,7 @@ std_switch_description_header = ['DEVICE'] def get_dropstat_dir(): - dropstat_dir_prefix = '/tmp/dropstat' - return "{}-{}/".format(dropstat_dir_prefix, os.getuid()) + return UserCache().get_directory() class DropStat(object): @@ -411,18 +411,7 @@ Examples: group = args.group counter_type = args.type - dropstat_dir = get_dropstat_dir() - - # Create the directory to hold clear results - if not os.path.exists(dropstat_dir): - try: - os.makedirs(dropstat_dir) - except IOError as e: - print(e) - sys.exit(e.errno) - dcstat = DropStat() - if command == 'clear': dcstat.clear_drop_counts() elif command == 'show': diff --git a/scripts/fast-reboot b/scripts/fast-reboot index 2942c8ba23..ac96726281 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -462,21 +462,14 @@ function unload_kernel() } function save_counters_folder() { - debug "Saving counters folder before warmboot..." + if [[ "$REBOOT_TYPE" = "warm-reboot" ]]; then + debug "Saving counters folder before warmboot..." - counters_folder="/host/counters" - if [[ ! -d $counters_folder ]]; then - mkdir $counters_folder - fi - if [[ "$REBOOT_TYPE" = "warm-reboot" || "$REBOOT_TYPE" = "fastfast-reboot" ]]; then - modules=("portstat-0" "dropstat" "pfcstat-0" "queuestat-0" "intfstat-0") - for module in ${modules[@]} - do - statfile="/tmp/$module" - if [[ -d $statfile ]]; then - cp -rf $statfile $counters_folder - fi - done + counters_folder="/host/counters" + if [[ ! -d $counters_folder ]]; then + mkdir $counters_folder + fi + cp -rf /tmp/cache $counters_folder fi } @@ -696,6 +689,12 @@ else fi for service in ${SERVICES_TO_STOP}; do + # Skip the masked services + state=$(systemctl is-enabled ${service}) + if [[ $state == "masked" ]]; then + continue + fi + debug "Stopping ${service} ..." # TODO: These exceptions for nat, sflow, lldp diff --git a/scripts/flow_counters_stat b/scripts/flow_counters_stat index 61c754e333..ac5ef94beb 100755 --- a/scripts/flow_counters_stat +++ b/scripts/flow_counters_stat @@ -27,6 +27,7 @@ import utilities_common.multi_asic as multi_asic_util from flow_counter_util.route import build_route_pattern, extract_route_pattern, exit_if_route_flow_counter_not_support, DEFAULT_VRF, COUNTERS_ROUTE_TO_PATTERN_MAP from utilities_common import constants from utilities_common.netstat import format_number_with_comma, table_as_json, ns_diff, format_prate +from utilities_common.cli import UserCache # Flow counter meta data, new type of flow counters can extend this dictinary to reuse existing logic flow_counter_meta = { @@ -57,9 +58,10 @@ class FlowCounterStats(object): meta_data = flow_counter_meta[args.type] self.name_map = meta_data['name_map'] self.headers = meta_data['headers'] - self.data_file = os.path.join('/tmp/{}-stats-{}'.format(args.type, os.getuid())) - if self.args.delete and os.path.exists(self.data_file): - os.remove(self.data_file) + self.cache = UserCache() + self.data_file = os.path.join(self.cache.get_directory(), "flow-counter-stats") + if self.args.delete: + self.cache.remove() self.data = {} def show(self): diff --git a/scripts/generate_dump b/scripts/generate_dump index 62a5a75f17..c9e165c82e 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -885,6 +885,10 @@ collect_mellanox() { ${CMD_PREFIX}docker exec syncd mkdir -p $sai_dump_folder ${CMD_PREFIX}docker exec syncd saisdkdump -f $sai_dump_filename + if [ $? != 0 ]; then + echo "Failed to collect saisdkdump." + fi + copy_from_docker syncd $sai_dump_folder $sai_dump_folder echo "$sai_dump_folder" for file in `ls $sai_dump_folder`; do diff --git a/scripts/intfstat b/scripts/intfstat index 1d5da781b6..30cfbf084d 100755 --- a/scripts/intfstat +++ b/scripts/intfstat @@ -28,6 +28,7 @@ from collections import namedtuple, OrderedDict from natsort import natsorted from tabulate import tabulate from utilities_common.netstat import ns_diff, table_as_json, STATUS_NA, format_brate, format_prate +from utilities_common.cli import UserCache from swsscommon.swsscommon import SonicV2Connector nstat_fields = ( @@ -274,63 +275,34 @@ def main(): delete_saved_stats = args.delete delete_all_stats = args.delete_all use_json = args.json - tag_name = args.tag if args.tag else "" - uid = str(os.getuid()) + tag_name = args.tag wait_time_in_seconds = args.period interface_name = args.interface if args.interface else "" - # fancy filename with dashes: uid-tag / uid etc - filename_components = [uid, tag_name] + cnstat_file = "intfstat" - cnstat_file = "-".join(filter(None, filename_components)) + cache = UserCache(tag=tag_name) - cnstat_dir = "/tmp/intfstat-" + uid + cache_general = UserCache() + cnstat_dir = cache.get_directory() + cnstat_general_dir = cache_general.get_directory() + + cnstat_fqn_general_file = cnstat_general_dir + "/" + cnstat_file cnstat_fqn_file = cnstat_dir + "/" + cnstat_file if delete_all_stats: - # There is nothing to delete - if not os.path.isdir(cnstat_dir): - sys.exit(0) - - for file in os.listdir(cnstat_dir): - os.remove(cnstat_dir + "/" + file) - - try: - os.rmdir(cnstat_dir) - sys.exit(0) - except IOError as e: - print(e.errno, e) - sys.exit(e) + cache.remove_all() if delete_saved_stats: - try: - os.remove(cnstat_fqn_file) - except IOError as e: - if e.errno != ENOENT: - print(e.errno, e) - sys.exit(1) - finally: - if os.listdir(cnstat_dir) == []: - os.rmdir(cnstat_dir) - sys.exit(0) + cache.remove() intfstat = Intfstat() cnstat_dict, ratestat_dict = intfstat.get_cnstat(rif=interface_name) - # At this point, either we'll create a file or open an existing one. - if not os.path.exists(cnstat_dir): - try: - os.makedirs(cnstat_dir) - except IOError as e: - print(e.errno, e) - sys.exit(1) - if save_fresh_stats: try: # Add the information also to the general file - i.e. without the tag name - if tag_name != '' and tag_name in cnstat_fqn_file.split('/')[-1]: - gen_index = cnstat_fqn_file.rfind('/') - cnstat_fqn_general_file = cnstat_fqn_file[:gen_index] + cnstat_fqn_file[gen_index:].split('-')[0] + if tag_name is not None: if os.path.isfile(cnstat_fqn_general_file): try: general_data = pickle.load(open(cnstat_fqn_general_file, 'rb')) @@ -354,9 +326,6 @@ def main(): sys.exit(0) if wait_time_in_seconds == 0: - gen_index = cnstat_fqn_file.rfind('/') - cnstat_fqn_general_file = cnstat_fqn_file[:gen_index] + cnstat_fqn_file[gen_index:].split('-')[0] - if os.path.isfile(cnstat_fqn_file) or (os.path.isfile(cnstat_fqn_general_file)): try: cnstat_cached_dict = {} diff --git a/scripts/intfutil b/scripts/intfutil index e327d1a607..fb351687a8 100755 --- a/scripts/intfutil +++ b/scripts/intfutil @@ -5,13 +5,6 @@ import os import re import sys -from natsort import natsorted -from tabulate import tabulate -from utilities_common import constants -from utilities_common import multi_asic as multi_asic_util -from utilities_common.intf_filter import parse_interface_in_filter -from sonic_py_common.interface import get_intf_longname - # mock the redis for unit test purposes # try: if os.environ["UTILITIES_UNIT_TESTING"] == "2": @@ -20,6 +13,8 @@ try: sys.path.insert(0, modules_path) sys.path.insert(0, tests_path) import mock_tables.dbconnector + from mock_platform_sfputil.mock_platform_sfputil import mock_platform_sfputil_helper + mock_platform_sfputil_helper() if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic": import mock_tables.mock_multi_asic mock_tables.dbconnector.load_namespace_config() @@ -27,6 +22,14 @@ try: except KeyError: pass +from natsort import natsorted +from tabulate import tabulate +from utilities_common import constants +from utilities_common import multi_asic as multi_asic_util +from utilities_common.intf_filter import parse_interface_in_filter +from utilities_common.platform_sfputil_helper import is_rj45_port, RJ45_PORT_TYPE +from sonic_py_common.interface import get_intf_longname + # ========================== Common interface-utils logic ========================== @@ -49,7 +52,7 @@ PORT_RMT_ADV_SPEEDS = 'rmt_adv_speeds' PORT_INTERFACE_TYPE = 'interface_type' PORT_ADV_INTERFACE_TYPES = 'adv_interface_types' PORT_TPID = "tpid" -OPTICS_TYPE_RJ45 = 'RJ45' +OPTICS_TYPE_RJ45 = RJ45_PORT_TYPE PORT_LINK_TRAINING = 'link_training' PORT_LINK_TRAINING_STATUS = 'link_training_status' @@ -161,10 +164,10 @@ def appl_db_port_status_get(appl_db, intf_name, status_type): if status is None: return "N/A" if status_type == PORT_SPEED and status != "N/A": - optics_type = state_db_port_optics_get(appl_db, intf_name, PORT_OPTICS_TYPE) + optics_type = port_optics_get(appl_db, intf_name, PORT_OPTICS_TYPE) status = port_speed_parse(status, optics_type) elif status_type == PORT_ADV_SPEEDS and status != "N/A" and status != "all": - optics_type = state_db_port_optics_get(appl_db, intf_name, PORT_OPTICS_TYPE) + optics_type = port_optics_get(appl_db, intf_name, PORT_OPTICS_TYPE) speed_list = status.split(',') new_speed_list = [] for s in natsorted(speed_list): @@ -181,7 +184,7 @@ def state_db_port_status_get(db, intf_name, field): if not status: return "N/A" if field in [PORT_RMT_ADV_SPEEDS] and status not in ["N/A", "all"]: - optics_type = state_db_port_optics_get(db, intf_name, PORT_OPTICS_TYPE) + optics_type = port_optics_get(db, intf_name, PORT_OPTICS_TYPE) speed_list = status.split(',') new_speed_list = [] for s in natsorted(speed_list): @@ -198,7 +201,7 @@ def port_oper_speed_get(db, intf_name): if oper_speed is None or oper_speed == "N/A" or oper_status != "up": return appl_db_port_status_get(db, intf_name, PORT_SPEED) else: - optics_type = state_db_port_optics_get(db, intf_name, PORT_OPTICS_TYPE) + optics_type = port_optics_get(db, intf_name, PORT_OPTICS_TYPE) return port_speed_parse(oper_speed, optics_type) def port_oper_speed_get_raw(db, intf_name): @@ -211,14 +214,17 @@ def port_oper_speed_get_raw(db, intf_name): speed = db.get(db.APPL_DB, PORT_STATUS_TABLE_PREFIX + intf_name, PORT_SPEED) return speed -def state_db_port_optics_get(state_db, intf_name, type): +def port_optics_get(state_db, intf_name, type): """ Get optic type info for port """ full_table_id = PORT_TRANSCEIVER_TABLE_PREFIX + intf_name optics_type = state_db.get(state_db.STATE_DB, full_table_id, type) if optics_type is None: - return "N/A" + if is_rj45_port(intf_name): + return OPTICS_TYPE_RJ45 + else: + return "N/A" return optics_type def merge_dicts(x,y): @@ -325,13 +331,13 @@ def po_speed_dict(po_int_dict, appl_db): # If no speed was returned, append None without format po_list.append(None) else: - optics_type = state_db_port_optics_get(appl_db, value[0], PORT_OPTICS_TYPE) + optics_type = port_optics_get(appl_db, value[0], PORT_OPTICS_TYPE) interface_speed = port_speed_parse(interface_speed, optics_type) po_list.append(interface_speed) elif len(value) > 1: for intf in value: temp_speed = port_oper_speed_get_raw(appl_db, intf) - optics_type = state_db_port_optics_get(appl_db, intf, PORT_OPTICS_TYPE) + optics_type = port_optics_get(appl_db, intf, PORT_OPTICS_TYPE) temp_speed = int(temp_speed) if temp_speed else 0 agg_speed_list.append(temp_speed) interface_speed = sum(agg_speed_list) @@ -477,7 +483,7 @@ class IntfStatus(object): config_db_vlan_port_keys_get(self.combined_int_to_vlan_po_dict, self.front_panel_ports_list, key), appl_db_port_status_get(self.db, key, PORT_OPER_STATUS), appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS), - state_db_port_optics_get(self.db, key, PORT_OPTICS_TYPE), + port_optics_get(self.db, key, PORT_OPTICS_TYPE), appl_db_port_status_get(self.db, key, PORT_PFC_ASYM_STATUS))) for po, value in self.portchannel_speed_dict.items(): diff --git a/scripts/mellanox_buffer_migrator.py b/scripts/mellanox_buffer_migrator.py index bc5c7cab16..6706969be1 100755 --- a/scripts/mellanox_buffer_migrator.py +++ b/scripts/mellanox_buffer_migrator.py @@ -480,8 +480,8 @@ def __init__(self, configDB, appDB, stateDB): "spc2_3800-d24c52_t1_pool_shp", "spc2_3800-d28c50_t1_pool_shp"], } }, - "version_3_0_0": { - # Version 3.0.0 is introduced for dynamic buffer calculation + "version_2_0_0": { + # Version 2.0.0 is introduced for dynamic buffer calculation # "pool_mapped_from_old_version": { "spc1_t0_pool": "spc1_pool", diff --git a/scripts/pfcstat b/scripts/pfcstat index 6d11361527..fb7e6018b6 100755 --- a/scripts/pfcstat +++ b/scripts/pfcstat @@ -18,9 +18,6 @@ from natsort import natsorted from tabulate import tabulate from sonic_py_common.multi_asic import get_external_ports -from utilities_common.netstat import ns_diff, STATUS_NA, format_number_with_comma -from utilities_common import multi_asic as multi_asic_util -from utilities_common import constants # mock the redis for unit test purposes # try: @@ -37,6 +34,12 @@ try: except KeyError: pass +from utilities_common.netstat import ns_diff, STATUS_NA, format_number_with_comma +from utilities_common import multi_asic as multi_asic_util +from utilities_common import constants +from utilities_common.cli import UserCache + + PStats = namedtuple("PStats", "pfc0, pfc1, pfc2, pfc3, pfc4, pfc5, pfc6, pfc7") header_Rx = ['Port Rx', 'PFC0', 'PFC1', 'PFC2', 'PFC3', 'PFC4', 'PFC5', 'PFC6', 'PFC7'] @@ -224,10 +227,10 @@ Examples: save_fresh_stats = args.clear delete_all_stats = args.delete - uid = str(os.getuid()) - cnstat_file = uid + cache = UserCache() + cnstat_file = 'pfcstat' - cnstat_dir = os.path.join(os.sep, "tmp", "pfcstat-{}".format(uid)) + cnstat_dir = cache.get_directory() cnstat_fqn_file_rx = os.path.join(cnstat_dir, "{}rx".format(cnstat_file)) cnstat_fqn_file_tx = os.path.join(cnstat_dir, "{}tx".format(cnstat_file)) @@ -239,15 +242,7 @@ Examples: pfcstat = Pfcstat(args.namespace, args.show) if delete_all_stats: - for file in os.listdir(cnstat_dir): - os.remove(os.path.join(cnstat_dir, file)) - - try: - os.rmdir(cnstat_dir) - sys.exit(0) - except IOError as e: - print(e.errno, e) - sys.exit(e) + cache.remove() """ Get the counters of pfc rx counter @@ -259,14 +254,6 @@ Examples: """ cnstat_dict_tx = deepcopy(pfcstat.get_cnstat(False)) - # At this point, either we'll create a file or open an existing one. - if not os.path.exists(cnstat_dir): - try: - os.makedirs(cnstat_dir) - except IOError as e: - print(e.errno, e) - sys.exit(1) - if save_fresh_stats: try: pickle.dump(cnstat_dict_rx, open(cnstat_fqn_file_rx, 'wb')) diff --git a/scripts/pg-drop b/scripts/pg-drop index b437e53bba..fee95124bd 100755 --- a/scripts/pg-drop +++ b/scripts/pg-drop @@ -26,6 +26,7 @@ try: except KeyError: pass +from utilities_common.cli import UserCache from swsscommon.swsscommon import ConfigDBConnector, SonicV2Connector STATUS_NA = 'N/A' @@ -38,8 +39,7 @@ COUNTERS_PG_PORT_MAP = "COUNTERS_PG_PORT_MAP" COUNTERS_PG_INDEX_MAP = "COUNTERS_PG_INDEX_MAP" def get_dropstat_dir(): - dropstat_dir_prefix = '/tmp/dropstat' - return "{}-{}/".format(dropstat_dir_prefix, os.getuid()) + return UserCache().get_directory() class PgDropStat(object): diff --git a/scripts/portconfig b/scripts/portconfig index 63bb463868..2bb2098cc9 100755 --- a/scripts/portconfig +++ b/scripts/portconfig @@ -65,7 +65,7 @@ SWITCH_CAPABILITY = "SWITCH_CAPABILITY|switch" # STATE_DB constants PORT_STATE_TABLE_NAME = "PORT_TABLE" PORT_STATE_SUPPORTED_SPEEDS = "supported_speeds" - +PORT_STATE_SUPPORTED_FECS = "supported_fecs" VALID_INTERFACE_TYPE_SET = set(['CR','CR2','CR4','CR8','SR','SR2','SR4','SR8', 'LR','LR4','LR8','KR','KR4','KR8','CAUI','GMII', @@ -131,6 +131,13 @@ class portconfig(object): def set_fec(self, port, fec): if self.verbose: print("Setting fec %s on port %s" % (fec, port)) + supported_fecs = self.get_supported_fecs(port) + if fec not in supported_fecs: + if supported_fecs: + print('fec {} is not in {}'.format(fec, supported_fecs)) + else: + print('Setting fec is not supported on port {}'.format(port)) + exit(1) self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_FEC_CONFIG_FIELD_NAME: fec}) def set_mtu(self, port, mtu): @@ -226,6 +233,26 @@ class portconfig(object): state_db.connect(state_db.STATE_DB) return state_db.get(state_db.STATE_DB, '{}|{}'.format(PORT_STATE_TABLE_NAME, port), PORT_STATE_SUPPORTED_SPEEDS) + def get_supported_fecs(self, port): + # If there is supported_fecs exposed in STATE_DB, let's use it. + # Otherwise, take the default + if not self.namespace: + state_db = SonicV2Connector(host="127.0.0.1") + else: + state_db = SonicV2Connector(host="127.0.0.1", namespace=self.namespace, use_unix_socket_path=True) + state_db.connect(state_db.STATE_DB) + + supported_fecs_str = state_db.get(state_db.STATE_DB, '{}|{}'.format(PORT_STATE_TABLE_NAME, port), PORT_STATE_SUPPORTED_FECS) + if supported_fecs_str: + if supported_fecs_str != 'N/A': + supported_fecs_list = supported_fecs_str.split(',') + else: + supported_fecs_list = [] + else: + supported_fecs_list = ["rs", "fc", "none"] + + return supported_fecs_list + def set_tpid(self, port, tpid): if self.verbose: print("Setting tpid %s on port %s" % (tpid, port)) @@ -276,7 +303,7 @@ def main(): parser.add_argument('-p', '--port', type=str, help='port name (e.g. Ethernet0)', required=True, default=None) parser.add_argument('-l', '--list', action='store_true', help='list port parametars', default=False) parser.add_argument('-s', '--speed', type=int, help='port speed value in Mbit', default=None) - parser.add_argument('-f', '--fec', type=str, help='port fec mode value in (none, rs, fc)', default=None) + parser.add_argument('-f', '--fec', type=str, help='port fec mode value (default is: none, rs, fc)', default=None) parser.add_argument('-m', '--mtu', type=int, help='port mtu value in bytes', default=None) parser.add_argument('-tp', '--tpid', type=str, help='port TPID value in hex (e.g. 0x8100)', default=None) parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0') diff --git a/scripts/portstat b/scripts/portstat index abc1bc67aa..3c3c5117f3 100755 --- a/scripts/portstat +++ b/scripts/portstat @@ -39,6 +39,8 @@ from utilities_common.intf_filter import parse_interface_in_filter import utilities_common.multi_asic as multi_asic_util from utilities_common.netstat import ns_diff, table_as_json, format_brate, format_prate, format_util, format_number_with_comma +from utilities_common.cli import UserCache + """ The order and count of statistics mentioned below needs to be in sync with the values in portstat script So, any fields added/deleted in here should be reflected in portstat script also @@ -49,12 +51,14 @@ NStats = namedtuple("NStats", "rx_ok, rx_err, rx_drop, rx_ovr, tx_ok,\ rx_uca, rx_mca, rx_bca, rx_all,\ tx_64, tx_65_127, tx_128_255, tx_256_511, tx_512_1023, tx_1024_1518, tx_1519_2047, tx_2048_4095, tx_4096_9216, tx_9217_16383,\ tx_uca, tx_mca, tx_bca, tx_all,\ - rx_jbr, rx_frag, rx_usize, rx_ovrrun") + rx_jbr, rx_frag, rx_usize, rx_ovrrun,\ + fec_corr, fec_uncorr, fec_symbol_err") header_all = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'RX_ERR', 'RX_DRP', 'RX_OVR', 'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_UTIL', 'TX_ERR', 'TX_DRP', 'TX_OVR'] header_std = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_UTIL', 'RX_ERR', 'RX_DRP', 'RX_OVR', 'TX_OK', 'TX_BPS', 'TX_UTIL', 'TX_ERR', 'TX_DRP', 'TX_OVR'] header_errors_only = ['IFACE', 'STATE', 'RX_ERR', 'RX_DRP', 'RX_OVR', 'TX_ERR', 'TX_DRP', 'TX_OVR'] +header_fec_only = ['IFACE', 'STATE', 'FEC_CORR', 'FEC_UNCORR', 'FEC_SYMBOL_ERR'] header_rates_only = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_UTIL'] rates_key_list = [ 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'TX_BPS', 'TX_PPS', 'TX_UTIL' ] @@ -65,7 +69,7 @@ RateStats = namedtuple("RateStats", ratestat_fields) The order and count of statistics mentioned below needs to be in sync with the values in portstat script So, any fields added/deleted in here should be reflected in portstat script also """ -BUCKET_NUM = 42 +BUCKET_NUM = 45 counter_bucket_dict = { 0:['SAI_PORT_STAT_IF_IN_UCAST_PKTS', 'SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS'], 1:['SAI_PORT_STAT_IF_IN_ERRORS'], @@ -108,7 +112,10 @@ counter_bucket_dict = { 38:['SAI_PORT_STAT_ETHER_STATS_JABBERS'], 39:['SAI_PORT_STAT_ETHER_STATS_FRAGMENTS'], 40:['SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS'], - 41:['SAI_PORT_STAT_IP_IN_RECEIVES'] + 41:['SAI_PORT_STAT_IP_IN_RECEIVES'], + 42:['SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES'], + 43:['SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES'], + 44:['SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS'] } STATUS_NA = 'N/A' @@ -248,7 +255,7 @@ class Portstat(object): return STATUS_NA - def cnstat_print(self, cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail=False): + def cnstat_print(self, cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail=False): """ Print the cnstat. """ @@ -293,6 +300,12 @@ class Portstat(object): format_number_with_comma(data.tx_err), format_number_with_comma(data.tx_drop), format_number_with_comma(data.tx_ovr))) + elif fec_stats_only: + header = header_fec_only + table.append((key, self.get_port_state(key), + format_number_with_comma(data.fec_corr), + format_number_with_comma(data.fec_uncorr), + format_number_with_comma(data.fec_symbol_err))) elif rates_only: header = header_rates_only table.append((key, self.get_port_state(key), @@ -386,7 +399,10 @@ class Portstat(object): print("Time Since Counters Last Cleared............... " + str(cnstat_old_dict.get('time'))) - def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail=False): + def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, + ratestat_dict, intf_list, use_json, + print_all, errors_only, fec_stats_only, + rates_only, detail=False): """ Print the difference between two cnstat results. """ @@ -463,6 +479,19 @@ class Portstat(object): format_number_with_comma(cntr.tx_err), format_number_with_comma(cntr.tx_drop), format_number_with_comma(cntr.tx_ovr))) + elif fec_stats_only: + header = header_fec_only + if old_cntr is not None: + table.append((key, self.get_port_state(key), + ns_diff(cntr.fec_corr, old_cntr.fec_corr), + ns_diff(cntr.fec_uncorr, old_cntr.fec_uncorr), + ns_diff(cntr.fec_symbol_err, old_cntr.fec_symbol_err))) + else: + table.append((key, self.get_port_state(key), + format_number_with_comma(cntr.fec_corr), + format_number_with_comma(cntr.fec_uncorr), + format_number_with_comma(cntr.fec_symbol_err))) + elif rates_only: header = header_rates_only if old_cntr is not None: @@ -549,6 +578,7 @@ Examples: parser.add_argument('-d', '--delete', action='store_true', help='Delete saved stats, either the uid or the specified tag') parser.add_argument('-D', '--delete-all', action='store_true', help='Delete all saved stats') parser.add_argument('-e', '--errors', action='store_true', help='Display interface errors') + parser.add_argument('-f', '--fec-stats', action='store_true', help='Display FEC related statistics') parser.add_argument('-j', '--json', action='store_true', help='Display in JSON format') parser.add_argument('-r', '--raw', action='store_true', help='Raw stats (unmodified output of netstat)') parser.add_argument('-R', '--rate', action='store_true', help='Display interface rates') @@ -565,11 +595,11 @@ Examples: delete_saved_stats = args.delete delete_all_stats = args.delete_all errors_only = args.errors + fec_stats_only = args.fec_stats rates_only = args.rate use_json = args.json raw_stats = args.raw tag_name = args.tag - uid = str(os.getuid()) wait_time_in_seconds = args.period print_all = args.all intf_fs = args.interface @@ -577,36 +607,17 @@ Examples: display_option = args.show detail = args.detail - if tag_name is not None: - cnstat_file = uid + "-" + tag_name - else: - cnstat_file = uid + cache = UserCache(tag=tag_name) - cnstat_dir = "/tmp/portstat-" + uid + cnstat_file = "portstat" + cnstat_dir = cache.get_directory() cnstat_fqn_file = cnstat_dir + "/" + cnstat_file if delete_all_stats: - for file in os.listdir(cnstat_dir): - os.remove(cnstat_dir + "/" + file) - - try: - os.rmdir(cnstat_dir) - sys.exit(0) - except IOError as e: - print(e.errno, e) - sys.exit(e) + cache.remove_all() if delete_saved_stats: - try: - os.remove(cnstat_fqn_file) - except IOError as e: - if e.errno != ENOENT: - print(e.errno, e) - sys.exit(1) - finally: - if os.listdir(cnstat_dir) == []: - os.rmdir(cnstat_dir) - sys.exit(0) + cache.remove() intf_list = parse_interface_in_filter(intf_fs) @@ -621,18 +632,9 @@ Examples: # Now decide what information to display if raw_stats: - portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only) + portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only) sys.exit(0) - # At this point, either we'll create a file or open an existing one. - if not os.path.exists(cnstat_dir): - try: - os.makedirs(cnstat_dir) - except IOError as e: - print(e.errno, e) - sys.exit(1) - - if save_fresh_stats: try: pickle.dump(cnstat_dict, open(cnstat_fqn_file, 'wb')) @@ -649,7 +651,7 @@ Examples: cnstat_cached_dict = pickle.load(open(cnstat_fqn_file, 'rb')) if not detail: print("Last cached time was " + str(cnstat_cached_dict.get('time'))) - portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail) + portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail) except IOError as e: print(e.errno, e) else: @@ -657,13 +659,13 @@ Examples: print("\nFile '%s' does not exist" % cnstat_fqn_file) print("Did you run 'portstat -c -t %s' to record the counters via tag %s?\n" % (tag_name, tag_name)) else: - portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail) + portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail) else: #wait for the specified time and then gather the new stats and output the difference. time.sleep(wait_time_in_seconds) print("The rates are calculated within %s seconds period" % wait_time_in_seconds) cnstat_new_dict, ratestat_new_dict = portstat.get_cnstat_dict() - portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, ratestat_new_dict, intf_list, use_json, print_all, errors_only, rates_only, detail) + portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, ratestat_new_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail) if __name__ == "__main__": main() diff --git a/scripts/queuestat b/scripts/queuestat index 1455494701..bb6539bbb8 100755 --- a/scripts/queuestat +++ b/scripts/queuestat @@ -29,6 +29,7 @@ except KeyError: pass from swsscommon.swsscommon import SonicV2Connector +from utilities_common.cli import UserCache QueueStats = namedtuple("QueueStats", "queueindex, queuetype, totalpacket, totalbytes, droppacket, dropbytes") header = ['Port', 'TxQ', 'Counter/pkts', 'Counter/bytes', 'Drop/pkts', 'Drop/bytes'] @@ -313,13 +314,6 @@ class Queuestat(object): print(json_dump(json_output)) def save_fresh_stats(self): - if not os.path.exists(cnstat_dir): - try: - os.makedirs(cnstat_dir) - except IOError as e: - print(e.errno, e) - sys.exit(1) - # Get stat for each port and save for port in natsorted(self.counter_port_name_map): cnstat_dict = self.get_cnstat(self.port_queues_map[port]) @@ -354,28 +348,19 @@ Examples: args = parser.parse_args() save_fresh_stats = args.clear - delete_all_stats = args.delete + delete_stats = args.delete voq = args.voq json_opt = args.json_opt port_to_show_stats = args.port - uid = str(os.getuid()) - cnstat_file = uid - - cnstat_dir = "/tmp/queuestat-" + uid - cnstat_fqn_file = cnstat_dir + "/" + cnstat_file + cache = UserCache() - if delete_all_stats: - for file in os.listdir(cnstat_dir): - os.remove(cnstat_dir + "/" + file) + cnstat_dir = cache.get_directory() + cnstat_fqn_file = os.path.join(cnstat_dir, 'queuestat') - try: - os.rmdir(cnstat_dir) - sys.exit(0) - except IOError as e: - print(e.errno, e) - sys.exit(e) + if delete_stats: + cache.remove() queuestat = Queuestat( voq ) diff --git a/scripts/route_check.py b/scripts/route_check.py index fdbdd0a5f8..90bdc7ea5c 100755 --- a/scripts/route_check.py +++ b/scripts/route_check.py @@ -281,7 +281,10 @@ def get_routes(): valid_rt = [] for k in keys: - if not is_vrf(k) and not is_local(k): + if (is_vrf(k)): + k = k.split(":", 1)[1] + + if not is_local(k): valid_rt.append(add_prefix_ifnot(k.lower())) print_message(syslog.LOG_DEBUG, json.dumps({"ROUTE_TABLE": sorted(valid_rt)}, indent=4)) @@ -447,6 +450,45 @@ def filter_out_vnet_routes(routes): return updated_routes +def filter_out_standalone_tunnel_routes(routes): + config_db = swsscommon.ConfigDBConnector() + config_db.connect() + device_metadata = config_db.get_table('DEVICE_METADATA') + subtype = device_metadata['localhost'].get('subtype', '') + + if subtype.lower() != 'dualtor': + return routes + + app_db = swsscommon.DBConnector('APPL_DB', 0) + neigh_table = swsscommon.Table(app_db, 'NEIGH_TABLE') + neigh_keys = neigh_table.getKeys() + standalone_tunnel_route_ips = [] + updated_routes = [] + + for neigh in neigh_keys: + _, mac = neigh_table.hget(neigh, 'neigh') + if mac == '00:00:00:00:00:00': + # remove preceding 'VlanXXXX' to get just the neighbor IP + neigh_ip = ':'.join(neigh.split(':')[1:]) + standalone_tunnel_route_ips.append(neigh_ip) + + if not standalone_tunnel_route_ips: + return routes + + for route in routes: + ip, subnet = route.split('/') + ip_version = ipaddress.ip_address(ip).version + + # we want to keep the route if it is not a standalone tunnel route. + # if the route subnet contains more than one address, it is not a + # standalone tunnel route + if (ip not in standalone_tunnel_route_ips) or \ + ((ip_version == 6 and subnet != '128') or (ip_version == 4 and subnet != '32')): + updated_routes.append(route) + + return updated_routes + + def check_routes(): """ The heart of this script which runs the checks. @@ -483,6 +525,7 @@ def check_routes(): _, rt_asic_miss = diff_sorted_lists(intf_appl, rt_asic_miss) rt_asic_miss = filter_out_default_routes(rt_asic_miss) rt_asic_miss = filter_out_vnet_routes(rt_asic_miss) + rt_asic_miss = filter_out_standalone_tunnel_routes(rt_asic_miss) # Check APPL-DB INTF_TABLE with ASIC table route entries intf_appl_miss, _ = diff_sorted_lists(intf_appl, rt_asic) diff --git a/scripts/route_check_test.sh b/scripts/route_check_test.sh index 505253863e..989cbfae0b 100755 --- a/scripts/route_check_test.sh +++ b/scripts/route_check_test.sh @@ -2,22 +2,36 @@ # add a route, interface & route-entry to simulate error # -sonic-db-cli APPL_DB hmset "ROUTE_TABLE:20c0:d9b8:99:80::/64" "nexthop" "fc00::72,fc00::76,fc00::7a,fc00::7e" "ifname" "PortChannel01,PortChannel02,PortChannel03,PortChannel04" +sonic-db-cli APPL_DB hmset "ROUTE_TABLE:20c0:d9b8:99:80::/64" "nexthop" "fc00::72,fc00::76,fc00::7a,fc00::7e" "ifname" "PortChannel01,PortChannel02,PortChannel03,PortChannel04" > /dev/null +sonic-db-cli ASIC_DB hmset "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"192.193.120.255/25\",\"switch_id\":\"oid:0x21000000000000\",\"vr\":\"oid:0x3000000000022\"}" "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID" "oid:0x5000000000614" > /dev/null +sonic-db-cli APPL_DB hmset "INTF_TABLE:PortChannel01:10.0.0.99/31" "scope" "global" "family" "IPv4" > /dev/null +echo "------" +echo "expect errors!" +echo "Running Route Check..." +./route_check.py +echo "return value: $?" -sonic-db-cli ASIC_DB hmset "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"192.193.120.255/25\",\"switch_id\":\"oid:0x21000000000000\",\"vr\":\"oid:0x3000000000022\"}" "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID" "oid:0x5000000000614" +sonic-db-cli APPL_DB del "ROUTE_TABLE:20c0:d9b8:99:80::/64" > /dev/null +sonic-db-cli ASIC_DB del "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"192.193.120.255/25\",\"switch_id\":\"oid:0x21000000000000\",\"vr\":\"oid:0x3000000000022\"}" > /dev/null +sonic-db-cli APPL_DB del "INTF_TABLE:PortChannel01:10.0.0.99/31" > /dev/null -sonic-db-cli APPL_DB hmset "INTF_TABLE:PortChannel01:10.0.0.99/31" "scope" "global" "family" "IPv4" +# add standalone tunnel route to simulate unreachable neighbor scenario on dual ToR +# in this scenario, we expect the route mismatch to be ignored +sonic-db-cli APPL_DB hmset "NEIGH_TABLE:Vlan1000:fc02:1000::99" "neigh" "00:00:00:00:00:00" "family" "IPv6" > /dev/null +sonic-db-cli ASIC_DB hmset 'ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{"dest":"fc02:1000::99/128","switch_id":"oid:0x21000000000000","vr":"oid:0x300000000007c"}' "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID" "oid:0x400000000167d" "SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION" "SAI_PACKET_ACTION_FORWARD" > /dev/null -echo "expect errors!\n------\nRunning Route Check...\n" +echo "------" +echo "expect success on dualtor, expect error on all other devices!" +echo "Running Route Check..." ./route_check.py echo "return value: $?" -sonic-db-cli APPL_DB del "ROUTE_TABLE:20c0:d9b8:99:80::/64" -sonic-db-cli ASIC_DB del "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"192.193.120.255/25\",\"switch_id\":\"oid:0x21000000000000\",\"vr\":\"oid:0x3000000000022\"}" -sonic-db-cli APPL_DB del "INTF_TABLE:PortChannel01:10.0.0.99/31" - +sonic-db-cli APPL_DB del "NEIGH_TABLE:Vlan1000:fc02:1000::99" > /dev/null +sonic-db-cli ASIC_DB del 'ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{"dest":"fc02:1000::99/128","switch_id":"oid:0x21000000000000","vr":"oid:0x300000000007c"}' > /dev/null -echo "expect success!\n------\nRunning Route Check...\n" +echo "------" +echo "expect success!" +echo "Running Route Check..." ./route_check.py echo "return value: $?" diff --git a/scripts/sfpshow b/scripts/sfpshow index 3d71408202..d292dddb85 100755 --- a/scripts/sfpshow +++ b/scripts/sfpshow @@ -17,7 +17,6 @@ from natsort import natsorted from sonic_py_common.interface import front_panel_prefix, backplane_prefix, inband_prefix, recirc_prefix from sonic_py_common import multi_asic from tabulate import tabulate -from utilities_common import multi_asic as multi_asic_util # Mock the redis DB for unit test purposes try: @@ -27,12 +26,17 @@ try: sys.path.insert(0, modules_path) sys.path.insert(0, test_path) import mock_tables.dbconnector + from mock_platform_sfputil.mock_platform_sfputil import mock_platform_sfputil_helper + mock_platform_sfputil_helper() if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic": import mock_tables.mock_multi_asic mock_tables.dbconnector.load_namespace_config() except KeyError: pass +from utilities_common import multi_asic as multi_asic_util +from utilities_common.platform_sfputil_helper import is_rj45_port, RJ45_PORT_TYPE + # TODO: We should share these maps and the formatting functions between sfputil and sfpshow QSFP_DATA_MAP = { 'model': 'Vendor PN', @@ -215,8 +219,6 @@ QSFP_DD_DOM_VALUE_UNIT_MAP = { 'voltage': 'Volts' } -RJ45_PORT_TYPE = 'RJ45' - def display_invalid_intf_eeprom(intf_name): output = intf_name + ': SFP EEPROM Not detected\n' @@ -231,7 +233,6 @@ def display_invalid_intf_presence(intf_name): class SFPShow(object): - def __init__(self, intf_name, namespace_option, dump_dom=False): super(SFPShow, self).__init__() self.db = None @@ -394,41 +395,47 @@ class SFPShow(object): output = '' sfp_info_dict = state_db.get_all(state_db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(interface_name)) - if sfp_info_dict['type'] == RJ45_PORT_TYPE: - output = 'SFP EEPROM is not applicable for RJ45 port\n' + if sfp_info_dict: + if sfp_info_dict['type'] == RJ45_PORT_TYPE: + output = 'SFP EEPROM is not applicable for RJ45 port\n' + else: + output = 'SFP EEPROM detected\n' + sfp_info_output = self.convert_sfp_info_to_output_string(sfp_info_dict) + output += sfp_info_output + + if dump_dom: + sfp_type = sfp_info_dict['type'] + dom_info_dict = state_db.get_all(state_db.STATE_DB, 'TRANSCEIVER_DOM_SENSOR|{}'.format(interface_name)) + dom_output = self.convert_dom_to_output_string(sfp_type, dom_info_dict) + output += dom_output else: - output = 'SFP EEPROM detected\n' - sfp_info_output = self.convert_sfp_info_to_output_string(sfp_info_dict) - output += sfp_info_output - - if dump_dom: - sfp_type = sfp_info_dict['type'] - dom_info_dict = state_db.get_all(state_db.STATE_DB, 'TRANSCEIVER_DOM_SENSOR|{}'.format(interface_name)) - dom_output = self.convert_dom_to_output_string(sfp_type, dom_info_dict) - output += dom_output + if is_rj45_port(interface_name): + output = 'SFP EEPROM is not applicable for RJ45 port\n' + else: + output = "SFP EEPROM Not detected\n" return output @multi_asic_util.run_on_multi_asic def get_eeprom(self): if self.intf_name is not None: - presence = self.db.exists(self.db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(self.intf_name)) - if presence: - self.intf_eeprom[self.intf_name] = self.convert_interface_sfp_info_to_cli_output_string( - self.db, self.intf_name, self.dump_dom) - else: - self.intf_eeprom[self.intf_name] = "SFP EEPROM Not detected\n" + self.intf_eeprom[self.intf_name] = self.convert_interface_sfp_info_to_cli_output_string( + self.db, self.intf_name, self.dump_dom) else: port_table_keys = self.db.keys(self.db.APPL_DB, "PORT_TABLE:*") for i in port_table_keys: interface = re.split(':', i, maxsplit=1)[-1].strip() if interface and interface.startswith(front_panel_prefix()) and not interface.startswith((backplane_prefix(), inband_prefix(), recirc_prefix())): - presence = self.db.exists(self.db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(interface)) - if presence: - self.intf_eeprom[interface] = self.convert_interface_sfp_info_to_cli_output_string( - self.db, interface, self.dump_dom) - else: - self.intf_eeprom[interface] = "SFP EEPROM Not detected\n" + self.intf_eeprom[interface] = self.convert_interface_sfp_info_to_cli_output_string( + self.db, interface, self.dump_dom) + + def convert_interface_sfp_presence_state_to_cli_output_string(self, state_db, interface_name): + sfp_info_dict = state_db.get_all(self.db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(interface_name)) + if sfp_info_dict: + output = 'Present' + else: + output = 'Not present' + return output @multi_asic_util.run_on_multi_asic @@ -436,21 +443,15 @@ class SFPShow(object): port_table = [] if self.intf_name is not None: - presence = self.db.exists(self.db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(self.intf_name)) - if presence: - port_table.append((self.intf_name, 'Present')) - else: - port_table.append((self.intf_name, 'Not present')) + presence_string = self.convert_interface_sfp_presence_state_to_cli_output_string(self.db, self.intf_name) + port_table.append((self.intf_name, presence_string)) else: port_table_keys = self.db.keys(self.db.APPL_DB, "PORT_TABLE:*") for i in port_table_keys: key = re.split(':', i, maxsplit=1)[-1].strip() if key and key.startswith(front_panel_prefix()) and not key.startswith((backplane_prefix(), inband_prefix(), recirc_prefix())): - presence = self.db.exists(self.db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(key)) - if presence: - port_table.append((key, 'Present')) - else: - port_table.append((key, 'Not present')) + presence_string = self.convert_interface_sfp_presence_state_to_cli_output_string(self.db, key) + port_table.append((key, presence_string)) self.table += port_table diff --git a/scripts/tunnelstat b/scripts/tunnelstat index 00aab5d832..8b045ec684 100755 --- a/scripts/tunnelstat +++ b/scripts/tunnelstat @@ -29,6 +29,7 @@ from collections import namedtuple, OrderedDict from natsort import natsorted from tabulate import tabulate from utilities_common.netstat import ns_diff, table_as_json, STATUS_NA, format_prate +from utilities_common.cli import UserCache from swsscommon.swsscommon import SonicV2Connector @@ -112,12 +113,12 @@ class Tunnelstat(object): if counter_tunnel_type_map is None: print("No %s in the DB!" % COUNTERS_TUNNEL_TYPE_MAP) - sys.exit(1) + sys.exit(1) if tun_type and tun_type not in counter_types: print("Unknown tunnel type %s" % tun_type) sys.exit(1) - + if tunnel and not tunnel in counter_tunnel_name_map: print("Interface %s missing from %s! Make sure it exists" % (tunnel, COUNTERS_TUNNEL_NAME_MAP)) sys.exit(2) @@ -250,56 +251,26 @@ def main(): delete_all_stats = args.delete_all use_json = args.json tag_name = args.tag if args.tag else "" - uid = str(os.getuid()) wait_time_in_seconds = args.period tunnel_name = args.tunnel if args.tunnel else "" tunnel_type = args.type if args.type else "" - # fancy filename with dashes: uid-tag-tunnel / uid-tunnel / uid-tag etc - filename_components = [uid, tag_name] - cnstat_file = "-".join(filter(None, filename_components)) + cache = UserCache(tag=tag_name) + + cnstat_file = "tunnelstat" - cnstat_dir = "/tmp/tunnelstat-" + uid + cnstat_dir = cache.get_directory() cnstat_fqn_file = cnstat_dir + "/" + cnstat_file if delete_all_stats: - # There is nothing to delete - if not os.path.isdir(cnstat_dir): - sys.exit(0) - - for file in os.listdir(cnstat_dir): - os.remove(cnstat_dir + "/" + file) - - try: - os.rmdir(cnstat_dir) - sys.exit(0) - except IOError as e: - print(e.errno, e) - sys.exit(e) + cache.remove_all() if delete_saved_stats: - try: - os.remove(cnstat_fqn_file) - except IOError as e: - if e.errno != ENOENT: - print(e.errno, e) - sys.exit(1) - finally: - if os.listdir(cnstat_dir) == []: - os.rmdir(cnstat_dir) - sys.exit(0) + cache.remove() tunnelstat = Tunnelstat() cnstat_dict,ratestat_dict = tunnelstat.get_cnstat(tunnel=tunnel_name, tun_type=tunnel_type) - # At this point, either we'll create a file or open an existing one. - if not os.path.exists(cnstat_dir): - try: - os.makedirs(cnstat_dir) - except IOError as e: - print(e.errno, e) - sys.exit(1) - if save_fresh_stats: try: pickle.dump(cnstat_dict, open(cnstat_fqn_file, 'wb')) diff --git a/scripts/watermarkstat b/scripts/watermarkstat index 025f87691d..745a82d674 100755 --- a/scripts/watermarkstat +++ b/scripts/watermarkstat @@ -208,16 +208,23 @@ class Watermarkstat(object): self.header_list = ['Port'] header_map = wm_type["obj_map"] - single_key = list(header_map.keys())[0] - header_len = len(header_map[single_key]) - min_idx = sys.maxsize - for name, counter_oid in header_map[single_key].items(): - curr_idx = int(wm_type["idx_func"](counter_oid)) - min_idx = min(min_idx, curr_idx) + max_idx = 0 + min_idx = sys.maxsize + for port in header_map.keys(): + for element in header_map[port].keys(): + element_idx = int(element.split(':')[1]) + if element_idx > max_idx: + max_idx = element_idx + if min_idx > element_idx: + min_idx = element_idx + + if min_idx == sys.maxsize: + print("Object map is empty!", file=sys.stderr) + sys.exit(1) self.min_idx = min_idx - self.header_list += ["{}{}".format(wm_type["header_prefix"], idx) for idx in range(self.min_idx, self.min_idx + header_len)] + self.header_list += ["{}{}".format(wm_type["header_prefix"], idx) for idx in range(self.min_idx, max_idx + 1)] def get_counters(self, table_prefix, port_obj, idx_func, watermark): """ diff --git a/sfputil/main.py b/sfputil/main.py index d567f39a0d..96653bb622 100644 --- a/sfputil/main.py +++ b/sfputil/main.py @@ -16,6 +16,7 @@ import click import sonic_platform import sonic_platform_base.sonic_sfp.sfputilhelper +from sonic_platform_base.sfp_base import SfpBase from swsscommon.swsscommon import SonicV2Connector from natsort import natsorted from sonic_py_common import device_info, logger, multi_asic @@ -291,33 +292,16 @@ def is_sfp_present(port_name): return bool(presence) -# Below defined two flavors of functions to determin whether a port is a RJ45 port. -# They serve different types of SFP utilities. One type of SFP utility consume the -# info stored in the STATE_DB, these utilities shall call 'is_rj45_port_from_db' -# to judge the port type. Another type of utilities will call the platform API -# directly to access SFP, for them shall use 'is_rj45_port_from_api'. -def is_rj45_port_from_db(port_name, db): - intf_type = db.get(db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(port_name), 'type') - return intf_type == RJ45_PORT_TYPE - - -def is_rj45_port_from_api(port_name): +def is_port_type_rj45(port_name): physical_port = logical_port_to_physical_port_index(port_name) - sfp = platform_chassis.get_sfp(physical_port) try: - port_type = sfp.get_transceiver_info()['type'] + port_types = platform_chassis.get_port_or_cage_type(physical_port) + return SfpBase.SFP_PORT_TYPE_BIT_RJ45 == port_types except NotImplementedError: - click.echo("Not able to judge the port type due to get_transceiver_info not implemented!", err=True) - sys.exit(ERROR_NOT_IMPLEMENTED) + pass - return port_type == RJ45_PORT_TYPE - - -def skip_if_port_is_rj45(port_name): - if is_rj45_port_from_api(port_name): - click.echo("This functionality is not applicable for RJ45 port {}.".format(port_name)) - sys.exit(EXIT_FAIL) + return False # ========================== Methods for formatting output ========================== # Convert dict values to cli output string @@ -659,7 +643,7 @@ def eeprom(port, dump_dom, namespace): for physical_port in physical_port_list: port_name = get_physical_port_name(logical_port_name, i, ganged) - if is_rj45_port_from_api(port_name): + if is_port_type_rj45(port_name): output += "{}: SFP EEPROM is not applicable for RJ45 port\n".format(port_name) output += '\n' continue @@ -725,6 +709,7 @@ def presence(port): logical_port_list = [port] + logical_port_list = natsort.natsorted(logical_port_list) for logical_port_name in logical_port_list: ganged = False i = 1 @@ -817,7 +802,7 @@ def fetch_error_status_from_platform_api(port): physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) port_name = get_physical_port_name(logical_port_name, 1, False) - if is_rj45_port_from_api(logical_port_name): + if is_port_type_rj45(logical_port_name): output.append([port_name, "N/A"]) else: output.append([port_name, output_dict.get(physical_port_list[0])]) @@ -843,7 +828,7 @@ def fetch_error_status_from_state_db(port, state_db): sorted_ports = natsort.natsorted(status) output = [] for port in sorted_ports: - if is_rj45_port_from_db(port, state_db): + if is_port_type_rj45(port): description = "N/A" else: statestring = status[port].get('status') @@ -919,7 +904,7 @@ def lpmode(port): click.echo("Error: No physical ports found for logical port '{}'".format(logical_port_name)) return - if is_rj45_port_from_api(logical_port_name): + if is_port_type_rj45(logical_port_name): output_table.append([logical_port_name, "N/A"]) else: if len(physical_port_list) > 1: @@ -962,7 +947,7 @@ def fwversion(port_name): physical_port = logical_port_to_physical_port_index(port_name) sfp = platform_chassis.get_sfp(physical_port) - if is_rj45_port_from_api(port_name): + if is_port_type_rj45(port_name): click.echo("Show firmware version is not applicable for RJ45 port {}.".format(port_name)) sys.exit(EXIT_FAIL) @@ -1001,7 +986,7 @@ def set_lpmode(logical_port, enable): click.echo("Error: No physical ports found for logical port '{}'".format(logical_port)) return - if is_rj45_port_from_api(logical_port): + if is_port_type_rj45(logical_port): click.echo("{} low-power mode is not applicable for RJ45 port {}.".format("Enabling" if enable else "Disabling", logical_port)) sys.exit(EXIT_FAIL) @@ -1061,7 +1046,7 @@ def reset(port_name): click.echo("Error: No physical ports found for logical port '{}'".format(port_name)) return - if is_rj45_port_from_api(port_name): + if is_port_type_rj45(port_name): click.echo("Reset is not applicable for RJ45 port {}.".format(port_name)) sys.exit(EXIT_FAIL) @@ -1226,12 +1211,14 @@ def download_firmware(port_name, filepath): def run(port_name, mode): """Run the firmware with default mode=1""" + if is_port_type_rj45(port_name): + click.echo("This functionality is not applicable for RJ45 port {}.".format(port_name)) + sys.exit(EXIT_FAIL) + if not is_sfp_present(port_name): click.echo("{}: SFP EEPROM not detected\n".format(port_name)) sys.exit(EXIT_FAIL) - skip_if_port_is_rj45(port_name) - status = run_firmware(port_name, int(mode)) if status != 1: click.echo('Failed to run firmware in mode={}! CDB status: {}'.format(mode, status)) @@ -1245,12 +1232,14 @@ def run(port_name, mode): def commit(port_name): """Commit the running firmware""" + if is_port_type_rj45(port_name): + click.echo("This functionality is not applicable for RJ45 port {}.".format(port_name)) + sys.exit(EXIT_FAIL) + if not is_sfp_present(port_name): click.echo("{}: SFP EEPROM not detected\n".format(port_name)) sys.exit(EXIT_FAIL) - skip_if_port_is_rj45(port_name) - status = commit_firmware(port_name) if status != 1: click.echo('Failed to commit firmware! CDB status: {}'.format(status)) @@ -1267,12 +1256,14 @@ def upgrade(port_name, filepath): physical_port = logical_port_to_physical_port_index(port_name) + if is_port_type_rj45(port_name): + click.echo("This functionality is not applicable for RJ45 port {}.".format(port_name)) + sys.exit(EXIT_FAIL) + if not is_sfp_present(port_name): click.echo("{}: SFP EEPROM not detected\n".format(port_name)) sys.exit(EXIT_FAIL) - skip_if_port_is_rj45(port_name) - show_firmware_version(physical_port) status = download_firmware(port_name, filepath) @@ -1303,12 +1294,14 @@ def upgrade(port_name, filepath): def download(port_name, filepath): """Download firmware on the transceiver""" + if is_port_type_rj45(port_name): + click.echo("This functionality is not applicable for RJ45 port {}.".format(port_name)) + sys.exit(EXIT_FAIL) + if not is_sfp_present(port_name): click.echo("{}: SFP EEPROM not detected\n".format(port_name)) sys.exit(EXIT_FAIL) - skip_if_port_is_rj45(port_name) - start = time.time() status = download_firmware(port_name, filepath) if status == 1: @@ -1329,7 +1322,9 @@ def unlock(port_name, password): physical_port = logical_port_to_physical_port_index(port_name) sfp = platform_chassis.get_sfp(physical_port) - skip_if_port_is_rj45(port_name) + if is_port_type_rj45(port_name): + click.echo("This functionality is not applicable for RJ45 port {}.".format(port_name)) + sys.exit(EXIT_FAIL) if not is_sfp_present(port_name): click.echo("{}: SFP EEPROM not detected\n".format(port_name)) diff --git a/show/interfaces/__init__.py b/show/interfaces/__init__.py index 7f218f41db..25cfd045e0 100644 --- a/show/interfaces/__init__.py +++ b/show/interfaces/__init__.py @@ -338,7 +338,7 @@ def expected(db, interfacename): @click.pass_context def mpls(ctx, interfacename, namespace, display): """Show Interface MPLS status""" - + #Edge case: Force show frontend interfaces on single asic if not (multi_asic.is_multi_asic()): if (display == 'frontend' or display == 'all' or display is None): @@ -346,7 +346,7 @@ def mpls(ctx, interfacename, namespace, display): else: print("Error: Invalid display option command for single asic") return - + display = "all" if interfacename else display masic = multi_asic_util.MultiAsic(display_option=display, namespace_option=namespace) ns_list = masic.get_ns_list_based_on_options() @@ -372,13 +372,13 @@ def mpls(ctx, interfacename, namespace, display): if (interfacename is not None): if (interfacename != ifname): continue - + intf_found = True - + if (display != "all"): if ("Loopback" in ifname): continue - + if ifname.startswith("Ethernet") and multi_asic.is_port_internal(ifname, ns): continue @@ -391,11 +391,11 @@ def mpls(ctx, interfacename, namespace, display): if 'mpls' not in mpls_intf or mpls_intf['mpls'] == 'disable': intfs_data.update({ifname: 'disable'}) else: - intfs_data.update({ifname: mpls_intf['mpls']}) - + intfs_data.update({ifname: mpls_intf['mpls']}) + # Check if interface is valid if (interfacename is not None and not intf_found): - ctx.fail('interface {} doesn`t exist'.format(interfacename)) + ctx.fail('interface {} doesn`t exist'.format(interfacename)) header = ['Interface', 'MPLS State'] body = [] @@ -558,6 +558,23 @@ def errors(verbose, period, namespace, display): clicommon.run_command(cmd, display_cmd=verbose) +# 'fec-stats' subcommand ("show interfaces counters errors") +@counters.command('fec-stats') +@click.option('-p', '--period') +@multi_asic_util.multi_asic_click_options +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def fec_stats(verbose, period, namespace, display): + """Show interface counters fec-stats""" + cmd = "portstat -f" + if period is not None: + cmd += " -p {}".format(period) + + cmd += " -s {}".format(display) + if namespace is not None: + cmd += " -n {}".format(namespace) + + clicommon.run_command(cmd, display_cmd=verbose) + # 'rates' subcommand ("show interfaces counters rates") @counters.command() @click.option('-p', '--period') diff --git a/show/plugins/pbh.py b/show/plugins/pbh.py index e50f6507a5..407c596163 100644 --- a/show/plugins/pbh.py +++ b/show/plugins/pbh.py @@ -14,14 +14,14 @@ import utilities_common.cli as clicommon from swsscommon.swsscommon import SonicV2Connector -PBH_COUNTERS_LOCATION = '/tmp/.pbh_counters.txt' - COUNTER_PACKETS_ATTR = "SAI_ACL_COUNTER_ATTR_PACKETS" COUNTER_BYTES_ATTR = "SAI_ACL_COUNTER_ATTR_BYTES" COUNTERS = "COUNTERS" ACL_COUNTER_RULE_MAP = "ACL_COUNTER_RULE_MAP" +PBH_COUNTERS_CACHE_FILENAME = "pbh-counters" + pbh_hash_field_tbl_name = 'PBH_HASH_FIELD' pbh_hash_tbl_name = 'PBH_HASH' pbh_table_tbl_name = 'PBH_TABLE' @@ -428,15 +428,18 @@ def deserialize_pbh_counters(): obj: counters dict. """ + cache = clicommon.UserCache('pbh') + counters_cache_file = os.path.join(cache.get_directory(), PBH_COUNTERS_CACHE_FILENAME) + def remap_keys(obj): res = {} for e in obj: res[e['key'][0], e['key'][1]] = e['value'] return res - if os.path.isfile(PBH_COUNTERS_LOCATION): + if os.path.isfile(counters_cache_file): try: - with open(PBH_COUNTERS_LOCATION, 'r') as f: + with open(counters_cache_file, 'r') as f: return remap_keys(json.load(f)) except Exception as err: pass diff --git a/show/plugins/sonic-passwh_yang.py b/show/plugins/sonic-passwh_yang.py new file mode 100644 index 0000000000..04f56877a0 --- /dev/null +++ b/show/plugins/sonic-passwh_yang.py @@ -0,0 +1,126 @@ +""" +Auto-generated show CLI plugin. + + +""" + +import click +import tabulate +import utilities_common.cli as clicommon + + + + + +def format_attr_value(entry, attr): + """ Helper that formats attribute to be presented in the table output. + + Args: + entry (Dict[str, str]): CONFIG DB entry configuration. + attr (Dict): Attribute metadata. + + Returns: + str: fomatted attribute value. + """ + + if attr["is-leaf-list"]: + return "\n".join(entry.get(attr["name"], [])) + return entry.get(attr["name"], "N/A") + + +@click.group(name="passw-hardening", + cls=clicommon.AliasedGroup) +def PASSW_HARDENING(): + """ PASSWORD HARDENING part of config_db.json """ + + pass + + + +@PASSW_HARDENING.command(name="policies") +@clicommon.pass_db +def PASSW_HARDENING_POLICIES(db): + """ """ + + header = [ + +"STATE", +"EXPIRATION", +"EXPIRATION WARNING", +"HISTORY CNT", +"LEN MIN", +"REJECT USER PASSW MATCH", +"LOWER CLASS", +"UPPER CLASS", +"DIGITS CLASS", +"SPECIAL CLASS", + +] + + body = [] + + table = db.cfgdb.get_table("PASSW_HARDENING") + entry = table.get("POLICIES", {}) + row = [ + format_attr_value( + entry, + {'name': 'state', 'description': 'state of the feature', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'expiration', 'description': 'expiration time (days unit)', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'expiration_warning', 'description': 'expiration warning time (days unit)', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'history_cnt', 'description': 'num of old password that the system will recorded', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'len_min', 'description': 'password min length', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'reject_user_passw_match', 'description': 'username password match', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'lower_class', 'description': 'password lower chars policy', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'upper_class', 'description': 'password upper chars policy', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'digits_class', 'description': 'password digits chars policy', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), + format_attr_value( + entry, + {'name': 'special_class', 'description': 'password special chars policy', 'is-leaf-list': False, 'is-mandatory': False, 'group': ''} + ), +] + + body.append(row) + click.echo(tabulate.tabulate(body, header)) + + + + + +def register(cli): + """ Register new CLI nodes in root CLI. + + Args: + cli (click.core.Command): Root CLI node. + Raises: + Exception: when root CLI already has a command + we are trying to register. + """ + cli_node = PASSW_HARDENING + if cli_node.name in cli.commands: + raise Exception(f"{cli_node.name} already exists in CLI") + cli.add_command(PASSW_HARDENING) diff --git a/sonic-utilities-data/templates/service_mgmt.sh.j2 b/sonic-utilities-data/templates/service_mgmt.sh.j2 index e46ba47380..d206049015 100644 --- a/sonic-utilities-data/templates/service_mgmt.sh.j2 +++ b/sonic-utilities-data/templates/service_mgmt.sh.j2 @@ -28,7 +28,7 @@ if [[ -f /etc/sonic/${SERVICE}_dependent ]]; then fi if [[ -f /etc/sonic/${SERVICE}_multi_inst_dependent ]]; then - MULTI_INST_DEPENDENT="${MULTI_INST_DEPENDENT} cat /etc/sonic/${SERVICE}_multi_inst_dependent" + MULTI_INST_DEPENDENT="${MULTI_INST_DEPENDENT} $(cat /etc/sonic/${SERVICE}_multi_inst_dependent)" fi function debug() diff --git a/sonic_installer/main.py b/sonic_installer/main.py index 0056bb77e5..9b75db6add 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -134,7 +134,7 @@ def get_docker_tag_name(image): def echo_and_log(msg, priority=LOG_NOTICE, fg=None): - if priority >= LOG_ERR: + if priority == LOG_ERR: # Print to stderr if priority is error click.secho(msg, fg=fg, err=True) else: @@ -460,8 +460,10 @@ def __enter__(self): meminfo = self.read_from_meminfo() mem_total_in_bytes = meminfo["MemTotal"] * SWAPAllocator.KiB_TO_BYTES_FACTOR mem_avail_in_bytes = meminfo["MemAvailable"] * SWAPAllocator.KiB_TO_BYTES_FACTOR - if (mem_total_in_bytes < self.total_mem_threshold * SWAPAllocator.MiB_TO_BYTES_FACTOR - or mem_avail_in_bytes < self.available_mem_threshold * SWAPAllocator.MiB_TO_BYTES_FACTOR): + swap_total_in_bytes = meminfo["SwapTotal"] * SWAPAllocator.KiB_TO_BYTES_FACTOR + swap_free_in_bytes = meminfo["SwapFree"] * SWAPAllocator.KiB_TO_BYTES_FACTOR + if (mem_total_in_bytes + swap_total_in_bytes < self.total_mem_threshold * SWAPAllocator.MiB_TO_BYTES_FACTOR + or mem_avail_in_bytes + swap_free_in_bytes < self.available_mem_threshold * SWAPAllocator.MiB_TO_BYTES_FACTOR): echo_and_log("Setup SWAP memory") swapfile = SWAPAllocator.SWAP_FILE_PATH if os.path.exists(swapfile): @@ -647,7 +649,7 @@ def set_fips(image, enable_fips): bootloader = get_bootloader() if not image: image = bootloader.get_next_image() - if image not in bootloader.get_installed_images(): + if image not in bootloader.get_installed_images(): echo_and_log('Error: Image does not exist', LOG_ERR) sys.exit(1) bootloader.set_fips(image, enable=enable_fips) @@ -743,7 +745,8 @@ def cleanup(): "swss", "syncd", "teamd", - "telemetry" + "telemetry", + "mgmt-framework" ] # Upgrade docker image @@ -786,16 +789,8 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): echo_and_log("Image file '{}' does not exist or is not a regular file. Aborting...".format(image_path), LOG_ERR) raise click.Abort() - warm_configured = False # warm restart enable/disable config is put in stateDB, not persistent across cold reboot, not saved to config_DB.json file - state_db = SonicV2Connector(host='127.0.0.1') - state_db.connect(state_db.STATE_DB, False) - TABLE_NAME_SEPARATOR = '|' - prefix = 'WARM_RESTART_ENABLE_TABLE' + TABLE_NAME_SEPARATOR - _hash = '{}{}'.format(prefix, container_name) - if state_db.get(state_db.STATE_DB, _hash, "enable") == "true": - warm_configured = True - state_db.close(state_db.STATE_DB) + warm_configured = hget_warm_restart_table('STATE_DB', 'WARM_RESTART_ENABLE_TABLE', container_name, 'enable') == "true" if container_name == "swss" or container_name == "bgp" or container_name == "teamd": if warm_configured is False and warm: @@ -866,23 +861,19 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): run_command("docker tag %s:latest %s:%s" % (image_name, image_name, tag)) run_command("systemctl restart %s" % container_name) - # All images id under the image name - image_id_all = get_container_image_id_all(image_name) - - # this is image_id for image with "latest" tag - image_id_latest = get_container_image_id(image_latest) - - for id in image_id_all: - if id != image_id_latest: - # Unless requested, the previoud docker image will be preserved - if not cleanup_image and id == image_id_previous: - continue - run_command("docker rmi -f %s" % id) + if cleanup_image: + # All images id under the image name + image_id_all = get_container_image_id_all(image_name) + # Unless requested, the previoud docker image will be preserved + for id in image_id_all: + if id == image_id_previous: + run_command("docker rmi -f %s" % id) + break exp_state = "reconciled" state = "" # post warm restart specific procssing for swss, bgp and teamd dockers, wait for reconciliation state. - if warm_configured is True or warm: + if warm_app_names and (warm_configured is True or warm): count = 0 for warm_app_name in warm_app_names: state = "" @@ -939,6 +930,7 @@ def rollback_docker(container_name): for id in image_id_all: if id != image_id_previous: version_tag = get_docker_tag_name(id) + break # make previous image as latest run_command("docker tag %s:%s %s:latest" % (image_name, version_tag, image_name)) diff --git a/sonic_package_manager/registry.py b/sonic_package_manager/registry.py index 5cac5469bf..7351323570 100644 --- a/sonic_package_manager/registry.py +++ b/sonic_package_manager/registry.py @@ -43,10 +43,9 @@ def get_token(bearer: Dict) -> str: content = json.loads(response.content) token = content['token'] - expires_in = content['expires_in'] log.debug(f'authentication token for bearer={bearer}: ' - f'token={token} expires_in={expires_in}') + f'token={token}') return token diff --git a/tests/acl_input/incremental_1.json b/tests/acl_input/incremental_1.json new file mode 100644 index 0000000000..57ede08bf7 --- /dev/null +++ b/tests/acl_input/incremental_1.json @@ -0,0 +1,32 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "ntp-acl": { + "acl-entries": { + "acl-entry": { + "1": { + "ip": { + "config": { + "source-ip-address": "20.0.0.12/32" + } + }, + "config": { + "sequence-id": 1 + }, + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + } + } + } + }, + "config": { + "name": "ntp-acl" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/acl_input/incremental_2.json b/tests/acl_input/incremental_2.json new file mode 100644 index 0000000000..b7de6edbac --- /dev/null +++ b/tests/acl_input/incremental_2.json @@ -0,0 +1,32 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "ntp-acl": { + "acl-entries": { + "acl-entry": { + "1": { + "ip": { + "config": { + "source-ip-address": "20.0.0.12/32" + } + }, + "config": { + "sequence-id": 1 + }, + "actions": { + "config": { + "forwarding-action": "DROP" + } + } + } + } + }, + "config": { + "name": "ntp-acl" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/acl_loader_test.py b/tests/acl_loader_test.py index bae24de9a4..20b7283319 100644 --- a/tests/acl_loader_test.py +++ b/tests/acl_loader_test.py @@ -1,6 +1,7 @@ import sys import os import pytest +from unittest import mock test_path = os.path.dirname(os.path.abspath(__file__)) modules_path = os.path.dirname(test_path) @@ -200,3 +201,19 @@ def test_icmp_fields_with_non_tcp_protocol(self, acl_loader): acl_loader.rules_info = {} acl_loader.load_rules_from_file(os.path.join(test_path, 'acl_input/tcp_bad_protocol_number.json')) assert not acl_loader.rules_info.get("RULE_1") + + def test_incremental_update(self, acl_loader): + acl_loader.rules_info = {} + acl_loader.tables_db_info['NTP_ACL'] = { + "stage": "INGRESS", + "type": "CTRLPLANE" + } + acl_loader.load_rules_from_file(os.path.join(test_path, 'acl_input/incremental_1.json')) + acl_loader.rules_db_info = acl_loader.rules_info + assert acl_loader.rules_info[(('NTP_ACL', 'RULE_1'))]["PACKET_ACTION"] == "ACCEPT" + acl_loader.per_npu_configdb = None + acl_loader.configdb.mod_entry = mock.MagicMock(return_value=True) + acl_loader.configdb.set_entry = mock.MagicMock(return_value=True) + acl_loader.load_rules_from_file(os.path.join(test_path, 'acl_input/incremental_2.json')) + acl_loader.incremental_update() + assert acl_loader.rules_info[(('NTP_ACL', 'RULE_1'))]["PACKET_ACTION"] == "DROP" diff --git a/tests/aclshow_test.py b/tests/aclshow_test.py index d0a92174f4..90fe46f683 100644 --- a/tests/aclshow_test.py +++ b/tests/aclshow_test.py @@ -192,8 +192,8 @@ def nullify_counters(self): This method is used to empty dumped counters if exist in /tmp/.counters_acl.p (by default). """ - if os.path.isfile(aclshow.COUNTER_POSITION): - with open(aclshow.COUNTER_POSITION, 'w') as fp: + if os.path.isfile(aclshow.COUNTERS_CACHE): + with open(aclshow.COUNTERS_CACHE, 'w') as fp: json.dump([], fp) def runTest(self): diff --git a/tests/config_an_test.py b/tests/config_an_test.py index 2da1879ebd..a888006354 100644 --- a/tests/config_an_test.py +++ b/tests/config_an_test.py @@ -84,6 +84,22 @@ def test_config_mtu(self, ctx): result = self.basic_check("mtu", ["PortChannel0001", "1514"], ctx, operator.ne) assert 'Invalid port PortChannel0001' in result.output + def test_config_fec(self, ctx): + # Set a fec mode which is in supported_fec list but not default + # on an interface with supported_fec + self.basic_check("fec", ["Ethernet0", "test"], ctx) + # Set a fec mode which is one of default values on an interface without supported_fecs + self.basic_check("fec", ["Ethernet4", "rs"], ctx) + # Negative case: Set a fec mode which is default but not in port's supported_fecs + result = self.basic_check("fec", ["Ethernet0", "fc"], ctx, operator.ne) + assert "fec fc is not in ['rs', 'none', 'test']" in result.output + # Negative case: set a fec mode which is not default on an interface without supported_fecs + result = self.basic_check("fec", ["Ethernet4", "test"], ctx, operator.ne) + assert "fec test is not in ['rs', 'fc', 'none']" in result.output + # Negative case: set a fec mode on a port where setting fec is not supported + result = self.basic_check("fec", ["Ethernet112", "test"], ctx, operator.ne) + assert "Setting fec is not supported" in result.output + def basic_check(self, command_name, para_list, ctx, op=operator.eq, expect_result=0): runner = CliRunner() result = runner.invoke(config.config.commands["interface"].commands[command_name], para_list, obj = ctx) diff --git a/tests/config_override_input/empty_table_removal.json b/tests/config_override_input/empty_table_removal.json new file mode 100644 index 0000000000..2230911ae6 --- /dev/null +++ b/tests/config_override_input/empty_table_removal.json @@ -0,0 +1,96 @@ +{ + "running_config": { + "ACL_TABLE": { + "DATAACL": { + "policy_desc": "DATAACL", + "ports": [ + "Ethernet4" + ], + "stage": "ingress", + "type": "L3" + }, + "NTP_ACL": { + "policy_desc": "NTP_ACL", + "services": [ + "NTP" + ], + "stage": "ingress", + "type": "CTRLPLANE" + } + }, + "AUTO_TECHSUPPORT_FEATURE": { + "bgp": { + "rate_limit_interval": "600", + "state": "enabled" + }, + "database": { + "rate_limit_interval": "600", + "state": "enabled" + } + }, + "PORT": { + "Ethernet4": { + "admin_status": "up", + "alias": "fortyGigE0/4", + "description": "Servers0:eth0", + "index": "1", + "lanes": "29,30,31,32", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + }, + "Ethernet8": { + "admin_status": "up", + "alias": "fortyGigE0/8", + "description": "Servers1:eth0", + "index": "2", + "lanes": "33,34,35,36", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + } + } + }, + "golden_config": { + "ACL_TABLE": { + } + }, + "expected_config": { + "AUTO_TECHSUPPORT_FEATURE": { + "bgp": { + "rate_limit_interval": "600", + "state": "enabled" + }, + "database": { + "rate_limit_interval": "600", + "state": "enabled" + } + }, + "PORT": { + "Ethernet4": { + "admin_status": "up", + "alias": "fortyGigE0/4", + "description": "Servers0:eth0", + "index": "1", + "lanes": "29,30,31,32", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + }, + "Ethernet8": { + "admin_status": "up", + "alias": "fortyGigE0/8", + "description": "Servers1:eth0", + "index": "2", + "lanes": "33,34,35,36", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + } + } + } +} diff --git a/tests/config_override_input/final_config_yang_failure.json b/tests/config_override_input/final_config_yang_failure.json new file mode 100644 index 0000000000..51e5e40098 --- /dev/null +++ b/tests/config_override_input/final_config_yang_failure.json @@ -0,0 +1,71 @@ +{ + "running_config": { + "ACL_TABLE": { + "DATAACL": { + "policy_desc": "DATAACL", + "ports": [ + "Ethernet4" + ], + "stage": "ingress", + "type": "L3" + }, + "NTP_ACL": { + "policy_desc": "NTP_ACL", + "services": [ + "NTP" + ], + "stage": "ingress", + "type": "CTRLPLANE" + } + }, + "AUTO_TECHSUPPORT_FEATURE": { + "bgp": { + "rate_limit_interval": "600", + "state": "enabled" + }, + "database": { + "rate_limit_interval": "600", + "state": "enabled" + } + }, + "PORT": { + "Ethernet4": { + "admin_status": "up", + "alias": "fortyGigE0/4", + "description": "Servers0:eth0", + "index": "1", + "lanes": "29,30,31,32", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + }, + "Ethernet8": { + "admin_status": "up", + "alias": "fortyGigE0/8", + "description": "Servers1:eth0", + "index": "2", + "lanes": "33,34,35,36", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + } + } + }, + "golden_config": { + "PORT": { + "Ethernet12": { + "admin_status": "up", + "alias": "fortyGigE0/12", + "description": "Servers2:eth0", + "index": "3", + "lanes": "37,38,39,40", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + } + } + } +} diff --git a/tests/config_override_input/golden_input_yang_failure.json b/tests/config_override_input/golden_input_yang_failure.json new file mode 100644 index 0000000000..4b533e1598 --- /dev/null +++ b/tests/config_override_input/golden_input_yang_failure.json @@ -0,0 +1,89 @@ +{ + "running_config": { + "ACL_TABLE": { + "DATAACL": { + "policy_desc": "DATAACL", + "ports": [ + "Ethernet4" + ], + "stage": "ingress", + "type": "L3" + }, + "NTP_ACL": { + "policy_desc": "NTP_ACL", + "services": [ + "NTP" + ], + "stage": "ingress", + "type": "CTRLPLANE" + } + }, + "AUTO_TECHSUPPORT_FEATURE": { + "bgp": { + "rate_limit_interval": "600", + "state": "enabled" + }, + "database": { + "rate_limit_interval": "600", + "state": "enabled" + } + }, + "PORT": { + "Ethernet4": { + "admin_status": "up", + "alias": "fortyGigE0/4", + "description": "Servers0:eth0", + "index": "1", + "lanes": "29,30,31,32", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + }, + "Ethernet8": { + "admin_status": "up", + "alias": "fortyGigE0/8", + "description": "Servers1:eth0", + "index": "2", + "lanes": "33,34,35,36", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + } + } + }, + "golden_config": { + "ACL_TABLE": { + "EVERFLOWV6": { + "policy_desc": "EVERFLOWV6", + "ports": [ + "Ethernet0" + ], + "stage": "ingress", + "type": "MIRRORV6" + } + }, + "AUTO_TECHSUPPORT_FEATURE": { + "bgp": { + "state": "disabled" + }, + "database": { + "state": "disabled" + } + }, + "PORT": { + "Ethernet12": { + "admin_status": "up", + "alias": "fortyGigE0/12", + "description": "Servers2:eth0", + "index": "3", + "lanes": "37,38,39,40", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + } + } + } +} diff --git a/tests/config_override_input/running_config_yang_failure.json b/tests/config_override_input/running_config_yang_failure.json new file mode 100644 index 0000000000..7060dd4d22 --- /dev/null +++ b/tests/config_override_input/running_config_yang_failure.json @@ -0,0 +1,89 @@ +{ + "running_config": { + "ACL_TABLE": { + "DATAACL": { + "policy_desc": "DATAACL", + "ports": [ + "Ethernet0" + ], + "stage": "ingress", + "type": "L3" + }, + "NTP_ACL": { + "policy_desc": "NTP_ACL", + "services": [ + "NTP" + ], + "stage": "ingress", + "type": "CTRLPLANE" + } + }, + "AUTO_TECHSUPPORT_FEATURE": { + "bgp": { + "rate_limit_interval": "600", + "state": "enabled" + }, + "database": { + "rate_limit_interval": "600", + "state": "enabled" + } + }, + "PORT": { + "Ethernet4": { + "admin_status": "up", + "alias": "fortyGigE0/4", + "description": "Servers0:eth0", + "index": "1", + "lanes": "29,30,31,32", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + }, + "Ethernet8": { + "admin_status": "up", + "alias": "fortyGigE0/8", + "description": "Servers1:eth0", + "index": "2", + "lanes": "33,34,35,36", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + } + } + }, + "golden_config": { + "ACL_TABLE": { + "EVERFLOWV6": { + "policy_desc": "EVERFLOWV6", + "ports": [ + "Ethernet12" + ], + "stage": "ingress", + "type": "MIRRORV6" + } + }, + "AUTO_TECHSUPPORT_FEATURE": { + "bgp": { + "state": "disabled" + }, + "database": { + "state": "disabled" + } + }, + "PORT": { + "Ethernet12": { + "admin_status": "up", + "alias": "fortyGigE0/12", + "description": "Servers2:eth0", + "index": "3", + "lanes": "37,38,39,40", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000", + "tpid": "0x8100" + } + } + } +} diff --git a/tests/config_override_test.py b/tests/config_override_test.py index 37edcfa8d8..1b058ace13 100644 --- a/tests/config_override_test.py +++ b/tests/config_override_test.py @@ -16,10 +16,17 @@ NEW_FEATURE_CONFIG = os.path.join(DATA_DIR, "new_feature_config.json") FULL_CONFIG_OVERRIDE = os.path.join(DATA_DIR, "full_config_override.json") PORT_CONFIG_OVERRIDE = os.path.join(DATA_DIR, "port_config_override.json") +EMPTY_TABLE_REMOVAL = os.path.join(DATA_DIR, "empty_table_removal.json") +RUNNING_CONFIG_YANG_FAILURE = os.path.join(DATA_DIR, "running_config_yang_failure.json") +GOLDEN_INPUT_YANG_FAILURE = os.path.join(DATA_DIR, "golden_input_yang_failure.json") +FINAL_CONFIG_YANG_FAILURE = os.path.join(DATA_DIR, "final_config_yang_failure.json") # Load sonic-cfggen from source since /usr/local/bin/sonic-cfggen does not have .py extension. sonic_cfggen = load_module_from_source('sonic_cfggen', '/usr/local/bin/sonic-cfggen') +config_mgmt_py_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config_mgmt.py') +config_mgmt = load_module_from_source('config_mgmt', config_mgmt_py_path) + def write_init_config_db(cfgdb, config): tables = cfgdb.get_config() @@ -137,6 +144,15 @@ def test_golden_config_db_port_config(self): db, config, read_data['running_config'], read_data['golden_config'], read_data['expected_config']) + def test_golden_config_db_empty_table_removal(self): + """Golden Config empty table does table removal""" + db = Db() + with open(EMPTY_TABLE_REMOVAL, "r") as f: + read_data = json.load(f) + self.check_override_config_table( + db, config, read_data['running_config'], read_data['golden_config'], + read_data['expected_config']) + def check_override_config_table(self, db, config, running_config, golden_config, expected_config): def read_json_file_side_effect(filename): @@ -153,6 +169,89 @@ def read_json_file_side_effect(filename): assert result.exit_code == 0 assert current_config == expected_config + def test_yang_verification_enabled(self): + def is_yang_config_validation_enabled_side_effect(filename): + return True + + def config_mgmt_side_effect(): + return config_mgmt.ConfigMgmt(source=CONFIG_DB_JSON_FILE) + + db = Db() + with open(FULL_CONFIG_OVERRIDE, "r") as f: + read_data = json.load(f) + + # ConfigMgmt will call ConfigDBConnector to load default config_db.json. + # Here I modify the ConfigMgmt initialization and make it initiated with + # a source file which share the same as what we write to cfgdb. + CONFIG_DB_JSON_FILE = "startConfigDb.json" + write_config_to_file(read_data['running_config'], CONFIG_DB_JSON_FILE) + with mock.patch('config.main.device_info.is_yang_config_validation_enabled', + mock.MagicMock(side_effect=is_yang_config_validation_enabled_side_effect)), \ + mock.patch('config.main.ConfigMgmt', + mock.MagicMock(side_effect=config_mgmt_side_effect)): + self.check_override_config_table( + db, config, read_data['running_config'], read_data['golden_config'], + read_data['expected_config']) + + + def test_running_config_yang_failure(self): + def is_yang_config_validation_enabled_side_effect(filename): + return True + db = Db() + with open(RUNNING_CONFIG_YANG_FAILURE, "r") as f: + read_data = json.load(f) + with mock.patch('config.main.device_info.is_yang_config_validation_enabled', + mock.MagicMock(side_effect=is_yang_config_validation_enabled_side_effect)): + self.check_yang_verification_failure( + db, config, read_data['running_config'], read_data['golden_config'], "running config") + + def test_golden_input_yang_failure(self): + def is_yang_config_validation_enabled_side_effect(filename): + return True + db = Db() + with open(GOLDEN_INPUT_YANG_FAILURE, "r") as f: + read_data = json.load(f) + with mock.patch('config.main.device_info.is_yang_config_validation_enabled', + mock.MagicMock(side_effect=is_yang_config_validation_enabled_side_effect)): + self.check_yang_verification_failure( + db, config, read_data['running_config'], read_data['golden_config'], "config_input") + + def test_final_config_yang_failure(self): + def is_yang_config_validation_enabled_side_effect(filename): + return True + db = Db() + with open(FINAL_CONFIG_YANG_FAILURE, "r") as f: + read_data = json.load(f) + with mock.patch('config.main.device_info.is_yang_config_validation_enabled', + mock.MagicMock(side_effect=is_yang_config_validation_enabled_side_effect)): + self.check_yang_verification_failure( + db, config, read_data['running_config'], read_data['golden_config'], "updated_config") + + def check_yang_verification_failure(self, db, config, running_config, + golden_config, jname): + def read_json_file_side_effect(filename): + return golden_config + + def config_mgmt_side_effect(): + return config_mgmt.ConfigMgmt(source=CONFIG_DB_JSON_FILE) + + # ConfigMgmt will call ConfigDBConnector to load default config_db.json. + # Here I modify the ConfigMgmt initialization and make it initiated with + # a source file which share the same as what we write to cfgdb. + CONFIG_DB_JSON_FILE = "startConfigDb.json" + write_config_to_file(running_config, CONFIG_DB_JSON_FILE) + with mock.patch('config.main.read_json_file', + mock.MagicMock(side_effect=read_json_file_side_effect)), \ + mock.patch('config.main.ConfigMgmt', + mock.MagicMock(side_effect=config_mgmt_side_effect)): + write_init_config_db(db.cfgdb, running_config) + + runner = CliRunner() + result = runner.invoke(config.config.commands["override-config-table"], + ['golden_config_db.json'], obj=db) + assert result.exit_code == 1 + assert "Failed to validate {}. Error:".format(jname) in result.output + @classmethod def teardown_class(cls): print("TEARDOWN") diff --git a/tests/config_test.py b/tests/config_test.py index e9dbae4194..a9f4982548 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -80,7 +80,6 @@ """ RELOAD_CONFIG_DB_OUTPUT = """\ -Running command: rm -rf /tmp/dropstat-* Stopping SONiC target ... Running command: /usr/local/bin/sonic-cfggen -j /tmp/config.json --write-to-db Restarting SONiC target ... @@ -88,7 +87,6 @@ """ RELOAD_YANG_CFG_OUTPUT = """\ -Running command: rm -rf /tmp/dropstat-* Stopping SONiC target ... Running command: /usr/local/bin/sonic-cfggen -Y /tmp/config.json --write-to-db Restarting SONiC target ... @@ -96,7 +94,6 @@ """ RELOAD_MASIC_CONFIG_DB_OUTPUT = """\ -Running command: rm -rf /tmp/dropstat-* Stopping SONiC target ... Running command: /usr/local/bin/sonic-cfggen -j /tmp/config.json --write-to-db Running command: /usr/local/bin/sonic-cfggen -j /tmp/config.json -n asic0 --write-to-db @@ -106,11 +103,9 @@ """ reload_config_with_sys_info_command_output="""\ -Running command: rm -rf /tmp/dropstat-* Running command: /usr/local/bin/sonic-cfggen -H -k Seastone-DX010-25-50 --write-to-db""" reload_config_with_disabled_service_output="""\ -Running command: rm -rf /tmp/dropstat-* Stopping SONiC target ... Running command: /usr/local/bin/sonic-cfggen -j /tmp/config.json --write-to-db Restarting SONiC target ... @@ -227,7 +222,7 @@ def test_config_reload(self, get_cmd_module, setup_single_broadcom_asic): obj = {'config_db': db.cfgdb} # simulate 'config reload' to provoke load_sys_info option - result = runner.invoke(config.config.commands["reload"], ["-l", "-n", "-y"], obj=obj) + result = runner.invoke(config.config.commands["reload"], ["-l", "-n", "-y", "--disable_arp_cache"], obj=obj) print(result.exit_code) print(result.output) @@ -235,7 +230,7 @@ def test_config_reload(self, get_cmd_module, setup_single_broadcom_asic): assert result.exit_code == 0 - assert "\n".join([l.rstrip() for l in result.output.split('\n')][:2]) == reload_config_with_sys_info_command_output + assert "\n".join([l.rstrip() for l in result.output.split('\n')][:1]) == reload_config_with_sys_info_command_output def test_config_reload_untriggered_timer(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_untriggered_timer)) as mock_run_command: @@ -293,9 +288,9 @@ def test_load_minigraph(self, get_cmd_module, setup_single_broadcom_asic): traceback.print_tb(result.exc_info[2]) assert result.exit_code == 0 assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == load_minigraph_command_output - # Verify "systemctl reset-failed" is called for services under sonic.target + # Verify "systemctl reset-failed" is called for services under sonic.target mock_run_command.assert_any_call('systemctl reset-failed swss') - # Verify "systemctl reset-failed" is called for services under sonic-delayed.target + # Verify "systemctl reset-failed" is called for services under sonic-delayed.target mock_run_command.assert_any_call('systemctl reset-failed snmp') assert mock_run_command.call_count == 11 @@ -489,7 +484,7 @@ def test_reload_config(self, get_cmd_module, setup_single_broadcom_asic): result = runner.invoke( config.config.commands["reload"], - [self.dummy_cfg_file, '-y', '-f']) + [self.dummy_cfg_file, '-y', '-f', "--disable_arp_cache"]) print(result.exit_code) print(result.output) @@ -506,7 +501,7 @@ def test_config_reload_disabled_service(self, get_cmd_module, setup_single_broad (config, show) = get_cmd_module runner = CliRunner() - result = runner.invoke(config.config.commands["reload"], [self.dummy_cfg_file, "-y"]) + result = runner.invoke(config.config.commands["reload"], [self.dummy_cfg_file, "-y", "--disable_arp_cache"]) print(result.exit_code) print(result.output) @@ -526,12 +521,12 @@ def test_reload_config_masic(self, get_cmd_module, setup_multi_broadcom_masic): runner = CliRunner() # 3 config files: 1 for host and 2 for asic cfg_files = "{},{},{}".format( - self.dummy_cfg_file, + self.dummy_cfg_file, self.dummy_cfg_file, self.dummy_cfg_file) result = runner.invoke( config.config.commands["reload"], - [cfg_files, '-y', '-f']) + [cfg_files, '-y', '-f', "--disable_arp_cache"]) print(result.exit_code) print(result.output) @@ -550,7 +545,7 @@ def test_reload_yang_config(self, get_cmd_module, runner = CliRunner() result = runner.invoke(config.config.commands["reload"], - [self.dummy_cfg_file, '-y','-f' ,'-t', 'config_yang']) + [self.dummy_cfg_file, "--disable_arp_cache", '-y', '-f', '-t', 'config_yang']) print(result.exit_code) print(result.output) @@ -565,7 +560,7 @@ def teardown_class(cls): os.remove(cls.dummy_cfg_file) print("TEARDOWN") - + class TestConfigCbf(object): @classmethod def setup_class(cls): @@ -1585,3 +1580,37 @@ def test_config_rate(self, get_cmd_module, setup_single_broadcom_asic): def teardown_class(cls): print("TEARDOWN") os.environ['UTILITIES_UNIT_TESTING'] = "0" + + +class TestConfigHostname(object): + @classmethod + def setup_class(cls): + print("SETUP") + import config.main + importlib.reload(config.main) + + @mock.patch('config.main.ConfigDBConnector') + def test_hostname_add(self, db_conn_patch, get_cmd_module): + db_conn_patch().mod_entry = mock.Mock() + (config, show) = get_cmd_module + + runner = CliRunner() + result = runner.invoke(config.config.commands["hostname"], + ["new_hostname"]) + + # Verify success + assert result.exit_code == 0 + + # Check was called + args_list = db_conn_patch().mod_entry.call_args_list + assert len(args_list) > 0 + + args, _ = args_list[0] + assert len(args) > 0 + + # Check new hostname was part of args + assert {'hostname': 'new_hostname'} in args + + @classmethod + def teardown_class(cls): + print("TEARDOWN") diff --git a/tests/counterpoll_test.py b/tests/counterpoll_test.py index 7a8171825a..4a4da07ee9 100644 --- a/tests/counterpoll_test.py +++ b/tests/counterpoll_test.py @@ -25,7 +25,8 @@ QUEUE_WATERMARK_STAT default (60000) enable PG_WATERMARK_STAT default (60000) enable PG_DROP_STAT 10000 enable -ACL 10000 enable +ACL 5000 enable +TUNNEL_STAT 3000 enable FLOW_CNT_TRAP_STAT 10000 enable FLOW_CNT_ROUTE_STAT 10000 enable """ diff --git a/tests/crm_test.py b/tests/crm_test.py index 24ced116ce..6b3f32ed9d 100644 --- a/tests/crm_test.py +++ b/tests/crm_test.py @@ -1033,6 +1033,8 @@ """ +crm_config_interval_too_big = "Error: Invalid value for \"INTERVAL\": 30000 is not in the valid range of 1 to 9999." + class TestCrm(object): @classmethod def setup_class(cls): @@ -1053,6 +1055,18 @@ def test_crm_show_summary(self): assert result.exit_code == 0 assert result.output == crm_new_show_summary + def test_crm_config_polling_interval(self): + runner = CliRunner() + db = Db() + result = runner.invoke(crm.cli, ['config', 'polling', 'interval', '10'], obj=db) + print(sys.stderr, result.output) + assert result.exit_code == 0 + result = runner.invoke(crm.cli, ['config', 'polling', 'interval', '30000'], obj=db) + print(sys.stderr, result.output) + assert result.exit_code == 2 + assert crm_config_interval_too_big in result.output + + def test_crm_show_thresholds_acl_group(self): runner = CliRunner() db = Db() diff --git a/tests/db_migrator_input/config_db/qos_map_table_expected.json b/tests/db_migrator_input/config_db/qos_map_table_expected.json index e75740f02c..47381ec550 100644 --- a/tests/db_migrator_input/config_db/qos_map_table_expected.json +++ b/tests/db_migrator_input/config_db/qos_map_table_expected.json @@ -3,32 +3,32 @@ "VERSION": "version_3_0_5" }, "PORT_QOS_MAP|Ethernet0": { - "dscp_to_tc_map": "[DSCP_TO_TC_MAP|AZURE]", + "dscp_to_tc_map": "AZURE", "pfc_enable": "3,4", "pfcwd_sw_enable": "3,4", - "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", - "tc_to_pg_map": "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", - "tc_to_queue_map": "[TC_TO_QUEUE_MAP|AZURE]" + "pfc_to_queue_map": "AZURE", + "tc_to_pg_map": "AZURE", + "tc_to_queue_map": "AZURE" }, "PORT_QOS_MAP|Ethernet100": { - "dscp_to_tc_map": "[DSCP_TO_TC_MAP|AZURE]", + "dscp_to_tc_map": "AZURE", "pfc_enable": "3,4", "pfcwd_sw_enable": "3,4", - "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", - "tc_to_pg_map": "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", - "tc_to_queue_map": "[TC_TO_QUEUE_MAP|AZURE]" + "pfc_to_queue_map": "AZURE", + "tc_to_pg_map": "AZURE", + "tc_to_queue_map": "AZURE" }, "PORT_QOS_MAP|Ethernet92": { - "dscp_to_tc_map": "[DSCP_TO_TC_MAP|AZURE]", - "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", - "tc_to_pg_map": "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", - "tc_to_queue_map": "[TC_TO_QUEUE_MAP|AZURE]" + "dscp_to_tc_map": "AZURE", + "pfc_to_queue_map": "AZURE", + "tc_to_pg_map": "AZURE", + "tc_to_queue_map": "AZURE" }, "PORT_QOS_MAP|Ethernet96": { - "dscp_to_tc_map": "[DSCP_TO_TC_MAP|AZURE]", - "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", - "tc_to_pg_map": "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", - "tc_to_queue_map": "[TC_TO_QUEUE_MAP|AZURE]" + "dscp_to_tc_map": "AZURE", + "pfc_to_queue_map": "AZURE", + "tc_to_pg_map": "AZURE", + "tc_to_queue_map": "AZURE" } } diff --git a/tests/db_migrator_input/config_db/qos_map_table_global_expected.json b/tests/db_migrator_input/config_db/qos_map_table_global_expected.json new file mode 100644 index 0000000000..db2096144f --- /dev/null +++ b/tests/db_migrator_input/config_db/qos_map_table_global_expected.json @@ -0,0 +1,12 @@ +{ + "VERSIONS|DATABASE": { + "VERSION": "version_2_0_1" + }, + "DSCP_TO_TC_MAP|AZURE": { + "0": "0", + "1": "1" + }, + "PORT_QOS_MAP|global": { + "dscp_to_tc_map": "AZURE" + } +} diff --git a/tests/db_migrator_input/config_db/qos_map_table_global_input.json b/tests/db_migrator_input/config_db/qos_map_table_global_input.json new file mode 100644 index 0000000000..dd4b78bb0e --- /dev/null +++ b/tests/db_migrator_input/config_db/qos_map_table_global_input.json @@ -0,0 +1,10 @@ +{ + "VERSIONS|DATABASE": { + "VERSION": "version_2_0_0" + }, + "DSCP_TO_TC_MAP|AZURE": { + "0": "0", + "1": "1" + } +} + diff --git a/tests/db_migrator_input/config_db/qos_map_table_input.json b/tests/db_migrator_input/config_db/qos_map_table_input.json index 4bb237588a..c62e293daf 100644 --- a/tests/db_migrator_input/config_db/qos_map_table_input.json +++ b/tests/db_migrator_input/config_db/qos_map_table_input.json @@ -3,29 +3,29 @@ "VERSION": "version_3_0_4" }, "PORT_QOS_MAP|Ethernet0": { - "dscp_to_tc_map": "[DSCP_TO_TC_MAP|AZURE]", + "dscp_to_tc_map": "AZURE", "pfc_enable": "3,4", - "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", - "tc_to_pg_map": "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", - "tc_to_queue_map": "[TC_TO_QUEUE_MAP|AZURE]" + "pfc_to_queue_map": "AZURE", + "tc_to_pg_map": "AZURE", + "tc_to_queue_map": "AZURE" }, "PORT_QOS_MAP|Ethernet100": { - "dscp_to_tc_map": "[DSCP_TO_TC_MAP|AZURE]", + "dscp_to_tc_map": "AZURE", "pfc_enable": "3,4", - "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", - "tc_to_pg_map": "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", - "tc_to_queue_map": "[TC_TO_QUEUE_MAP|AZURE]" + "pfc_to_queue_map": "AZURE", + "tc_to_pg_map": "AZURE", + "tc_to_queue_map": "AZURE" }, "PORT_QOS_MAP|Ethernet92": { - "dscp_to_tc_map": "[DSCP_TO_TC_MAP|AZURE]", - "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", - "tc_to_pg_map": "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", - "tc_to_queue_map": "[TC_TO_QUEUE_MAP|AZURE]" + "dscp_to_tc_map": "AZURE", + "pfc_to_queue_map": "AZURE", + "tc_to_pg_map": "AZURE", + "tc_to_queue_map": "AZURE" }, "PORT_QOS_MAP|Ethernet96": { - "dscp_to_tc_map": "[DSCP_TO_TC_MAP|AZURE]", - "pfc_to_queue_map": "[MAP_PFC_PRIORITY_TO_QUEUE|AZURE]", - "tc_to_pg_map": "[TC_TO_PRIORITY_GROUP_MAP|AZURE]", - "tc_to_queue_map": "[TC_TO_QUEUE_MAP|AZURE]" + "dscp_to_tc_map": "AZURE", + "pfc_to_queue_map": "AZURE", + "tc_to_pg_map": "AZURE", + "tc_to_queue_map": "AZURE" } } diff --git a/tests/db_migrator_input/config_db/reclaiming-buffer-warmreboot-expected.json b/tests/db_migrator_input/config_db/reclaiming-buffer-warmreboot-expected.json index 5037644faf..ccedec4d9d 100644 --- a/tests/db_migrator_input/config_db/reclaiming-buffer-warmreboot-expected.json +++ b/tests/db_migrator_input/config_db/reclaiming-buffer-warmreboot-expected.json @@ -2043,6 +2043,6 @@ "admin_status": "up" }, "VERSIONS|DATABASE": { - "VERSION": "version_3_0_4" + "VERSION": "version_3_0_5" } } diff --git a/tests/db_migrator_test.py b/tests/db_migrator_test.py index b688aa2de5..faa09c7ed2 100644 --- a/tests/db_migrator_test.py +++ b/tests/db_migrator_test.py @@ -374,3 +374,39 @@ def test_pfc_enable_migrator(self): diff = DeepDiff(resulting_table, expected_table, ignore_order=True) assert not diff + +class TestGlobalDscpToTcMapMigrator(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "2" + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + dbconnector.dedicated_dbs['CONFIG_DB'] = None + + def test_global_dscp_to_tc_map_migrator(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'qos_map_table_global_input') + import db_migrator + dbmgtr = db_migrator.DBMigrator(None) + dbmgtr.asic_type = "broadcom" + dbmgtr.hwsku = "vs" + dbmgtr.migrate() + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'qos_map_table_global_expected') + expected_db = Db() + + resulting_table = dbmgtr.configDB.get_table('PORT_QOS_MAP') + expected_table = expected_db.cfgdb.get_table('PORT_QOS_MAP') + + diff = DeepDiff(resulting_table, expected_table, ignore_order=True) + assert not diff + + # Check port_qos_map|global is not generated on mellanox asic + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'qos_map_table_global_input') + dbmgtr_mlnx = db_migrator.DBMigrator(None) + dbmgtr_mlnx.asic_type = "mellanox" + dbmgtr_mlnx.hwsku = "vs" + dbmgtr_mlnx.migrate() + resulting_table = dbmgtr_mlnx.configDB.get_table('PORT_QOS_MAP') + assert resulting_table == {} + diff --git a/tests/decode_syseeprom_test.py b/tests/decode_syseeprom_test.py index ae3801d369..50c7667453 100644 --- a/tests/decode_syseeprom_test.py +++ b/tests/decode_syseeprom_test.py @@ -192,3 +192,12 @@ def test_print_model(self, capsys): decode_syseeprom.print_model(True) captured = capsys.readouterr() assert captured.out == 'S6100-ON\n' + + @mock.patch('os.geteuid', lambda: 0) + @mock.patch('sonic_py_common.device_info.get_platform', lambda: 'arista') + @mock.patch('decode-syseeprom.read_and_print_eeprom') + @mock.patch('decode-syseeprom.read_eeprom_from_db') + def test_support_platforms_not_db_based(self, mockDbBased, mockNotDbBased): + decode_syseeprom.main() + assert mockNotDbBased.called + assert not mockDbBased.called diff --git a/tests/generic_config_updater/change_applier_test.py b/tests/generic_config_updater/change_applier_test.py index 63944b2571..afe166b008 100644 --- a/tests/generic_config_updater/change_applier_test.py +++ b/tests/generic_config_updater/change_applier_test.py @@ -281,6 +281,7 @@ def test_apply__calls_apply_change_to_config_db(self): # Act applier.apply(change) + applier.remove_backend_tables_from_config(change) # Assert applier.config_wrapper.apply_change_to_config_db.assert_has_calls([call(change)]) diff --git a/tests/installer_docker_test.py b/tests/installer_docker_test.py new file mode 100644 index 0000000000..8897b8413f --- /dev/null +++ b/tests/installer_docker_test.py @@ -0,0 +1,127 @@ +import pytest +import sonic_installer.main as sonic_installer + +from click.testing import CliRunner +from unittest.mock import patch, MagicMock + +SUCCESS = 0 + + +@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr')) +@patch('sonic_installer.main.get_container_image_id_all', MagicMock(return_value=['1', '2'])) +@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value=['1'])) +@patch('sonic_installer.main.get_docker_tag_name', MagicMock(return_value='some_tag')) +@patch('sonic_installer.main.echo_and_log', MagicMock()) +@patch('sonic_installer.main.run_command') +def test_rollback_docker_basic(mock_run_cmd): + runner = CliRunner() + result = runner.invoke( + sonic_installer.sonic_installer.commands['rollback-docker'], ['-y', 'bgp'] + ) + + assert result.exit_code == SUCCESS + expect_docker_tag_command = 'docker tag docker-fpm-frr:some_tag docker-fpm-frr:latest' + mock_run_cmd.assert_called_with(expect_docker_tag_command) + + mock_run_cmd.reset() + result = runner.invoke( + sonic_installer.sonic_installer.commands['rollback-docker'], ['-y', 'snmp'] + ) + + assert result.exit_code == SUCCESS + mock_run_cmd.assert_any_call('systemctl restart snmp') + + +@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr')) +@patch('sonic_installer.main.get_container_image_id_all', MagicMock(return_value=['1'])) +def test_rollback_docker_no_extra_image(): + runner = CliRunner() + result = runner.invoke( + sonic_installer.sonic_installer.commands['rollback-docker'], ['-y', 'bgp'] + ) + assert result.exit_code != SUCCESS + + +@pytest.mark.parametrize("container", ['bgp', 'swss', 'teamd', 'pmon']) +@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr')) +@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value='1')) +@patch('sonic_installer.main.get_container_image_id_all', MagicMock(return_value=['1', '2'])) +@patch('sonic_installer.main.validate_url_or_abort', MagicMock()) +@patch('sonic_installer.main.urlretrieve', MagicMock()) +@patch('os.path.isfile', MagicMock(return_value=True)) +@patch('sonic_installer.main.get_docker_tag_name', MagicMock(return_value='some_tag')) +@patch('sonic_installer.main.run_command', MagicMock()) +@patch("sonic_installer.main.subprocess.Popen") +@patch('sonic_installer.main.hget_warm_restart_table') +def test_upgrade_docker_basic(mock_hget, mock_popen, container): + def mock_hget_impl(db_name, table_name, warm_app_name, key): + if table_name == 'WARM_RESTART_ENABLE_TABLE': + return "false" + elif table_name == 'WARM_RESTART_TABLE': + return 'reconciled' + + mock_hget.side_effect = mock_hget_impl + mock_proc = MagicMock() + mock_proc.communicate = MagicMock(return_value=(None, None)) + mock_proc.returncode = 0 + mock_popen.return_value = mock_proc + + runner = CliRunner() + result = runner.invoke( + sonic_installer.sonic_installer.commands['upgrade-docker'], + ['-y', '--cleanup_image', '--warm', container, 'http://'] + ) + + print(result.output) + assert result.exit_code == SUCCESS + + +@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr')) +@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value=['1'])) +@patch('sonic_installer.main.validate_url_or_abort', MagicMock()) +@patch('sonic_installer.main.urlretrieve', MagicMock(side_effect=Exception('download failed'))) +def test_upgrade_docker_download_fail(): + runner = CliRunner() + result = runner.invoke( + sonic_installer.sonic_installer.commands['upgrade-docker'], + ['-y', '--cleanup_image', '--warm', 'bgp', 'http://'] + ) + assert 'download failed' in result.output + assert result.exit_code != SUCCESS + + +@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr')) +@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value=['1'])) +@patch('sonic_installer.main.validate_url_or_abort', MagicMock()) +@patch('sonic_installer.main.urlretrieve', MagicMock(side_effect=Exception('download failed'))) +def test_upgrade_docker_image_not_exist(): + runner = CliRunner() + result = runner.invoke( + sonic_installer.sonic_installer.commands['upgrade-docker'], + ['-y', '--cleanup_image', '--warm', 'bgp', 'invalid_url'] + ) + assert 'does not exist' in result.output + assert result.exit_code != SUCCESS + + +@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr')) +@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value=['1'])) +@patch('sonic_installer.main.validate_url_or_abort', MagicMock()) +@patch('sonic_installer.main.urlretrieve', MagicMock()) +@patch('os.path.isfile', MagicMock(return_value=True)) +@patch('sonic_installer.main.get_docker_tag_name', MagicMock(return_value='some_tag')) +@patch('sonic_installer.main.run_command', MagicMock()) +@patch('sonic_installer.main.hget_warm_restart_table', MagicMock(return_value='false')) +@patch("sonic_installer.main.subprocess.Popen") +def test_upgrade_docker_image_swss_check_failed(mock_popen): + mock_proc = MagicMock() + mock_proc.communicate = MagicMock(return_value=(None, None)) + mock_proc.returncode = 1 + mock_popen.return_value = mock_proc + runner = CliRunner() + result = runner.invoke( + sonic_installer.sonic_installer.commands['upgrade-docker'], + ['-y', '--cleanup_image', '--warm', 'swss', 'http://'] + ) + assert 'RESTARTCHECK failed' in result.output + assert result.exit_code != SUCCESS diff --git a/tests/intfutil_test.py b/tests/intfutil_test.py index 82075b1352..2a13075919 100644 --- a/tests/intfutil_test.py +++ b/tests/intfutil_test.py @@ -209,8 +209,9 @@ def test_subintf_status(self): expected_output = ( "Sub port interface Speed MTU Vlan Admin Type\n" "-------------------- ------- ----- ------ ------- --------------------\n" - " Eth32.10 40G 9100 100 up 802.1q-encapsulation\n" - " Ethernet0.10 25G 9100 10 up 802.1q-encapsulation" + " Eth36.10 10M 9100 100 up 802.1q-encapsulation\n" + " Ethernet0.10 25G 9100 10 up 802.1q-encapsulation\n" + " Po0001.10 40G 9100 100 up 802.1q-encapsulation" ) self.assertEqual(result.output.strip(), expected_output) @@ -247,10 +248,20 @@ def test_single_subintf_status(self): expected_output = ( "Sub port interface Speed MTU Vlan Admin Type\n" "-------------------- ------- ----- ------ ------- --------------------\n" - " Eth32.10 40G 9100 100 up 802.1q-encapsulation" + " Eth36.10 10M 9100 100 up 802.1q-encapsulation" ) - # Test 'intfutil status Eth32.10' - output = subprocess.check_output('intfutil -c status -i Eth32.10', stderr=subprocess.STDOUT, shell=True, text=True) + # Test 'intfutil status Eth36.10' + output = subprocess.check_output('intfutil -c status -i Eth36.10', stderr=subprocess.STDOUT, shell=True, text=True) + print(output, file=sys.stderr) + self.assertEqual(output.strip(), expected_output) + + expected_output = ( + "Sub port interface Speed MTU Vlan Admin Type\n" + "-------------------- ------- ----- ------ ------- --------------------\n" + " Po0001.10 40G 9100 100 up 802.1q-encapsulation" + ) + # Test 'intfutil status Po0001.10' + output = subprocess.check_output('intfutil -c status -i Po0001.10', stderr=subprocess.STDOUT, shell=True, text=True) print(output, file=sys.stderr) self.assertEqual(output.strip(), expected_output) @@ -261,9 +272,14 @@ def test_single_subintf_status_verbose(self): expected_output = "Command: intfutil -c status -i Ethernet0.10" self.assertEqual(result.output.split('\n')[0], expected_output) - result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["Eth32.10", "--verbose"]) + result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["Eth36.10", "--verbose"]) + print(result.output, file=sys.stderr) + expected_output = "Command: intfutil -c status -i Eth36.10" + self.assertEqual(result.output.split('\n')[0], expected_output) + + result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["Po0001.10", "--verbose"]) print(result.output, file=sys.stderr) - expected_output = "Command: intfutil -c status -i Eth32.10" + expected_output = "Command: intfutil -c status -i Po0001.10" self.assertEqual(result.output.split('\n')[0], expected_output) # Test status of single sub interface in alias naming mode diff --git a/tests/ip_config_input/patch_ipv6.test b/tests/ip_config_input/patch_ipv6.test new file mode 100644 index 0000000000..00b43fda4c --- /dev/null +++ b/tests/ip_config_input/patch_ipv6.test @@ -0,0 +1,6 @@ +[ + { + "path": "/INTERFACE/Ethernet12|FC00::1~1126", + "op": "remove" + } +] diff --git a/tests/ip_config_test.py b/tests/ip_config_test.py index 47f82fb959..fd6b4feb9f 100644 --- a/tests/ip_config_test.py +++ b/tests/ip_config_test.py @@ -1,3 +1,5 @@ +import json +import jsonpatch import os import traceback from unittest import mock @@ -8,8 +10,25 @@ import show.main as show from utilities_common.db import Db +test_path = os.path.dirname(os.path.abspath(__file__)) +ip_config_input_path = os.path.join(test_path, "ip_config_input") + ERROR_MSG = "Error: IP address is not valid" +INVALID_VRF_MSG ="""\ +Usage: bind [OPTIONS] +Try "bind --help" for help. + +Error: VRF Vrf2 does not exist! +""" + +INVALID_MGMT_VRF_MSG ="""\ +Usage: bind [OPTIONS] +Try "bind --help" for help. + +Error: VRF mgmt does not exist! +""" + class TestConfigIP(object): @classmethod def setup_class(cls): @@ -29,12 +48,36 @@ def test_add_del_interface_valid_ipv4(self): assert result.exit_code == 0 assert ('Ethernet64', '10.10.10.1/24') in db.cfgdb.get_table('INTERFACE') + # config int ip add Ethernet0.10 10.11.10.1/24 + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet0.10", "10.11.10.1/24"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Ethernet0.10', '10.11.10.1/24') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + # config int ip add Eth36.10 32.11.10.1/24 + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Eth36.10", "32.11.10.1/24"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Eth36.10', '32.11.10.1/24') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + # config int ip remove Ethernet64 10.10.10.1/24 result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet64", "10.10.10.1/24"], obj=obj) print(result.exit_code, result.output) assert result.exit_code != 0 assert ('Ethernet64', '10.10.10.1/24') not in db.cfgdb.get_table('INTERFACE') + # config int ip remove Ethernet0.10 10.11.10.1/24 + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet0.10", "10.11.10.1/24"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert ('Ethernet0.10', '10.11.10.1/24') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + # config int ip remove Eth36.10 32.11.10.1/24 + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Eth36.10", "32.11.10.1/24"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert ('Eth36.10', '32.11.10.1/24') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + def test_add_interface_invalid_ipv4(self): db = Db() runner = CliRunner() @@ -81,12 +124,32 @@ def test_add_del_interface_valid_ipv6(self): assert result.exit_code == 0 assert ('Ethernet72', '2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') in db.cfgdb.get_table('INTERFACE') + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet0.10", "1010:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Ethernet0.10', '1010:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Eth36.10", "3210:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Eth36.10', '3210:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + # config int ip remove Ethernet72 2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34 result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet72", "2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) print(result.exit_code, result.output) assert result.exit_code != 0 assert ('Ethernet72', '2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') not in db.cfgdb.get_table('INTERFACE') + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet0.10", "1010:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert ('Ethernet0.10', '1010:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Eth36.10", "3210:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert ('Eth36.10', '3210:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + def test_del_interface_case_sensitive_ipv6(self): db = Db() runner = CliRunner() @@ -157,6 +220,55 @@ def test_add_del_interface_shortened_ipv6_with_leading_zeros(self): assert result.exit_code != 0 assert ('Ethernet68', '3000::1/64') not in db.cfgdb.get_table('INTERFACE') + def test_remove_interface_case_sensitive_mock_ipv6_w_apply_patch(self): + runner = CliRunner() + any_patch_as_json = [{"op": "remove", "path": "/INTERFACE/Ethernet12|FC00::1~1126"}] + any_patch = jsonpatch.JsonPatch(any_patch_as_json) + any_patch_as_text = json.dumps(any_patch_as_json) + ipv6_patch_file = os.path.join(ip_config_input_path, 'patch_ipv6.test') + + # config apply-patch patch + mock_generic_updater = mock.Mock() + with mock.patch('config.main.GenericUpdater', return_value=mock_generic_updater): + with mock.patch('builtins.open', mock.mock_open(read_data=any_patch_as_text)): + result = runner.invoke(config.config.commands["apply-patch"], [ipv6_patch_file], catch_exceptions=False) + print(result.exit_code, result.output) + assert "converted ipv6 address to lowercase fc00::1~1126 with prefix /INTERFACE/Ethernet12| in value: /INTERFACE/Ethernet12|FC00::1~1126" in result.output + + def test_intf_vrf_bind_unbind(self): + runner = CliRunner() + db = Db() + obj = {'config_db':db.cfgdb, 'namespace':db.db.namespace} + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["bind"], ["Ethernet64", "Vrf1"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Ethernet64"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + + def test_intf_unknown_vrf_bind(self): + runner = CliRunner() + db = Db() + obj = {'config_db':db.cfgdb, 'namespace':db.db.namespace} + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["bind"], ["Ethernet64", "Vrf2"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert result.output == INVALID_VRF_MSG + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["bind"], ["Ethernet64", "mgmt"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert result.output == INVALID_MGMT_VRF_MSG + + result = runner.invoke(config.config.commands["vrf"].commands["add"], ["mgmt"], obj=obj) + print(result.exit_code, result.output) + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["bind"], ["Ethernet64", "mgmt"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + @classmethod def teardown_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "0" diff --git a/tests/loopback_action_test.py b/tests/loopback_action_test.py index 58942b0c4b..b88d36973d 100644 --- a/tests/loopback_action_test.py +++ b/tests/loopback_action_test.py @@ -7,7 +7,7 @@ show_ip_interfaces_loopback_action_output="""\ Interface Action --------------- -------- -Eth32.10 drop +Eth36.10 drop Ethernet0 forward PortChannel0001 drop Vlan3000 forward diff --git a/tests/mock_platform_sfputil/mock_platform_sfputil.py b/tests/mock_platform_sfputil/mock_platform_sfputil.py new file mode 100644 index 0000000000..5c3ea2e248 --- /dev/null +++ b/tests/mock_platform_sfputil/mock_platform_sfputil.py @@ -0,0 +1,41 @@ +import json +import os +from sonic_platform_base.platform_base import PlatformBase +from sonic_platform_base.chassis_base import ChassisBase +from sonic_platform_base.sfp_base import SfpBase +import utilities_common.platform_sfputil_helper as platform_sfputil_helper + +portMap = None +RJ45Ports = None + +class mock_Chassis(ChassisBase): + def __init__(self): + ChassisBase.__init__(self) + + def get_port_or_cage_type(self, index): + if index in RJ45Ports: + return SfpBase.SFP_PORT_TYPE_BIT_RJ45 + else: + raise NotImplementedError + +def mock_logical_port_name_to_physical_port_list(port_name): + index = portMap.get(port_name) + if not index: + index = 0 + return [index] + +def mock_platform_sfputil_read_porttab_mappings(): + global portMap + global RJ45Ports + + with open(os.path.join(os.path.dirname(__file__), 'portmap.json')) as pm: + jsonobj = json.load(pm) + portMap = jsonobj['portMap'] + RJ45Ports = jsonobj['RJ45Ports'] + +def mock_platform_sfputil_helper(): + platform_sfputil_helper.platform_chassis = mock_Chassis() + platform_sfputil_helper.platform_sfputil = True + platform_sfputil_helper.platform_porttab_mapping_read = False + platform_sfputil_helper.platform_sfputil_read_porttab_mappings = mock_platform_sfputil_read_porttab_mappings + platform_sfputil_helper.logical_port_name_to_physical_port_list = mock_logical_port_name_to_physical_port_list diff --git a/tests/mock_platform_sfputil/portmap.json b/tests/mock_platform_sfputil/portmap.json new file mode 100644 index 0000000000..eec9a9f40d --- /dev/null +++ b/tests/mock_platform_sfputil/portmap.json @@ -0,0 +1,8 @@ +{ + "portMap": { + "Ethernet29": 1 + }, + "RJ45Ports": [ + 1 + ] +} diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index cff7ff64a5..e72cb47a73 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -188,10 +188,36 @@ "admin_status": "up", "vlan": "10" }, - "INTF_TABLE:Eth32.10": { + "INTF_TABLE:Eth36.10": { "admin_status": "up", + "vrf_name": "Vrf1", "vlan": "100" }, + "INTF_TABLE:Po0001.10": { + "admin_status": "up", + "vrf_name": "Vrf1", + "vlan": "100" + }, + "INTF_TABLE:Ethernet0.10|10.11.12.13/24": { + "family": "IPv4", + "scope": "global" + }, + "INTF_TABLE:Eth36.10|32.10.11.12/24": { + "family": "IPv4", + "scope": "global" + }, + "INTF_TABLE:Po0001.10|10.10.11.12/24": { + "family": "IPv4", + "scope": "global" + }, + "INTF_TABLE:Eth36.10|3210::12/126": { + "family": "IPv6", + "scope": "global" + }, + "INTF_TABLE:Po0001.10|1010::12/126": { + "family": "IPv6", + "scope": "global" + }, "_GEARBOX_TABLE:phy:1": { "name": "sesto-1", "phy_id": "1", diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 060115d8a9..fcb16e8f2d 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -376,12 +376,27 @@ "VLAN_SUB_INTERFACE|Ethernet0.10|10.11.12.13/24": { "NULL" : "NULL" }, - "VLAN_SUB_INTERFACE|Eth32.10": { + "VLAN_SUB_INTERFACE|Eth36.10": { "admin_status": "up", "loopback_action": "drop", + "vrf_name": "Vrf1", "vlan": "100" }, - "VLAN_SUB_INTERFACE|Eth32.10|32.10.11.12/24": { + "VLAN_SUB_INTERFACE|Eth36.10|32.10.11.12/24": { + "NULL" : "NULL" + }, + "VLAN_SUB_INTERFACE|Eth36.10|3210::12/126": { + "NULL" : "NULL" + }, + "VLAN_SUB_INTERFACE|Po0001.10": { + "admin_status": "up", + "vrf_name": "Vrf1", + "vlan": "100" + }, + "VLAN_SUB_INTERFACE|Po0001.10|10.10.11.12/24": { + "NULL" : "NULL" + }, + "VLAN_SUB_INTERFACE|Po0001.10|1010::12/126": { "NULL" : "NULL" }, "ACL_RULE|NULL_ROUTE_V4|DEFAULT_RULE": { @@ -813,7 +828,8 @@ "mac": "1d:34:db:16:a6:00", "platform": "x86_64-mlnx_msn3800-r0", "peer_switch": "sonic-switch", - "type": "ToRRouter" + "type": "ToRRouter", + "subtype": "DualToR" }, "SNMP_COMMUNITY|msft": { "TYPE": "RO" @@ -1648,7 +1664,11 @@ "FLEX_COUNTER_STATUS": "enable" }, "FLEX_COUNTER_TABLE|ACL": { - "POLL_INTERVAL": "10000", + "POLL_INTERVAL": "5000", + "FLEX_COUNTER_STATUS": "enable" + }, + "FLEX_COUNTER_TABLE|TUNNEL": { + "POLL_INTERVAL": "3000", "FLEX_COUNTER_STATUS": "enable" }, "FLEX_COUNTER_TABLE|FLOW_CNT_TRAP": { diff --git a/tests/mock_tables/counters_db.json b/tests/mock_tables/counters_db.json index e12e3347fd..03b29cdded 100644 --- a/tests/mock_tables/counters_db.json +++ b/tests/mock_tables/counters_db.json @@ -855,7 +855,10 @@ "SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0", "SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0", "SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0", - "SAI_PORT_STAT_ETHER_STATS_JABBERS": "0" + "SAI_PORT_STAT_ETHER_STATS_JABBERS": "0", + "SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "130402", + "SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "3", + "SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "4" }, "COUNTERS:oid:0x1000000000013": { "SAI_PORT_STAT_IF_IN_UCAST_PKTS": "4", @@ -912,7 +915,10 @@ "SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0", "SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0", "SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0", - "SAI_PORT_STAT_ETHER_STATS_JABBERS": "0" + "SAI_PORT_STAT_ETHER_STATS_JABBERS": "0", + "SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "110412", + "SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "1", + "SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "0" }, "COUNTERS:oid:0x1000000000014": { "SAI_PORT_STAT_IF_IN_UCAST_PKTS": "6", @@ -969,7 +975,10 @@ "SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0", "SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0", "SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0", - "SAI_PORT_STAT_ETHER_STATS_JABBERS": "0" + "SAI_PORT_STAT_ETHER_STATS_JABBERS": "0", + "SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "100317", + "SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "0", + "SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "0" }, "COUNTERS:oid:0x21000000000000": { "SAI_SWITCH_STAT_OUT_DROP_REASON_RANGE_BASE": "1000", diff --git a/tests/mock_tables/dbconnector.py b/tests/mock_tables/dbconnector.py index f6db0d9794..4ccb392368 100644 --- a/tests/mock_tables/dbconnector.py +++ b/tests/mock_tables/dbconnector.py @@ -60,6 +60,8 @@ def connect_SonicV2Connector(self, db_name, retry_on=True): def _subscribe_keyspace_notification(self, db_name, client): pass +def mock_close(self, db_name): + pass def config_set(self, *args): pass @@ -201,6 +203,7 @@ def get(self, counter, name): swsssdk.interface.DBInterface._subscribe_keyspace_notification = _subscribe_keyspace_notification +swsssdk.interface.DBInterface.close = mock_close mockredis.MockRedis.config_set = config_set redis.StrictRedis = SwssSyncClient SonicV2Connector.connect = connect_SonicV2Connector diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index f249b1e88c..beecb681fc 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -698,6 +698,7 @@ "rmt_adv_speeds" : "10,100,1000", "speed" : "100000", "supported_speeds": "10000,25000,40000,100000", + "supported_fecs": "rs,none,test", "link_training_status": "not_trained" }, "PORT_TABLE|Ethernet32": { @@ -705,6 +706,7 @@ }, "PORT_TABLE|Ethernet112": { "speed": "40000", + "supported_fecs": "N/A", "link_training_status": "off" }, "PCIE_DEVICE|00:01.0": { diff --git a/tests/muxcable_test.py b/tests/muxcable_test.py index 2b4d221171..965ae91ea8 100644 --- a/tests/muxcable_test.py +++ b/tests/muxcable_test.py @@ -2209,6 +2209,40 @@ def test_show_muxcable_tunnel_route_json_port(self): assert result.exit_code == 0 assert result.output == show_muxcable_tunnel_route_expected_output_port_json + @mock.patch('config.muxcable.swsscommon.DBConnector', mock.MagicMock(return_value=0)) + @mock.patch('config.muxcable.swsscommon.Table', mock.MagicMock(return_value=0)) + @mock.patch('config.muxcable.swsscommon.Select', mock.MagicMock(return_value=0)) + def test_config_muxcable_telemetry_enable_without_patch(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["muxcable"].commands["telemetry"], [ + "enable"], obj=db) + assert result.exit_code == 1 + + @mock.patch('config.muxcable.swsscommon.DBConnector', mock.MagicMock(return_value=0)) + @mock.patch('config.muxcable.swsscommon.Table', mock.MagicMock(return_value=0)) + @mock.patch('config.muxcable.swsscommon.Select', mock.MagicMock(return_value=0)) + def test_config_muxcable_telemetry_disable_without_patch(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["muxcable"].commands["telemetry"], [ + "disable"], obj=db) + assert result.exit_code == 1 + + @mock.patch('config.muxcable.swsscommon.DBConnector', mock.MagicMock(return_value=0)) + @mock.patch('config.muxcable.swsscommon.Table', mock.MagicMock(return_value=0)) + @mock.patch('config.muxcable.swsscommon.Select', mock.MagicMock(return_value=0)) + @mock.patch('config.muxcable.update_configdb_ycable_telemetry_data', mock.MagicMock(return_value=0)) + def test_config_muxcable_telemetry_enable(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["muxcable"].commands["telemetry"], [ + "enable"], obj=db) + assert result.exit_code == 0 + @classmethod def teardown_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "0" diff --git a/tests/passw_hardening_input/assert_show_output.py b/tests/passw_hardening_input/assert_show_output.py new file mode 100644 index 0000000000..9500c98be4 --- /dev/null +++ b/tests/passw_hardening_input/assert_show_output.py @@ -0,0 +1,40 @@ +""" +Module holding the correct values for show CLI command outputs for the passw_hardening_test.py +""" + +show_passw_hardening_policies_default="""\ +STATE EXPIRATION EXPIRATION WARNING HISTORY CNT LEN MIN REJECT USER PASSW MATCH LOWER CLASS UPPER CLASS DIGITS CLASS SPECIAL CLASS +-------- ------------ -------------------- ------------- --------- ------------------------- ------------- ------------- -------------- --------------- +disabled 180 15 10 8 true true true true true +""" + +show_passw_hardening_policies_classes_disabled="""\ +STATE EXPIRATION EXPIRATION WARNING HISTORY CNT LEN MIN REJECT USER PASSW MATCH LOWER CLASS UPPER CLASS DIGITS CLASS SPECIAL CLASS +-------- ------------ -------------------- ------------- --------- ------------------------- ------------- ------------- -------------- --------------- +disabled 180 15 10 8 false false false false false +""" + +show_passw_hardening_policies_enabled="""\ +STATE EXPIRATION EXPIRATION WARNING HISTORY CNT LEN MIN REJECT USER PASSW MATCH LOWER CLASS UPPER CLASS DIGITS CLASS SPECIAL CLASS +------- ------------ -------------------- ------------- --------- ------------------------- ------------- ------------- -------------- --------------- +enabled 180 15 10 8 true true true true true +""" + + +show_passw_hardening_policies_expiration="""\ +STATE EXPIRATION EXPIRATION WARNING HISTORY CNT LEN MIN REJECT USER PASSW MATCH LOWER CLASS UPPER CLASS DIGITS CLASS SPECIAL CLASS +------- ------------ -------------------- ------------- --------- ------------------------- ------------- ------------- -------------- --------------- +enabled 100 15 10 8 true true true true true +""" + +show_passw_hardening_policies_history_cnt="""\ +STATE EXPIRATION EXPIRATION WARNING HISTORY CNT LEN MIN REJECT USER PASSW MATCH LOWER CLASS UPPER CLASS DIGITS CLASS SPECIAL CLASS +-------- ------------ -------------------- ------------- --------- ------------------------- ------------- ------------- -------------- --------------- +disabled 180 15 40 8 true true true true true +""" + +show_passw_hardening_policies_len_min="""\ +STATE EXPIRATION EXPIRATION WARNING HISTORY CNT LEN MIN REJECT USER PASSW MATCH LOWER CLASS UPPER CLASS DIGITS CLASS SPECIAL CLASS +-------- ------------ -------------------- ------------- --------- ------------------------- ------------- ------------- -------------- --------------- +disabled 180 15 10 30 true true true true true +""" \ No newline at end of file diff --git a/tests/passw_hardening_input/default_config_db.json b/tests/passw_hardening_input/default_config_db.json new file mode 100644 index 0000000000..0eb363eb41 --- /dev/null +++ b/tests/passw_hardening_input/default_config_db.json @@ -0,0 +1,14 @@ +{ + "PASSW_HARDENING|POLICIES": { + "state": "disabled", + "expiration": "180", + "expiration_warning": "15", + "history_cnt": "10", + "len_min": "8", + "reject_user_passw_match": "true", + "digits_class": "true", + "lower_class": "true", + "special_class": "true", + "upper_class": "true" + } +} diff --git a/tests/passw_hardening_test.py b/tests/passw_hardening_test.py new file mode 100644 index 0000000000..e57fdfd0c8 --- /dev/null +++ b/tests/passw_hardening_test.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python + +import os +import logging +import show.main as show +import config.main as config + +from .passw_hardening_input import assert_show_output +from utilities_common.db import Db +from click.testing import CliRunner +from .mock_tables import dbconnector + +logger = logging.getLogger(__name__) +test_path = os.path.dirname(os.path.abspath(__file__)) +mock_db_path = os.path.join(test_path, "passw_hardening_input") + +SUCCESS = 0 +ERROR = 1 +INVALID_VALUE = 'INVALID' +EXP_GOOD_FLOW = 1 +EXP_BAD_FLOW = 0 + +class TestPasswHardening: + @classmethod + def setup_class(cls): + logger.info("SETUP") + os.environ['UTILITIES_UNIT_TESTING'] = "2" + + + @classmethod + def teardown_class(cls): + logger.info("TEARDOWN") + os.environ['UTILITIES_UNIT_TESTING'] = "0" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + dbconnector.dedicated_dbs['CONFIG_DB'] = None + + def verify_passw_policies_output(self, db, runner, output, expected=EXP_GOOD_FLOW): + result = runner.invoke(show.cli.commands["passw-hardening"].commands["policies"], [], obj=db) + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + + if expected: # good flow expected (default) + assert result.exit_code == SUCCESS + assert result.output == output + else: # bad flow expected + assert result.exit_code == ERROR + + def passw_hardening_set_policy(self, runner, db, attr, value, expected=EXP_GOOD_FLOW): + result = runner.invoke( + config.config.commands["passw-hardening"].commands["policies"].commands[attr], + [value], obj=db + ) + + if expected: # good flow expected (default) + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == SUCCESS + else: # bad flow expected + assert result.exit_code == ERROR + + + ######### PASSW-HARDENING ######### + + def test_passw_hardening_default(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.verify_passw_policies_output(db, runner, assert_show_output.show_passw_hardening_policies_default) + + def test_passw_hardening_feature_enabled(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "state", "enabled") + + self.verify_passw_policies_output(db, runner, assert_show_output.show_passw_hardening_policies_enabled) + + def test_passw_hardening_feature_disabled(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "state", "enabled") + self.passw_hardening_set_policy(runner, db, "state", "disabled") + + self.verify_passw_policies_output(db, runner, assert_show_output.show_passw_hardening_policies_default) + + def test_passw_hardening_policies_classes_disabled(self): + """Disable passw hardening classes & reject user passw match policies""" + + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + passw_classes = { "reject-user-passw-match": "false", + "digits-class": "false", + "lower-class": "false", + "special-class": "false", + "upper-class": "false" + } + + for k, v in passw_classes.items(): + self.passw_hardening_set_policy(runner, db, k, v) + + self.verify_passw_policies_output(db, runner, assert_show_output.show_passw_hardening_policies_classes_disabled) + + def test_passw_hardening_policies_exp_time(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "state", "enabled") + self.passw_hardening_set_policy(runner, db, "expiration", "100") + self.passw_hardening_set_policy(runner, db, "expiration-warning", "15") + + self.verify_passw_policies_output(db, runner, assert_show_output.show_passw_hardening_policies_expiration) + + def test_passw_hardening_policies_history(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "history-cnt", "40") + + self.verify_passw_policies_output(db, runner, assert_show_output.show_passw_hardening_policies_history_cnt) + + def test_passw_hardening_policies_len_min(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "len-min", "30") + + self.verify_passw_policies_output(db, runner, assert_show_output.show_passw_hardening_policies_len_min) + + def test_passw_hardening_bad_flow_len_min(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "state", "enabled") + self.passw_hardening_set_policy(runner, db, "len-min", "10000", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_history_cnt(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "state", "enabled") + self.passw_hardening_set_policy(runner, db, "history-cnt", "100000", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_state(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "state", "0", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_expiration(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "expiration", "####", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_expiration_warning(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "expiration-warning", "4000", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_upper_class(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "upper-class", "1", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_lower_class(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "lower-class", "1", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_special_class(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "special-class", "1", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_digits_class(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "digits-class", "1", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_reject_user_passw_match(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + + self.passw_hardening_set_policy(runner, db, "reject-user-passw-match", "1", EXP_BAD_FLOW) + + def test_passw_hardening_bad_flow_policy(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'default_config_db') + db = Db() + runner = CliRunner() + try: + self.passw_hardening_set_policy(runner, db, "no-exist-command", "1", EXP_BAD_FLOW) + except Exception as e: + # import pdb;pdb.set_trace() + if 'no-exist-command' in str(e): + pass + else: + raise e + diff --git a/tests/pbh_test.py b/tests/pbh_test.py index 1972747782..7dddfea9ca 100644 --- a/tests/pbh_test.py +++ b/tests/pbh_test.py @@ -10,6 +10,7 @@ from .pbh_input import assert_show_output from utilities_common.db import Db +from utilities_common.cli import UserCache from click.testing import CliRunner from .mock_tables import dbconnector from .mock_tables import mock_single_asic @@ -876,10 +877,7 @@ def test_show_pbh_rule(self): def remove_pbh_counters_file(self): - SAVED_PBH_COUNTERS_FILE = '/tmp/.pbh_counters.txt' - if os.path.isfile(SAVED_PBH_COUNTERS_FILE): - os.remove(SAVED_PBH_COUNTERS_FILE) - + UserCache('pbh').remove_all() def test_show_pbh_statistics_on_empty_config(self): dbconnector.dedicated_dbs['CONFIG_DB'] = None diff --git a/tests/pfcstat_test.py b/tests/pfcstat_test.py index 955db3c23b..75f7ea6f59 100644 --- a/tests/pfcstat_test.py +++ b/tests/pfcstat_test.py @@ -8,6 +8,7 @@ import show.main as show from .utils import get_result_and_return_code +from utilities_common.cli import UserCache test_path = os.path.dirname(os.path.abspath(__file__)) modules_path = os.path.dirname(test_path) @@ -130,9 +131,8 @@ def del_cached_stats(): - uid = str(os.getuid()) - cnstat_dir = os.path.join(os.sep, "tmp", "pfcstat-{}".format(uid)) - shutil.rmtree(cnstat_dir, ignore_errors=True, onerror=None) + cache = UserCache("pfcstat") + cache.remove_all() def pfc_clear(expected_output): @@ -143,17 +143,6 @@ def pfc_clear(expected_output): 'pfcstat -c' ) - # verify that files are created with stats - uid = str(os.getuid()) - cnstat_dir = os.path.join(os.sep, "tmp", "pfcstat-{}".format(uid)) - cnstat_fqn_file_rx = "{}rx".format(uid) - cnstat_fqn_file_tx = "{}tx".format(uid) - file_list = [cnstat_fqn_file_tx, cnstat_fqn_file_rx] - file_list.sort() - files = os.listdir(cnstat_dir) - files.sort() - assert files == file_list - return_code, result = get_result_and_return_code( 'pfcstat -s all' ) diff --git a/tests/pgdropstat_test.py b/tests/pgdropstat_test.py index 3aea0f2959..a46a05b25b 100644 --- a/tests/pgdropstat_test.py +++ b/tests/pgdropstat_test.py @@ -9,6 +9,8 @@ from click.testing import CliRunner from shutil import copyfile +from utilities_common.cli import UserCache + test_path = os.path.dirname(os.path.abspath(__file__)) modules_path = os.path.dirname(test_path) scripts_path = os.path.join(modules_path, "scripts") @@ -88,25 +90,9 @@ def executor(self, clear_before_show): assert result.exit_code == 0 assert result.output == show_output - def test_show_pg_drop_config_reload(self): - runner = CliRunner() - self.test_show_pg_drop_clear() - - # simulate 'config reload' to provoke counters recalculation (remove backup from /tmp folder) - result = runner.invoke(config.config.commands["reload"], [ "--no_service_restart", "-y"]) - - print(result.exit_code) - print(result.output) - - assert result.exit_code == 0 - - self.test_show_pg_drop_show() - @classmethod def teardown_class(cls): os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) os.environ['UTILITIES_UNIT_TESTING'] = "0" - dropstat_dir_prefix = '/tmp/dropstat' - dir_path = "{}-{}/".format(dropstat_dir_prefix, os.getuid()) - os.system("rm -rf {}".format(dir_path)) + UserCache('pg-drop').remove_all() print("TEARDOWN") diff --git a/tests/portchannel_test.py b/tests/portchannel_test.py index 7c9a7b601c..bd30c73649 100644 --- a/tests/portchannel_test.py +++ b/tests/portchannel_test.py @@ -1,4 +1,5 @@ import os +import pytest import traceback from click.testing import CliRunner @@ -60,7 +61,32 @@ def test_delete_non_existing_portchannel(self): print(result.output) assert result.exit_code != 0 assert "Error: PortChannel0005 is not present." in result.output - + + @pytest.mark.parametrize("fast_rate", ["False", "True", "false", "true"]) + def test_add_portchannel_with_fast_rate(self, fast_rate): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel with fats rate + result = runner.invoke(config.config.commands["portchannel"].commands["add"], ["PortChannel0005", "--fast-rate", fast_rate], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + @pytest.mark.parametrize("fast_rate", ["Fls", "tru"]) + def test_add_portchannel_with_invalid_fast_rate(self, fast_rate): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + # add a portchannel with invalid fats rate + result = runner.invoke(config.config.commands["portchannel"].commands["add"], ["PortChannel0005", "--fast-rate", fast_rate], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert 'Invalid value for "--fast-rate"' in result.output + def test_add_portchannel_member_with_invalid_name(self): runner = CliRunner() db = Db() @@ -121,6 +147,19 @@ def test_add_portchannel_member_which_has_ipaddress(self): assert result.exit_code != 0 assert "Error: Ethernet0 has ip address configured" in result.output + def test_add_portchannel_member_which_has_subintf(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", "Ethernet36"], obj=obj) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + print(result.output) + assert "Error: Ethernet36 has subinterfaces configured" in result.output + def test_add_portchannel_member_which_is_member_of_vlan(self): runner = CliRunner() db = Db() diff --git a/tests/portstat_test.py b/tests/portstat_test.py index b8dd055733..2a70d0befc 100644 --- a/tests/portstat_test.py +++ b/tests/portstat_test.py @@ -6,6 +6,7 @@ import clear.main as clear import show.main as show from .utils import get_result_and_return_code +from utilities_common.cli import UserCache root_path = os.path.dirname(os.path.abspath(__file__)) modules_path = os.path.dirname(root_path) @@ -33,6 +34,23 @@ Ethernet8 N/A 6 1350.00 KB/s 9000.00/s N/A 100 10 N/A 60 13.37 MB/s 9000.00/s N/A N/A N/A N/A """ +intf_fec_counters = """\ + IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR +--------- ------- ---------- ------------ ---------------- +Ethernet0 D 130,402 3 4 +Ethernet4 N/A 110,412 1 0 +Ethernet8 N/A 100,317 0 0 +""" + +intf_fec_counters_period = """\ +The rates are calculated within 3 seconds period + IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR +--------- ------- ---------- ------------ ---------------- +Ethernet0 D 0 0 0 +Ethernet4 N/A 0 0 0 +Ethernet8 N/A 0 0 0 +""" + intf_counters_period = """\ The rates are calculated within 3 seconds period IFACE STATE RX_OK RX_BPS RX_UTIL RX_ERR RX_DRP RX_OVR TX_OK TX_BPS TX_UTIL TX_ERR TX_DRP TX_OVR @@ -191,9 +209,8 @@ def remove_tmp_cnstat_file(): # remove the tmp portstat - uid = str(os.getuid()) - cnstat_dir = os.path.join(os.sep, "tmp", "portstat-{}".format(uid)) - shutil.rmtree(cnstat_dir, ignore_errors=True, onerror=None) + cache = UserCache("portstat") + cache.remove_all() def verify_after_clear(output, expected_out): @@ -258,6 +275,39 @@ def test_show_intf_counters_all(self): assert return_code == 0 assert result == intf_counters_all + def test_show_intf_fec_counters(self): + runner = CliRunner() + result = runner.invoke( + show.cli.commands["interfaces"].commands["counters"].commands["fec-stats"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == intf_fec_counters + + return_code, result = get_result_and_return_code('portstat -f') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_fec_counters + + def test_show_intf_fec_counters_period(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["fec-stats"], + ["-p {}".format(TEST_PERIOD)]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == intf_fec_counters_period + + return_code, result = get_result_and_return_code( + 'portstat -f -p {}'.format(TEST_PERIOD)) + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_fec_counters_period + + + def test_show_intf_counters_period(self): runner = CliRunner() result = runner.invoke(show.cli.commands["interfaces"].commands["counters"], [ diff --git a/tests/route_check_test.py b/tests/route_check_test.py index b4fd3ce17d..746e09c7f8 100644 --- a/tests/route_check_test.py +++ b/tests/route_check_test.py @@ -24,6 +24,7 @@ OP_SET = "SET" OP_DEL = "DEL" +NEIGH_TABLE = 'NEIGH_TABLE' ROUTE_TABLE = 'ROUTE_TABLE' VNET_ROUTE_TABLE = 'VNET_ROUTE_TABLE' INTF_TABLE = 'INTF_TABLE' @@ -293,6 +294,49 @@ } } } + }, + "7": { + DESCR: "dualtor standalone tunnel route case", + ARGS: "route_check", + PRE: { + APPL_DB: { + NEIGH_TABLE: { + "Vlan1000:fc02:1000::99": { "neigh": "00:00:00:00:00:00", "family": "IPv6"} + } + }, + ASIC_DB: { + RT_ENTRY_TABLE: { + RT_ENTRY_KEY_PREFIX + "fc02:1000::99/128" + RT_ENTRY_KEY_SUFFIX: {}, + } + } + } + }, + "8": { + DESCR: "Good one with VRF routes", + ARGS: "route_check", + PRE: { + APPL_DB: { + ROUTE_TABLE: { + "Vrf1:0.0.0.0/0" : { "ifname": "portchannel0" }, + "Vrf1:10.10.196.12/31" : { "ifname": "portchannel0" }, + "Vrf1:10.10.196.20/31" : { "ifname": "portchannel0" } + }, + 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: {} + } + } + } } } @@ -376,6 +420,11 @@ def get(self, key): return (True, ret) + def hget(self, key, field): + ret = copy.deepcopy(self.data.get(key, {}).get(field, {})) + return True, ret + + db_conns = {"APPL_DB": APPL_DB, "ASIC_DB": ASIC_DB} def conn_side_effect(arg, _): return db_conns[arg] diff --git a/tests/sfp_test.py b/tests/sfp_test.py index a69872ab76..d762b9f8ae 100644 --- a/tests/sfp_test.py +++ b/tests/sfp_test.py @@ -364,6 +364,14 @@ def test_sfp_presence(self): assert result.exit_code == 0 assert result.output == expected + result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["presence"], ["Ethernet29"]) + expected = """Port Presence +---------- ----------- +Ethernet29 Not present +""" + assert result.exit_code == 0 + assert result.output == expected + result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["presence"], ["Ethernet36"]) expected = """Port Presence ---------- ---------- @@ -487,6 +495,13 @@ def test_sfp_eeprom_dom_all(self): assert result.exit_code == 0 assert "\n".join([ l.rstrip() for l in result.output.split('\n')]) == test_sfp_eeprom_dom_all_output + def test_is_rj45_port(self): + import utilities_common.platform_sfputil_helper as platform_sfputil_helper + platform_sfputil_helper.platform_chassis = None + if 'sonic_platform' in sys.modules: + sys.modules.pop('sonic_platform') + assert platform_sfputil_helper.is_rj45_port("Ethernet0") == False + @classmethod def teardown_class(cls): print("TEARDOWN") diff --git a/tests/sfputil_test.py b/tests/sfputil_test.py index a4d568d20e..1231ba67d7 100644 --- a/tests/sfputil_test.py +++ b/tests/sfputil_test.py @@ -268,15 +268,16 @@ def test_version(self): result = runner.invoke(sfputil.cli.commands['version'], []) assert result.output.rstrip() == 'sfputil version {}'.format(sfputil.VERSION) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=False)) def test_error_status_from_db(self): db = Db() expected_output = [['Ethernet0', 'Blocking Error|High temperature'], ['Ethernet4', 'OK'], ['Ethernet8', 'Unplugged'], ['Ethernet12', 'Unknown state: 255'], - ['Ethernet16', 'N/A'], - ['Ethernet28', 'N/A'], - ['Ethernet36', 'N/A']] + ['Ethernet16', 'Unplugged'], + ['Ethernet28', 'Unplugged'], + ['Ethernet36', 'Unknown']] output = sfputil.fetch_error_status_from_state_db(None, db.db) assert output == expected_output @@ -284,11 +285,7 @@ def test_error_status_from_db(self): output = sfputil.fetch_error_status_from_state_db('Ethernet0', db.db) assert output == expected_output_ethernet0 - expected_output_ethernet16 = expected_output[4:5] - output = sfputil.fetch_error_status_from_state_db('Ethernet16', db.db) - assert output == expected_output_ethernet16 - - @patch('sfputil.main.is_rj45_port_from_db', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) def test_error_status_from_db_RJ45(self): db = Db() expected_output = [['Ethernet0', 'N/A'], @@ -305,13 +302,9 @@ def test_error_status_from_db_RJ45(self): output = sfputil.fetch_error_status_from_state_db('Ethernet0', db.db) assert output == expected_output_ethernet0 - expected_output_ethernet16 = expected_output[4:5] - output = sfputil.fetch_error_status_from_state_db('Ethernet16', db.db) - assert output == expected_output_ethernet16 - @patch('sfputil.main.logical_port_name_to_physical_port_list', MagicMock(return_value=[1])) @patch('sfputil.main.logical_port_to_physical_port_index', MagicMock(return_value=1)) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=False)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=False)) @patch('subprocess.check_output', MagicMock(return_value="['0:OK']")) def test_fetch_error_status_from_platform_api(self): output = sfputil.fetch_error_status_from_platform_api('Ethernet0') @@ -320,7 +313,7 @@ def test_fetch_error_status_from_platform_api(self): @patch('sfputil.main.logical_port_name_to_physical_port_list', MagicMock(return_value=[1])) @patch('sfputil.main.logical_port_to_physical_port_index', MagicMock(return_value=1)) @patch('subprocess.check_output', MagicMock(return_value="['0:OK']")) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) def test_fetch_error_status_from_platform_api_RJ45(self): output = sfputil.fetch_error_status_from_platform_api('Ethernet0') assert output == [['Ethernet0', 'N/A']] @@ -401,6 +394,7 @@ def test_show_lpmode(self, mock_chassis): mock_sfp.get_lpmode.return_value = False mock_sfp.get_transceiver_info = MagicMock(return_value={'type': sfputil.RJ45_PORT_TYPE}) + mock_chassis.get_port_or_cage_type = MagicMock(return_value=sfputil.SfpBase.SFP_PORT_TYPE_BIT_RJ45) result = runner.invoke(sfputil.cli.commands['show'].commands['lpmode'], ["-p", "Ethernet0"]) assert result.exit_code == 0 expected_output = """Port Low-power Mode @@ -413,7 +407,7 @@ def test_show_lpmode(self, mock_chassis): @patch('sfputil.main.logical_port_to_physical_port_index', MagicMock(return_value=1)) @patch('sfputil.main.logical_port_name_to_physical_port_list', MagicMock(return_value=[1])) @patch('sfputil.main.platform_sfputil', MagicMock(is_logical_port=MagicMock(return_value=1))) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) def test_show_eeprom_RJ45(self, mock_chassis): mock_sfp = MagicMock() mock_api = MagicMock() @@ -424,14 +418,8 @@ def test_show_eeprom_RJ45(self, mock_chassis): expected_output = "Ethernet16: SFP EEPROM is not applicable for RJ45 port\n\n\n" assert result.output == expected_output - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) - @patch('sys.exit', MagicMock(return_value=EXIT_FAIL)) - def test_skip_if_port_is_rj45(self): - result = sfputil.skip_if_port_is_rj45('Ethernet0') - assert result == None - @patch('sfputil.main.logical_port_name_to_physical_port_list', MagicMock(return_value=1)) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) @patch('sfputil.main.platform_sfputil', MagicMock(is_logical_port=MagicMock(return_value=1))) def test_lpmode_set(self): runner = CliRunner() @@ -440,7 +428,7 @@ def test_lpmode_set(self): assert result.exit_code == EXIT_FAIL @patch('sfputil.main.logical_port_name_to_physical_port_list', MagicMock(return_value=1)) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) @patch('sfputil.main.platform_sfputil', MagicMock(is_logical_port=MagicMock(return_value=1))) def test_reset_RJ45(self): runner = CliRunner() @@ -463,7 +451,7 @@ def test_unlock_firmware(self, mock_chassis): @patch('sfputil.main.platform_chassis') @patch('sfputil.main.logical_port_to_physical_port_index', MagicMock(return_value=1)) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) def test_show_fwversion_Rj45(self, mock_chassis): mock_sfp = MagicMock() mock_api = MagicMock() @@ -500,7 +488,7 @@ def test_commit_firmwre(self, mock_chassis): assert status == 1 @patch('sfputil.main.is_sfp_present', MagicMock(return_value=True)) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) def test_firmware_run_RJ45(self): runner = CliRunner() result = runner.invoke(sfputil.cli.commands['firmware'].commands['run'], ["--mode", "0", "Ethernet0"]) @@ -508,7 +496,7 @@ def test_firmware_run_RJ45(self): assert result.exit_code == EXIT_FAIL @patch('sfputil.main.is_sfp_present', MagicMock(return_value=True)) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) def test_firmware_commit_RJ45(self): runner = CliRunner() result = runner.invoke(sfputil.cli.commands['firmware'].commands['commit'], ["Ethernet0"]) @@ -516,7 +504,7 @@ def test_firmware_commit_RJ45(self): assert result.exit_code == EXIT_FAIL @patch('sfputil.main.logical_port_to_physical_port_index', MagicMock(return_value=1)) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) @patch('sfputil.main.is_sfp_present', MagicMock(return_value=1)) def test_firmware_upgrade_RJ45(self): runner = CliRunner() @@ -525,7 +513,7 @@ def test_firmware_upgrade_RJ45(self): assert result.exit_code == EXIT_FAIL @patch('sfputil.main.logical_port_to_physical_port_index', MagicMock(return_value=1)) - @patch('sfputil.main.is_rj45_port_from_api', MagicMock(return_value=True)) + @patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=True)) @patch('sfputil.main.is_sfp_present', MagicMock(return_value=1)) def test_firmware_download_RJ45(self): runner = CliRunner() diff --git a/tests/show_vrf_test.py b/tests/show_vrf_test.py index 3c6d1c5b36..269a968477 100644 --- a/tests/show_vrf_test.py +++ b/tests/show_vrf_test.py @@ -4,6 +4,7 @@ from swsscommon.swsscommon import SonicV2Connector from utilities_common.db import Db +import config.main as config import show.main as show test_path = os.path.dirname(os.path.abspath(__file__)) @@ -28,9 +29,95 @@ def test_vrf_show(self): Vrf101 Ethernet0.10 Vrf102 PortChannel0002 Vlan40 - Eth32.10 + Eth36.10 Vrf103 Ethernet4 Loopback0 + Po0002.101 +""" + + result = runner.invoke(show.cli.commands['vrf'], [], obj=db) + dbconnector.dedicated_dbs = {} + assert result.exit_code == 0 + assert result.output == expected_output + + def test_vrf_bind_unbind(self): + from .mock_tables import dbconnector + jsonfile_config = os.path.join(mock_db_path, "config_db") + dbconnector.dedicated_dbs['CONFIG_DB'] = jsonfile_config + runner = CliRunner() + db = Db() + expected_output = """\ +VRF Interfaces +------ --------------- +Vrf1 +Vrf101 Ethernet0.10 +Vrf102 PortChannel0002 + Vlan40 + Eth36.10 +Vrf103 Ethernet4 + Loopback0 + Po0002.101 +""" + + result = runner.invoke(show.cli.commands['vrf'], [], obj=db) + dbconnector.dedicated_dbs = {} + assert result.exit_code == 0 + assert result.output == expected_output + + obj = {'config_db':db.cfgdb} + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Ethernet4"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert 'Ethernet4' not in db.cfgdb.get_table('INTERFACE') + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Loopback0"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert 'Loopback0' not in db.cfgdb.get_table('LOOPBACK_INTERFACE') + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Vlan40"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert 'Vlan40' not in db.cfgdb.get_table('VLAN_INTERFACE') + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["PortChannel0002"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert 'PortChannel002' not in db.cfgdb.get_table('PORTCHANNEL_INTERFACE') + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Eth36.10"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('vrf_name', 'Vrf102') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Eth36.10'] + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Ethernet0.10"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('vrf_name', 'Vrf101') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Ethernet0.10'] + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Po0002.101"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('vrf_name', 'Vrf103') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Po0002.101'] + + + #Bind click CLI cannot be tested as it tries to connecte to statedb + #for verification of all IP address delete before applying new vrf configuration + jsonfile_config = os.path.join(mock_db_path, "config_db") + dbconnector.dedicated_dbs['CONFIG_DB'] = jsonfile_config + + expected_output = """\ +VRF Interfaces +------ --------------- +Vrf1 +Vrf101 Ethernet0.10 +Vrf102 PortChannel0002 + Vlan40 + Eth36.10 +Vrf103 Ethernet4 + Loopback0 + Po0002.101 """ result = runner.invoke(show.cli.commands['vrf'], [], obj=db) diff --git a/tests/static_routes_test.py b/tests/static_routes_test.py index 4d60c3ef0c..da8a4ea97b 100644 --- a/tests/static_routes_test.py +++ b/tests/static_routes_test.py @@ -45,8 +45,8 @@ def test_simple_static_route(self): result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "1.2.3.4/32", "nexthop", "30.0.0.5"], obj=obj) print(result.exit_code, result.output) - assert ('1.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '1.2.3.4/32') == {'nexthop': '30.0.0.5', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} + assert ('default', '1.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|1.2.3.4/32') == {'nexthop': '30.0.0.5', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} # config route del prefix 1.2.3.4/32 nexthop 30.0.0.5 result = runner.invoke(config.config.commands["route"].commands["del"], \ @@ -93,6 +93,8 @@ def test_vrf_static_route(self): obj = {'config_db':db.cfgdb} # config route add prefix vrf Vrf-BLUE 2.2.3.4/32 nexthop 30.0.0.6 + result = runner.invoke(config.config.commands["vrf"].commands["add"], ["Vrf-BLUE"], obj=obj) + print(result.exit_code, result.output) result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "vrf", "Vrf-BLUE", "2.2.3.4/32", "nexthop", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) @@ -111,11 +113,14 @@ def test_dest_vrf_static_route(self): obj = {'config_db':db.cfgdb} # config route add prefix 3.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.6 + result = runner.invoke(config.config.commands["vrf"].commands["add"], ["Vrf-RED"], obj=obj) + print(result.exit_code, result.output) result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "3.2.3.4/32", "nexthop", "vrf", "Vrf-RED", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert ('3.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '3.2.3.4/32') == {'nexthop': '30.0.0.6', 'nexthop-vrf': 'Vrf-RED', 'blackhole': 'false', 'distance': '0', 'ifname': ''} + print(db.cfgdb.get_table('STATIC_ROUTE')) + assert ('default', '3.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|3.2.3.4/32') == {'nexthop': '30.0.0.6', 'nexthop-vrf': 'Vrf-RED', 'blackhole': 'false', 'distance': '0', 'ifname': ''} # config route del prefix 3.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["del"], \ @@ -129,26 +134,28 @@ def test_multiple_nexthops_with_vrf_static_route(self): obj = {'config_db':db.cfgdb} ''' Add ''' + result = runner.invoke(config.config.commands["vrf"].commands["add"], ["Vrf-RED"], obj=obj) + print(result.exit_code, result.output) # config route add prefix 6.2.3.4/32 nexthop vrf Vrf-RED "30.0.0.6,30.0.0.7" result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "6.2.3.4/32", "nexthop", "vrf", "Vrf-RED", "30.0.0.6,30.0.0.7"], obj=obj) print(result.exit_code, result.output) - assert ('6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '6.2.3.4/32') == {'nexthop': '30.0.0.6,30.0.0.7', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ',', 'nexthop-vrf': 'Vrf-RED,Vrf-RED'} + assert ('default', '6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|6.2.3.4/32') == {'nexthop': '30.0.0.6,30.0.0.7', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ',', 'nexthop-vrf': 'Vrf-RED,Vrf-RED'} ''' Del ''' # config route del prefix 6.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.7 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "6.2.3.4/32", "nexthop", "vrf", "Vrf-RED", "30.0.0.7"], obj=obj) print(result.exit_code, result.output) - assert ('6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '6.2.3.4/32') == {'nexthop': '30.0.0.6', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': 'Vrf-RED'} + assert ('default', '6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|6.2.3.4/32') == {'nexthop': '30.0.0.6', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': 'Vrf-RED'} # config route del prefix 6.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "6.2.3.4/32", "nexthop", "vrf", "Vrf-RED", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert not ('6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert not ('default', '6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') def test_multiple_nexthops_static_route(self): db = Db() @@ -160,30 +167,30 @@ def test_multiple_nexthops_static_route(self): result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "6.2.3.4/32", "nexthop", "30.0.0.6,30.0.0.7"], obj=obj) print(result.exit_code, result.output) - assert ('6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '6.2.3.4/32') == {'nexthop': '30.0.0.6,30.0.0.7', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ',', 'nexthop-vrf': ','} + assert ('default', '6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|6.2.3.4/32') == {'nexthop': '30.0.0.6,30.0.0.7', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ',', 'nexthop-vrf': ','} # config route add prefix 6.2.3.4/32 nexthop 30.0.0.8 result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "6.2.3.4/32", "nexthop", "30.0.0.8"], obj=obj) print(result.exit_code, result.output) - assert ('6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '6.2.3.4/32') == {'nexthop': '30.0.0.6,30.0.0.7,30.0.0.8', 'blackhole': 'false,false,false', 'distance': '0,0,0', 'ifname': ',,', 'nexthop-vrf': ',,'} + assert ('default', '6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|6.2.3.4/32') == {'nexthop': '30.0.0.6,30.0.0.7,30.0.0.8', 'blackhole': 'false,false,false', 'distance': '0,0,0', 'ifname': ',,', 'nexthop-vrf': ',,'} ''' Del ''' # config route del prefix 6.2.3.4/32 nexthop 30.0.0.8 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "6.2.3.4/32", "nexthop", "30.0.0.8"], obj=obj) print(result.exit_code, result.output) - assert ('6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '6.2.3.4/32') == {"nexthop": '30.0.0.6,30.0.0.7', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ',', 'nexthop-vrf': ','} + assert ('default', '6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|6.2.3.4/32') == {"nexthop": '30.0.0.6,30.0.0.7', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ',', 'nexthop-vrf': ','} # config route del prefix 6.2.3.4/32 nexthop 30.0.0.7 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "6.2.3.4/32", "nexthop", "30.0.0.7"], obj=obj) print(result.exit_code, result.output) - assert ('6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '6.2.3.4/32') == {'nexthop': '30.0.0.6', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} + assert ('default', '6.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|6.2.3.4/32') == {'nexthop': '30.0.0.6', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} # config route del prefix 6.2.3.4/32 nexthop 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["del"], \ @@ -221,23 +228,23 @@ def test_static_route_ECMP_nexthop(self): result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "10.2.3.4/32", "nexthop", "30.0.0.5"], obj=obj) print(result.exit_code, result.output) - assert ('10.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '10.2.3.4/32') == {'nexthop': '30.0.0.5', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} + assert ('default', '10.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|10.2.3.4/32') == {'nexthop': '30.0.0.5', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} # config route add prefix 10.2.3.4/32 nexthop 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "10.2.3.4/32", "nexthop", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert ('10.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '10.2.3.4/32') == {'nexthop': '30.0.0.5,30.0.0.6', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ',', 'nexthop-vrf': ','} + assert ('default', '10.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|10.2.3.4/32') == {'nexthop': '30.0.0.5,30.0.0.6', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ',', 'nexthop-vrf': ','} ''' Del ''' # config route del prefix 10.2.3.4/32 nexthop 30.0.0.5 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "10.2.3.4/32", "nexthop", "30.0.0.5"], obj=obj) print(result.exit_code, result.output) - assert ('10.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '10.2.3.4/32') == {'nexthop': '30.0.0.6', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} + assert ('default', '10.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|10.2.3.4/32') == {'nexthop': '30.0.0.6', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} # config route del prefix 1.2.3.4/32 nexthop 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["del"], \ @@ -251,33 +258,37 @@ def test_static_route_ECMP_nexthop_with_vrf(self): obj = {'config_db':db.cfgdb} ''' Add ''' + result = runner.invoke(config.config.commands["vrf"].commands["add"], ["Vrf-RED"], obj=obj) + print(result.exit_code, result.output) # config route add prefix 11.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.5 result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "11.2.3.4/32", "nexthop", "vrf", "Vrf-RED", "30.0.0.5"], obj=obj) print(result.exit_code, result.output) - assert ('11.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '11.2.3.4/32') == {'nexthop': '30.0.0.5', 'nexthop-vrf': 'Vrf-RED', 'blackhole': 'false', 'distance': '0', 'ifname': ''} + assert ('default', '11.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|11.2.3.4/32') == {'nexthop': '30.0.0.5', 'nexthop-vrf': 'Vrf-RED', 'blackhole': 'false', 'distance': '0', 'ifname': ''} + result = runner.invoke(config.config.commands["vrf"].commands["add"], ["Vrf-BLUE"], obj=obj) + print(result.exit_code, result.output) # config route add prefix 11.2.3.4/32 nexthop vrf Vrf-BLUE 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "11.2.3.4/32", "nexthop", "vrf", "Vrf-BLUE", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert ('11.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '11.2.3.4/32') == {"nexthop": "30.0.0.5,30.0.0.6", "nexthop-vrf": "Vrf-RED,Vrf-BLUE", 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ','} + assert ('default', '11.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|11.2.3.4/32') == {"nexthop": "30.0.0.5,30.0.0.6", "nexthop-vrf": "Vrf-RED,Vrf-BLUE", 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ','} ''' Del ''' # config route del prefix 11.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.5 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "11.2.3.4/32", "nexthop", "vrf", "Vrf-RED", "30.0.0.5"], obj=obj) print(result.exit_code, result.output) - assert ('11.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '11.2.3.4/32') == {"nexthop": "30.0.0.6", "nexthop-vrf": "Vrf-BLUE", 'blackhole': 'false', 'distance': '0', 'ifname': ''} + assert ('default', '11.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|11.2.3.4/32') == {"nexthop": "30.0.0.6", "nexthop-vrf": "Vrf-BLUE", 'blackhole': 'false', 'distance': '0', 'ifname': ''} # config route del prefix 11.2.3.4/32 nexthop vrf Vrf-BLUE 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "11.2.3.4/32", "nexthop", "vrf", "Vrf-BLUE", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert not ('11.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert not ('default', '11.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') def test_static_route_ECMP_mixed_nextfop(self): db = Db() @@ -289,29 +300,31 @@ def test_static_route_ECMP_mixed_nextfop(self): result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "12.2.3.4/32", "nexthop", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert ('12.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '12.2.3.4/32') == {'nexthop': '30.0.0.6', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} + assert ('default', '12.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|12.2.3.4/32') == {'nexthop': '30.0.0.6', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} + result = runner.invoke(config.config.commands["vrf"].commands["add"], ["Vrf-RED"], obj=obj) + print(result.exit_code, result.output) # config route add prefix 12.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.7 result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "12.2.3.4/32", "nexthop", "vrf", "Vrf-RED", "30.0.0.7"], obj=obj) print(result.exit_code, result.output) - assert ('12.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '12.2.3.4/32') == {'nexthop': '30.0.0.6,30.0.0.7', 'nexthop-vrf': ',Vrf-RED', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ','} + assert ('default', '12.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|12.2.3.4/32') == {'nexthop': '30.0.0.6,30.0.0.7', 'nexthop-vrf': ',Vrf-RED', 'blackhole': 'false,false', 'distance': '0,0', 'ifname': ','} ''' Del ''' # config route del prefix 12.2.3.4/32 nexthop vrf Vrf-Red 30.0.0.7 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "12.2.3.4/32", "nexthop", "vrf", "Vrf-RED", "30.0.0.7"], obj=obj) print(result.exit_code, result.output) - assert ('12.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '12.2.3.4/32') == {'nexthop': '30.0.0.6', 'nexthop-vrf': '', 'ifname': '', 'blackhole': 'false', 'distance': '0'} + assert ('default', '12.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|12.2.3.4/32') == {'nexthop': '30.0.0.6', 'nexthop-vrf': '', 'ifname': '', 'blackhole': 'false', 'distance': '0'} # config route del prefix 12.2.3.4/32 nexthop 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "12.2.3.4/32", "nexthop", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert not ('12.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert not ('default', '12.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') def test_del_nonexist_key_static_route(self): db = Db() @@ -322,7 +335,7 @@ def test_del_nonexist_key_static_route(self): result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "17.2.3.4/32", "nexthop", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert ERROR_DEL_NONEXIST_KEY_STR.format("17.2.3.4/32") in result.output + assert ERROR_DEL_NONEXIST_KEY_STR.format("default|17.2.3.4/32") in result.output def test_del_nonexist_entry_static_route(self): db = Db() @@ -333,20 +346,20 @@ def test_del_nonexist_entry_static_route(self): result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "13.2.3.4/32", "nexthop", "30.0.0.5"], obj=obj) print(result.exit_code, result.output) - assert ('13.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '13.2.3.4/32') == {'nexthop': '30.0.0.5', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} + assert ('default', '13.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|13.2.3.4/32') == {'nexthop': '30.0.0.5', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} # config route del prefix 13.2.3.4/32 nexthop 30.0.0.6 <- nh ip that doesnt exist result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "13.2.3.4/32", "nexthop", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert ERROR_DEL_NONEXIST_ENTRY_STR.format(('30.0.0.6', '', ''), "13.2.3.4/32") in result.output + assert ERROR_DEL_NONEXIST_ENTRY_STR.format(('30.0.0.6', '', ''), "default|13.2.3.4/32") in result.output # config route del prefix 13.2.3.4/32 nexthop 30.0.0.5 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "13.2.3.4/32", "nexthop", "30.0.0.5"], obj=obj) print(result.exit_code, result.output) - assert not '13.2.3.4/32' in db.cfgdb.get_table('STATIC_ROUTE') + assert not ('default', '13.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') def test_del_entire_ECMP_static_route(self): db = Db() @@ -357,20 +370,20 @@ def test_del_entire_ECMP_static_route(self): result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "14.2.3.4/32", "nexthop", "30.0.0.5"], obj=obj) print(result.exit_code, result.output) - assert ('14.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '14.2.3.4/32') == {'nexthop': '30.0.0.5', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} + assert ('default', '14.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|14.2.3.4/32') == {'nexthop': '30.0.0.5', 'blackhole': 'false', 'distance': '0', 'ifname': '', 'nexthop-vrf': ''} # config route add prefix 14.2.3.4/32 nexthop 30.0.0.6 result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "14.2.3.4/32", "nexthop", "30.0.0.6"], obj=obj) print(result.exit_code, result.output) - assert ('14.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '14.2.3.4/32') == {'nexthop': '30.0.0.5,30.0.0.6', 'nexthop-vrf': ',', 'ifname': ',', 'blackhole': 'false,false', 'distance': '0,0'} + assert ('default', '14.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|14.2.3.4/32') == {'nexthop': '30.0.0.5,30.0.0.6', 'nexthop-vrf': ',', 'ifname': ',', 'blackhole': 'false,false', 'distance': '0,0'} # config route del prefix 14.2.3.4/32 result = runner.invoke(config.config.commands["route"].commands["del"], ["prefix", "14.2.3.4/32"], obj=obj) print(result.exit_code, result.output) - assert not '14.2.3.4/32' in db.cfgdb.get_table('STATIC_ROUTE') + assert not ('default', '14.2.3.4/32') in db.cfgdb.get_table('STATIC_ROUTE') def test_static_route_nexthop_subinterface(self): db = Db() @@ -381,27 +394,27 @@ def test_static_route_nexthop_subinterface(self): result = runner.invoke(config.config.commands["route"].commands["add"], \ ["prefix", "2.2.3.5/32", "nexthop", "dev", "Ethernet0.10"], obj=obj) print(result.exit_code, result.output) - assert ('2.2.3.5/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '2.2.3.5/32') == {'nexthop': '', 'blackhole': 'false', 'distance': '0', 'ifname': 'Ethernet0.10', 'nexthop-vrf': ''} + assert ('default', '2.2.3.5/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|2.2.3.5/32') == {'nexthop': '', 'blackhole': 'false', 'distance': '0', 'ifname': 'Ethernet0.10', 'nexthop-vrf': ''} # config route del prefix 2.2.3.5/32 nexthop dev Ethernet0.10 result = runner.invoke(config.config.commands["route"].commands["del"], \ ["prefix", "2.2.3.5/32", "nexthop", "dev", "Ethernet0.10"], obj=obj) print(result.exit_code, result.output) - assert not ('2.2.3.5/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert not ('default', '2.2.3.5/32') in db.cfgdb.get_table('STATIC_ROUTE') - # config route add prefix 2.2.3.5/32 nexthop dev Eth32.10 + # config route add prefix 2.2.3.5/32 nexthop dev Eth36.10 result = runner.invoke(config.config.commands["route"].commands["add"], \ - ["prefix", "2.2.3.5/32", "nexthop", "dev", "Eth32.10"], obj=obj) + ["prefix", "2.2.3.5/32", "nexthop", "dev", "Eth36.10"], obj=obj) print(result.exit_code, result.output) - assert ('2.2.3.5/32') in db.cfgdb.get_table('STATIC_ROUTE') - assert db.cfgdb.get_entry('STATIC_ROUTE', '2.2.3.5/32') == {'nexthop': '', 'blackhole': 'false', 'distance': '0', 'ifname': 'Eth32.10', 'nexthop-vrf': ''} + assert ('default', '2.2.3.5/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert db.cfgdb.get_entry('STATIC_ROUTE', 'default|2.2.3.5/32') == {'nexthop': '', 'blackhole': 'false', 'distance': '0', 'ifname': 'Eth36.10', 'nexthop-vrf': ''} - # config route del prefix 2.2.3.5/32 nexthop dev Eth32.10 + # config route del prefix 2.2.3.5/32 nexthop dev Eth36.10 result = runner.invoke(config.config.commands["route"].commands["del"], \ - ["prefix", "2.2.3.5/32", "nexthop", "dev", "Eth32.10"], obj=obj) + ["prefix", "2.2.3.5/32", "nexthop", "dev", "Eth36.10"], obj=obj) print(result.exit_code, result.output) - assert not ('2.2.3.5/32') in db.cfgdb.get_table('STATIC_ROUTE') + assert not ('default', '2.2.3.5/32') in db.cfgdb.get_table('STATIC_ROUTE') @classmethod def teardown_class(cls): diff --git a/tests/subintf_test.py b/tests/subintf_test.py new file mode 100644 index 0000000000..c69d87572e --- /dev/null +++ b/tests/subintf_test.py @@ -0,0 +1,231 @@ +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 + +SUB_INTF_ON_LAG_MEMBER_ERR="""\ +Usage: add [OPTIONS] +Try "add --help" for help. + +Error: Ethernet32 is configured as a member of portchannel. Cannot configure subinterface +""" + +class TestSubinterface(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "1" + print("SETUP") + + def test_add_del_subintf_short_name(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Eth0.102", "1002"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Eth0.102') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Eth0.102']['vlan'] == '1002' + assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Eth0.102']['admin_status'] == 'up' + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Po0004.104", "1004"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Po0004.104') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Po0004.104']['vlan'] == '1004' + assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Po0004.104']['admin_status'] == 'up' + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Eth0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Eth0.102') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Po0004.104"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Po0004.104') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + def test_add_del_subintf_with_long_name(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethernet0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Ethernet0.102') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Ethernet0.102']['admin_status'] == 'up' + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["PortChannel0004.104"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('PortChannel0004.104') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['PortChannel0004.104']['admin_status'] == 'up' + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Ethernet0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Ethernet0.102') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["PortChannel0004.104"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('PortChannel0004.104') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + + def test_add_existing_subintf_again(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethernet0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Ethernet0.102') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Ethernet0.102']['admin_status'] == 'up' + + #Check if same long format subintf creation is rejected + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethernet0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + #Check if same short format subintf creation with same encap vlan is rejected + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Eth0.1002", "102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert ('Eth0.1002') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + + def test_delete_non_existing_subintf(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Ethernet0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Eth0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["PortChannel0004.104"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Po0004.104"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + def test_invalid_subintf_creation(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethernet1000.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["PortChannel0008.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethe0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + #Short format subintf without encap vlan should be rejected + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Eth0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Po0004.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + + def test_subintf_creation_on_lag_member(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethernet32.10"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert(result.output == SUB_INTF_ON_LAG_MEMBER_ERR) + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Eth32.20"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert(result.output == SUB_INTF_ON_LAG_MEMBER_ERR) + + def test_subintf_vrf_bind_unbind(self): + runner = CliRunner() + db = Db() + obj = {'db':db.cfgdb} + + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethernet0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Ethernet0.102') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Ethernet0.102']['admin_status'] == 'up' + + vrf_obj = {'config_db':db.cfgdb, 'namespace':db.db.namespace} + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["bind"], ["Ethernet0.102", "Vrf1"], obj=vrf_obj) + assert result.exit_code == 0 + assert ('Vrf1') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Ethernet0.102']['vrf_name'] + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Ethernet0.102"], obj=vrf_obj) + assert result.exit_code == 0 + assert ('vrf_name') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Ethernet0.102'] + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Ethernet0.102"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Ethernet0.102') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + #shut name subintf vrf bind unbind check + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Eth0.1002", "2002"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Eth0.1002') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["bind"], ["Eth0.1002", "Vrf1"], obj=vrf_obj) + assert result.exit_code == 0 + assert ('Vrf1') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Eth0.1002']['vrf_name'] + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Eth0.1002"], obj=vrf_obj) + assert result.exit_code == 0 + assert ('vrf_name') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Eth0.1002'] + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Eth0.1002"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Eth0.1002') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + #Po subintf vrf bind unbind check + result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Po0004.1004", "2004"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Po0004.1004') in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["bind"], ["Po0004.1004", "Vrf1"], obj=vrf_obj) + assert result.exit_code == 0 + assert ('Vrf1') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Po0004.1004']['vrf_name'] + + result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Po0004.1004"], obj=vrf_obj) + assert result.exit_code == 0 + assert ('vrf_name') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Po0004.1004'] + + result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Po0004.1004"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert ('Po0004.1004') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + print("TEARDOWN") diff --git a/tests/swap_allocator_test.py b/tests/swap_allocator_test.py index 033b215dd2..960d4e8caf 100644 --- a/tests/swap_allocator_test.py +++ b/tests/swap_allocator_test.py @@ -17,6 +17,8 @@ def test_read_from_meminfo(self): proc_meminfo_lines = [ "MemTotal: 32859496 kB", "MemFree: 16275512 kB", + "SwapTotal: 2000000 kB", + "SwapFree: 1000000 kB", "HugePages_Total: 0", "HugePages_Free: 0", ] @@ -24,6 +26,8 @@ def test_read_from_meminfo(self): read_meminfo_expected_return = { "MemTotal": 32859496, "MemFree": 16275512, + "SwapTotal": 2000000, + "SwapFree": 1000000, "HugePages_Total": 0, "HugePages_Free": 0 } @@ -113,6 +117,8 @@ def test_swap_allocator_context_enter_allocate_true_insufficient_total_memory(se mock_meminfo.return_value = { "MemTotal": 2000000, "MemAvailable": 1900000, + "SwapTotal": 0, + "SwapFree": 0, } mock_exists.return_value = False @@ -135,6 +141,56 @@ def test_swap_allocator_context_enter_allocate_true_insufficient_available_memor mock_meminfo.return_value = { "MemTotal": 3000000, "MemAvailable": 1000000, + "SwapTotal": 0, + "SwapFree": 0, + } + mock_exists.return_value = False + + swap_allocator = SWAPAllocator(allocate=True) + try: + swap_allocator.__enter__() + except Exception as detail: + pytest.fail("SWAPAllocator context manager should not raise exception %s" % repr(detail)) + mock_setup.assert_called_once() + mock_remove.assert_not_called() + assert swap_allocator.is_allocated is True + + def test_swap_allocator_context_enter_allocate_true_insufficient_total_memory_plus_swap(self): + with mock.patch("sonic_installer.main.SWAPAllocator.get_disk_freespace") as mock_disk_free, \ + mock.patch("sonic_installer.main.SWAPAllocator.read_from_meminfo") as mock_meminfo, \ + mock.patch("sonic_installer.main.SWAPAllocator.setup_swapmem") as mock_setup, \ + mock.patch("sonic_installer.main.SWAPAllocator.remove_swapmem") as mock_remove, \ + mock.patch("os.path.exists") as mock_exists: + mock_disk_free.return_value = 10 * 1024 * 1024 * 1024 + mock_meminfo.return_value = { + "MemTotal": 1000000, + "MemAvailable": 900000, + "SwapTotal": 1000000, + "SwapFree": 1000000, + } + mock_exists.return_value = False + + swap_allocator = SWAPAllocator(allocate=True) + try: + swap_allocator.__enter__() + except Exception as detail: + pytest.fail("SWAPAllocator context manager should not raise exception %s" % repr(detail)) + mock_setup.assert_called_once() + mock_remove.assert_not_called() + assert swap_allocator.is_allocated is True + + def test_swap_allocator_context_enter_allocate_true_insufficient_available_memory_plus_swap(self): + with mock.patch("sonic_installer.main.SWAPAllocator.get_disk_freespace") as mock_disk_free, \ + mock.patch("sonic_installer.main.SWAPAllocator.read_from_meminfo") as mock_meminfo, \ + mock.patch("sonic_installer.main.SWAPAllocator.setup_swapmem") as mock_setup, \ + mock.patch("sonic_installer.main.SWAPAllocator.remove_swapmem") as mock_remove, \ + mock.patch("os.path.exists") as mock_exists: + mock_disk_free.return_value = 10 * 1024 * 1024 * 1024 + mock_meminfo.return_value = { + "MemTotal": 2000000, + "MemAvailable": 500000, + "SwapTotal": 1000000, + "SwapFree": 500000, } mock_exists.return_value = False @@ -157,6 +213,8 @@ def test_swap_allocator_context_enter_allocate_true_insufficient_disk_space(self mock_meminfo.return_value = { "MemTotal": 32859496, "MemAvailable": 16275512, + "SwapTotal": 0, + "SwapFree": 0, } mock_exists.return_value = False @@ -179,6 +237,8 @@ def test_swap_allocator_context_enter_allocate_true_swapfile_present(self): mock_meminfo.return_value = { "MemTotal": 32859496, "MemAvailable": 1000000, + "SwapTotal": 0, + "SwapFree": 0, } mock_exists.return_value = True @@ -201,6 +261,8 @@ def test_swap_allocator_context_enter_setup_error(self): mock_meminfo.return_value = { "MemTotal": 32859496, "MemAvailable": 1000000, + "SwapTotal": 0, + "SwapFree": 0, } mock_exists.return_value = False expected_err_str = "Pseudo Error" @@ -225,6 +287,8 @@ def test_swap_allocator_context_enter_allocate_false(self): mock_meminfo.return_value = { "MemTotal": 32859496, "MemAvailable": 1000000, + "SwapTotal": 0, + "SwapFree": 0, } mock_exists.return_value = False diff --git a/tests/vrf_input/config_db.json b/tests/vrf_input/config_db.json index 6d646f2f2b..1746c14c4f 100644 --- a/tests/vrf_input/config_db.json +++ b/tests/vrf_input/config_db.json @@ -3,11 +3,16 @@ "vrf_name": "Vrf101", "admin_status": "up" }, - "VLAN_SUB_INTERFACE|Eth32.10": { + "VLAN_SUB_INTERFACE|Eth36.10": { "vrf_name": "Vrf102", "admin_status": "up", "vlan": "100" }, + "VLAN_SUB_INTERFACE|Po0002.101": { + "vrf_name": "Vrf103", + "admin_status": "up", + "vlan": "1001" + }, "VLAN_INTERFACE|Vlan40": { "vrf_name": "Vrf102" }, diff --git a/tests/yang_config_validation_test.py b/tests/yang_config_validation_test.py new file mode 100644 index 0000000000..37b9e448cc --- /dev/null +++ b/tests/yang_config_validation_test.py @@ -0,0 +1,33 @@ +from click.testing import CliRunner +import config.main as config + +class TestYangConfigValidation(object): + @classmethod + def setup_class(cls): + print("SETUP") + + def __check_result(self, result_msg, mode): + if mode == "enable" or mode == "disable": + expected_msg = """Wrote %s yang config validation into CONFIG_DB""" % mode + else: + expected_msg = "Error: Invalid argument %s, expect either enable or disable" % mode + + return expected_msg in result_msg + + def test_yang_config_validation(self): + runner = CliRunner() + + result = runner.invoke(config.config.commands["yang_config_validation"], ["enable"]) + print(result.output) + assert result.exit_code == 0 + assert self.__check_result(result.output, "enable") + + result = runner.invoke(config.config.commands["yang_config_validation"], ["disable"]) + print(result.output) + assert result.exit_code == 0 + assert self.__check_result(result.output, "disable") + + result = runner.invoke(config.config.commands["yang_config_validation"], ["invalid-input"]) + print(result.output) + assert result.exit_code != 0 + assert self.__check_result(result.output, "invalid-input") diff --git a/utilities_common/cli.py b/utilities_common/cli.py index d6d8a111bf..6aaedcb209 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -4,6 +4,7 @@ import re import subprocess import sys +import shutil import click import json @@ -663,3 +664,41 @@ def query_yes_no(question, default="yes"): else: sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") + + +class UserCache: + """ General purpose cache directory created per user """ + + CACHE_DIR = "/tmp/cache/" + + def __init__(self, app_name=None, tag=None): + """ Initialize UserCache and create a cache directory if it does not exist. + + Args: + tag (str): Tag the user cache. Different tags correspond to different cache directories even for the same user. + """ + self.uid = os.getuid() + self.app_name = os.path.basename(sys.argv[0]) if app_name is None else app_name + self.cache_directory_suffix = str(self.uid) if tag is None else f"{self.uid}-{tag}" + self.cache_directory_app = os.path.join(self.CACHE_DIR, self.app_name) + + prev_umask = os.umask(0) + try: + os.makedirs(self.cache_directory_app, exist_ok=True) + finally: + os.umask(prev_umask) + + self.cache_directory = os.path.join(self.cache_directory_app, self.cache_directory_suffix) + os.makedirs(self.cache_directory, exist_ok=True) + + def get_directory(self): + """ Return the cache directory path """ + return self.cache_directory + + def remove(self): + """ Remove the content of the cache directory """ + shutil.rmtree(self.cache_directory) + + def remove_all(self): + """ Remove the content of the cache for all users """ + shutil.rmtree(self.cache_directory_app) diff --git a/utilities_common/platform_sfputil_helper.py b/utilities_common/platform_sfputil_helper.py index a7f4477660..89ade3fc04 100644 --- a/utilities_common/platform_sfputil_helper.py +++ b/utilities_common/platform_sfputil_helper.py @@ -6,7 +6,11 @@ from sonic_py_common import multi_asic, device_info platform_sfputil = None +platform_chassis = None +platform_sfp_base = None +platform_porttab_mapping_read = False +RJ45_PORT_TYPE = 'RJ45' def load_platform_sfputil(): @@ -22,6 +26,10 @@ def load_platform_sfputil(): def platform_sfputil_read_porttab_mappings(): + global platform_porttab_mapping_read + + if platform_porttab_mapping_read: + return 0 try: @@ -35,6 +43,8 @@ def platform_sfputil_read_porttab_mappings(): # For single ASIC platforms we pass port_config_file_path and the asic_inst as 0 port_config_file_path = device_info.get_path_to_port_config_file() platform_sfputil.read_porttab_mappings(port_config_file_path, 0) + + platform_porttab_mapping_read = True except Exception as e: click.echo("Error reading port info (%s)" % str(e)) sys.exit(1) @@ -70,7 +80,7 @@ def get_physical_to_logical(): def get_interface_name(port, db): - if port is not "all" and port is not None: + if port != "all" and port is not None: alias = port iface_alias_converter = clicommon.InterfaceAliasConverter(db) if clicommon.get_interface_naming_mode() == "alias": @@ -83,7 +93,7 @@ def get_interface_name(port, db): def get_interface_alias(port, db): - if port is not "all" and port is not None: + if port != "all" and port is not None: alias = port iface_alias_converter = clicommon.InterfaceAliasConverter(db) if clicommon.get_interface_naming_mode() == "alias": @@ -93,3 +103,42 @@ def get_interface_alias(port, db): sys.exit(1) return port + + +def is_rj45_port(port_name): + global platform_sfputil + global platform_chassis + global platform_sfp_base + global platform_sfputil_loaded + + try: + if not platform_chassis: + import sonic_platform + platform_chassis = sonic_platform.platform.Platform().get_chassis() + if not platform_sfp_base: + import sonic_platform_base + platform_sfp_base = sonic_platform_base.sfp_base.SfpBase + except ModuleNotFoundError as e: + # This method is referenced by intfutil which is called on vs image + # However, there is no platform API supported on vs image + # So False is returned in such case + return False + + if platform_chassis and platform_sfp_base: + if not platform_sfputil: + load_platform_sfputil() + + if not platform_porttab_mapping_read: + platform_sfputil_read_porttab_mappings() + + port_type = None + try: + physical_port = platform_sfputil.logical_port_name_to_physical_port_list(port_name) + if physical_port: + port_type = platform_chassis.get_port_or_cage_type(physical_port[0]) + except Exception as e: + pass + + return port_type == platform_sfp_base.SFP_PORT_TYPE_BIT_RJ45 + + return False