diff --git a/sonic-xcvrd/tests/test_xcvrd.py b/sonic-xcvrd/tests/test_xcvrd.py index dff8291af..85fdccd0d 100644 --- a/sonic-xcvrd/tests/test_xcvrd.py +++ b/sonic-xcvrd/tests/test_xcvrd.py @@ -277,6 +277,19 @@ def test_post_port_dom_info_to_db(self, mock_get_sfp_type): mock_get_sfp_type.return_value = 'QSFP_DD' post_port_dom_info_to_db(logical_port_name, port_mapping, dom_tbl, stop_event) + @patch('xcvrd.xcvrd_utilities.port_mapping.PortMapping.logical_port_name_to_physical_port_list', MagicMock(return_value=[0])) + @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) + @patch('xcvrd.xcvrd._wrapper_get_transceiver_firmware_info', MagicMock(return_value={'active_firmware': '2.1.1', + 'inactive_firmware': '1.2.4'})) + def test_post_port_sfp_firmware_info_to_db(self): + logical_port_name = "Ethernet0" + port_mapping = PortMapping() + stop_event = threading.Event() + firmware_info_tbl = Table("STATE_DB", TRANSCEIVER_FIRMWARE_INFO_TABLE) + assert firmware_info_tbl.get_size() == 0 + post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, firmware_info_tbl, stop_event) + assert firmware_info_tbl.get_size_for_key(logical_port_name) == 2 + def test_post_port_dom_threshold_info_to_db(self, mock_get_sfp_type): logical_port_name = "Ethernet0" port_mapping = PortMapping() @@ -312,7 +325,8 @@ def test_del_port_sfp_dom_info_from_db(self): dom_threshold_tbl = Table("STATE_DB", TRANSCEIVER_DOM_THRESHOLD_TABLE) init_tbl = Table("STATE_DB", TRANSCEIVER_INFO_TABLE) pm_tbl = Table("STATE_DB", TRANSCEIVER_PM_TABLE) - del_port_sfp_dom_info_from_db(logical_port_name, port_mapping, init_tbl, dom_tbl, dom_threshold_tbl, pm_tbl) + firmware_info_tbl = Table("STATE_DB", TRANSCEIVER_FIRMWARE_INFO_TABLE) + del_port_sfp_dom_info_from_db(logical_port_name, port_mapping, init_tbl, dom_tbl, dom_threshold_tbl, pm_tbl, firmware_info_tbl) @patch('xcvrd.xcvrd.get_physical_port_name_dict', MagicMock(return_value={0: 'Ethernet0'})) @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) @@ -1459,6 +1473,7 @@ def test_SfpStateUpdateTask_retry_eeprom_reading(self, mock_post_sfp_info): task.xcvr_table_helper.get_dom_threshold_tbl = MagicMock(return_value=mock_table) task.xcvr_table_helper.get_app_port_tbl = MagicMock(return_value=mock_table) task.xcvr_table_helper.get_status_tbl = MagicMock(return_value=mock_table) + task.xcvr_table_helper.get_firmware_info_tbl = MagicMock(return_value=mock_table) task.retry_eeprom_reading() assert mock_post_sfp_info.call_count == 0 @@ -1507,12 +1522,13 @@ def test_SfpStateUpdateTask_mapping_event_from_change_event(self): @patch('xcvrd.xcvrd._wrapper_get_transceiver_change_event') @patch('xcvrd.xcvrd.del_port_sfp_dom_info_from_db') @patch('xcvrd.xcvrd_utilities.media_settings_parser.notify_media_setting') + @patch('xcvrd.xcvrd.post_port_sfp_firmware_info_to_db') @patch('xcvrd.xcvrd.post_port_dom_threshold_info_to_db') @patch('xcvrd.xcvrd.post_port_sfp_info_to_db') @patch('xcvrd.xcvrd.update_port_transceiver_status_table_sw') @patch('xcvrd.xcvrd.delete_port_from_status_table_hw') def test_SfpStateUpdateTask_task_worker(self, mock_del_status_hw, - mock_update_status, mock_post_sfp_info, mock_post_dom_th, mock_update_media_setting, + mock_update_status, mock_post_sfp_info, mock_post_dom_th, mock_post_firmware_info, mock_update_media_setting, mock_del_dom, mock_change_event, mock_mapping_event, mock_os_kill): port_mapping = PortMapping() stop_event = threading.Event() @@ -1564,6 +1580,7 @@ def test_SfpStateUpdateTask_task_worker(self, mock_del_status_hw, assert mock_update_status.call_count == 1 assert mock_post_sfp_info.call_count == 2 # first call and retry call assert mock_post_dom_th.call_count == 0 + assert mock_post_firmware_info.call_count == 0 assert mock_update_media_setting.call_count == 0 assert 'Ethernet0' in task.retry_eeprom_set task.retry_eeprom_set.clear() @@ -1577,6 +1594,7 @@ def test_SfpStateUpdateTask_task_worker(self, mock_del_status_hw, assert mock_update_status.call_count == 1 assert mock_post_sfp_info.call_count == 1 assert mock_post_dom_th.call_count == 1 + assert mock_post_firmware_info.call_count == 1 assert mock_update_media_setting.call_count == 1 stop_event.is_set = MagicMock(side_effect=[False, True]) @@ -1757,6 +1775,21 @@ def test_wrapper_get_transceiver_info(self, mock_sfputil, mock_chassis): mock_sfputil.get_transceiver_info_dict = MagicMock(return_value=False) assert not _wrapper_get_transceiver_info(1) + @patch('xcvrd.xcvrd.platform_chassis') + @patch('xcvrd.xcvrd.platform_sfputil') + def test_wrapper_get_transceiver_firmware_info(self, mock_sfputil, mock_chassis): + mock_object = MagicMock() + mock_object.get_transceiver_bulk_status = MagicMock(return_value=True) + mock_chassis.get_sfp = MagicMock(return_value=mock_object) + from xcvrd.xcvrd import _wrapper_get_transceiver_firmware_info + assert _wrapper_get_transceiver_firmware_info(1) + + mock_object.get_transceiver_info_firmware_versions = MagicMock(return_value={}) + assert _wrapper_get_transceiver_firmware_info(1) == {} + + mock_chassis.get_sfp = MagicMock(side_effect=NotImplementedError) + assert _wrapper_get_transceiver_firmware_info(1) == {} + @patch('xcvrd.xcvrd.platform_chassis') @patch('xcvrd.xcvrd.platform_sfputil') def test_wrapper_get_transceiver_dom_info(self, mock_sfputil, mock_chassis): diff --git a/sonic-xcvrd/xcvrd/xcvrd.py b/sonic-xcvrd/xcvrd/xcvrd.py index 60b9d8d09..38d884ff4 100644 --- a/sonic-xcvrd/xcvrd/xcvrd.py +++ b/sonic-xcvrd/xcvrd/xcvrd.py @@ -45,6 +45,7 @@ PLATFORM_SPECIFIC_CLASS_NAME = "SfpUtil" TRANSCEIVER_INFO_TABLE = 'TRANSCEIVER_INFO' +TRANSCEIVER_FIRMWARE_INFO_TABLE = 'TRANSCEIVER_FIRMWARE_INFO' TRANSCEIVER_DOM_SENSOR_TABLE = 'TRANSCEIVER_DOM_SENSOR' TRANSCEIVER_DOM_THRESHOLD_TABLE = 'TRANSCEIVER_DOM_THRESHOLD' TRANSCEIVER_STATUS_TABLE = 'TRANSCEIVER_STATUS' @@ -250,6 +251,13 @@ def _wrapper_get_transceiver_info(physical_port): pass return platform_sfputil.get_transceiver_info_dict(physical_port) +def _wrapper_get_transceiver_firmware_info(physical_port): + if platform_chassis is not None: + try: + return platform_chassis.get_sfp(physical_port).get_transceiver_info_firmware_versions() + except NotImplementedError: + pass + return {} def _wrapper_get_transceiver_dom_info(physical_port): if platform_chassis is not None: @@ -436,10 +444,6 @@ def post_port_sfp_info_to_db(logical_port_name, port_mapping, table, transceiver ('dom_capability', port_info_dict['dom_capability'] if 'dom_capability' in port_info_dict else 'N/A'), ('cmis_rev', port_info_dict['cmis_rev'] if 'cmis_rev' in port_info_dict else 'N/A'), - ('active_firmware', port_info_dict['active_firmware'] - if 'active_firmware' in port_info_dict else 'N/A'), - ('inactive_firmware', port_info_dict['inactive_firmware'] - if 'inactive_firmware' in port_info_dict else 'N/A'), ('hardware_rev', port_info_dict['hardware_rev'] if 'hardware_rev' in port_info_dict else 'N/A'), ('media_interface_code', port_info_dict['media_interface_code'] @@ -513,6 +517,29 @@ def post_port_sfp_info_to_db(logical_port_name, port_mapping, table, transceiver helper_logger.log_error("This functionality is currently not implemented for this platform") sys.exit(NOT_IMPLEMENTED_ERROR) +# Update port sfp firmware info in db + +def post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, table, + stop_event=threading.Event()): + for physical_port, physical_port_name in get_physical_port_name_dict(logical_port_name, port_mapping).items(): + if stop_event.is_set(): + break + + if not _wrapper_get_presence(physical_port): + continue + + try: + transceiver_firmware_info_dict = _wrapper_get_transceiver_firmware_info(physical_port) + if transceiver_firmware_info_dict is not None: + fvs = swsscommon.FieldValuePairs([(k, v) for k, v in transceiver_firmware_info_dict.items()]) + table.set(physical_port_name, fvs) + else: + return SFP_EEPROM_NOT_READY + + except NotImplementedError: + helper_logger.log_error("Transceiver firmware info functionality is currently not implemented for this platform") + sys.exit(NOT_IMPLEMENTED_ERROR) + # Update port dom threshold info in db @@ -623,7 +650,7 @@ def post_port_pm_info_to_db(logical_port_name, port_mapping, table, stop_event=t # Delete port dom/sfp info from db -def del_port_sfp_dom_info_from_db(logical_port_name, port_mapping, int_tbl, dom_tbl, dom_threshold_tbl, pm_tbl): +def del_port_sfp_dom_info_from_db(logical_port_name, port_mapping, int_tbl, dom_tbl, dom_threshold_tbl, pm_tbl, firmware_info_tbl): for physical_port_name in get_physical_port_name_dict(logical_port_name, port_mapping).values(): try: if int_tbl: @@ -634,6 +661,8 @@ def del_port_sfp_dom_info_from_db(logical_port_name, port_mapping, int_tbl, dom_ dom_threshold_tbl._del(physical_port_name) if pm_tbl: pm_tbl._del(physical_port_name) + if firmware_info_tbl: + firmware_info_tbl._del(physical_port_name) except NotImplementedError: helper_logger.log_error("This functionality is currently not implemented for this platform") @@ -1638,13 +1667,14 @@ def on_remove_logical_port(self, port_change_event): """ # To avoid race condition, remove the entry TRANSCEIVER_DOM_SENSOR, TRANSCEIVER_PM and HW section of TRANSCEIVER_STATUS table. # This thread only updates TRANSCEIVER_DOM_SENSOR, TRANSCEIVER_PM and HW section of TRANSCEIVER_STATUS table, - # so we don't have to remove entries from TRANSCEIVER_INFO and TRANSCEIVER_DOM_THRESHOLD + # so we don't have to remove entries from TRANSCEIVER_INFO, TRANSCEIVER_FIRMWARE_INFO and TRANSCEIVER_DOM_THRESHOLD del_port_sfp_dom_info_from_db(port_change_event.port_name, self.port_mapping, None, self.xcvr_table_helper.get_dom_tbl(port_change_event.asic_id), None, - self.xcvr_table_helper.get_pm_tbl(port_change_event.asic_id)) + self.xcvr_table_helper.get_pm_tbl(port_change_event.asic_id), + None) delete_port_from_status_table_hw(port_change_event.port_name, self.port_mapping, self.xcvr_table_helper.get_status_tbl(port_change_event.asic_id)) @@ -1722,6 +1752,7 @@ def _post_port_sfp_info_and_dom_thr_to_db_once(self, port_mapping, xcvr_table_he rc = post_port_sfp_info_to_db(logical_port_name, port_mapping, xcvr_table_helper.get_intf_tbl(asic_index), transceiver_dict, stop_event) if rc != SFP_EEPROM_NOT_READY: post_port_dom_threshold_info_to_db(logical_port_name, port_mapping, xcvr_table_helper.get_dom_threshold_tbl(asic_index), stop_event) + post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, xcvr_table_helper.get_firmware_info_tbl(asic_index), stop_event) # Do not notify media settings during warm reboot to avoid dataplane traffic impact if is_warm_start == False: @@ -1947,6 +1978,7 @@ def task_worker(self, stopping_event, sfp_error_event): if rc != SFP_EEPROM_NOT_READY: post_port_dom_threshold_info_to_db(logical_port, self.port_mapping, self.xcvr_table_helper.get_dom_threshold_tbl(asic_index)) + post_port_sfp_firmware_info_to_db(logical_port, self.port_mapping, self.xcvr_table_helper.get_firmware_info_tbl(asic_index)) media_settings_parser.notify_media_setting(logical_port, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(asic_index), self.xcvr_table_helper.get_cfg_port_tbl(asic_index), self.port_mapping) transceiver_dict.clear() elif value == sfp_status_helper.SFP_STATUS_REMOVED: @@ -1958,7 +1990,8 @@ def task_worker(self, stopping_event, sfp_error_event): self.xcvr_table_helper.get_intf_tbl(asic_index), self.xcvr_table_helper.get_dom_tbl(asic_index), self.xcvr_table_helper.get_dom_threshold_tbl(asic_index), - self.xcvr_table_helper.get_pm_tbl(asic_index)) + self.xcvr_table_helper.get_pm_tbl(asic_index), + self.xcvr_table_helper.get_firmware_info_tbl(asic_index)) delete_port_from_status_table_hw(logical_port, self.port_mapping, self.xcvr_table_helper.get_status_tbl(asic_index)) else: try: @@ -1986,7 +2019,8 @@ def task_worker(self, stopping_event, sfp_error_event): None, self.xcvr_table_helper.get_dom_tbl(asic_index), self.xcvr_table_helper.get_dom_threshold_tbl(asic_index), - self.xcvr_table_helper.get_pm_tbl(asic_index)) + self.xcvr_table_helper.get_pm_tbl(asic_index), + self.xcvr_table_helper.get_firmware_info_tbl(asic_index)) delete_port_from_status_table_hw(logical_port, self.port_mapping, self.xcvr_table_helper.get_status_tbl(asic_index)) except (TypeError, ValueError) as e: helper_logger.log_error("{}: Got unrecognized event {}, ignored".format(logical_port, value)) @@ -2083,7 +2117,8 @@ def on_remove_logical_port(self, port_change_event): self.xcvr_table_helper.get_intf_tbl(port_change_event.asic_id), self.xcvr_table_helper.get_dom_tbl(port_change_event.asic_id), self.xcvr_table_helper.get_dom_threshold_tbl(port_change_event.asic_id), - self.xcvr_table_helper.get_pm_tbl(port_change_event.asic_id)) + self.xcvr_table_helper.get_pm_tbl(port_change_event.asic_id), + self.xcvr_table_helper.get_firmware_info_tbl(port_change_event.asic_id)) delete_port_from_status_table_sw(port_change_event.port_name, self.xcvr_table_helper.get_status_tbl(port_change_event.asic_id)) delete_port_from_status_table_hw(port_change_event.port_name, self.port_mapping, @@ -2112,6 +2147,7 @@ def on_add_logical_port(self, port_change_event): status_tbl = self.xcvr_table_helper.get_status_tbl(port_change_event.asic_id) int_tbl = self.xcvr_table_helper.get_intf_tbl(port_change_event.asic_id) dom_threshold_tbl = self.xcvr_table_helper.get_dom_threshold_tbl(port_change_event.asic_id) + firmware_info_tbl = self.xcvr_table_helper.get_firmware_info_tbl(port_change_event.asic_id) error_description = 'N/A' status = None @@ -2146,6 +2182,7 @@ def on_add_logical_port(self, port_change_event): self.retry_eeprom_set.add(port_change_event.port_name) else: post_port_dom_threshold_info_to_db(port_change_event.port_name, self.port_mapping, dom_threshold_tbl) + post_port_sfp_firmware_info_to_db(port_change_event.port_name, self.port_mapping, firmware_info_tbl) media_settings_parser.notify_media_setting(port_change_event.port_name, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(port_change_event.asic_id), self.xcvr_table_helper.get_cfg_port_tbl(port_change_event.asic_id), self.port_mapping) else: status = sfp_status_helper.SFP_STATUS_REMOVED if not status else status @@ -2172,6 +2209,7 @@ def retry_eeprom_reading(self): rc = post_port_sfp_info_to_db(logical_port, self.port_mapping, self.xcvr_table_helper.get_intf_tbl(asic_index), transceiver_dict) if rc != SFP_EEPROM_NOT_READY: post_port_dom_threshold_info_to_db(logical_port, self.port_mapping, self.xcvr_table_helper.get_dom_threshold_tbl(asic_index)) + post_port_sfp_firmware_info_to_db(logical_port, self.port_mapping, self.xcvr_table_helper.get_firmware_info_tbl(asic_index)) media_settings_parser.notify_media_setting(logical_port, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(asic_index), self.xcvr_table_helper.get_cfg_port_tbl(asic_index), self.port_mapping) transceiver_dict.clear() retry_success_set.add(logical_port) @@ -2301,7 +2339,8 @@ def deinit(self): self.xcvr_table_helper.get_intf_tbl(asic_index), self.xcvr_table_helper.get_dom_tbl(asic_index), self.xcvr_table_helper.get_dom_threshold_tbl(asic_index), - self.xcvr_table_helper.get_pm_tbl(asic_index)) + self.xcvr_table_helper.get_pm_tbl(asic_index), + self.xcvr_table_helper.get_firmware_info_tbl(asic_index)) delete_port_from_status_table_sw(logical_port_name, self.xcvr_table_helper.get_status_tbl(asic_index)) delete_port_from_status_table_hw(logical_port_name, port_mapping_data, self.xcvr_table_helper.get_status_tbl(asic_index)) @@ -2381,7 +2420,7 @@ def run(self): class XcvrTableHelper: def __init__(self, namespaces): self.int_tbl, self.dom_tbl, self.dom_threshold_tbl, self.status_tbl, self.app_port_tbl, \ - self.cfg_port_tbl, self.state_port_tbl, self.pm_tbl = {}, {}, {}, {}, {}, {}, {}, {} + self.cfg_port_tbl, self.state_port_tbl, self.pm_tbl, self.firmware_info_tbl = {}, {}, {}, {}, {}, {}, {}, {}, {} self.state_db = {} self.cfg_db = {} for namespace in namespaces: @@ -2392,6 +2431,7 @@ def __init__(self, namespaces): self.dom_threshold_tbl[asic_id] = swsscommon.Table(self.state_db[asic_id], TRANSCEIVER_DOM_THRESHOLD_TABLE) self.status_tbl[asic_id] = swsscommon.Table(self.state_db[asic_id], TRANSCEIVER_STATUS_TABLE) self.pm_tbl[asic_id] = swsscommon.Table(self.state_db[asic_id], TRANSCEIVER_PM_TABLE) + self.firmware_info_tbl[asic_id] = swsscommon.Table(self.state_db[asic_id], TRANSCEIVER_FIRMWARE_INFO_TABLE) self.state_port_tbl[asic_id] = swsscommon.Table(self.state_db[asic_id], swsscommon.STATE_PORT_TABLE_NAME) appl_db = daemon_base.db_connect("APPL_DB", namespace) self.app_port_tbl[asic_id] = swsscommon.ProducerStateTable(appl_db, swsscommon.APP_PORT_TABLE_NAME) @@ -2413,6 +2453,9 @@ def get_status_tbl(self, asic_id): def get_pm_tbl(self, asic_id): return self.pm_tbl[asic_id] + def get_firmware_info_tbl(self, asic_id): + return self.firmware_info_tbl[asic_id] + def get_app_port_tbl(self, asic_id): return self.app_port_tbl[asic_id]