diff --git a/config/main.py b/config/main.py index 2ea1d50711..05bf129f4a 100644 --- a/config/main.py +++ b/config/main.py @@ -902,18 +902,36 @@ def interface_is_in_portchannel(portchannel_member_table, interface_name): return False -def interface_has_mirror_config(mirror_table, interface_name): - """ Check if port is already configured with mirror config """ - for _, v in mirror_table.items(): - if 'src_port' in v and v['src_port'] == interface_name: +def check_mirror_direction_config(v, direction): + """ Check if port is already configured for mirror in same direction """ + if direction: + direction=direction.upper() + if ('direction' in v and v['direction'] == 'BOTH') or (direction == 'BOTH'): return True - if 'dst_port' in v and v['dst_port'] == interface_name: + if 'direction' in v and v['direction'] == direction: return True + else: + return True + +def interface_has_mirror_config(ctx, mirror_table, dst_port, src_port, direction): + """ Check if dst/src port is already configured with mirroring in same direction """ + for _, v in mirror_table.items(): + if src_port: + for port in src_port.split(","): + if 'dst_port' in v and v['dst_port'] == port: + ctx.fail("Error: Source Interface {} already has mirror config".format(port)) + if 'src_port' in v and re.search(port,v['src_port']): + if check_mirror_direction_config(v, direction): + ctx.fail("Error: Source Interface {} already has mirror config in same direction".format(port)) + if dst_port: + if ('dst_port' in v and v['dst_port'] == dst_port) or ('src_port' in v and re.search(dst_port,v['src_port'])): + ctx.fail("Error: Destination Interface {} already has mirror config".format(dst_port)) return False def validate_mirror_session_config(config_db, session_name, dst_port, src_port, direction): """ Check if SPAN mirror-session config is valid """ + ctx = click.get_current_context() if len(config_db.get_entry('MIRROR_SESSION', session_name)) != 0: click.echo("Error: {} already exists".format(session_name)) return False @@ -924,41 +942,34 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port, if dst_port: if not interface_name_is_valid(config_db, dst_port): - click.echo("Error: Destination Interface {} is invalid".format(dst_port)) - return False + ctx.fail("Error: Destination Interface {} is invalid".format(dst_port)) + + if is_portchannel_present_in_db(config_db, dst_port): + ctx.fail("Error: Destination Interface {} is not supported".format(dst_port)) if interface_is_in_vlan(vlan_member_table, dst_port): - click.echo("Error: Destination Interface {} has vlan config".format(dst_port)) - return False + ctx.fail("Error: Destination Interface {} has vlan config".format(dst_port)) - if interface_has_mirror_config(mirror_table, dst_port): - click.echo("Error: Destination Interface {} already has mirror config".format(dst_port)) - return False if interface_is_in_portchannel(portchannel_member_table, dst_port): - click.echo("Error: Destination Interface {} has portchannel config".format(dst_port)) - return False + ctx.fail("Error: Destination Interface {} has portchannel config".format(dst_port)) if clicommon.is_port_router_interface(config_db, dst_port): - click.echo("Error: Destination Interface {} is a L3 interface".format(dst_port)) - return False + ctx.fail("Error: Destination Interface {} is a L3 interface".format(dst_port)) if src_port: for port in src_port.split(","): if not interface_name_is_valid(config_db, port): - click.echo("Error: Source Interface {} is invalid".format(port)) - return False + ctx.fail("Error: Source Interface {} is invalid".format(port)) if dst_port and dst_port == port: - click.echo("Error: Destination Interface cant be same as Source Interface") - return False - if interface_has_mirror_config(mirror_table, port): - click.echo("Error: Source Interface {} already has mirror config".format(port)) - return False + ctx.fail("Error: Destination Interface cant be same as Source Interface") + + if interface_has_mirror_config(ctx, mirror_table, dst_port, src_port, direction): + return False if direction: if direction not in ['rx', 'tx', 'both']: - click.echo("Error: Direction {} is invalid".format(direction)) - return False + ctx.fail("Error: Direction {} is invalid".format(direction)) return True @@ -2159,7 +2170,7 @@ def add_span(session_name, dst_port, src_port, direction, queue, policer): dst_port = interface_alias_to_name(None, dst_port) if dst_port is None: click.echo("Error: Destination Interface {} is invalid".format(dst_port)) - return + return False session_info = { "type" : "SPAN", diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 748a273d85..4beef0cafa 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -5281,8 +5281,11 @@ If vrf-name is also provided as part of the command, if the vrf is created it wi default Vlan20 Vrf-red Vlan100 Loopback11 + Eth0.100 Vrf-blue Loopback100 Loopback102 + Ethernet0.10 + PortChannel101 ```` ### VRF config commands diff --git a/scripts/fast-reboot b/scripts/fast-reboot index d5b6e204bd..5749e3745a 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -738,6 +738,26 @@ for service in ${SERVICES_TO_STOP}; do fi done +# Kill other containers to make the reboot faster +# We call `docker kill ...` to ensure the container stops as quickly as possible, +# then immediately call `systemctl stop ...` to prevent the service from +# restarting the container automatically. +debug "Stopping all remaining containers ..." +if test -f /usr/local/bin/ctrmgr_tools.py +then + /usr/local/bin/ctrmgr_tools.py kill-all +else + for CONTAINER_NAME in $(docker ps --format '{{.Names}}'); do + CONTAINER_STOP_RC=0 + docker kill $CONTAINER_NAME &> /dev/null || CONTAINER_STOP_RC=$? + systemctl stop $CONTAINER_NAME || debug "Ignore stopping $CONTAINER_NAME error $?" + if [[ CONTAINER_STOP_RC -ne 0 ]]; then + debug "Failed killing container $CONTAINER_NAME RC $CONTAINER_STOP_RC ." + fi + done +fi +debug "Stopped all remaining containers ..." + # Stop the docker container engine. Otherwise we will have a broken docker storage systemctl stop docker.service || debug "Ignore stopping docker service error $?" diff --git a/scripts/intfutil b/scripts/intfutil index c25b896b59..375e2c1d9d 100755 --- a/scripts/intfutil +++ b/scripts/intfutil @@ -45,6 +45,7 @@ PORT_OPTICS_TYPE = "type" PORT_PFC_ASYM_STATUS = "pfc_asym" PORT_AUTONEG = 'autoneg' PORT_ADV_SPEEDS = 'adv_speeds' +PORT_RMT_ADV_SPEEDS = 'rmt_adv_speeds' PORT_INTERFACE_TYPE = 'interface_type' PORT_ADV_INTERFACE_TYPES = 'adv_interface_types' PORT_TPID = "tpid" @@ -140,8 +141,12 @@ def port_speed_parse(in_speed, optics_type): speed = int(in_speed) if optics_type == OPTICS_TYPE_RJ45 and speed <= 1000: out_speed = '{}M'.format(speed) + elif speed < 1000: + out_speed = '{}M'.format(speed) + elif speed % 1000 >= 100: + out_speed = '{:.1f}G'.format(speed / 1000) else: - out_speed = '{}G'.format(int(speed/1000)) + out_speed = '{:.0f}G'.format(speed / 1000) return out_speed @@ -165,6 +170,23 @@ def appl_db_port_status_get(appl_db, intf_name, status_type): status = ','.join(new_speed_list) return status +def state_db_port_status_get(db, intf_name, field): + """ + Get the port status + """ + full_table_id = PORT_STATE_TABLE_PREFIX + intf_name + status = db.get(db.STATE_DB, full_table_id, 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) + speed_list = status.split(',') + new_speed_list = [] + for s in natsorted(speed_list): + new_speed_list.append(port_speed_parse(s, optics_type)) + status = ','.join(new_speed_list) + return status + def port_oper_speed_get(db, intf_name): """ Get port oper speed @@ -566,7 +588,7 @@ class IntfDescription(object): # ========================== interface-autoneg logic ========================== -header_autoneg = ['Interface', 'Auto-Neg Mode', 'Speed', 'Adv Speeds', 'Type', 'Adv Types', 'Oper', 'Admin'] +header_autoneg = ['Interface', 'Auto-Neg Mode', 'Speed', 'Adv Speeds', 'Rmt Adv Speeds', 'Type', 'Adv Types', 'Oper', 'Admin'] class IntfAutoNegStatus(object): @@ -616,6 +638,7 @@ class IntfAutoNegStatus(object): autoneg_mode, port_oper_speed_get(self.db, key), appl_db_port_status_get(self.db, key, PORT_ADV_SPEEDS), + state_db_port_status_get(self.db, key, PORT_RMT_ADV_SPEEDS), appl_db_port_status_get(self.db, key, PORT_INTERFACE_TYPE), appl_db_port_status_get(self.db, key, PORT_ADV_INTERFACE_TYPES), appl_db_port_status_get(self.db, key, PORT_OPER_STATUS), diff --git a/sfputil/main.py b/sfputil/main.py index 5eb72195d7..91cb372bac 100644 --- a/sfputil/main.py +++ b/sfputil/main.py @@ -63,6 +63,48 @@ 'application_advertisement': 'Application Advertisement' } +QSFP_DD_DATA_MAP = { + 'model': 'Vendor PN', + 'vendor_oui': 'Vendor OUI', + 'vendor_date': 'Vendor Date Code(YYYY-MM-DD Lot)', + 'manufacturer': 'Vendor Name', + 'vendor_rev': 'Vendor Rev', + 'serial': 'Vendor SN', + 'type': 'Identifier', + 'ext_identifier': 'Extended Identifier', + 'ext_rateselect_compliance': 'Extended RateSelect Compliance', + 'cable_length': 'cable_length', + 'cable_type': 'Length', + 'nominal_bit_rate': 'Nominal Bit Rate(100Mbs)', + 'specification_compliance': 'Specification compliance', + 'encoding': 'Encoding', + 'connector': 'Connector', + 'application_advertisement': 'Application Advertisement', + 'active_firmware': 'Active Firmware Version', + 'inactive_firmware': 'Inactive Firmware Version', + 'hardware_rev': 'Hardware Revision', + 'media_interface_code': 'Media Interface Code', + 'host_electrical_interface': 'Host Electrical Interface', + 'host_lane_count': 'Host Lane Count', + 'media_lane_count': 'Media Lane Count', + 'host_lane_assignment_option': 'Host Lane Assignment Options', + 'media_lane_assignment_option': 'Media Lane Assignment Options', + 'active_apsel_hostlane1': 'Active App Selection Host Lane 1', + 'active_apsel_hostlane2': 'Active App Selection Host Lane 2', + 'active_apsel_hostlane3': 'Active App Selection Host Lane 3', + 'active_apsel_hostlane4': 'Active App Selection Host Lane 4', + 'active_apsel_hostlane5': 'Active App Selection Host Lane 5', + 'active_apsel_hostlane6': 'Active App Selection Host Lane 6', + 'active_apsel_hostlane7': 'Active App Selection Host Lane 7', + 'active_apsel_hostlane8': 'Active App Selection Host Lane 8', + 'media_interface_technology': 'Media Interface Technology', + 'cmis_rev': 'CMIS Revision', + 'supported_max_tx_power': 'Supported Max TX Power', + 'supported_min_tx_power': 'Supported Min TX Power', + 'supported_max_laser_freq': 'Supported Max Laser Frequency', + 'supported_min_laser_freq': 'Supported Min Laser Frequency' +} + SFP_DOM_CHANNEL_MONITOR_MAP = { 'rx1power': 'RXPower', 'tx1bias': 'TXBias', @@ -273,31 +315,68 @@ def format_dict_value_to_string(sorted_key_table, def convert_sfp_info_to_output_string(sfp_info_dict): indent = ' ' * 8 output = '' - - sorted_qsfp_data_map_keys = sorted(QSFP_DATA_MAP, key=QSFP_DATA_MAP.get) - for key in sorted_qsfp_data_map_keys: - if key == 'cable_type': - output += '{}{}: {}\n'.format(indent, sfp_info_dict['cable_type'], sfp_info_dict['cable_length']) - elif key == 'cable_length': - pass - elif key == 'specification_compliance': - if sfp_info_dict['type'] == "QSFP-DD Double Density 8X Pluggable Transceiver" or \ - sfp_info_dict['type'] == "OSFP 8X Pluggable Transceiver" or \ - sfp_info_dict['type'] == "QSFP+ or later with CMIS": - output += '{}{}: {}\n'.format(indent, QSFP_DATA_MAP[key], sfp_info_dict[key]) + sfp_type = sfp_info_dict['type'] + # CMIS supported module types include QSFP-DD and OSFP + if sfp_type.startswith('QSFP-DD') or sfp_type.startswith('OSFP'): + sorted_qsfp_data_map_keys = sorted(QSFP_DD_DATA_MAP, key=QSFP_DD_DATA_MAP.get) + for key in sorted_qsfp_data_map_keys: + if key == 'cable_type': + output += '{}{}: {}\n'.format(indent, sfp_info_dict['cable_type'], sfp_info_dict['cable_length']) + elif key == 'cable_length': + pass + elif key == 'specification_compliance': + if sfp_info_dict['type'] == "QSFP-DD Double Density 8X Pluggable Transceiver" or \ + sfp_info_dict['type'] == "OSFP 8X Pluggable Transceiver" or \ + sfp_info_dict['type'] == "QSFP+ or later with CMIS": + output += '{}{}: {}\n'.format(indent, QSFP_DD_DATA_MAP[key], sfp_info_dict[key]) + else: + output += '{}{}:\n'.format(indent, QSFP_DD_DATA_MAP['specification_compliance']) + + spec_compliance_dict = {} + try: + spec_compliance_dict = ast.literal_eval(sfp_info_dict['specification_compliance']) + sorted_compliance_key_table = natsorted(spec_compliance_dict) + for compliance_key in sorted_compliance_key_table: + output += '{}{}: {}\n'.format((indent * 2), compliance_key, spec_compliance_dict[compliance_key]) + except ValueError as e: + output += '{}N/A\n'.format((indent * 2)) + elif key == 'application_advertisement': + pass + elif key == 'supported_max_tx_power' or key == 'supported_min_tx_power': + output += '{}{}: {}dBm\n'.format(indent, QSFP_DD_DATA_MAP[key], sfp_info_dict[key]) + elif key == 'supported_max_laser_freq' or key == 'supported_min_laser_freq': + output += '{}{}: {}GHz\n'.format(indent, QSFP_DD_DATA_MAP[key], sfp_info_dict[key]) else: - output += '{}{}:\n'.format(indent, QSFP_DATA_MAP['specification_compliance']) - - spec_compliance_dict = {} try: - spec_compliance_dict = ast.literal_eval(sfp_info_dict['specification_compliance']) - sorted_compliance_key_table = natsorted(spec_compliance_dict) - for compliance_key in sorted_compliance_key_table: - output += '{}{}: {}\n'.format((indent * 2), compliance_key, spec_compliance_dict[compliance_key]) - except ValueError as e: - output += '{}N/A\n'.format((indent * 2)) - else: - output += '{}{}: {}\n'.format(indent, QSFP_DATA_MAP[key], sfp_info_dict[key]) + output += '{}{}: {}\n'.format(indent, QSFP_DD_DATA_MAP[key], sfp_info_dict[key]) + except (KeyError, ValueError) as e: + output += '{}{}: N/A\n'.format(indent, QSFP_DD_DATA_MAP[key]) + + else: + sorted_qsfp_data_map_keys = sorted(QSFP_DATA_MAP, key=QSFP_DATA_MAP.get) + for key in sorted_qsfp_data_map_keys: + if key == 'cable_type': + output += '{}{}: {}\n'.format(indent, sfp_info_dict['cable_type'], sfp_info_dict['cable_length']) + elif key == 'cable_length': + pass + elif key == 'specification_compliance': + if sfp_info_dict['type'] == "QSFP-DD Double Density 8X Pluggable Transceiver" or \ + sfp_info_dict['type'] == "OSFP 8X Pluggable Transceiver" or \ + sfp_info_dict['type'] == "QSFP+ or later with CMIS": + output += '{}{}: {}\n'.format(indent, QSFP_DATA_MAP[key], sfp_info_dict[key]) + else: + output += '{}{}:\n'.format(indent, QSFP_DATA_MAP['specification_compliance']) + + spec_compliance_dict = {} + try: + spec_compliance_dict = ast.literal_eval(sfp_info_dict['specification_compliance']) + sorted_compliance_key_table = natsorted(spec_compliance_dict) + for compliance_key in sorted_compliance_key_table: + output += '{}{}: {}\n'.format((indent * 2), compliance_key, spec_compliance_dict[compliance_key]) + except ValueError as e: + output += '{}N/A\n'.format((indent * 2)) + else: + output += '{}{}: {}\n'.format(indent, QSFP_DATA_MAP[key], sfp_info_dict[key]) return output diff --git a/show/main.py b/show/main.py index 2c300f250e..173c84bd7e 100755 --- a/show/main.py +++ b/show/main.py @@ -293,7 +293,7 @@ def cli(ctx): def get_interface_bind_to_vrf(config_db, vrf_name): """Get interfaces belong to vrf """ - tables = ['INTERFACE', 'PORTCHANNEL_INTERFACE', 'VLAN_INTERFACE', 'LOOPBACK_INTERFACE'] + tables = ['INTERFACE', 'PORTCHANNEL_INTERFACE', 'VLAN_INTERFACE', 'LOOPBACK_INTERFACE', 'VLAN_SUB_INTERFACE'] data = [] for table_name in tables: interface_dict = config_db.get_table(table_name) diff --git a/sonic_package_manager/service_creator/feature.py b/sonic_package_manager/service_creator/feature.py index eb8e1a0710..73e35566dd 100644 --- a/sonic_package_manager/service_creator/feature.py +++ b/sonic_package_manager/service_creator/feature.py @@ -1,9 +1,10 @@ #!/usr/bin/env python """ This module implements new feature registration/de-registration in SONiC system. """ - +import copy from typing import Dict, Type +from sonic_package_manager.logger import log from sonic_package_manager.manifest import Manifest from sonic_package_manager.service_creator.sonic_db import SonicDB @@ -15,6 +16,14 @@ 'set_owner': 'local' } +AUTO_TS_GLOBAL = "AUTO_TECHSUPPORT" +AUTO_TS_FEATURE = "AUTO_TECHSUPPORT_FEATURE" +CFG_STATE = "state" +# TODO: Enable available_mem_threshold once the mem_leak_auto_ts feature is available +DEFAULT_AUTO_TS_FEATURE_CONFIG = { + 'state': 'disabled', + 'rate_limit_interval': '600' +} def is_enabled(cfg): return cfg.get('state', 'disabled').lower() == 'enabled' @@ -25,8 +34,11 @@ def is_multi_instance(cfg): class FeatureRegistry: - """ FeatureRegistry class provides an interface to - register/de-register new feature persistently. """ + """ 1) FeatureRegistry class provides an interface to + register/de-register new feature tables persistently. + 2) Writes persistent configuration to FEATURE & + AUTO_TECHSUPPORT_FEATURE tables + """ def __init__(self, sonic_db: Type[SonicDB]): self._sonic_db = sonic_db @@ -60,6 +72,9 @@ def register(self, new_cfg = {**new_cfg, **non_cfg_entries} conn.set_entry(FEATURE, name, new_cfg) + + if self.register_auto_ts(name): + log.info(f'{name} entry is added to {AUTO_TS_FEATURE} table') def deregister(self, name: str): """ Deregister feature by name. @@ -73,6 +88,7 @@ def deregister(self, name: str): db_connetors = self._sonic_db.get_connectors() for conn in db_connetors: conn.set_entry(FEATURE, name, None) + conn.set_entry(AUTO_TS_FEATURE, name, None) def update(self, old_manifest: Manifest, @@ -103,6 +119,9 @@ def update(self, new_cfg = {**new_cfg, **non_cfg_entries} conn.set_entry(FEATURE, new_name, new_cfg) + + if self.register_auto_ts(new_name, old_name): + log.info(f'{new_name} entry is added to {AUTO_TS_FEATURE} table') def is_feature_enabled(self, name: str) -> bool: """ Returns whether the feature is current enabled @@ -123,6 +142,46 @@ def get_multi_instance_features(self): features = conn.get_table(FEATURE) return [feature for feature, cfg in features.items() if is_multi_instance(cfg)] + def infer_auto_ts_capability(self, init_cfg_conn): + """ Determine whether to enable/disable the state for new feature + AUTO_TS provides a compile-time knob to enable/disable this feature + Default State for the new feature follows the decision made at compile time. + + Args: + init_cfg_conn: PersistentConfigDbConnector for init_cfg.json + Returns: + Capability: Tuple: (bool, ["enabled", "disabled"]) + """ + cfg = init_cfg_conn.get_entry(AUTO_TS_GLOBAL, "GLOBAL") + default_state = cfg.get(CFG_STATE, "") + if not default_state: + return (False, "disabled") + else: + return (True, default_state) + + def register_auto_ts(self, new_name, old_name=None): + """ Registers auto_ts feature + """ + # Infer and update default config + init_cfg_conn = self._sonic_db.get_initial_db_connector() + def_cfg = DEFAULT_AUTO_TS_FEATURE_CONFIG.copy() + (auto_ts_add_cfg, auto_ts_state) = self.infer_auto_ts_capability(init_cfg_conn) + def_cfg['state'] = auto_ts_state + + if not auto_ts_add_cfg: + log.debug("Skip adding AUTO_TECHSUPPORT_FEATURE table because no AUTO_TECHSUPPORT|GLOBAL entry is found") + return False + + for conn in self._sonic_db.get_connectors(): + new_cfg = copy.deepcopy(def_cfg) + if old_name: + current_cfg = conn.get_entry(AUTO_TS_FEATURE, old_name) + conn.set_entry(AUTO_TS_FEATURE, old_name, None) + new_cfg.update(current_cfg) + + conn.set_entry(AUTO_TS_FEATURE, new_name, new_cfg) + return True + @staticmethod def get_default_feature_entries(state=None, owner=None) -> Dict[str, str]: """ Get configurable feature table entries: diff --git a/tests/config_mirror_session_test.py b/tests/config_mirror_session_test.py index fe440e799d..986df2e711 100644 --- a/tests/config_mirror_session_test.py +++ b/tests/config_mirror_session_test.py @@ -164,8 +164,88 @@ def test_mirror_session_span_add(): assert result.exit_code != 0 assert ERR_MSG_VALUE_FAILURE in result.stdout + # Verify invalid dst port + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethern", "Ethernet4", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Destination Interface Ethern is invalid" in result.stdout + + # Verify destination port not have vlan config + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet24", "Ethernet4", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Destination Interface Ethernet24 has vlan config" in result.stdout + + # Verify destination port is not part of portchannel + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet116", "Ethernet4", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Destination Interface Ethernet116 has portchannel config" in result.stdout + + # Verify destination port not router interface + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet0", "Ethernet4", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Destination Interface Ethernet0 is a L3 interface" in result.stdout + + # Verify destination port not Portchannel + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "PortChannel1001"]) + assert result.exit_code != 0 + assert "Error: Destination Interface PortChannel1001 is not supported" in result.output + + # Verify source interface is invalid + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet52", "Ethern", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Source Interface Ethern is invalid" in result.stdout + + # Verify source interface is not same as destination + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet52", "Ethernet52", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Destination Interface cant be same as Source Interface" in result.stdout + + # Verify destination port not have mirror config + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet44", "Ethernet56", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Destination Interface Ethernet44 already has mirror config" in result.output + + # Verify source port is not configured as dstport in other session + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet52", "Ethernet44", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Source Interface Ethernet44 already has mirror config" in result.output + + # Verify source port is not configured in same direction + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet52", "Ethernet8,Ethernet40", "rx", "100"]) + assert result.exit_code != 0 + assert "Error: Source Interface Ethernet40 already has mirror config in same direction" in result.output + + # Verify direction is invalid + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet52", "Ethernet56", "px", "100"]) + assert result.exit_code != 0 + assert "Error: Direction px is invalid" in result.stdout + # Positive case with mock.patch('config.main.add_span') as mocked: + result = runner.invoke( + config.config.commands["mirror_session"].commands["span"].commands["add"], + ["test_session", "Ethernet8", "Ethernet4", "tx", "100"]) result = runner.invoke( config.config.commands["mirror_session"].commands["span"].commands["add"], ["test_session", "Ethernet0", "Ethernet4", "rx", "100"]) diff --git a/tests/dump_tests/dump_state_test.py b/tests/dump_tests/dump_state_test.py index fb1a211056..8e3cda0c0e 100644 --- a/tests/dump_tests/dump_state_test.py +++ b/tests/dump_tests/dump_state_test.py @@ -34,6 +34,7 @@ def compare_json_output(exp_json, rec, exclude_paths=None): | | | | PORT_TABLE|Ethernet0 | +------------------+--------------------------+ | | | | | | | | field | value | | | | | | | | |------------------+--------------------------| | | +| | | | | | rmt_adv_speeds | 10,100,1000 | | | | | | | | | speed | 100000 | | | | | | | | | supported_speeds | 10000,25000,40000,100000 | | | | | | | | +------------------+--------------------------+ | | @@ -120,7 +121,7 @@ def test_identifier_single(self): expected = {'Ethernet0': {'CONFIG_DB': {'keys': [{'PORT|Ethernet0': {'alias': 'etp1', 'description': 'etp1', 'index': '0', 'lanes': '25,26,27,28', 'mtu': '9100', 'pfc_asym': 'off', 'speed': '40000'}}], 'tables_not_found': []}, 'APPL_DB': {'keys': [{'PORT_TABLE:Ethernet0': {'index': '0', 'lanes': '0', 'alias': 'Ethernet0', 'description': 'ARISTA01T2:Ethernet1', 'speed': '25000', 'oper_status': 'down', 'pfc_asym': 'off', 'mtu': '9100', 'fec': 'rs', 'admin_status': 'up'}}], 'tables_not_found': []}, 'ASIC_DB': {'keys': [{'ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF:oid:0xd00000000056d': {'SAI_HOSTIF_ATTR_NAME': 'Ethernet0', 'SAI_HOSTIF_ATTR_OBJ_ID': 'oid:0x10000000004a4', 'SAI_HOSTIF_ATTR_OPER_STATUS': 'true', 'SAI_HOSTIF_ATTR_TYPE': 'SAI_HOSTIF_TYPE_NETDEV', 'SAI_HOSTIF_ATTR_VLAN_TAG': 'SAI_HOSTIF_VLAN_TAG_STRIP'}}, {'ASIC_STATE:SAI_OBJECT_TYPE_PORT:oid:0x10000000004a4': {'NULL': 'NULL', 'SAI_PORT_ATTR_ADMIN_STATE': 'true', 'SAI_PORT_ATTR_MTU': '9122', 'SAI_PORT_ATTR_SPEED': '100000'}}], 'tables_not_found': [], 'vidtorid': {'oid:0xd00000000056d': 'oid:0xd', 'oid:0x10000000004a4': 'oid:0x1690000000001'}}, - 'STATE_DB': {'keys': [{'PORT_TABLE|Ethernet0': {'speed': '100000', 'supported_speeds': '10000,25000,40000,100000'}}], 'tables_not_found': []}}} + 'STATE_DB': {'keys': [{'PORT_TABLE|Ethernet0': {'rmt_adv_speeds': '10,100,1000', 'speed': '100000', 'supported_speeds': '10000,25000,40000,100000'}}], 'tables_not_found': []}}} assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info) # Cause other tests depend and change these paths in the mock_db, this test would fail everytime when a field or a value in changed in this path, creating noise @@ -137,7 +138,7 @@ def test_identifier_multiple(self): {"CONFIG_DB": {"keys": [{"PORT|Ethernet0": {"alias": "etp1", "description": "etp1", "index": "0", "lanes": "25,26,27,28", "mtu": "9100", "pfc_asym": "off", "speed": "40000"}}], "tables_not_found": []}, "APPL_DB": {"keys": [{"PORT_TABLE:Ethernet0": {"index": "0", "lanes": "0", "alias": "Ethernet0", "description": "ARISTA01T2:Ethernet1", "speed": "25000", "oper_status": "down", "pfc_asym": "off", "mtu": "9100", "fec": "rs", "admin_status": "up"}}], "tables_not_found": []}, "ASIC_DB": {"keys": [{"ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF:oid:0xd00000000056d": {"SAI_HOSTIF_ATTR_NAME": "Ethernet0", "SAI_HOSTIF_ATTR_OBJ_ID": "oid:0x10000000004a4", "SAI_HOSTIF_ATTR_OPER_STATUS": "true", "SAI_HOSTIF_ATTR_TYPE": "SAI_HOSTIF_TYPE_NETDEV", "SAI_HOSTIF_ATTR_VLAN_TAG": "SAI_HOSTIF_VLAN_TAG_STRIP"}}, {"ASIC_STATE:SAI_OBJECT_TYPE_PORT:oid:0x10000000004a4": {"NULL": "NULL", "SAI_PORT_ATTR_ADMIN_STATE": "true", "SAI_PORT_ATTR_MTU": "9122", "SAI_PORT_ATTR_SPEED": "100000"}}], "tables_not_found": [], "vidtorid": {"oid:0xd00000000056d": "oid:0xd", "oid:0x10000000004a4": "oid:0x1690000000001"}}, - "STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"speed": "100000", "supported_speeds": "10000,25000,40000,100000"}}], "tables_not_found": []}}, + "STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"rmt_adv_speeds": "10,100,1000", "speed": "100000", "supported_speeds": "10000,25000,40000,100000"}}], "tables_not_found": []}}, "Ethernet4": {"CONFIG_DB": {"keys": [{"PORT|Ethernet4": {"admin_status": "up", "alias": "etp2", "description": "Servers0:eth0", "index": "1", "lanes": "29,30,31,32", "mtu": "9100", "pfc_asym": "off", "speed": "40000"}}], "tables_not_found": []}, "APPL_DB": {"keys": [], "tables_not_found": ["PORT_TABLE"]}, @@ -166,7 +167,7 @@ def test_option_db_filtering(self): result = runner.invoke(dump.state, ["port", "Ethernet0", "--db", "ASIC_DB", "--db", "STATE_DB"]) print(result.output) expected = {"Ethernet0": {"ASIC_DB": {"keys": [{"ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF:oid:0xd00000000056d": {"SAI_HOSTIF_ATTR_NAME": "Ethernet0", "SAI_HOSTIF_ATTR_OBJ_ID": "oid:0x10000000004a4", "SAI_HOSTIF_ATTR_OPER_STATUS": "true", "SAI_HOSTIF_ATTR_TYPE": "SAI_HOSTIF_TYPE_NETDEV", "SAI_HOSTIF_ATTR_VLAN_TAG": "SAI_HOSTIF_VLAN_TAG_STRIP"}}, {"ASIC_STATE:SAI_OBJECT_TYPE_PORT:oid:0x10000000004a4": {"NULL": "NULL", "SAI_PORT_ATTR_ADMIN_STATE": "true", "SAI_PORT_ATTR_MTU": "9122", "SAI_PORT_ATTR_SPEED": "100000"}}], "tables_not_found": [], "vidtorid": {"oid:0xd00000000056d": "oid:0xd", "oid:0x10000000004a4": "oid:0x1690000000001"}}, - "STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"speed": "100000", "supported_speeds": "10000,25000,40000,100000"}}], "tables_not_found": []}}} + "STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"rmt_adv_speeds": "10,100,1000", "speed": "100000", "supported_speeds": "10000,25000,40000,100000"}}], "tables_not_found": []}}} assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info) ddiff = compare_json_output(expected, result.output) assert not ddiff, ddiff diff --git a/tests/intfutil_test.py b/tests/intfutil_test.py index 0129573881..2023b5939e 100644 --- a/tests/intfutil_test.py +++ b/tests/intfutil_test.py @@ -71,30 +71,30 @@ """ show_interface_auto_neg_status_output = """\ - Interface Auto-Neg Mode Speed Adv Speeds Type Adv Types Oper Admin ------------ --------------- ------- ------------ ------ ----------- ------ ------- - Ethernet0 enabled 25G 10G,50G CR4 CR4,CR2 down up - Ethernet16 N/A 100M N/A N/A N/A up up - Ethernet24 N/A 1G N/A N/A N/A up up - Ethernet28 N/A 1000M N/A N/A N/A up up - Ethernet32 disabled 40G all N/A all up up - Ethernet36 N/A 10M N/A N/A N/A up up -Ethernet112 N/A 40G N/A N/A N/A up up -Ethernet116 N/A 40G N/A N/A N/A up up -Ethernet120 N/A 40G N/A N/A N/A up up -Ethernet124 N/A 40G N/A N/A N/A up up + Interface Auto-Neg Mode Speed Adv Speeds Rmt Adv Speeds Type Adv Types Oper Admin +----------- --------------- ------- ------------ ---------------- ------ ----------- ------ ------- + Ethernet0 enabled 25G 10G,50G 10M,100M,1G CR4 CR4,CR2 down up + Ethernet16 N/A 100M N/A N/A N/A N/A up up + Ethernet24 N/A 1G N/A N/A N/A N/A up up + Ethernet28 N/A 1000M N/A N/A N/A N/A up up + Ethernet32 disabled 40G all N/A N/A all up up + Ethernet36 N/A 10M N/A N/A N/A N/A up up +Ethernet112 N/A 40G N/A N/A N/A N/A up up +Ethernet116 N/A 40G N/A N/A N/A N/A up up +Ethernet120 N/A 40G N/A N/A N/A N/A up up +Ethernet124 N/A 40G N/A N/A N/A N/A up up """ show_interface_auto_neg_status_Ethernet0_output = """\ - Interface Auto-Neg Mode Speed Adv Speeds Type Adv Types Oper Admin ------------ --------------- ------- ------------ ------ ----------- ------ ------- - Ethernet0 enabled 25G 10G,50G CR4 CR4,CR2 down up + Interface Auto-Neg Mode Speed Adv Speeds Rmt Adv Speeds Type Adv Types Oper Admin +----------- --------------- ------- ------------ ---------------- ------ ----------- ------ ------- + Ethernet0 enabled 25G 10G,50G 10M,100M,1G CR4 CR4,CR2 down up """ show_interface_auto_neg_status_eth9_output = """\ - Interface Auto-Neg Mode Speed Adv Speeds Type Adv Types Oper Admin ------------ --------------- ------- ------------ ------ ----------- ------ ------- - Ethernet32 disabled 40G all N/A all up up + Interface Auto-Neg Mode Speed Adv Speeds Rmt Adv Speeds Type Adv Types Oper Admin +----------- --------------- ------- ------------ ---------------- ------ ----------- ------ ------- + Ethernet32 disabled 40G all N/A N/A all up up """ diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 26f374d03d..efb3190245 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -2571,5 +2571,10 @@ }, "QUEUE|Ethernet96|6": { "scheduler": "[SCHEDULER|scheduler.0]" + }, + "MIRROR_SESSION|test_session_db1": { + "dst_port": "Ethernet44", + "src_port": "Ethernet40,Ethernet48", + "direction": "RX" } } diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index fc0fb8bb0a..720c14d164 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -683,6 +683,7 @@ "access": "False" }, "PORT_TABLE|Ethernet0": { + "rmt_adv_speeds" : "10,100,1000", "speed" : "100000", "supported_speeds": "10000,25000,40000,100000" }, diff --git a/tests/sfputil_test.py b/tests/sfputil_test.py index c3b6b96a9e..83d2f8c4cc 100644 --- a/tests/sfputil_test.py +++ b/tests/sfputil_test.py @@ -77,47 +77,135 @@ def test_format_dict_value_to_string(self): sfputil.QSFP_DOM_CHANNEL_MONITOR_MAP, sfputil.DOM_VALUE_UNIT_MAP) assert output == expected_output - - def test_convert_sfp_info_to_output_string(self): - sfp_info_dict = { - 'type': 'QSFP28 or later', - 'type_abbrv_name': 'QSFP28', - 'manufacturer': 'Mellanox', - 'model': 'MCP1600-C003', - 'vendor_rev': 'A2', - 'serial': 'MT1636VS10561', - 'vendor_oui': '00-02-c9', - 'vendor_date': '2016-07-18', - 'connector': 'No separable connector', - 'encoding': '64B66B', - 'ext_identifier': 'Power Class 1(1.5W max)', - 'ext_rateselect_compliance': 'QSFP+ Rate Select Version 1', - 'cable_type': 'Length Cable Assembly(m)', - 'cable_length': '3', - 'application_advertisement': 'N/A', - 'specification_compliance': "{'10/40G Ethernet Compliance Code': '40GBASE-CR4'}", - 'dom_capability': "{'Tx_power_support': 'no', 'Rx_power_support': 'no', 'Voltage_support': 'no', 'Temp_support': 'no'}", - 'nominal_bit_rate': '255' - } - - expected_output = '''\ - Application Advertisement: N/A - Connector: No separable connector - Encoding: 64B66B - Extended Identifier: Power Class 1(1.5W max) - Extended RateSelect Compliance: QSFP+ Rate Select Version 1 - Identifier: QSFP28 or later - Length Cable Assembly(m): 3 - Nominal Bit Rate(100Mbs): 255 - Specification compliance: - 10/40G Ethernet Compliance Code: 40GBASE-CR4 - Vendor Date Code(YYYY-MM-DD Lot): 2016-07-18 - Vendor Name: Mellanox - Vendor OUI: 00-02-c9 - Vendor PN: MCP1600-C003 - Vendor Rev: A2 - Vendor SN: MT1636VS10561 -''' + @pytest.mark.parametrize("sfp_info_dict, expected_output",[ + # Non-CMIS module + ( + # sfp_info_dict + { + 'type': 'QSFP28 or later', + 'type_abbrv_name': 'QSFP28', + 'manufacturer': 'Mellanox', + 'model': 'MCP1600-C003', + 'vendor_rev': 'A2', + 'serial': 'MT1636VS10561', + 'vendor_oui': '00-02-c9', + 'vendor_date': '2016-07-18', + 'connector': 'No separable connector', + 'encoding': '64B66B', + 'ext_identifier': 'Power Class 1(1.5W max)', + 'ext_rateselect_compliance': 'QSFP+ Rate Select Version 1', + 'cable_type': 'Length Cable Assembly(m)', + 'cable_length': '3', + 'application_advertisement': 'N/A', + 'specification_compliance': "{'10/40G Ethernet Compliance Code': '40GBASE-CR4'}", + 'dom_capability': "{'Tx_power_support': 'no', 'Rx_power_support': 'no', 'Voltage_support': 'no', 'Temp_support': 'no'}", + 'nominal_bit_rate': '255' + }, + # expected_output + " Application Advertisement: N/A\n" + " Connector: No separable connector\n" + " Encoding: 64B66B\n" + " Extended Identifier: Power Class 1(1.5W max)\n" + " Extended RateSelect Compliance: QSFP+ Rate Select Version 1\n" + " Identifier: QSFP28 or later\n" + " Length Cable Assembly(m): 3\n" + " Nominal Bit Rate(100Mbs): 255\n" + " Specification compliance:\n" + " 10/40G Ethernet Compliance Code: 40GBASE-CR4\n" + " Vendor Date Code(YYYY-MM-DD Lot): 2016-07-18\n" + " Vendor Name: Mellanox\n" + " Vendor OUI: 00-02-c9\n" + " Vendor PN: MCP1600-C003\n" + " Vendor Rev: A2\n" + " Vendor SN: MT1636VS10561\n" + ), + # CMIS compliant module + ( + # sfp_info_dict + { + 'type': 'QSFP-DD Double Density 8X Pluggable Transceiver', + 'type_abbrv_name': 'QSFP-DD', + 'manufacturer': 'abc', + 'model': 'def', + 'vendor_rev': 'ghi', + 'serial': 'jkl', + 'vendor_oui': '00-00-00', + 'vendor_date': '2000-01-01', + 'connector': 'LC', + 'encoding': 'N/A', + 'ext_identifier': 'Power Class 8 (18.0W Max)', + 'ext_rateselect_compliance': 'N/A', + 'cable_type': 'Length Cable Assembly(m)', + 'cable_length': '0', + 'application_advertisement': 'N/A', + 'specification_compliance': "sm_media_interface", + 'dom_capability': "{'Tx_power_support': 'no', 'Rx_power_support': 'no', 'Voltage_support': 'no', 'Temp_support': 'no'}", + 'nominal_bit_rate': '0', + 'active_firmware': '0.1', + 'inactive_firmware': '0.0', + 'hardware_rev': '0.0', + 'media_interface_code': '400ZR, DWDM, amplified', + 'host_electrical_interface': '400GAUI-8 C2M (Annex 120E)', + 'host_lane_count': 8, + 'media_lane_count': 1, + 'host_lane_assignment_option': 1, + 'media_lane_assignment_option': 1, + 'active_apsel_hostlane1': 1, + 'active_apsel_hostlane2': 1, + 'active_apsel_hostlane3': 1, + 'active_apsel_hostlane4': 1, + 'active_apsel_hostlane5': 1, + 'active_apsel_hostlane6': 1, + 'active_apsel_hostlane7': 1, + 'active_apsel_hostlane8': 1, + 'media_interface_technology': 'C-band tunable laser', + 'cmis_rev': '5.0', + 'supported_max_tx_power': 0, + 'supported_min_tx_power': -20, + 'supported_max_laser_freq': 196100, + 'supported_min_laser_freq': 191300 + }, + # expected_output + " Active App Selection Host Lane 1: 1\n" + " Active App Selection Host Lane 2: 1\n" + " Active App Selection Host Lane 3: 1\n" + " Active App Selection Host Lane 4: 1\n" + " Active App Selection Host Lane 5: 1\n" + " Active App Selection Host Lane 6: 1\n" + " Active App Selection Host Lane 7: 1\n" + " Active App Selection Host Lane 8: 1\n" + " Active Firmware Version: 0.1\n" + " CMIS Revision: 5.0\n" + " Connector: LC\n" + " Encoding: N/A\n" + " Extended Identifier: Power Class 8 (18.0W Max)\n" + " Extended RateSelect Compliance: N/A\n" + " Hardware Revision: 0.0\n" + " Host Electrical Interface: 400GAUI-8 C2M (Annex 120E)\n" + " Host Lane Assignment Options: 1\n" + " Host Lane Count: 8\n" + " Identifier: QSFP-DD Double Density 8X Pluggable Transceiver\n" + " Inactive Firmware Version: 0.0\n" + " Length Cable Assembly(m): 0\n" + " Media Interface Code: 400ZR, DWDM, amplified\n" + " Media Interface Technology: C-band tunable laser\n" + " Media Lane Assignment Options: 1\n" + " Media Lane Count: 1\n" + " Nominal Bit Rate(100Mbs): 0\n" + " Specification compliance: sm_media_interface\n" + " Supported Max Laser Frequency: 196100GHz\n" + " Supported Max TX Power: 0dBm\n" + " Supported Min Laser Frequency: 191300GHz\n" + " Supported Min TX Power: -20dBm\n" + " Vendor Date Code(YYYY-MM-DD Lot): 2000-01-01\n" + " Vendor Name: abc\n" + " Vendor OUI: 00-00-00\n" + " Vendor PN: def\n" + " Vendor Rev: ghi\n" + " Vendor SN: jkl\n" + ), + ]) + def test_convert_sfp_info_to_output_string(self, sfp_info_dict, expected_output): output = sfputil.convert_sfp_info_to_output_string(sfp_info_dict) assert output == expected_output diff --git a/tests/show_vrf_test.py b/tests/show_vrf_test.py new file mode 100644 index 0000000000..3c6d1c5b36 --- /dev/null +++ b/tests/show_vrf_test.py @@ -0,0 +1,39 @@ +import os +import sys +from click.testing import CliRunner +from swsscommon.swsscommon import SonicV2Connector +from utilities_common.db import Db + +import show.main as show + +test_path = os.path.dirname(os.path.abspath(__file__)) +mock_db_path = os.path.join(test_path, "vrf_input") + +class TestShowVrf(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + def test_vrf_show(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 + Eth32.10 +Vrf103 Ethernet4 + Loopback0 +""" + + result = runner.invoke(show.cli.commands['vrf'], [], obj=db) + dbconnector.dedicated_dbs = {} + assert result.exit_code == 0 + assert result.output == expected_output diff --git a/tests/sonic_package_manager/test_service_creator.py b/tests/sonic_package_manager/test_service_creator.py index 295e80dc52..c943289362 100644 --- a/tests/sonic_package_manager/test_service_creator.py +++ b/tests/sonic_package_manager/test_service_creator.py @@ -205,6 +205,7 @@ def test_feature_registration(mock_sonic_db, manifest): mock_connector = Mock() mock_connector.get_entry = Mock(return_value={}) mock_sonic_db.get_connectors = Mock(return_value=[mock_connector]) + mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_connector) feature_registry = FeatureRegistry(mock_sonic_db) feature_registry.register(manifest) mock_connector.set_entry.assert_called_with('FEATURE', 'test', { @@ -258,6 +259,7 @@ def test_feature_registration_with_timer(mock_sonic_db, manifest): mock_connector = Mock() mock_connector.get_entry = Mock(return_value={}) mock_sonic_db.get_connectors = Mock(return_value=[mock_connector]) + mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_connector) feature_registry = FeatureRegistry(mock_sonic_db) feature_registry.register(manifest) mock_connector.set_entry.assert_called_with('FEATURE', 'test', { @@ -275,6 +277,7 @@ def test_feature_registration_with_non_default_owner(mock_sonic_db, manifest): mock_connector = Mock() mock_connector.get_entry = Mock(return_value={}) mock_sonic_db.get_connectors = Mock(return_value=[mock_connector]) + mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_connector) feature_registry = FeatureRegistry(mock_sonic_db) feature_registry.register(manifest, owner='kube') mock_connector.set_entry.assert_called_with('FEATURE', 'test', { @@ -286,3 +289,107 @@ def test_feature_registration_with_non_default_owner(mock_sonic_db, manifest): 'has_global_scope': 'True', 'has_timer': 'False', }) + + +class AutoTSHelp: + """ Helper class for Auto TS Feature Registry Tests + """ + GLOBAL_STATE = {} + + @classmethod + def get_entry(cls, table, key): + if table == "AUTO_TECHSUPPORT" and key == "GLOBAL": + return AutoTSHelp.GLOBAL_STATE + elif table == "AUTO_TECHSUPPORT_FEATURE" and key == "test": + return {"state" : "enabled", "rate_limit_interval" : "600"} + else: + return {} + + @classmethod + def get_entry_running_cfg(cls, table, key): + if table == "AUTO_TECHSUPPORT_FEATURE" and key == "test": + return {"state" : "disabled", "rate_limit_interval" : "1000"} + else: + return {} + + +def test_auto_ts_global_disabled(mock_sonic_db, manifest): + mock_init_cfg = Mock() + AutoTSHelp.GLOBAL_STATE = {"state" : "disabled"} + mock_init_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry) + mock_sonic_db.get_connectors = Mock(return_value=[mock_init_cfg]) + mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_init_cfg) + feature_registry = FeatureRegistry(mock_sonic_db) + feature_registry.register(manifest) + mock_init_cfg.set_entry.assert_any_call("AUTO_TECHSUPPORT_FEATURE", "test", { + "state" : "disabled", + "rate_limit_interval" : "600" + } + ) + + +def test_auto_ts_global_enabled(mock_sonic_db, manifest): + mock_init_cfg = Mock() + AutoTSHelp.GLOBAL_STATE = {"state" : "enabled"} + mock_init_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry) + mock_sonic_db.get_connectors = Mock(return_value=[mock_init_cfg]) + mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_init_cfg) + feature_registry = FeatureRegistry(mock_sonic_db) + feature_registry.register(manifest) + mock_init_cfg.set_entry.assert_any_call("AUTO_TECHSUPPORT_FEATURE", "test", { + "state" : "enabled", + "rate_limit_interval" : "600" + } + ) + + +def test_auto_ts_deregister(mock_sonic_db): + mock_connector = Mock() + mock_sonic_db.get_connectors = Mock(return_value=[mock_connector]) + feature_registry = FeatureRegistry(mock_sonic_db) + feature_registry.deregister("test") + mock_connector.set_entry.assert_any_call("AUTO_TECHSUPPORT_FEATURE", "test", None) + + +def test_auto_ts_feature_update_flow(mock_sonic_db, manifest): + new_manifest = copy.deepcopy(manifest) + new_manifest['service']['name'] = 'test_new' + new_manifest['service']['delayed'] = True + + AutoTSHelp.GLOBAL_STATE = {"state" : "enabled"} + # Mock init_cfg connector + mock_init_cfg = Mock() + mock_init_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry) + + # Mock running/peristent cfg connector + mock_other_cfg = Mock() + mock_other_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry_running_cfg) + + # Setup sonic_db class + mock_sonic_db.get_connectors = Mock(return_value=[mock_init_cfg, mock_other_cfg]) + mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_init_cfg) + + feature_registry = FeatureRegistry(mock_sonic_db) + feature_registry.update(manifest, new_manifest) + + mock_init_cfg.set_entry.assert_has_calls( + [ + call("AUTO_TECHSUPPORT_FEATURE", "test", None), + call("AUTO_TECHSUPPORT_FEATURE", "test_new", { + "state" : "enabled", + "rate_limit_interval" : "600" + }) + ], + any_order = True + ) + + mock_other_cfg.set_entry.assert_has_calls( + [ + call("AUTO_TECHSUPPORT_FEATURE", "test", None), + call("AUTO_TECHSUPPORT_FEATURE", "test_new", { + "state" : "disabled", + "rate_limit_interval" : "1000" + }) + ], + any_order = True + ) diff --git a/tests/vrf_input/config_db.json b/tests/vrf_input/config_db.json new file mode 100644 index 0000000000..6d646f2f2b --- /dev/null +++ b/tests/vrf_input/config_db.json @@ -0,0 +1,35 @@ +{ + "VLAN_SUB_INTERFACE|Ethernet0.10": { + "vrf_name": "Vrf101", + "admin_status": "up" + }, + "VLAN_SUB_INTERFACE|Eth32.10": { + "vrf_name": "Vrf102", + "admin_status": "up", + "vlan": "100" + }, + "VLAN_INTERFACE|Vlan40": { + "vrf_name": "Vrf102" + }, + "PORTCHANNEL_INTERFACE|PortChannel0002": { + "vrf_name": "Vrf102" + }, + "INTERFACE|Ethernet4": { + "vrf_name": "Vrf103" + }, + "LOOPBACK_INTERFACE|Loopback0": { + "vrf_name": "Vrf103" + }, + "VRF|Vrf1": { + "fallback": "false" + }, + "VRF|Vrf101": { + "NULL": "NULL" + }, + "VRF|Vrf102": { + "NULL": "NULL" + }, + "VRF|Vrf103": { + "NULL": "NULL" + } +}