Skip to content

Commit

Permalink
[Mellanox] Implement low power mode for cmis host management
Browse files Browse the repository at this point in the history
  • Loading branch information
Junchao-Mellanox committed Nov 9, 2023
1 parent 357ab54 commit a1f8373
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,12 @@ def get_cpld_component_list(cls):
# Currently, only fetching BIOS version is supported
return ComponentCPLDSN2201.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'
32 changes: 32 additions & 0 deletions platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
SFP_SYSFS_STATUS_ERROR = 'statuserror'
SFP_SYSFS_PRESENT = 'present'
SFP_SYSFS_RESET = 'reset'
SFP_SYSFS_HWRESET = 'hw_reset'
SFP_SYSFS_POWER_MODE = 'power_mode'
SFP_SYSFS_POWER_MODE_POLICY = 'power_mode_policy'
POWER_MODE_POLICY_HIGH = 1
Expand Down Expand Up @@ -318,6 +319,13 @@ def get_lpmode(self):
Returns:
A Boolean, True if lpmode is enabled, False if disabled
"""
try:
if self.is_sw_control():
api = self.get_xcvr_api()
return api.get_lpmode() if api else False
except Exception as e:
print(e)
return False
file_path = SFP_SDK_MODULE_SYSFS_ROOT_TEMPLATE.format(self.sdk_index) + SFP_SYSFS_POWER_MODE
power_mode = utils.read_int_from_file(file_path)
return power_mode == POWER_MODE_LOW
Expand Down Expand Up @@ -345,6 +353,19 @@ def set_lpmode(self, lpmode):
Returns:
A boolean, True if lpmode is set successfully, False if not
"""
try:
if self.is_sw_control():
api = self.get_xcvr_api()
if not api:
return False
if api.get_lpmode() == lpmode:
return True
api.set_lpmode(lpmode)
return api.get_lpmode() == lpmode
except Exception as e:
print(e)
return False

print('\nNotice: please set port admin status to down before setting power mode, ignore this message if already set')
file_path = SFP_SDK_MODULE_SYSFS_ROOT_TEMPLATE.format(self.sdk_index) + SFP_SYSFS_POWER_MODE_POLICY
target_admin_mode = POWER_MODE_POLICY_AUTO if lpmode else POWER_MODE_POLICY_HIGH
Expand Down Expand Up @@ -541,6 +562,17 @@ def get_xcvr_api(self):
self._xcvr_api.get_tx_fault = self.get_tx_fault
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')
if not control_type:
raise Exception(f'Module {self.sdk_index} is in initialization, please retry later')

return control_type == 'SW_CONTROL'


class RJ45Port(NvidiaSFPCommon):
"""class derived from SFP, representing RJ45 ports"""
Expand Down
28 changes: 23 additions & 5 deletions platform/mellanox/mlnx-platform-api/sonic_platform/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2020-2021 NVIDIA CORPORATION & AFFILIATES.
# Copyright (c) 2020-2023 NVIDIA CORPORATION & AFFILIATES.
# Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -100,15 +100,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)


def _key_value_converter(content):
def _key_value_converter(content, delimeter):
ret = {}
for line in content.splitlines():
k,v = line.split(':')
k,v = line.split(delimeter)
ret[k.strip()] = v.strip()
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:
key1:value1
key2:value2
Expand All @@ -119,7 +119,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.
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):
Expand Down Expand Up @@ -285,3 +286,20 @@ def wait_until(predict, timeout, interval=1, *args, **kwargs):
time.sleep(interval)
timeout -= interval
return False


class DbUtils:
db_instances = {}

@classmethod
def get_db_instance(cls, db_name, **kargs):
try:
if db_name not in cls.db_instances:
from swsscommon.swsscommon import SonicV2Connector
db = SonicV2Connector(use_unix_socket_path=True)
db.connect(db_name)
cls.db_instances[db_name] = db
return cls.db_instances[db_name]
except Exception as e:
logger.log_error(f'Failed to get DB instance for DB {db_name} - {e}')
raise e
9 changes: 9 additions & 0 deletions platform/mellanox/mlnx-platform-api/tests/test_device_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,14 @@ def test_get_linecard_max_port_count(self):
def test_get_bios_component(self):
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()




55 changes: 53 additions & 2 deletions platform/mellanox/mlnx-platform-api/tests/test_sfp.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,27 +247,59 @@ def test_reset(self, mock_write):
assert sfp.reset()
mock_write.assert_called_with('/sys/module/sx_core/asic0/module0/reset', '1')

@mock.patch('sonic_platform.sfp.SFP.is_sw_control')
@mock.patch('sonic_platform.utils.read_int_from_file')
def test_get_lpmode(self, mock_read_int):
def test_get_lpmode(self, mock_read_int, mock_control):
sfp = SFP(0)
mock_control.return_value = False
mock_read_int.return_value = 1
assert sfp.get_lpmode()
mock_read_int.assert_called_with('/sys/module/sx_core/asic0/module0/power_mode')

mock_read_int.return_value = 2
assert not sfp.get_lpmode()

mock_control.return_value = True
sfp.get_xcvr_api = mock.MagicMock()
sfp.get_xcvr_api.return_value = None
assert not sfp.get_lpmode()
mock_api = mock.MagicMock()
mock_api.get_lpmode = mock.MagicMock(return_value=True)
sfp.get_xcvr_api.return_value = mock_api
assert sfp.get_lpmode()
mock_control.side_effect = Exception('')
assert not sfp.get_lpmode()

@mock.patch('sonic_platform.sfp.SFP.is_sw_control')
@mock.patch('sonic_platform.utils.write_file')
@mock.patch('sonic_platform.utils.read_int_from_file')
def test_set_lpmode(self, mock_read_int, mock_write):
def test_set_lpmode(self, mock_read_int, mock_write, mock_control):
sfp = SFP(0)
mock_control.return_value = False
mock_read_int.return_value = 1
assert sfp.set_lpmode(False)
assert mock_write.call_count == 0

assert sfp.set_lpmode(True)
mock_write.assert_called_with('/sys/module/sx_core/asic0/module0/power_mode_policy', '2')

mock_control.return_value = True
sfp.get_xcvr_api = mock.MagicMock()
sfp.get_xcvr_api.return_value = None
assert not sfp.set_lpmode(True)

mock_api = mock.MagicMock()
mock_api.get_lpmode = mock.MagicMock(return_value=True)
sfp.get_xcvr_api.return_value = mock_api
assert sfp.set_lpmode(True)

mock_api.get_lpmode.return_value = False
mock_api.set_lpmode = mock.MagicMock(return_value=True)
assert not sfp.set_lpmode(True)

mock_control.side_effect = Exception('')
assert not sfp.set_lpmode(False)

@mock.patch('sonic_platform.sfp.SFP.read_eeprom')
def test_get_xcvr_api(self, mock_read):
sfp = SFP(0)
Expand All @@ -289,3 +321,22 @@ def test_rj45_basic(self):
assert sfp.get_transceiver_bulk_status()
assert sfp.get_transceiver_threshold_info()
sfp.reinit()

@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):
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_db.get.return_value = 'FW_CONTROL'
assert not sfp.is_sw_control()
mock_db.get.return_value = 'SW_CONTROL'
assert sfp.is_sw_control()
3 changes: 3 additions & 0 deletions platform/mellanox/mlnx-platform-api/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,6 @@ def test_read_key_value_file(self):
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') == {'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'}

0 comments on commit a1f8373

Please sign in to comment.