Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deterministic link bring-up feature for SFF compliant modules #383

Merged
merged 26 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0af24ab
Add initial support for sff_mgr
longhuan-cisco Jun 21, 2023
297e22d
Remove unnecessary check and add comments
longhuan-cisco Jun 21, 2023
6eea7d8
Update comments
longhuan-cisco Jun 22, 2023
04fa51e
Add return value to handle_port_update_event and update comments
longhuan-cisco Jun 22, 2023
82ce3f3
Update comments
longhuan-cisco Jun 22, 2023
43ad2e1
Rely on asic_id from event update instead from static port_mapping
longhuan-cisco Jun 23, 2023
94a0c9d
Update comment
longhuan-cisco Jun 23, 2023
c93f851
Move sff_mgr to a new file
longhuan-cisco Jun 26, 2023
bad4fd8
Revert space change
longhuan-cisco Jun 26, 2023
4821c81
Update comments
longhuan-cisco Jun 30, 2023
dbf2ea5
Add admin_status sanity check
longhuan-cisco Jul 14, 2023
a66831e
Revert unnecessary space change on xcvrd.py
longhuan-cisco Sep 6, 2023
741ac26
Merge branch 'master' into sff_mgr
longhuan-cisco Oct 16, 2023
689e46b
Add NotImplementedError catching
longhuan-cisco Oct 24, 2023
288da9b
Merge branch 'master' into sff_mgr
longhuan-cisco Dec 6, 2023
a9d6fec
Address comments and update subport handling
longhuan-cisco Dec 20, 2023
4a56969
Add unit tests for SffManagerTask.get_active_lanes_for_lport() method
longhuan-cisco Dec 20, 2023
5515851
Move get_active_lanes_for_lport after checking xcvr_type
longhuan-cisco Dec 23, 2023
e83f9bd
Update on_port_update_event checks and log format
longhuan-cisco Jan 8, 2024
e03e8dd
Change to get enable_sff_mgr flag from process argument
longhuan-cisco Feb 7, 2024
e3dcff2
Fix typo
longhuan-cisco Feb 7, 2024
bb7cdde
Merge branch 'master' into sff_mgr
longhuan-cisco Feb 23, 2024
51d16c3
Resolve conflicts
longhuan-cisco Feb 25, 2024
83f7bc3
Remove unused import and class variable in port_event_helper.py
longhuan-cisco Feb 25, 2024
178716c
Change to update 'lanes' and 'subport' fields independently
longhuan-cisco Mar 7, 2024
236079e
Merge branch 'master' into sff_mgr
longhuan-cisco Mar 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 221 additions & 5 deletions sonic-xcvrd/tests/test_xcvrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from xcvrd.xcvrd_utilities.port_mapping import *
from xcvrd.xcvrd_utilities.sfp_status_helper import *
from xcvrd.xcvrd import *
from xcvrd.sff_mgr import *
from xcvrd.xcvrd_utilities.xcvr_table_helper import *
import pytest
import copy
import os
Expand Down Expand Up @@ -43,6 +45,26 @@

class TestXcvrdThreadException(object):

@patch('xcvrd.xcvrd_utilities.port_mapping.subscribe_port_update_event',
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)
exception_received = None
trace = None
try:
sff_mgr.start()
sff_mgr.join()
except Exception as e1:
exception_received = e1
trace = traceback.format_exc()

assert not sff_mgr.is_alive()
assert(type(exception_received) == NotImplementedError)
assert("NotImplementedError" in str(trace) and "effect" in str(trace))
assert("sonic-xcvrd/xcvrd/sff_mgr.py" in str(trace))
assert("subscribe_port_update_event" in str(trace))

@patch('xcvrd.xcvrd.platform_chassis', MagicMock())
def test_CmisManagerTask_task_run_with_exception(self):
port_mapping = PortMapping()
Expand Down Expand Up @@ -109,25 +131,32 @@ def test_SfpStateUpdateTask_task_run_with_exception(self):
@patch('xcvrd.xcvrd.SfpStateUpdateTask.is_alive', MagicMock(return_value = False))
@patch('xcvrd.xcvrd.DomInfoUpdateTask.is_alive', MagicMock(return_value = False))
@patch('xcvrd.xcvrd.CmisManagerTask.is_alive', MagicMock(return_value = False))
@patch('xcvrd.xcvrd.CmisManagerTask.join', MagicMock(side_effect = NotImplementedError))
@patch('xcvrd.xcvrd.SffManagerTask.is_alive', MagicMock(return_value=False))
@patch('xcvrd.xcvrd.CmisManagerTask.join', MagicMock(side_effect=NotImplementedError))
@patch('xcvrd.xcvrd.CmisManagerTask.start', MagicMock())
@patch('xcvrd.xcvrd.SffManagerTask.start', MagicMock())
@patch('xcvrd.xcvrd.DomInfoUpdateTask.start', MagicMock())
@patch('xcvrd.xcvrd.SfpStateUpdateTask.start', MagicMock())
@patch('xcvrd.xcvrd.DaemonXcvrd.deinit', MagicMock())
@patch('os.kill')
@patch('xcvrd.xcvrd.DaemonXcvrd.init')
@patch('xcvrd.xcvrd.DomInfoUpdateTask.join')
@patch('xcvrd.xcvrd.SfpStateUpdateTask.join')
def test_DaemonXcvrd_run_with_exception(self, mock_task_join1, mock_task_join2, mock_init, mock_os_kill):
@patch('xcvrd.xcvrd.SffManagerTask.join')
def test_DaemonXcvrd_run_with_exception(self, mock_task_join_sff, mock_task_join_sfp,
mock_task_join_dom, mock_init, mock_os_kill):
mock_init.return_value = (PortMapping(), set())
xcvrd = DaemonXcvrd(SYSLOG_IDENTIFIER)
xcvrd.enable_sff_mgr = True
xcvrd.load_feature_flags = MagicMock()
xcvrd.stop_event.wait = MagicMock()
xcvrd.run()

