diff --git a/platform/mellanox/mlnx-platform-api/tests/test_modules_mgmt.py b/platform/mellanox/mlnx-platform-api/tests/test_modules_mgmt.py new file mode 100644 index 0000000000..d0cab978cf --- /dev/null +++ b/platform/mellanox/mlnx-platform-api/tests/test_modules_mgmt.py @@ -0,0 +1,800 @@ +# +# Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. +# Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import queue +import sys +import threading +import time +import types +import unittest + +from mock import MagicMock, patch, mock_open, Mock +if sys.version_info.major == 3: + from unittest import mock +else: + import mock + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, modules_path) + +from sonic_platform.device_data import DeviceDataManager +from sonic_py_common import device_info +from sonic_platform import modules_mgmt +from sonic_platform.modules_mgmt import ModulesMgmtTask +from sonic_platform_base.sonic_xcvr.api.public.cmis import CmisApi +from sonic_platform_base.sonic_xcvr.xcvr_eeprom import XcvrEeprom +from sonic_platform_base.sonic_xcvr.codes.public.cmis import CmisCodes +from sonic_platform_base.sonic_xcvr.mem_maps.public.cmis import CmisMemMap +from sonic_platform_base.sonic_xcvr.fields import consts + +DEFAULT_NUM_OF_PORTS_1 = 1 +DEFAULT_NUM_OF_PORTS_3 = 3 +DEFAULT_NUM_OF_PORTS_32 = 32 +POLLER_EXECUTED = False + +def _mock_sysfs_default_file_content(): + return { + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("0"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("1"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("2"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("0"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("1"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("2"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("0"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("1"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("2"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("0"): "48", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("1"): "48", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("2"): "48", + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("0"): "0", + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("1"): "0", + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("2"): "0", + modules_mgmt.SYSFS_INDEPENDENT_FD_HW_RESET: "", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT: "48", + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("0"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("1"): "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("2"): "1", + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE: "1", + modules_mgmt.PROC_CMDLINE: "" + } + + +mock_file_content = _mock_sysfs_default_file_content() + + +class MockPoller: + + def __init__(self, modules_mgmt_task_stopping_event, modules_mgmt_thrd=None, num_of_ports=3, port_plug_out=False + , feature_enabled=True, warm_reboot=False, port_plug_in=False, sleep_timeout=False): + self.fds_dict = {} + self.poller_iteration_count = 0 + self.modules_mgmt_task_stopping_event = modules_mgmt_task_stopping_event + self.modules_mgmt_thrd = modules_mgmt_thrd + self.num_of_ports = num_of_ports + self.port_plug_out = port_plug_out + self.port_plug_in = port_plug_in + self.feature_enabled = feature_enabled + self.warm_reboot = warm_reboot + self.port_plug_out_changed = False + self.port_plug_in_changed = False + self.sleep_timeout = sleep_timeout + + def register(self, fd, attrs): + self.fds_dict[fd.fileno()] = { fd : attrs } + assert fd.fileno() in self.fds_dict + + def unregister(self, fd): + if fd.fileno() in self.fds_dict.keys(): + del self.fds_dict[fd.fileno()] + assert fd.fileno() not in self.fds_dict.keys() + + def poll(self, timeout=1000): + global POLLER_EXECUTED + assert len(self.modules_mgmt_thrd.sfp_port_dict_initial) == self.num_of_ports + assert self.modules_mgmt_thrd.is_supported_indep_mods_system == self.feature_enabled + # counting the number of poller iterations to know when to do the checks after plug out (and plug in) + # have to check at least on iteration 7 to let ports reach final state + self.poller_iteration_count += 1 + if self.num_of_ports > 0: + if not self.port_plug_out_changed: + if self.port_plug_out: + # return first fd registered with some made up event number 870 + fd_no_to_return = list(self.fds_dict.keys())[0] + fd = list(self.fds_dict[fd_no_to_return].keys())[0] + fd.set_file_int_content(0) + event_to_return = 870 + self.port_plug_out_changed = True + return [(fd_no_to_return, event_to_return)] + if not self.port_plug_in_changed: + if self.port_plug_in: + # return first fd registered with some made up event number 871 + fd_no_to_return = list(self.fds_dict.keys())[0] + fd = list(self.fds_dict[fd_no_to_return].keys())[0] + fd.set_file_int_content(1) + event_to_return = 871 + self.port_plug_in_changed = True + return [(fd_no_to_return, event_to_return)] + if 7 == self.poller_iteration_count: + # when feature is enabled, need to check for each port both power_good and hw_present sysfs for + # cmis non-flat memory cables + num_of_sysfs_to_check = self.num_of_ports if (not self.port_plug_out or not self.feature_enabled + or self.warm_reboot) else self.num_of_ports * 2 + for i in range(num_of_sysfs_to_check): + # when feature is enabled, power_good sysfs is also registered for cmis non-flat memory cables + # so each SW controlled port has 2 fds registered + port_to_test = i if not self.feature_enabled else int(i / 2) + assert self.modules_mgmt_thrd.sfp_port_dict_initial[port_to_test].port_num == port_to_test + assert self.modules_mgmt_thrd.sfp_port_dict_initial[ + port_to_test].initial_state == modules_mgmt.STATE_HW_NOT_PRESENT + if self.feature_enabled: + module_obj = self.modules_mgmt_thrd.fds_mapping_to_obj[list(self.fds_dict.keys())[i]][ + 'module_obj'] + assert module_obj.port_num == port_to_test + if not self.warm_reboot: + # in tests other than warm reboot it creates only SW control ports + if not self.port_plug_out: + assert module_obj.final_state == modules_mgmt.STATE_SW_CONTROL + else: + assert module_obj.final_state == modules_mgmt.STATE_HW_NOT_PRESENT + else: + if not self.port_plug_out: + assert module_obj.final_state == modules_mgmt.STATE_HW_PRESENT + # in warm reboot test with plug out plug in test creates only FW control ports + elif self.port_plug_out and self.port_plug_in: + assert module_obj.final_state == modules_mgmt.STATE_FW_CONTROL + else: + assert module_obj.final_state == modules_mgmt.STATE_HW_NOT_PRESENT + POLLER_EXECUTED = True + self.modules_mgmt_task_stopping_event.set() + if self.sleep_timeout: + time.sleep(timeout/1000) + return [] + + +class MockOpen: + + def __init__(self, name='', file_no=None, indep_mode_supported=True): + self.name = name + self.file_no = file_no + self.indep_mode_supported = indep_mode_supported + self.retint = None + self.curr = 0 + + def read(self): + if self.fileno() in [SAI_PROFILE_FD_FILENO]: + pass + else: + # if return value was changed, i.e. sysfs content changed from 1 to 0 to simulate plug out + if self.retint is not None: + return str(self.retint) + # return default values (can be changed per test) + else: + return mock_file_content[self.name] + + def readline(self): + # if trying to read sai profile file, according to fd fileno + if self.fileno() in [SAI_PROFILE_FD_FILENO]: + if self.indep_mode_supported: + return "SAI_INDEPENDENT_MODULE_MODE=1" + else: + return "" + else: + return mock_file_content[self.name] + + def fileno(self): + return self.file_no + + def seek(self, seek_val): + self.curr = seek_val + + def close(self): + pass + + def write(self, write_val): + self.set_file_int_content(write_val) + + def set_file_int_content(self, retint): + self.retint = str(retint) + mock_file_content[self.name] = str(retint) + + def __enter__(self): + return self + + def __exit__(self, filename, *args, **kwargs): + pass + +class MockPollerStopEvent: + + def __init__(self, modules_mgmt_task_stopping_event, modules_mgmt_thrd=None, num_of_ports=DEFAULT_NUM_OF_PORTS_3 + , feature_enabled=True, ports_connected=True, fw_controlled_ports=False, sleep_timeout=False): + self.fds_dict = {} + self.modules_mgmt_task_stopping_event = modules_mgmt_task_stopping_event + self.modules_mgmt_thrd = modules_mgmt_thrd + self.num_of_ports = num_of_ports + self.feature_enabled = feature_enabled + self.ports_connected = ports_connected + self.sleep_timeout = sleep_timeout + self.fw_controlled_ports = fw_controlled_ports + + def register(self, fd, attrs): + self.fds_dict[fd.fileno()] = 1 & attrs + assert fd.fileno() in self.fds_dict + + def poll(self, timeout=0): + assert len(self.modules_mgmt_thrd.sfp_port_dict_initial) == self.num_of_ports + assert self.modules_mgmt_thrd.is_supported_indep_mods_system == self.feature_enabled + global POLLER_EXECUTED + if self.num_of_ports > 0: + # when feature is enabled, need to check for each port both power_good and hw_present sysfs for + # cmis non-flat memory cables + ports_to_test = self.num_of_ports if (not self.feature_enabled or not self.ports_connected + or self.fw_controlled_ports) else self.num_of_ports * 2 + for i in range(ports_to_test): + # when feature is enabled, power_good sysfs is also registered for cmis non-flat memory cables + port_to_test = i if (not self.feature_enabled or not self.ports_connected + or self.fw_controlled_ports) else int(i / 2) + assert self.modules_mgmt_thrd.sfp_port_dict_initial[port_to_test].port_num == port_to_test + assert self.modules_mgmt_thrd.sfp_port_dict_initial[port_to_test].initial_state == modules_mgmt.STATE_HW_NOT_PRESENT + module_obj = self.modules_mgmt_thrd.fds_mapping_to_obj[list(self.fds_dict.keys())[i]]['module_obj'] + assert module_obj.port_num == port_to_test + if self.ports_connected: + if self.feature_enabled: + if self.fw_controlled_ports: + assert module_obj.final_state == modules_mgmt.STATE_FW_CONTROL + else: + assert module_obj.final_state == modules_mgmt.STATE_SW_CONTROL + else: + assert module_obj.final_state == modules_mgmt.STATE_HW_PRESENT + else: + assert module_obj.final_state == modules_mgmt.STATE_HW_NOT_PRESENT + POLLER_EXECUTED = True + else: + POLLER_EXECUTED = True + self.modules_mgmt_task_stopping_event.set() + if self.sleep_timeout: + time.sleep(timeout/1000) + return [] + + +def _mock_is_file_indep_mode_disabled_content(): + return { + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_HW_RESET: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL: True, + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0"): True, + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("1"): True, + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("2"): True, + '//usr/share/sonic/platform/ACS-MSN4700/sai.profile' : True + } + +mock_is_file_indep_mode_disabled_content = _mock_is_file_indep_mode_disabled_content() + +def mock_is_file_indep_mode_disabled(file_path, **kwargs): + return mock_is_file_indep_mode_disabled_content[file_path] + +def _mock_is_file_indep_mode_enabled_content(): + return { + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_HW_RESET: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT: True, + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL: True, + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("0"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("1"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("2"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("0"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("1"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("2"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("0"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("1"): True, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("2"): True, + '//usr/share/sonic/platform/ACS-MSN4700/sai.profile' : True + } + +mock_is_file_indep_mode_enabled_content = _mock_is_file_indep_mode_enabled_content() + + +def mock_is_file_indep_mode_enabled(file_path, **kwargs): + return mock_is_file_indep_mode_enabled_content[file_path] + + +def mock_read_int_from_file(filename, *args): + return_dict = { + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0") : 1, + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("1") : 1, + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("2") : 1 + } + + return return_dict[filename] + + +class MockXcvrEeprom(): + def __init__(self, is_flat_memory, mem_map): + self.is_flat_memory = is_flat_memory + self.mem_map = mem_map + + def is_cmis_api(self): + return self.is_cmis_api + + def is_flat_memory(self): + return self.is_flat_memory + + def read(self, field): + if consts.FLAT_MEM_FIELD == field: + return 0 if self.is_flat_memory else 1 + else: + return 0 + + +class MockXcvrapi: + def __init__(self, is_cmis_api=True, is_flat_memory_bool=False): + self.is_cmis_api = is_cmis_api + self.is_flat_memory_bool = is_flat_memory_bool + self.xcvr_eeprom = MagicMock(autospec=XcvrEeprom, return_value=MockXcvrEeprom(is_flat_memory_bool, CmisMemMap(CmisCodes))) + + def is_flat_memory(self): + return self.is_flat_memory_bool + + def xcvr_eeprom(self): + return self.xcvr_eeprom + + +class MockSFPxcvrapi: + def __init__(self, xcvr_api_is_cmis_api=True, xcvr_eeprom_is_flat_memory=False): + self.xcvr_api = Mock(spec=CmisApi(MockXcvrEeprom(False, CmisMemMap(CmisCodes))), return_value=MockXcvrapi(xcvr_api_is_cmis_api, xcvr_eeprom_is_flat_memory)) + self.xcvr_api_is_cmis_api = xcvr_api_is_cmis_api + self.xcvr_eeprom_is_flat_memory = xcvr_eeprom_is_flat_memory + self.xcvr_api.is_flat_memory = types.MethodType(self.is_flat_memory, self) + + def get_xcvr_api(self): + return self.xcvr_api + + def is_flat_memory(self, ref): + return self.xcvr_eeprom_is_flat_memory + + +def check_power_cap(port, module_sm_obj): + pass + +SAI_PROFILE_FD_FILENO = 99 + + +class TestModulesMgmt(unittest.TestCase): + """Test class to test modules_mgmt.py. The test cases covers: + 1. cables detection for 1 to 3 ports - feature disabled / enabled / poller + 2. cable disconnection - plug out + 3. cable reconnection - plug in + 4. warm reboot normal flow with FW ports + 5. warm reboot flow with FW ports plugged out + 6. warm reboot flow with FW ports plugged out and then plugged in (stays FW controlled, no SFP mock change) + 7. test 32 FW controlled (non cmis flat mem) cables powered off + 8. test 32 SW controlled (cmis active non flat mem) cables powered off + """ + + def _mock_sysfs_file_content(self): + return { + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE : "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD : "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON : "0", + modules_mgmt.SYSFS_INDEPENDENT_FD_HW_RESET : "", + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT : "48", + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL : "1", + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0") : "1", + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("1") : "1", + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("2") : "1", + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("0"): "0" + } + + def mock_open_builtin(self, file_name, feature_enabled=True): + return_dict = { + (modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0"), 'r') : MockOpen(modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0"), 100), + (modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("1"), 'r') : MockOpen(modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("1"), 101), + (modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("2"), 'r') : MockOpen(modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("2"), 102), + '//usr/share/sonic/platform/ACS-MSN4700/sai.profile' : MockOpen('//usr/share/sonic/platform/ACS-MSN4700/sai.profile' + , SAI_PROFILE_FD_FILENO, feature_enabled), + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0") : MockOpen(modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0"), 100), + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("1") : MockOpen(modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("1"), 101), + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("2") : MockOpen(modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("2"), 102), + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("0"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("0"), 0), + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("1"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("1"), 1), + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("2"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("2"), 2), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("0"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("0"), 200), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("1"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("1"), 201), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("2"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("2"), 202), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("0"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("0"), 300), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("1"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("1"), 301), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("2"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("2"), 302), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("0"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("0"), 500), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("1"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("1"), 501), + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("2"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("2"), 502), + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("0"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("0"), 602), + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("1"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("1"), 602), + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("2"): MockOpen(modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("2"), 602), + modules_mgmt.PROC_CMDLINE: MockOpen(modules_mgmt.PROC_CMDLINE, self.fd_number_by_fd_name_dict[modules_mgmt.PROC_CMDLINE]) + } + return return_dict[file_name] + + # side effects are used in mock when want to create different mocks per variable, i.e. here it's filename + # see below mock_open_new_side_effect_poller_test where returning a new MockOpen passing it the filename + def mock_open_new_side_effect_feature_disabled(self, filename, *args, **kwargs): + mock_context = MagicMock() + mock_context.__enter__.return_value = self.mock_open_builtin(filename, False) + mock_context.__exit__.return_value = False + return mock_context + + def mock_open_new_side_effect_feature_enabled(self, filename, *args, **kwargs): + mock_context = MagicMock() + mock_context.__enter__.return_value = self.mock_open_builtin(filename) + mock_context.__exit__.return_value = False + return mock_context + + def mock_open_new_side_effect_poller_test(self, filename, *args, **kwargs): + if filename in ['//usr/share/sonic/platform/ACS-MSN4700/sai.profile']: + mock_context = MagicMock() + mock_context.__enter__.return_value = MockOpen(filename, SAI_PROFILE_FD_FILENO) + mock_context.__exit__.return_value = False + return mock_context + else: + mock_context = MagicMock() + mock_open_new = MockOpen(filename, self.fd_number_by_fd_name_dict[filename]) + mock_context.return_value = mock_open_new + mock_context.__enter__.return_value = mock_open_new + mock_context.__exit__.return_value = False + if 'hw_present' in filename or 'power_on' in filename or 'freq' in filename or 'control' in filename: + return mock_context + else: + return mock_context.return_value + + def mock_open_new_side_effect_warm_reboot(self, filename, *args, **kwargs): + if filename in ['//usr/share/sonic/platform/ACS-MSN4700/sai.profile']: + mock_context = MagicMock() + mock_context.__enter__.return_value = MockOpen(filename, SAI_PROFILE_FD_FILENO) + mock_context.__exit__.return_value = False + return mock_context + else: + mock_open_new = MockOpen(filename, self.fd_number_by_fd_name_dict[filename]) + return mock_open_new + + def setUp(cls): + cls.modules_mgmt_task_stopping_event = threading.Event() + cls.modules_changes_queue = queue.Queue() + global POLLER_EXECUTED + POLLER_EXECUTED = False + # start modules_mgmt thread and the test in poller part + cls.modules_mgmt_thrd = ModulesMgmtTask(main_thread_stop_event=cls.modules_mgmt_task_stopping_event, + q=cls.modules_changes_queue) + cls.modules_mgmt_thrd.check_power_cap = check_power_cap + assert cls.modules_mgmt_thrd.sfp_port_dict_initial == {} + + @classmethod + def setup_class(cls): + os.environ["MLNX_PLATFORM_API_UNIT_TESTING"] = "1" + cls.fd_number_by_fd_name_dict = { + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("0") : 100, + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("1") : 101, + modules_mgmt.SYSFS_LEGACY_FD_PRESENCE.format("2") : 102, + '//usr/share/sonic/platform/ACS-MSN4700/sai.profile' : SAI_PROFILE_FD_FILENO, + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("0") : 0, + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("1") : 1, + modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format("2") : 2, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("0") : 200, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("1") : 201, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format("2") : 202, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("0") : 300, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("1") : 301, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format("2") : 302, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("0") : 500, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("1") : 501, + modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_LIMIT.format("2") : 502, + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("0") : 600, + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("1") : 601, + modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format("2") : 602, + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("0") : 700, + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("1") : 701, + modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("2") : 702, + modules_mgmt.PROC_CMDLINE : 800 + } + # mock the directory holding relevant sai.profile + device_info.get_paths_to_platform_and_hwsku_dirs = mock.MagicMock(return_value=('', '/usr/share/sonic/platform/ACS-MSN4700')) + + + @patch('sonic_platform.device_data.DeviceDataManager.get_sfp_count', MagicMock(return_value=DEFAULT_NUM_OF_PORTS_3)) + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_disabled)) + @patch('sonic_platform.utils.read_int_from_file', MagicMock(side_effect=mock_read_int_from_file)) + @patch('builtins.open', spec=open) + def test_mdf_all_ports_feature_disabled(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_feature_disabled + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_3 + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, feature_enabled=False))): + self.modules_mgmt_thrd.run() + + @patch('sonic_platform.device_data.DeviceDataManager.get_sfp_count', MagicMock(return_value=DEFAULT_NUM_OF_PORTS_3)) + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi())) + def test_mdf_all_ports_feature_enabled(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_feature_enabled + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_3 + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd))): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi())) + def test_modules_mgmt_poller_events_3_ports(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_poller_test + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_3) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_3 + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd))): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi())) + def test_modules_mgmt_poller_events_single_port(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_poller_test + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_1) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_1 + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports))): + #with patch('builtins.open', MagicMock(side_effect=self.mock_open_new_side_effect_poller_test)): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi(False, True))) + def test_modules_mgmt_normal_warm_reboot(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_warm_reboot + # mock /proc/cmdline with warm reboot boot type key value + mock_file_content[modules_mgmt.PROC_CMDLINE] = f'{modules_mgmt.CMDLINE_STR_TO_LOOK_FOR}{modules_mgmt.CMDLINE_VAL_TO_LOOK_FOR}' + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_1) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_1 + # set the port to start with FW controlled before warm reboot takes place + mock_file_content[modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("0")] = "0" + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPoller(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports, warm_reboot=True))): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi(False, True))) + def test_modules_mgmt_plug_out_fw_cable_after_warm_reboot(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_warm_reboot + # mock /proc/cmdline with warm reboot boot type key value + mock_file_content[modules_mgmt.PROC_CMDLINE] = f'{modules_mgmt.CMDLINE_STR_TO_LOOK_FOR}{modules_mgmt.CMDLINE_VAL_TO_LOOK_FOR}' + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_1) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_1 + + # set the port to start with FW controlled before warm reboot takes place + mock_file_content[modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("0")] = "0" + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPoller(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports, port_plug_out=True, warm_reboot=True))): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi(False, True))) + def test_modules_mgmt_plug_out_plug_in_fw_cable_after_warm_reboot(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_warm_reboot + # mock /proc/cmdline with warm reboot boot type key value + mock_file_content[modules_mgmt.PROC_CMDLINE] = f'{modules_mgmt.CMDLINE_STR_TO_LOOK_FOR}{modules_mgmt.CMDLINE_VAL_TO_LOOK_FOR}' + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_1) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_1 + + mock_file_content[modules_mgmt.SYSFS_INDEPENDENT_FD_FW_CONTROL.format("0")] = "0" + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPoller(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports, port_plug_out=True, warm_reboot=True, port_plug_in=True))): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi(False, True))) + def test_modules_mgmt_no_ports(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_poller_test + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=0) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == 0 + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports))): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi(False, True))) + def test_modules_mgmt_ports_disconnected(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_poller_test + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_3) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_3 + + # update hw_present sysfs with value of 0 for each port + for i in range(num_of_tested_ports): + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format(f"{i}") + mock_file_content[modules_sysfs] = "0" + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports, ports_connected=False))): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi(False, True))) + def test_modules_mgmt_bad_flows_port_disconnected(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_poller_test + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_1) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_1 + + # update hw_present sysfs with value of 0 for each port + for i in range(num_of_tested_ports): + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format(f"{i}") + mock_file_content[modules_sysfs] = "0" + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports, ports_connected=False))): + self.modules_mgmt_thrd.run() + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi(False, True))) + def test_modules_mgmt_bad_flows_power_good(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_poller_test + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_1) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_1 + + # update power_good sysfs with value of 0 for each port + for i in range(num_of_tested_ports): + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format(f"{i}") + mock_file_content[modules_sysfs] = "0" + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports, ports_connected=False))): + self.modules_mgmt_thrd.run() + for i in range(num_of_tested_ports): + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format(f"{i}") + mock_file_content[modules_sysfs] = "1" + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi(False, True))) + def test_modules_mgmt_bad_flows_ports_powered_off_fw_controlled(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_poller_test + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_32) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_32 + + # create or update different sysfs and is_file mocking with relevant value for each port + for i in range(num_of_tested_ports): + # mock power_on sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format(f"{i}") + mock_file_content[modules_sysfs] = "0" + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = 300 + i + # mock hw_presence sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format(f'{i}') + mock_file_content[modules_sysfs] = "1" + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = i + # mock power_good sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format(f'{i}') + mock_file_content[modules_sysfs] = "1" + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = 200 + i + # mock hw_reset sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_HW_RESET.format(f'{i}') + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = 400 + i + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports, fw_controlled_ports=True))): + self.modules_mgmt_thrd.run() + + # change power_on sysfs values back to the default ones + for i in range(num_of_tested_ports): + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format(f"{i}") + mock_file_content[modules_sysfs] = "1" + + @patch('os.path.isfile', MagicMock(side_effect=mock_is_file_indep_mode_enabled)) + @patch('builtins.open', spec=open) + @patch('sonic_platform.sfp.SFP', MagicMock(return_value=MockSFPxcvrapi())) + def test_modules_mgmt_bad_flows_ports_powered_off_sw_controlled(self, mock_open): + mock_open.side_effect = self.mock_open_new_side_effect_poller_test + DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=DEFAULT_NUM_OF_PORTS_32) + num_of_tested_ports = DeviceDataManager.get_sfp_count() + assert num_of_tested_ports == DEFAULT_NUM_OF_PORTS_32 + + # create or update different sysfs and is_file mocking with relevant value for each port + for i in range(num_of_tested_ports): + # mock power_on sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format(f"{i}") + mock_file_content[modules_sysfs] = "0" + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = 300 + i + # mock hw_presence sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_PRESENCE.format(f'{i}') + mock_file_content[modules_sysfs] = "1" + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = i + # mock power_good sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_GOOD.format(f'{i}') + mock_file_content[modules_sysfs] = "1" + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = 200 + i + # mock hw_reset sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_HW_RESET.format(f'{i}') + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = 400 + i + # mock frequency_support sysfs for all ports + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_FREQ_SUPPORT.format(f'{i}') + mock_file_content[modules_sysfs] = "0" + mock_is_file_indep_mode_enabled_content[modules_sysfs] = True + self.fd_number_by_fd_name_dict[modules_sysfs] = 600 + i + + # start modules_mgmt thread and the test in poller part + with patch('select.poll', MagicMock(return_value=MockPollerStopEvent(self.modules_mgmt_task_stopping_event + , self.modules_mgmt_thrd, num_of_tested_ports))): + self.modules_mgmt_thrd.run() + + # change power_on sysfs values back to the default ones + for i in range(num_of_tested_ports): + modules_sysfs = modules_mgmt.SYSFS_INDEPENDENT_FD_POWER_ON.format(f"{i}") + mock_file_content[modules_sysfs] = "1" + + def tearDown(cls): + mock_file_content[modules_mgmt.PROC_CMDLINE] = '' + cls.modules_mgmt_thrd = None + # a check that modules mgmt thread ran and got into the poller part where the tests here has all checks + assert POLLER_EXECUTED