Skip to content

Commit

Permalink
Added VDM freeze and unfreeze support
Browse files Browse the repository at this point in the history
  • Loading branch information
mihirpat1 committed Jan 7, 2025
1 parent 39c7da7 commit ef286f7
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 23 deletions.
124 changes: 124 additions & 0 deletions sonic-xcvrd/tests/test_xcvrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2787,6 +2787,10 @@ def test_DomInfoUpdateTask_task_run_stop(self):
assert not task.is_alive()

@patch('xcvrd.xcvrd.XcvrTableHelper', MagicMock())
@patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True))
@patch('xcvrd.xcvrd._wrapper_is_transceiver_vdm_supported', MagicMock(return_value=True))
@patch('xcvrd.xcvrd._wrapper_freeze_vdm_stats_and_confirm', MagicMock(return_value=True))
@patch('xcvrd.xcvrd._wrapper_unfreeze_vdm_stats_and_confirm', MagicMock(return_value=True))
@patch('xcvrd.xcvrd_utilities.sfp_status_helper.detect_port_in_error_status')
@patch('xcvrd.dom_mgr.DomInfoUpdateTask.post_port_sfp_firmware_info_to_db')
@patch('xcvrd.dom_mgr.DomInfoUpdateTask.post_port_dom_info_to_db')
Expand Down Expand Up @@ -2837,6 +2841,85 @@ def test_DomInfoUpdateTask_task_worker(self, mock_post_pm_info, mock_post_flag_s
assert mock_post_flag_status.call_count == 1
assert mock_post_pm_info.call_count == 1

@patch('xcvrd.xcvrd.XcvrTableHelper', MagicMock())
@patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True))
@patch('xcvrd.xcvrd_utilities.sfp_status_helper.detect_port_in_error_status', MagicMock(return_value=False))
@patch('xcvrd.dom_mgr.DomInfoUpdateTask.post_port_sfp_firmware_info_to_db', MagicMock(return_value=True))
@patch('xcvrd.dom_mgr.DomInfoUpdateTask.post_port_dom_info_to_db', MagicMock(return_value=True))
@patch('xcvrd.dom_mgr.DomInfoUpdateTask.update_port_transceiver_status_table_hw', MagicMock())
@patch('xcvrd.xcvrd._wrapper_is_transceiver_vdm_supported', MagicMock(return_value=True))
@patch('swsscommon.swsscommon.Select.addSelectable', MagicMock())
@patch('swsscommon.swsscommon.SubscriberStateTable')
@patch('swsscommon.swsscommon.Select.select')
@patch('xcvrd.xcvrd._wrapper_freeze_vdm_stats_and_confirm')
@patch('xcvrd.xcvrd._wrapper_unfreeze_vdm_stats_and_confirm')
@patch('xcvrd.dom_mgr.DomInfoUpdateTask.post_port_diagnostic_values_to_db')
@patch('xcvrd.xcvrd.post_port_vdm_non_real_values_to_db')
@patch('xcvrd.dom_mgr.DomInfoUpdateTask.post_port_pm_info_to_db')
def test_DomInfoUpdateTask_task_worker_vdm_failure(self, mock_post_pm_info, mock_post_flag_status, mock_post_diagnostic_value,
mock_unfreeze_vdm_stats_and_confirm, mock_freeze_vdm_stats_and_confirm,
mock_select, mock_sub_table):
mock_selectable = MagicMock()
mock_selectable.pop = MagicMock(
side_effect=[('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), (None, None, None), (None, None, None), (None, None, None)])
mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable)
mock_sub_table.return_value = mock_selectable

port_mapping = PortMapping()
stop_event = threading.Event()
mock_cmis_manager = MagicMock()
task = DomInfoUpdateTask(DEFAULT_NAMESPACE, port_mapping, stop_event, mock_cmis_manager, helper_logger)
task.xcvr_table_helper = XcvrTableHelper(DEFAULT_NAMESPACE)
task.task_stopping_event.wait = MagicMock(side_effect=[False, True])
task.get_dom_polling_from_config_db = MagicMock(return_value='enabled')
task.is_port_in_cmis_terminal_state = MagicMock(return_value=False)
mock_freeze_vdm_stats_and_confirm.return_value = False
task.task_worker()
assert task.port_mapping.logical_port_list.count('Ethernet0')
assert task.port_mapping.get_asic_id_for_logical_port('Ethernet0') == 0
assert task.port_mapping.get_physical_to_logical(1) == ['Ethernet0']
assert task.port_mapping.get_logical_to_physical('Ethernet0') == [1]
assert mock_unfreeze_vdm_stats_and_confirm.call_count == 1
assert mock_post_diagnostic_value.call_count == 0
assert mock_post_flag_status.call_count == 0
assert mock_post_pm_info.call_count == 0

# clear the call count
mock_freeze_vdm_stats_and_confirm.reset_mock()
mock_unfreeze_vdm_stats_and_confirm.reset_mock()
mock_post_diagnostic_value.reset_mock()
mock_post_flag_status.reset_mock()
mock_post_pm_info.reset_mock()

# Test the case where the VDM stats are successfully frozen but the VDM stats are not successfully unfrozen
mock_freeze_vdm_stats_and_confirm.return_value = True
mock_unfreeze_vdm_stats_and_confirm.return_value = False
task.task_stopping_event.wait = MagicMock(side_effect=[False, True])
task.task_worker()
assert mock_freeze_vdm_stats_and_confirm.call_count == 1
assert mock_unfreeze_vdm_stats_and_confirm.call_count == 1
assert mock_post_diagnostic_value.call_count == 1
assert mock_post_flag_status.call_count == 1
assert mock_post_pm_info.call_count == 1

# clear the call count
mock_freeze_vdm_stats_and_confirm.reset_mock()
mock_unfreeze_vdm_stats_and_confirm.reset_mock()
mock_post_diagnostic_value.reset_mock()
mock_post_flag_status.reset_mock()
mock_post_pm_info.reset_mock()

# mock_post_diagnostic_value raises an exception
mock_unfreeze_vdm_stats_and_confirm.return_value = True
mock_post_diagnostic_value.side_effect = TypeError
task.task_stopping_event.wait = MagicMock(side_effect=[False, True])
task.task_worker()
assert mock_freeze_vdm_stats_and_confirm.call_count == 1
assert mock_unfreeze_vdm_stats_and_confirm.call_count == 1
assert mock_post_diagnostic_value.call_count == 1
assert mock_post_flag_status.call_count == 0
assert mock_post_pm_info.call_count == 0

@patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=False))
@patch('xcvrd.xcvrd.XcvrTableHelper')
@patch('xcvrd.xcvrd.delete_port_from_status_table_hw')
Expand Down Expand Up @@ -3223,6 +3306,47 @@ 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)