assert len(xcvrd.threads) == 3
assert len(xcvrd.threads) == 4
assert mock_init.call_count == 1
assert mock_task_join1.call_count == 1
assert mock_task_join2.call_count == 1
assert mock_task_join_sff.call_count == 1
assert mock_task_join_sfp.call_count == 1
assert mock_task_join_dom.call_count == 1
assert mock_os_kill.call_count == 1

class TestXcvrdScript(object):
Expand Down Expand Up @@ -593,6 +622,176 @@ def test_DaemonXcvrd_run(self, mock_task_stop1, mock_task_stop2, mock_task_run1,
assert mock_deinit.call_count == 1
assert mock_init.call_count == 1

def test_SffManagerTask_handle_port_change_event(self):
stop_event = threading.Event()
task = SffManagerTask(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)
assert len(task.port_dict) == 0

port_change_event = PortChangeEvent('PortInitDone', -1, 0, PortChangeEvent.PORT_SET)
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 0

port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_ADD)
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 0

port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_REMOVE)
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 0

port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_DEL)
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 0

port_dict = {'type': 'QSFP28', 'channel': '0', 'host_tx_ready': 'false'}
port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_SET, port_dict)
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 1

port_change_event = PortChangeEvent('Ethernet0', -1, 0, PortChangeEvent.PORT_DEL, {},
'STATE_DB', 'TRANSCEIVER_INFO')
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 1

port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_DEL, {},
'CONFIG_DB', 'PORT_TABLE')
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 0

@patch.object(XcvrTableHelper, 'get_state_port_tbl', return_value=MagicMock())
def test_SffManagerTask_get_host_tx_status(self, mock_get_state_port_tbl):
mock_get_state_port_tbl.return_value.get.return_value = (True, {'host_tx_ready': 'true'})

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

lport = 'Ethernet0'
assert sff_manager_task.get_host_tx_status(lport, 0) == 'true'
mock_get_state_port_tbl.assert_called_once_with(0)
mock_get_state_port_tbl.return_value.get.assert_called_once_with(lport)

@patch.object(XcvrTableHelper, 'get_cfg_port_tbl', return_value=MagicMock())
def test_SffManagerTask_get_admin_status(self, mock_get_cfg_port_tbl):
mock_get_cfg_port_tbl.return_value.get.return_value = (True, {'admin_status': 'up'})

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

lport = 'Ethernet0'
assert sff_manager_task.get_admin_status(lport, 0) == 'up'
mock_get_cfg_port_tbl.assert_called_once_with(0)
mock_get_cfg_port_tbl.return_value.get.assert_called_once_with(lport)

@patch('xcvrd.xcvrd.platform_chassis')
@patch('xcvrd.xcvrd_utilities.port_mapping.subscribe_port_update_event',
MagicMock(return_value=(None, None)))
@patch('xcvrd.xcvrd_utilities.port_mapping.handle_port_update_event', MagicMock(return_value=True))
def test_SffManagerTask_task_worker(self, mock_chassis):
mock_xcvr_api = MagicMock()
mock_xcvr_api.tx_disable_channel = MagicMock(return_value=True)
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_sfp = MagicMock()
mock_sfp.get_presence = MagicMock(return_value=True)
mock_sfp.get_xcvr_api = MagicMock(return_value=mock_xcvr_api)

mock_chassis.get_all_sfps = MagicMock(return_value=[mock_sfp])
mock_chassis.get_sfp = MagicMock(return_value=mock_sfp)

task = SffManagerTask(DEFAULT_NAMESPACE,
threading.Event(),
mock_chassis,
helper_logger)

