[Mellanox] Provide default implementation for sfp error description when CMIS host management is enabled (#17294)
- Why I did it Provide a dummy implementation for SFP error description when CMIS host management is enabled. A future feature shall be raised to implement SFP error description for such mode. - How I did it if SFP is under software control, provide "Not supported" as error description if SFP is under initialization, provide "Initializing" as error description - How to verify it unit test
This commit is contained in:
parent
48885b6ac9
commit
6d43d2f636
@ -234,3 +234,12 @@ class DeviceDataManager:
|
|||||||
# Currently, only fetching BIOS version is supported
|
# Currently, only fetching BIOS version is supported
|
||||||
return ComponentCPLDSN2201.get_component_list()
|
return ComponentCPLDSN2201.get_component_list()
|
||||||
return ComponentCPLD.get_component_list()
|
return ComponentCPLD.get_component_list()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@utils.read_only_cache()
|
||||||
|
def is_independent_mode(cls):
|
||||||
|
from sonic_py_common import device_info
|
||||||
|
_, hwsku_dir = device_info.get_paths_to_platform_and_hwsku_dirs()
|
||||||
|
sai_profile_file = os.path.join(hwsku_dir, 'sai.profile')
|
||||||
|
data = utils.read_key_value_file(sai_profile_file, delimeter='=')
|
||||||
|
return data.get('SAI_INDEPENDENT_MODULE_MODE') == '1'
|
||||||
|
@ -139,6 +139,7 @@ SFP_SYSFS_STATUS = 'status'
|
|||||||
SFP_SYSFS_STATUS_ERROR = 'statuserror'
|
SFP_SYSFS_STATUS_ERROR = 'statuserror'
|
||||||
SFP_SYSFS_PRESENT = 'present'
|
SFP_SYSFS_PRESENT = 'present'
|
||||||
SFP_SYSFS_RESET = 'reset'
|
SFP_SYSFS_RESET = 'reset'
|
||||||
|
SFP_SYSFS_HWRESET = 'hw_reset'
|
||||||
SFP_SYSFS_POWER_MODE = 'power_mode'
|
SFP_SYSFS_POWER_MODE = 'power_mode'
|
||||||
SFP_SYSFS_POWER_MODE_POLICY = 'power_mode_policy'
|
SFP_SYSFS_POWER_MODE_POLICY = 'power_mode_policy'
|
||||||
POWER_MODE_POLICY_HIGH = 1
|
POWER_MODE_POLICY_HIGH = 1
|
||||||
@ -638,6 +639,12 @@ class SFP(NvidiaSFPCommon):
|
|||||||
Returns:
|
Returns:
|
||||||
The error description
|
The error description
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
if self.is_sw_control():
|
||||||
|
return 'Not supported'
|
||||||
|
except:
|
||||||
|
return self.SFP_STATUS_INITIALIZING
|
||||||
|
|
||||||
oper_status, error_code = self._get_module_info(self.sdk_index)
|
oper_status, error_code = self._get_module_info(self.sdk_index)
|
||||||
if oper_status == SX_PORT_MODULE_STATUS_INITIALIZING:
|
if oper_status == SX_PORT_MODULE_STATUS_INITIALIZING:
|
||||||
error_description = self.SFP_STATUS_INITIALIZING
|
error_description = self.SFP_STATUS_INITIALIZING
|
||||||
@ -792,6 +799,21 @@ class SFP(NvidiaSFPCommon):
|
|||||||
self._xcvr_api.get_tx_fault = self.get_tx_fault
|
self._xcvr_api.get_tx_fault = self.get_tx_fault
|
||||||
return self._xcvr_api
|
return self._xcvr_api
|
||||||
|
|
||||||
|
def is_sw_control(self):
|
||||||
|
if not DeviceDataManager.is_independent_mode():
|
||||||
|
return False
|
||||||
|
|
||||||
|
db = utils.DbUtils.get_db_instance('STATE_DB')
|
||||||
|
control_type = db.get('STATE_DB', f'TRANSCEIVER_MODULES_MGMT|{self.sdk_index}', 'control_type')
|
||||||
|
control_file_value = utils.read_int_from_file(f'/sys/module/sx_core/asic0/module{self.sdk_index}/control')
|
||||||
|
|
||||||
|
if control_type == 'SW_CONTROL' and control_file_value == 1:
|
||||||
|
return True
|
||||||
|
elif control_type == 'FW_CONTROL' and control_file_value == 0:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
raise Exception(f'Module {self.sdk_index} is in initialization, please retry later')
|
||||||
|
|
||||||
|
|
||||||
class RJ45Port(NvidiaSFPCommon):
|
class RJ45Port(NvidiaSFPCommon):
|
||||||
"""class derived from SFP, representing RJ45 ports"""
|
"""class derived from SFP, representing RJ45 ports"""
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2020-2021 NVIDIA CORPORATION & AFFILIATES.
|
# Copyright (c) 2020-2023 NVIDIA CORPORATION & AFFILIATES.
|
||||||
# Apache-2.0
|
# Apache-2.0
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,6 +19,7 @@ import functools
|
|||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
from sonic_py_common import device_info
|
from sonic_py_common import device_info
|
||||||
@ -100,15 +101,15 @@ def read_float_from_file(file_path, default=0.0, raise_exception=False, log_func
|
|||||||
return read_from_file(file_path=file_path, target_type=float, default=default, raise_exception=raise_exception, log_func=log_func)
|
return read_from_file(file_path=file_path, target_type=float, default=default, raise_exception=raise_exception, log_func=log_func)
|
||||||
|
|
||||||
|
|
||||||
def _key_value_converter(content):
|
def _key_value_converter(content, delimeter):
|
||||||
ret = {}
|
ret = {}
|
||||||
for line in content.splitlines():
|
for line in content.splitlines():
|
||||||
k,v = line.split(':')
|
k,v = line.split(delimeter)
|
||||||
ret[k.strip()] = v.strip()
|
ret[k.strip()] = v.strip()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def read_key_value_file(file_path, default={}, raise_exception=False, log_func=logger.log_error):
|
def read_key_value_file(file_path, default={}, raise_exception=False, log_func=logger.log_error, delimeter=':'):
|
||||||
"""Read file content and parse the content to a dict. The file content should like:
|
"""Read file content and parse the content to a dict. The file content should like:
|
||||||
key1:value1
|
key1:value1
|
||||||
key2:value2
|
key2:value2
|
||||||
@ -119,7 +120,8 @@ def read_key_value_file(file_path, default={}, raise_exception=False, log_func=l
|
|||||||
raise_exception (bool, optional): If exception should be raised or hiden. Defaults to False.
|
raise_exception (bool, optional): If exception should be raised or hiden. Defaults to False.
|
||||||
log_func (optional): logger function.. Defaults to logger.log_error.
|
log_func (optional): logger function.. Defaults to logger.log_error.
|
||||||
"""
|
"""
|
||||||
return read_from_file(file_path=file_path, target_type=_key_value_converter, default=default, raise_exception=raise_exception, log_func=log_func)
|
converter = lambda content: _key_value_converter(content, delimeter)
|
||||||
|
return read_from_file(file_path=file_path, target_type=converter, default=default, raise_exception=raise_exception, log_func=log_func)
|
||||||
|
|
||||||
|
|
||||||
def write_file(file_path, content, raise_exception=False, log_func=logger.log_error):
|
def write_file(file_path, content, raise_exception=False, log_func=logger.log_error):
|
||||||
@ -285,3 +287,26 @@ def wait_until(predict, timeout, interval=1, *args, **kwargs):
|
|||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
timeout -= interval
|
timeout -= interval
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class DbUtils:
|
||||||
|
lock = threading.Lock()
|
||||||
|
db_instances = threading.local()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_db_instance(cls, db_name, **kargs):
|
||||||
|
try:
|
||||||
|
if not hasattr(cls.db_instances, 'data'):
|
||||||
|
with cls.lock:
|
||||||
|
if not hasattr(cls.db_instances, 'data'):
|
||||||
|
cls.db_instances.data = {}
|
||||||
|
|
||||||
|
if db_name not in cls.db_instances.data:
|
||||||
|
from swsscommon.swsscommon import SonicV2Connector
|
||||||
|
db = SonicV2Connector(use_unix_socket_path=True)
|
||||||
|
db.connect(db_name)
|
||||||
|
cls.db_instances.data[db_name] = db
|
||||||
|
return cls.db_instances.data[db_name]
|
||||||
|
except Exception as e:
|
||||||
|
logger.log_error(f'Failed to get DB instance for DB {db_name} - {e}')
|
||||||
|
raise e
|
||||||
|
@ -52,5 +52,14 @@ class TestDeviceData:
|
|||||||
def test_get_bios_component(self):
|
def test_get_bios_component(self):
|
||||||
assert DeviceDataManager.get_bios_component() is not None
|
assert DeviceDataManager.get_bios_component() is not None
|
||||||
|
|
||||||
|
@mock.patch('sonic_py_common.device_info.get_paths_to_platform_and_hwsku_dirs', mock.MagicMock(return_value=('', '/tmp')))
|
||||||
|
@mock.patch('sonic_platform.device_data.utils.read_key_value_file')
|
||||||
|
def test_is_independent_mode(self, mock_read):
|
||||||
|
mock_read.return_value = {}
|
||||||
|
assert not DeviceDataManager.is_independent_mode()
|
||||||
|
mock_read.return_value = {'SAI_INDEPENDENT_MODULE_MODE': '1'}
|
||||||
|
assert DeviceDataManager.is_independent_mode()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,17 +54,18 @@ class TestSfp:
|
|||||||
assert sfp.sdk_index == 1
|
assert sfp.sdk_index == 1
|
||||||
assert sfp.index == 5
|
assert sfp.index == 5
|
||||||
|
|
||||||
|
@mock.patch('sonic_platform.sfp.SFP.is_sw_control')
|
||||||
@mock.patch('sonic_platform.sfp.SFP.read_eeprom', mock.MagicMock(return_value=None))
|
@mock.patch('sonic_platform.sfp.SFP.read_eeprom', mock.MagicMock(return_value=None))
|
||||||
@mock.patch('sonic_platform.sfp.SFP.shared_sdk_handle', mock.MagicMock(return_value=2))
|
@mock.patch('sonic_platform.sfp.SFP.shared_sdk_handle', mock.MagicMock(return_value=2))
|
||||||
@mock.patch('sonic_platform.sfp.SFP._get_module_info')
|
@mock.patch('sonic_platform.sfp.SFP._get_module_info')
|
||||||
@mock.patch('sonic_platform.chassis.Chassis.get_num_sfps', mock.MagicMock(return_value=2))
|
@mock.patch('sonic_platform.chassis.Chassis.get_num_sfps', mock.MagicMock(return_value=2))
|
||||||
@mock.patch('sonic_platform.chassis.extract_RJ45_ports_index', mock.MagicMock(return_value=[]))
|
@mock.patch('sonic_platform.chassis.extract_RJ45_ports_index', mock.MagicMock(return_value=[]))
|
||||||
def test_sfp_get_error_status(self, mock_get_error_code):
|
def test_sfp_get_error_status(self, mock_get_error_code, mock_control):
|
||||||
chassis = Chassis()
|
chassis = Chassis()
|
||||||
|
|
||||||
# Fetch an SFP module to test
|
# Fetch an SFP module to test
|
||||||
sfp = chassis.get_sfp(1)
|
sfp = chassis.get_sfp(1)
|
||||||
|
mock_control.return_value = False
|
||||||
description_dict = sfp._get_error_description_dict()
|
description_dict = sfp._get_error_description_dict()
|
||||||
for error in description_dict.keys():
|
for error in description_dict.keys():
|
||||||
mock_get_error_code.return_value = (SX_PORT_MODULE_STATUS_PLUGGED_WITH_ERROR, error)
|
mock_get_error_code.return_value = (SX_PORT_MODULE_STATUS_PLUGGED_WITH_ERROR, error)
|
||||||
@ -88,6 +89,14 @@ class TestSfp:
|
|||||||
|
|
||||||
assert description == expected_description
|
assert description == expected_description
|
||||||
|
|
||||||
|
mock_control.return_value = True
|
||||||
|
description = sfp.get_error_description()
|
||||||
|
assert description == 'Not supported'
|
||||||
|
|
||||||
|
mock_control.side_effect = RuntimeError('')
|
||||||
|
description = sfp.get_error_description()
|
||||||
|
assert description == 'Initializing'
|
||||||
|
|
||||||
@mock.patch('sonic_platform.sfp.SFP._get_page_and_page_offset')
|
@mock.patch('sonic_platform.sfp.SFP._get_page_and_page_offset')
|
||||||
@mock.patch('sonic_platform.sfp.SFP._is_write_protected')
|
@mock.patch('sonic_platform.sfp.SFP._is_write_protected')
|
||||||
def test_sfp_write_eeprom(self, mock_limited_eeprom, mock_get_page):
|
def test_sfp_write_eeprom(self, mock_limited_eeprom, mock_get_page):
|
||||||
@ -278,6 +287,31 @@ class TestSfp:
|
|||||||
assert sfp.get_transceiver_threshold_info()
|
assert sfp.get_transceiver_threshold_info()
|
||||||
sfp.reinit()
|
sfp.reinit()
|
||||||
|
|
||||||
|
@mock.patch('sonic_platform.utils.read_int_from_file')
|
||||||
|
@mock.patch('sonic_platform.device_data.DeviceDataManager.is_independent_mode')
|
||||||
|
@mock.patch('sonic_platform.utils.DbUtils.get_db_instance')
|
||||||
|
def test_is_sw_control(self, mock_get_db, mock_mode, mock_read):
|
||||||
|
sfp = SFP(0)
|
||||||
|
mock_mode.return_value = False
|
||||||
|
assert not sfp.is_sw_control()
|
||||||
|
mock_mode.return_value = True
|
||||||
|
|
||||||
|
mock_db = mock.MagicMock()
|
||||||
|
mock_get_db.return_value = mock_db
|
||||||
|
mock_db.get = mock.MagicMock(return_value=None)
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
sfp.is_sw_control()
|
||||||
|
|
||||||
|
mock_read.return_value = 0
|
||||||
|
mock_db.get.return_value = 'FW_CONTROL'
|
||||||
|
assert not sfp.is_sw_control()
|
||||||
|
mock_read.return_value = 1
|
||||||
|
mock_db.get.return_value = 'SW_CONTROL'
|
||||||
|
assert sfp.is_sw_control()
|
||||||
|
mock_read.return_value = 0
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
sfp.is_sw_control()
|
||||||
|
|
||||||
@mock.patch('sonic_platform.utils.is_host', mock.MagicMock(side_effect = [True, True, False, False]))
|
@mock.patch('sonic_platform.utils.is_host', mock.MagicMock(side_effect = [True, True, False, False]))
|
||||||
@mock.patch('subprocess.check_output', mock.MagicMock(side_effect = ['True', 'False']))
|
@mock.patch('subprocess.check_output', mock.MagicMock(side_effect = ['True', 'False']))
|
||||||
@mock.patch('sonic_platform.sfp.SFP._get_lpmode', mock.MagicMock(side_effect = [True, False]))
|
@mock.patch('sonic_platform.sfp.SFP._get_lpmode', mock.MagicMock(side_effect = [True, False]))
|
||||||
|
@ -191,3 +191,6 @@ class TestUtils:
|
|||||||
mock_os_open = mock.mock_open(read_data='a:b')
|
mock_os_open = mock.mock_open(read_data='a:b')
|
||||||
with mock.patch('sonic_platform.utils.open', mock_os_open):
|
with mock.patch('sonic_platform.utils.open', mock_os_open):
|
||||||
assert utils.read_key_value_file('some_file') == {'a':'b'}
|
assert utils.read_key_value_file('some_file') == {'a':'b'}
|
||||||
|
mock_os_open = mock.mock_open(read_data='a=b')
|
||||||
|
with mock.patch('sonic_platform.utils.open', mock_os_open):
|
||||||
|
assert utils.read_key_value_file('some_file', delimeter='=') == {'a':'b'}
|
||||||
|
Loading…
Reference in New Issue
Block a user