@pytest.mark.parametrize("mock_sfp, expected", [
(MagicMock(is_transceiver_vdm_supported=MagicMock(side_effect=NotImplementedError)), False),
(MagicMock(is_transceiver_vdm_supported=MagicMock(return_value=False)), False),
(MagicMock(is_transceiver_vdm_supported=MagicMock(return_value=True)), True)
])
@patch('xcvrd.xcvrd.platform_chassis')
def test_wrapper_is_transceiver_vdm_supported(self, mock_chassis, mock_sfp, expected):
mock_chassis.get_sfp.return_value = mock_sfp

result = xcvrd._wrapper_is_transceiver_vdm_supported(1)
assert result == expected

@pytest.mark.parametrize("action_return, status_return, time_side_effect, expected", [
(True, True, [0, 0.1, 0.2, 0.3], True),
(True, False, [0, 0.1, 0.2, 0.3, 0.4, 0.5], False),
(False, False, [], False),
])
@patch('xcvrd.xcvrd.platform_chassis')
@patch('xcvrd.xcvrd.helper_logger')
@patch('xcvrd.xcvrd.time.sleep', MagicMock())
@patch('xcvrd.xcvrd.time.time')
def test_vdm_action_and_confirm(self, mock_time, mock_logger, mock_chassis,
action_return, status_return, time_side_effect, expected):
mock_sfp = MagicMock()
mock_sfp.freeze_vdm_stats.return_value = action_return
mock_sfp.get_vdm_freeze_status.return_value = status_return
mock_chassis.get_sfp.return_value = mock_sfp

mock_time.side_effect = time_side_effect

result = xcvrd._vdm_action_and_confirm(1, mock_sfp.freeze_vdm_stats, mock_sfp.get_vdm_freeze_status, "freeze")
assert result == expected

@patch('xcvrd.xcvrd.platform_chassis', MagicMock())
def test_vdm_action_and_confirm_exception(self):
mock_action = MagicMock()
mock_action.side_effect = NotImplementedError

result = xcvrd._vdm_action_and_confirm(1, mock_action, None, "freeze")
assert not result