# TX enable case:
port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_SET, {
'type': 'QSFP28',
'channel': '0'
})
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 1
task.get_host_tx_status = MagicMock(return_value='true')
task.get_admin_status = MagicMock(return_value='up')
mock_xcvr_api.get_tx_disable = MagicMock(return_value=[True, True, True, True])
task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True])
task.task_worker()
assert mock_xcvr_api.tx_disable_channel.call_count == 1
assert task.get_host_tx_status.call_count == 1
assert task.get_admin_status.call_count == 1

# TX disable case:
port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_SET,
{'host_tx_ready': 'false'})
task.on_port_update_event(port_change_event)
assert len(task.port_dict) == 1
mock_xcvr_api.get_tx_disable = MagicMock(return_value=[False, False, False, False])
task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True])
task.task_worker()
assert mock_xcvr_api.tx_disable_channel.call_count == 2

# No insertion and no change on host_tx_ready
task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True])
task.task_worker()
assert task.port_dict == task.port_dict_prev
assert mock_xcvr_api.tx_disable_channel.call_count == 2

# flat memory case
port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_SET,
{'host_tx_ready': 'true'})
task.on_port_update_event(port_change_event)
mock_xcvr_api.is_flat_memory = MagicMock(return_value=True)
mock_xcvr_api.is_flat_memory.call_count = 0
task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True])
task.task_worker()
assert mock_xcvr_api.is_flat_memory.call_count == 1
assert mock_xcvr_api.tx_disable_channel.call_count == 2
mock_xcvr_api.is_flat_memory = MagicMock(return_value=False)

# copper case
port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_SET,
{'host_tx_ready': 'false'})
task.on_port_update_event(port_change_event)
mock_xcvr_api.is_copper = MagicMock(return_value=True)
mock_xcvr_api.is_copper.call_count = 0
task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True])
task.task_worker()
assert mock_xcvr_api.is_copper.call_count == 1
assert mock_xcvr_api.tx_disable_channel.call_count == 2
mock_xcvr_api.is_copper = MagicMock(return_value=False)

# tx_disable not supported case
port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_SET,
{'host_tx_ready': 'true'})
task.on_port_update_event(port_change_event)
mock_xcvr_api.get_tx_disable_support = MagicMock(return_value=False)
mock_xcvr_api.get_tx_disable_support.call_count = 0
task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True])
task.task_worker()
assert mock_xcvr_api.get_tx_disable_support.call_count == 1
assert mock_xcvr_api.tx_disable_channel.call_count == 2
mock_xcvr_api.get_tx_disable_support = MagicMock(return_value=True)

# sfp not present case
port_change_event = PortChangeEvent('Ethernet0', 1, 0, PortChangeEvent.PORT_SET,
{'host_tx_ready': 'false'})
task.on_port_update_event(port_change_event)
mock_sfp.get_presence = MagicMock(return_value=False)
mock_sfp.get_presence.call_count = 0
task.task_stopping_event.is_set = MagicMock(side_effect=[False, False, True])
task.task_worker()
assert mock_sfp.get_presence.call_count == 1
assert mock_xcvr_api.tx_disable_channel.call_count == 2
mock_sfp.get_presence = MagicMock(return_value=True)

@patch('xcvrd.xcvrd._wrapper_get_sfp_type', MagicMock(return_value='QSFP_DD'))
def test_CmisManagerTask_handle_port_change_event(self):
port_mapping = PortMapping()
Expand Down Expand Up @@ -1498,6 +1697,23 @@ def test_DaemonXcvrd_init_deinit_fastboot_enabled(self):
xcvrd.init()
xcvrd.deinit()

@patch('builtins.open', new_callable=MagicMock)
@patch('xcvrd.xcvrd.DaemonXcvrd.get_platform_pmon_ctrl_file_path', return_value="dummy_path")
@patch('json.load', return_value={ENABLE_SFF_MGR_FLAG_NAME: True})
def test_DaemonXcvrd_load_feature_flags(self, mock_json_load, mock_get_path, mock_open):
xcvrd = DaemonXcvrd(SYSLOG_IDENTIFIER)
xcvrd.load_feature_flags()
mock_get_path.assert_called_once()
mock_open.assert_called_once_with('dummy_path')
mock_json_load.assert_called_once()
assert xcvrd.enable_sff_mgr == True

@patch('sonic_py_common.device_info.get_platform', MagicMock(return_value='x86_64-sonic-linux'))
@patch('os.path.isfile', MagicMock(return_value=True))
def test_DaemonXcvrd_get_platform_pmon_ctrl_file_path(self):
xcvrd = DaemonXcvrd(SYSLOG_IDENTIFIER)
assert xcvrd.get_platform_pmon_ctrl_file_path(
) == '/usr/share/sonic/platform/pmon_daemon_control.json'

def wait_until(total_wait_time, interval, call_back, *args, **kwargs):
wait_time = 0
Expand Down
Loading