Skip to content
Merged
57 changes: 50 additions & 7 deletions sonic-xcvrd/tests/test_xcvrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class TestXcvrdThreadException(object):
@patch('xcvrd.sff_mgr.PortChangeObserver', MagicMock(side_effect=NotImplementedError))
def test_SffManagerTask_task_run_with_exception(self):
stop_event = threading.Event()
sff_mgr = SffManagerTask(DEFAULT_NAMESPACE, stop_event, MagicMock(), helper_logger)
sff_mgr = SffManagerTask(True, DEFAULT_NAMESPACE, stop_event, MagicMock(), helper_logger)
exception_received = None
trace = None
try:
Expand Down Expand Up @@ -1654,7 +1654,7 @@ def test_DaemonXcvrd_run(self, mock_task_stop1, mock_task_stop2, mock_task_run1,

def test_SffManagerTask_handle_port_change_event(self):
stop_event = threading.Event()
task = SffManagerTask(DEFAULT_NAMESPACE, stop_event, MagicMock(), helper_logger)
task = SffManagerTask(True, DEFAULT_NAMESPACE, stop_event, MagicMock(), helper_logger)

port_change_event = PortChangeEvent('PortConfigDone', -1, 0, PortChangeEvent.PORT_SET)
task.on_port_update_event(port_change_event)
Expand Down Expand Up @@ -1692,7 +1692,7 @@ def test_SffManagerTask_handle_port_change_event(self):
assert len(task.port_dict) == 0

def test_SffManagerTask_get_active_lanes_for_lport(self):
sff_manager_task = SffManagerTask(DEFAULT_NAMESPACE,
sff_manager_task = SffManagerTask(True, DEFAULT_NAMESPACE,
threading.Event(),
MagicMock(),
helper_logger)
Expand Down Expand Up @@ -1746,7 +1746,7 @@ def test_SffManagerTask_get_active_lanes_for_lport(self):
assert result == expected_result

def test_SffManagerTask_get_active_lanes_for_lport_with_invalid_input(self):
sff_manager_task = SffManagerTask(DEFAULT_NAMESPACE,
sff_manager_task = SffManagerTask(True, DEFAULT_NAMESPACE,
threading.Event(),
MagicMock(),
helper_logger)
Expand All @@ -1769,7 +1769,7 @@ def test_SffManagerTask_get_active_lanes_for_lport_with_invalid_input(self):
def test_SffManagerTask_get_host_tx_status(self, mock_get_state_port_tbl):
mock_get_state_port_tbl.return_value.hget.return_value = (True, 'true')

sff_manager_task = SffManagerTask(DEFAULT_NAMESPACE,
sff_manager_task = SffManagerTask(True, DEFAULT_NAMESPACE,
threading.Event(),
MagicMock(),
helper_logger)
Expand All @@ -1783,7 +1783,7 @@ def test_SffManagerTask_get_host_tx_status(self, mock_get_state_port_tbl):
def test_SffManagerTask_get_admin_status(self, mock_get_cfg_port_tbl):
mock_get_cfg_port_tbl.return_value.hget.return_value = (True, 'up')

sff_manager_task = SffManagerTask(DEFAULT_NAMESPACE,
sff_manager_task = SffManagerTask(True, DEFAULT_NAMESPACE,
threading.Event(),
MagicMock(),
helper_logger)
Expand All @@ -1793,6 +1793,48 @@ def test_SffManagerTask_get_admin_status(self, mock_get_cfg_port_tbl):
mock_get_cfg_port_tbl.assert_called_once_with(0)
mock_get_cfg_port_tbl.return_value.hget.assert_called_once_with(lport, 'admin_status')

def test_SffManagerTask_handle_high_power_class(self):
mock_xcvr_api = MagicMock()
mock_xcvr_api.get_power_class = MagicMock(return_value=5)
mock_xcvr_api.set_high_power_class = MagicMock(return_value=True)
lport = 'Ethernet0'

sff_manager_task = SffManagerTask(True, DEFAULT_NAMESPACE,
threading.Event(),
MagicMock(),
helper_logger)

# Test with normal case
sff_manager_task.handle_high_power_class(mock_xcvr_api, lport)
assert mock_xcvr_api.get_power_class.call_count == 1
assert mock_xcvr_api.set_high_power_class.call_count == 1

# Test with get_power_class failed
mock_xcvr_api.get_power_class.return_value = None
sff_manager_task.handle_high_power_class(mock_xcvr_api, lport)
assert mock_xcvr_api.get_power_class.call_count == 2
assert mock_xcvr_api.set_high_power_class.call_count == 1

# Test for no need to set high power class
mock_xcvr_api.get_power_class.return_value = 4
sff_manager_task.handle_high_power_class(mock_xcvr_api, lport)
assert mock_xcvr_api.get_power_class.call_count == 3
assert mock_xcvr_api.set_high_power_class.call_count == 1

# Test for set_high_power_class failed
mock_xcvr_api.get_power_class.return_value = 5
mock_xcvr_api.set_high_power_class.return_value = False
sff_manager_task.handle_high_power_class(mock_xcvr_api, lport)
assert mock_xcvr_api.get_power_class.call_count == 4
assert mock_xcvr_api.set_high_power_class.call_count == 2

# Test for set_high_power_class not supported
mock_xcvr_api.get_power_class.return_value = 5
mock_xcvr_api.set_high_power_class = MagicMock(side_effect=AttributeError("Attribute not found"))
sff_manager_task.handle_high_power_class(mock_xcvr_api, lport)
assert mock_xcvr_api.get_power_class.call_count == 5
assert mock_xcvr_api.set_high_power_class.call_count == 1

@patch('xcvrd.xcvrd.platform_chassis')
@patch('xcvrd.sff_mgr.PortChangeObserver', MagicMock(handle_port_update_event=MagicMock()))
def test_SffManagerTask_task_worker(self, mock_chassis):
Expand All @@ -1801,6 +1843,7 @@ def test_SffManagerTask_task_worker(self, mock_chassis):
mock_xcvr_api.is_flat_memory = MagicMock(return_value=False)
mock_xcvr_api.is_copper = MagicMock(return_value=False)
mock_xcvr_api.get_tx_disable_support = MagicMock(return_value=True)
mock_xcvr_api.get_power_class = MagicMock(return_value=1)

mock_sfp = MagicMock()
mock_sfp.get_presence = MagicMock(return_value=True)
Expand All @@ -1809,7 +1852,7 @@ def test_SffManagerTask_task_worker(self, mock_chassis):
mock_chassis.get_all_sfps = MagicMock(return_value=[mock_sfp])
mock_chassis.get_sfp = MagicMock(return_value=mock_sfp)

task = SffManagerTask(DEFAULT_NAMESPACE,
task = SffManagerTask(True, DEFAULT_NAMESPACE,
threading.Event(),
mock_chassis,
helper_logger)
Expand Down
39 changes: 37 additions & 2 deletions sonic-xcvrd/xcvrd/sff_mgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ class SffManagerTask(threading.Thread):

SFF_LOGGER_PREFIX = "SFF-MAIN: "

def __init__(self, namespaces, main_thread_stop_event, platform_chassis, helper_logger):
def __init__(self, enable_sff_mgr_controlled_tx, namespaces, main_thread_stop_event, platform_chassis, helper_logger):
threading.Thread.__init__(self)
self.name = "SffManagerTask"
self.enable_sff_mgr_controlled_tx = enable_sff_mgr_controlled_tx
self.exc = None
self.task_stopping_event = threading.Event()
self.main_thread_stop_event = main_thread_stop_event
Expand All @@ -97,6 +98,9 @@ def __init__(self, namespaces, main_thread_stop_event, platform_chassis, helper_
self.xcvr_table_helper = XcvrTableHelper(namespaces)
self.namespaces = namespaces

if not self.enable_sff_mgr_controlled_tx:
self.log_notice("enable_sff_mgr_controlled_tx is False, skipping controlling module TX based on host_tx_ready")

def log_notice(self, message):
self.helper_logger.log_notice("{}{}".format(self.SFF_LOGGER_PREFIX, message))

Expand Down Expand Up @@ -285,6 +289,32 @@ def convert_bool_array_to_bit_mask(self, bool_array):
mask += (1 << i if flag else 0)
return mask

def handle_high_power_class(self, xcvr_api, lport):
"""
Enable high power class for the transceiver.

Args:
xcvr_api (XcvrApi): The XcvrApi instance for the transceiver.
lport (str): Logical port name.
"""
try:
power_class = xcvr_api.get_power_class()
if power_class is None:
self.log_error("{}: failed to get power class".format(lport))
return

if power_class < 5:
# No action needed for power class < 5
return

if xcvr_api.set_high_power_class(power_class, True):
self.log_notice("{}: done enabling high power class".format(lport))
else:
self.log_error("{}: failed to enable high power class".format(lport))

except (AttributeError, NotImplementedError):
pass

def task_worker(self):
'''
The main goal of sff_mgr is to make sure SFF compliant modules are
Expand Down Expand Up @@ -435,8 +465,10 @@ def task_worker(self):
except (AttributeError, NotImplementedError):
# Skip if these essential routines are not available
continue

if xcvr_inserted or (admin_status_changed and data[self.ADMIN_STATUS] == "up"):
self.handle_high_power_class(api, lport)

set_lp_success = (
sfp.set_lpmode(False)
if isinstance(api, Sff8472Api)
Expand All @@ -448,6 +480,9 @@ def task_worker(self):
lport)
)

if not self.enable_sff_mgr_controlled_tx:
continue

if active_lanes is None:
active_lanes = self.get_active_lanes_for_lport(lport, subport_idx,
len(lanes_list),
Expand Down
18 changes: 7 additions & 11 deletions sonic-xcvrd/xcvrd/xcvrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2033,12 +2033,12 @@ def retry_eeprom_reading(self):


class DaemonXcvrd(daemon_base.DaemonBase):
def __init__(self, log_identifier, skip_cmis_mgr=False, enable_sff_mgr=False):
def __init__(self, log_identifier, skip_cmis_mgr=False, enable_sff_mgr_controlled_tx=False):
super(DaemonXcvrd, self).__init__(log_identifier, enable_runtime_log_config=True)
self.stop_event = threading.Event()
self.sfp_error_event = threading.Event()
self.skip_cmis_mgr = skip_cmis_mgr
self.enable_sff_mgr = enable_sff_mgr
self.enable_sff_mgr_controlled_tx = enable_sff_mgr_controlled_tx
self.namespaces = ['']
self.threads = []
self.sfp_obj_dict = {}
Expand Down Expand Up @@ -2236,13 +2236,9 @@ def run(self):
port_mapping_data = self.init()

# Start the SFF manager
sff_manager = None
if self.enable_sff_mgr:
sff_manager = SffManagerTask(self.namespaces, self.stop_event, platform_chassis, helper_logger)
sff_manager.start()
self.threads.append(sff_manager)
else:
self.log_notice("Skipping SFF Task Manager")
sff_manager = SffManagerTask(self.enable_sff_mgr_controlled_tx, self.namespaces, self.stop_event, platform_chassis, helper_logger)
sff_manager.start()
self.threads.append(sff_manager)

# Start the CMIS manager
cmis_manager = None
Expand Down Expand Up @@ -2322,10 +2318,10 @@ def run(self):
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--skip_cmis_mgr', action='store_true')
parser.add_argument('--enable_sff_mgr', action='store_true')
parser.add_argument('--enable_sff_mgr_controlled_tx', action='store_true')

args = parser.parse_args()
xcvrd = DaemonXcvrd(SYSLOG_IDENTIFIER, args.skip_cmis_mgr, args.enable_sff_mgr)
xcvrd = DaemonXcvrd(SYSLOG_IDENTIFIER, args.skip_cmis_mgr, args.enable_sff_mgr_controlled_tx)
xcvrd.run()


Expand Down
Loading