@patch('xcvrd.xcvrd.platform_chassis')
def test_wrapper_get_transceiver_vdm_thresholds(self, mock_chassis):
mock_object = MagicMock()
Expand Down
75 changes: 52 additions & 23 deletions sonic-xcvrd/xcvrd/dom_mgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
as a child thread of xcvrd main thread.
"""

from contextlib import contextmanager


try:
import threading
import copy
Expand Down Expand Up @@ -281,6 +284,18 @@ def post_port_pm_info_to_db(self, logical_port_name, port_mapping, table, stop_e
else:
return xcvrd.SFP_EEPROM_NOT_READY

@contextmanager
def vdm_freeze_context(self, physical_port):
try:
if not xcvrd._wrapper_freeze_vdm_stats_and_confirm(physical_port):
self.log_error("Failed to freeze VDM stats in contextmanager for port {}".format(physical_port))
yield False
else:
yield True
finally:
if not xcvrd._wrapper_unfreeze_vdm_stats_and_confirm(physical_port):
self.log_error("Failed to unfreeze VDM stats in contextmanager for port {}".format(physical_port))

def task_worker(self):
self.xcvr_table_helper = XcvrTableHelper(self.namespaces)
self.log_notice("Start DOM monitoring loop")
Expand Down Expand Up @@ -315,7 +330,16 @@ def task_worker(self):
self.log_warning("Got invalid asic index for {}, ignored".format(logical_port_name))
continue

physical_port_list = self.port_mapping.get_logical_to_physical(logical_port_name)
if not physical_port_list:
self.log_warning("Got unknown physical port list {} for lport {}".format(physical_port_list, logical_port_name))
continue
physical_port = physical_port_list[0]

if not sfp_status_helper.detect_port_in_error_status(logical_port_name, self.xcvr_table_helper.get_status_tbl(asic_index)):
if not xcvrd._wrapper_get_presence(physical_port):
continue

try:
self.post_port_sfp_firmware_info_to_db(logical_port_name, self.port_mapping, self.xcvr_table_helper.get_firmware_info_tbl(asic_index), self.task_stopping_event, firmware_info_cache=firmware_info_cache)
except (KeyError, TypeError) as e:
Expand All @@ -338,29 +362,34 @@ def task_worker(self):
#continue to process next port since execption could be raised due to port reset, transceiver removal
self.log_warning("Got exception {} while processing transceiver status hw for port {}, ignored".format(repr(e), logical_port_name))
continue
try:
self.post_port_diagnostic_values_to_db(logical_port_name, self.xcvr_table_helper.get_vdm_real_value_tbl(asic_index),
xcvrd._wrapper_get_vdm_real_values, self.task_stopping_event,
db_cache=vdm_real_value_cache)
except (KeyError, TypeError) as e:
#continue to process next port since execption could be raised due to port reset, transceiver removal
self.log_warning("Got exception {} while processing vdm values for port {}, ignored".format(repr(e), logical_port_name))
continue
try:
xcvrd.post_port_vdm_non_real_values_to_db(logical_port_name, self.port_mapping,
self.xcvr_table_helper.get_vdm_flag_tbl,
xcvrd._wrapper_get_vdm_flags, self.task_stopping_event,
db_cache=vdm_flag_cache)
except (KeyError, TypeError) as e:
#continue to process next port since execption could be raised due to port reset, transceiver removal
self.log_warning("Got exception {} while processing vdm flags for port {}, ignored".format(repr(e), logical_port_name))
continue
try:
self.post_port_pm_info_to_db(logical_port_name, self.port_mapping, self.xcvr_table_helper.get_pm_tbl(asic_index), self.task_stopping_event, pm_info_cache=pm_info_cache)
except (KeyError, TypeError) as e:
#continue to process next port since execption could be raised due to port reset, transceiver removal
self.log_warning("Got exception {} while processing pm info for port {}, ignored".format(repr(e), logical_port_name))
continue
if xcvrd._wrapper_is_transceiver_vdm_supported(physical_port):
with self.vdm_freeze_context(physical_port) as vdm_frozen:
if not vdm_frozen:
self.log_error("Failed to freeze VDM stats for port {}".format(physical_port))
continue
try:
self.post_port_diagnostic_values_to_db(logical_port_name, self.xcvr_table_helper.get_vdm_real_value_tbl(asic_index),
xcvrd._wrapper_get_vdm_real_values, self.task_stopping_event,
db_cache=vdm_real_value_cache)
except (KeyError, TypeError) as e:
#continue to process next port since execption could be raised due to port reset, transceiver removal
self.log_warning("Got exception {} while processing vdm values for port {}, ignored".format(repr(e), logical_port_name))
continue
try:
xcvrd.post_port_vdm_non_real_values_to_db(logical_port_name, self.port_mapping,
self.xcvr_table_helper.get_vdm_flag_tbl,
xcvrd._wrapper_get_vdm_flags, self.task_stopping_event,
db_cache=vdm_flag_cache)
except (KeyError, TypeError) as e:
#continue to process next port since execption could be raised due to port reset, transceiver removal
self.log_warning("Got exception {} while processing vdm flags for port {}, ignored".format(repr(e), logical_port_name))
continue
try:
self.post_port_pm_info_to_db(logical_port_name, self.port_mapping, self.xcvr_table_helper.get_pm_tbl(asic_index), self.task_stopping_event, pm_info_cache=pm_info_cache)
except (KeyError, TypeError) as e:
#continue to process next port since execption could be raised due to port reset, transceiver removal
self.log_warning("Got exception {} while processing pm info for port {}, ignored".format(repr(e), logical_port_name))
continue

self.log_info("Stop DOM monitoring loop")

Expand Down
77 changes: 77 additions & 0 deletions sonic-xcvrd/xcvrd/xcvrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@
STATE_MACHINE_UPDATE_PERIOD_MSECS = 60000
TIME_FOR_SFP_READY_SECS = 1

MAX_tVDMF_TIME_MSECS = 10
MAX_VDM_FREEZE_UNFREEZE_TIME_MSECS = 500
FREEZE_UNFREEZE_DONE_POLLING_INTERVAL_MSECS = 1

EVENT_ON_ALL_SFP = '-1'
# events definition
SYSTEM_NOT_READY = 'system_not_ready'
Expand Down Expand Up @@ -270,6 +274,79 @@ def _wrapper_get_transceiver_info(physical_port):
pass
return platform_sfputil.get_transceiver_info_dict(physical_port)

def _wrapper_is_transceiver_vdm_supported(physical_port):
if platform_chassis is not None:
try:
return platform_chassis.get_sfp(physical_port).is_transceiver_vdm_supported()
except NotImplementedError:
pass
return False

def _vdm_action_and_confirm(physical_port, action, status_check, action_name):
"""
Helper function to perform VDM action (freeze/unfreeze) and confirm the status.
Args:
physical_port: The physical port index.
action: The action to perform (freeze/unfreeze).
status_check: The function to check the status.
action_name: The name of the action for logging purposes.
Returns:
True if the action is successful, False otherwise.
"""
if platform_chassis is not None:
try:
status = action()
if not status:
helper_logger.log_error(f"Failed to {action_name} VDM stats for port {physical_port}")
return False

# Wait for MAX_tVDMF_TIME_MSECS to allow the module to clear the done bit
time.sleep(MAX_tVDMF_TIME_MSECS / 1000)

# Poll for the done bit to be set
start_time = time.time()
while time.time() - start_time < MAX_VDM_FREEZE_UNFREEZE_TIME_MSECS / 1000:
if status_check():
return True
time.sleep(FREEZE_UNFREEZE_DONE_POLLING_INTERVAL_MSECS / 1000)

helper_logger.log_error(f"Failed to confirm VDM {action_name} status for port {physical_port}")
except NotImplementedError:
pass
return False

def _wrapper_freeze_vdm_stats_and_confirm(physical_port):
"""
Freezes and confirms the VDM freeze status of the transceiver.
Args:
physical_port: The physical port index.
Returns:
True if the VDM stats are frozen successfully, False otherwise.
"""
sfp = platform_chassis.get_sfp(physical_port)
return _vdm_action_and_confirm(
physical_port,
sfp.freeze_vdm_stats,
sfp.get_vdm_freeze_status,
"freeze"
)

def _wrapper_unfreeze_vdm_stats_and_confirm(physical_port):
"""
Unfreezes and confirms the VDM unfreeze status of the transceiver.
Args:
physical_port: The physical port index.
Returns:
True if the VDM stats are unfrozen successfully, False otherwise.
"""
sfp = platform_chassis.get_sfp(physical_port)
return _vdm_action_and_confirm(
physical_port,
sfp.unfreeze_vdm_stats,
sfp.get_vdm_unfreeze_status,
"unfreeze"
)

def _wrapper_get_transceiver_vdm_thresholds(physical_port):
if platform_chassis is not None:
try:
Expand Down

0 comments on commit ef286f7

Please sign in to comment.