sonic-buildimage/platform/mellanox/mlnx-platform-api/tests/test_component.py
mssonicbld 75b7ec361c
[Mellanox] Add more unit test coverage for platform API (#15842) (#16137)
- Why I did it
Increase UT coverage for Nvidia platform API code

Work item tracking
Microsoft ADO (number only):

- How I did it
Focus on low coverage file:
1. component.py
2. watchdog.py
3. pcie.py

- How to verify it
Run the unit test, the coverage has been changed from 70% to 90%

Co-authored-by: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com>
2023-08-14 22:40:38 +08:00

520 lines
24 KiB
Python

#
# Copyright (c) 2023 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 pytest
import subprocess
import sys
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.chassis import Chassis
from sonic_platform.component import ComponentONIE, \
ComponentSSD, \
ComponentBIOS, \
ComponentBIOSSN2201, \
ComponentCPLD, \
ComponentCPLDSN2201, \
MPFAManager, \
ONIEUpdater, \
Component
from sonic_platform_base.component_base import FW_AUTO_INSTALLED, \
FW_AUTO_UPDATED, \
FW_AUTO_SCHEDULED, \
FW_AUTO_ERR_BOOT_TYPE, \
FW_AUTO_ERR_IMAGE, \
FW_AUTO_ERR_UNKNOWN
class TestComponent:
@mock.patch('sonic_platform.chassis.utils.is_host')
@mock.patch('sonic_platform.chassis.DeviceDataManager.get_cpld_component_list', mock.MagicMock(return_value=[]))
def test_chassis_component(self, mock_is_host):
mock_is_host.return_value = False
c = Chassis()
assert not c.get_all_components()
mock_is_host.return_value = True
component_list = c.get_all_components()
assert len(component_list) > 0
assert c.get_num_components() > 0
assert c.get_component(0) is not None
@mock.patch('sonic_platform.component.ComponentONIE._check_file_validity')
@mock.patch('sonic_platform.component.ONIEUpdater', mock.MagicMock())
def test_onie_component(self, mock_check_file):
c = ComponentONIE()
assert c.get_name() == 'ONIE'
assert c.get_description() == 'ONIE - Open Network Install Environment'
c.onie_updater.get_onie_version = mock.MagicMock(return_value='1.0')
assert c.get_firmware_version() == '1.0'
c.onie_updater.get_onie_firmware_info = mock.MagicMock(return_value={})
with pytest.raises(RuntimeError):
c.get_available_firmware_version('')
c.onie_updater.get_onie_firmware_info = mock.MagicMock(return_value={'image_version': '1.1'})
assert c.get_available_firmware_version('') == '1.1'
assert c.get_firmware_update_notification('') == \
'Immediate cold reboot is required to complete ONIE firmware update'
mock_check_file.return_value = False
assert not c.install_firmware('')
c.update_firmware('')
mock_check_file.return_value = True
c.onie_updater.update_firmware = mock.MagicMock()
assert c.install_firmware('')
c.onie_updater.update_firmware.side_effect = RuntimeError('')
assert not c.install_firmware('')
@mock.patch('sonic_platform.component.os.path.exists')
@mock.patch('sonic_platform.component.subprocess.check_call')
@mock.patch('sonic_platform.component.subprocess.check_output')
def test_ssd_component(self, mock_check_output, mock_check_call, mock_exists):
c = ComponentSSD()
firmware_info = [
'Firmware Version:1.0',
'Available Firmware Version:1.1',
'Upgrade Required:yes',
'Power Cycle Required:yes'
]
mock_check_output.return_value = '\n'.join(firmware_info)
assert c.get_firmware_version() == '1.0'
assert c.get_available_firmware_version('') == '1.1'
assert c.get_firmware_update_notification('') == \
'Immediate power cycle is required to complete SSD firmware update'
mock_check_output.return_value = ''
with pytest.raises(RuntimeError):
c.get_firmware_version()
with pytest.raises(RuntimeError):
c.get_available_firmware_version('')
mock_check_output.return_value = 'Upgrade Required:invalid'
with pytest.raises(RuntimeError):
c.get_available_firmware_version('')
with pytest.raises(RuntimeError):
c.get_firmware_update_notification('')
mock_check_output.return_value = 'Upgrade Required:no'
with pytest.raises(RuntimeError):
c.get_available_firmware_version('')
assert c.get_firmware_update_notification('') is None
mock_check_output.return_value = 'Upgrade Required:yes'
with pytest.raises(RuntimeError):
c.get_available_firmware_version('')
firmware_info = [
'Power Cycle Required:invalid',
'Upgrade Required:yes'
]
mock_check_output.return_value = '\n'.join(firmware_info)
with pytest.raises(RuntimeError):
c.get_firmware_update_notification('')
firmware_info = [
'Firmware Version:1.0',
'Upgrade Required:yes'
]
mock_check_output.side_effect = subprocess.CalledProcessError(1, None)
with pytest.raises(RuntimeError):
c.get_firmware_version()
with pytest.raises(RuntimeError):
c.get_available_firmware_version('')
with pytest.raises(RuntimeError):
c.get_firmware_update_notification('')
# install firmware
c._check_file_validity = mock.MagicMock(return_value=False)
assert not c.install_firmware('')
c.update_firmware('')
c._check_file_validity = mock.MagicMock(return_value=True)
assert c.install_firmware('')
mock_check_call.assert_called_with(c.SSD_FIRMWARE_UPDATE_COMMAND, universal_newlines=True)
assert c.install_firmware('', False)
mock_check_call.assert_called_with(c.SSD_FIRMWARE_INSTALL_COMMAND, universal_newlines=True)
mock_check_call.side_effect = subprocess.CalledProcessError(1, None)
assert not c.install_firmware('')
# auto update firmware
mock_exists.return_value = False
assert c.auto_update_firmware('', '') == FW_AUTO_ERR_IMAGE
c.get_firmware_update_notification = mock.MagicMock(side_effect=RuntimeError(''))
mock_exists.return_value = True
assert c.auto_update_firmware('', '') == FW_AUTO_ERR_UNKNOWN
c.update_firmware = mock.MagicMock()
c.get_firmware_update_notification = mock.MagicMock(return_value=None)
assert c.auto_update_firmware('', '') == FW_AUTO_UPDATED
c.get_firmware_update_notification = mock.MagicMock(return_value='yes')
assert c.auto_update_firmware('', '') == FW_AUTO_ERR_BOOT_TYPE
assert c.auto_update_firmware('', 'cold') == FW_AUTO_SCHEDULED
@mock.patch('sonic_platform.component.subprocess.check_output')
def test_bios_component(self, mock_check_output):
c = ComponentBIOS()
mock_check_output.return_value = '1.0'
assert c.get_firmware_version() == '1.0'
mock_check_output.side_effect = subprocess.CalledProcessError(1, None)
with pytest.raises(RuntimeError):
c.get_firmware_version()
with pytest.raises(RuntimeError):
c.get_available_firmware_version('')
assert c.get_firmware_update_notification('') == \
'Immediate cold reboot is required to complete BIOS firmware update'
c.onie_updater.is_non_onie_firmware_update_supported = mock.MagicMock(return_value=False)
assert not c.install_firmware('')
c.update_firmware('')
c.onie_updater.is_non_onie_firmware_update_supported = mock.MagicMock(return_value=True)
c._check_file_validity = mock.MagicMock(return_value=False)
assert not c.install_firmware('')
c._check_file_validity = mock.MagicMock(return_value=True)
c.onie_updater.update_firmware = mock.MagicMock()
assert c.install_firmware('')
c.onie_updater.update_firmware = mock.MagicMock(side_effect=RuntimeError(''))
assert not c.install_firmware('')
@mock.patch('sonic_platform.component.subprocess.check_output')
def test_bios_2201_component(self, mock_check_output):
c = ComponentBIOSSN2201()
mock_check_output.return_value = 'Version: 1.0'
assert c.get_firmware_version() == '1.0'
mock_check_output.return_value = ''
assert c.get_firmware_version() == 'Unknown version'
mock_check_output.side_effect = subprocess.CalledProcessError(1, None)
with pytest.raises(RuntimeError):
c.get_firmware_version()
@mock.patch('sonic_platform.component.MPFAManager.cleanup', mock.MagicMock())
@mock.patch('sonic_platform.component.MPFAManager.extract', mock.MagicMock())
@mock.patch('sonic_platform.component.subprocess.check_call')
@mock.patch('sonic_platform.component.MPFAManager.get_path')
@mock.patch('sonic_platform.component.MPFAManager.get_metadata')
@mock.patch('sonic_platform.component.os.path.exists')
def test_cpld_component(self, mock_exists, mock_get_meta_data, mock_get_path, mock_check_call):
c = ComponentCPLD(1)
c._read_generic_file = mock.MagicMock(side_effect=[None, '1', None])
assert c.get_firmware_version() == 'CPLD000000_REV0100'
assert c.get_firmware_update_notification('a.txt') == \
'Immediate power cycle is required to complete CPLD1 firmware update'
assert c.get_firmware_update_notification('a.vme') == \
'Power cycle (with 30 sec delay) or refresh image is required to complete CPLD1 firmware update'
mock_meta_data = mock.MagicMock()
mock_meta_data.has_option = mock.MagicMock(return_value=False)
mock_get_meta_data.return_value = mock_meta_data
with pytest.raises(RuntimeError):
c.get_available_firmware_version('')
mock_meta_data.has_option = mock.MagicMock(return_value=True)
mock_meta_data.get = mock.MagicMock(return_value='1.1')
assert c.get_available_firmware_version('') == '1.1'
c._check_file_validity = mock.MagicMock(return_value=False)
assert not c._install_firmware('')
c._check_file_validity = mock.MagicMock(return_value=True)
c._ComponentCPLD__get_mst_device = mock.MagicMock(return_value=None)
assert not c._install_firmware('')
c._ComponentCPLD__get_mst_device = mock.MagicMock(return_value='some dev')
assert c._install_firmware('')
mock_check_call.side_effect = subprocess.CalledProcessError(1, None)
assert not c._install_firmware('')
c._install_firmware = mock.MagicMock()
mock_meta_data.has_option = mock.MagicMock(return_value=False)
with pytest.raises(RuntimeError):
c.install_firmware('a.mpfa')
mock_get_path.return_value = '/tmp'
mock_meta_data.has_option = mock.MagicMock(return_value=True)
mock_meta_data.get = mock.MagicMock(return_value='some_firmware')
c.install_firmware('a.mpfa')
c._install_firmware.assert_called_with('/tmp/some_firmware')
c._install_firmware.reset_mock()
c.install_firmware('a.txt')
c._install_firmware.assert_called_with('a.txt')
mock_meta_data.has_option = mock.MagicMock(return_value=False)
with pytest.raises(RuntimeError):
c.update_firmware('a.mpfa')
mock_meta_data.has_option = mock.MagicMock(side_effect=[True, False])
with pytest.raises(RuntimeError):
c.update_firmware('a.mpfa')
mock_meta_data.has_option = mock.MagicMock(return_value=True)
mock_meta_data.get = mock.MagicMock(side_effect=['burn', 'refresh'])
c._install_firmware.reset_mock()
c.update_firmware('a.mpfa')
assert c._install_firmware.call_count == 2
c._install_firmware.reset_mock()
c._install_firmware.return_value = False
mock_meta_data.get = mock.MagicMock(side_effect=['burn', 'refresh'])
c.update_firmware('a.mpfa')
assert c._install_firmware.call_count == 1
mock_exists.return_value = False
assert c.auto_update_firmware('', '') == FW_AUTO_ERR_IMAGE
mock_exists.return_value = True
assert c.auto_update_firmware('', '') == FW_AUTO_ERR_BOOT_TYPE
c.install_firmware = mock.MagicMock(return_value=False)
assert c.auto_update_firmware('', 'cold') == FW_AUTO_ERR_UNKNOWN
c.install_firmware = mock.MagicMock(return_value=True)
assert c.auto_update_firmware('', 'cold') == FW_AUTO_SCHEDULED
@mock.patch('sonic_platform.component.ComponentCPLD._read_generic_file', mock.MagicMock(return_value='3'))
def test_cpld_get_component_list(self):
component_list = ComponentCPLD.get_component_list()
assert len(component_list) == 3
for index, item in enumerate(component_list):
assert item.name == 'CPLD{}'.format(index + 1)
def test_cpld_get_mst_device(self):
ComponentCPLD.MST_DEVICE_PATH = '/tmp/mst'
os.system('rm -rf /tmp/mst')
c = ComponentCPLD(1)
assert c._ComponentCPLD__get_mst_device() is None
os.makedirs(ComponentCPLD.MST_DEVICE_PATH)
assert c._ComponentCPLD__get_mst_device() is None
with open('/tmp/mst/mt0_pci_cr0', 'w+') as f:
f.write('dummy')
assert c._ComponentCPLD__get_mst_device() == '/tmp/mst/mt0_pci_cr0'
@mock.patch('sonic_platform.component.subprocess.check_call')
def test_cpld_2201_component(self, mock_check_call):
c = ComponentCPLDSN2201(1)
assert c._install_firmware('')
mock_check_call.side_effect = subprocess.CalledProcessError(1, None)
assert not c._install_firmware('')
@mock.patch('sonic_platform.component.MPFAManager.cleanup')
@mock.patch('sonic_platform.component.MPFAManager.extract')
def test_mpfa_manager_context(self, mock_extract, mock_cleanup):
with MPFAManager('some_path') as mpfa:
assert isinstance(mpfa, MPFAManager)
mock_extract.assert_called_once()
mock_cleanup.assert_not_called()
mock_cleanup.assert_called_once()
@mock.patch('sonic_platform.component.tempfile.mkdtemp', mock.MagicMock(return_value='/tmp/mpfa'))
@mock.patch('sonic_platform.component.subprocess.check_call', mock.MagicMock())
@mock.patch('sonic_platform.component.os.path.isfile')
def test_mpfa_manager_extract_cleanup(self, mock_isfile):
m = MPFAManager('some_path')
mock_isfile.return_value = False
with pytest.raises(RuntimeError):
m.extract()
mock_isfile.return_value = True
with pytest.raises(RuntimeError):
m.extract()
m = MPFAManager('some_path.mpfa')
mock_isfile.side_effect = [True, False]
with pytest.raises(RuntimeError):
m.extract()
mock_isfile.side_effect = None
os.makedirs('/tmp/mpfa', exist_ok=True)
with open('/tmp/mpfa/metadata.ini', 'w+') as f:
f.write('[section1]\n')
f.write('a=b')
m = MPFAManager('some_path.mpfa')
m.extract()
assert m.get_path() == '/tmp/mpfa'
assert m.is_extracted()
assert m.get_metadata() is not None
# extract twice and verify no issue
m.extract()
m.cleanup()
assert m.get_path() is None
assert not m.is_extracted()
assert m.get_metadata() is None
def test_onie_updater_parse_onie_version(self):
o = ONIEUpdater()
onie_year, onie_month, onie_major, onie_minor, onie_release, onie_baudrate = \
o.parse_onie_version('2022.08-5.3.0010-9600')
assert onie_year == '2022'
assert onie_month == '08'
assert onie_major == '5'
assert onie_minor == '3'
assert onie_release == '0010'
assert onie_baudrate == '9600'
with pytest.raises(RuntimeError):
o.parse_onie_version('invalid', is_base=True)
with pytest.raises(RuntimeError):
o.parse_onie_version('invalid', is_base=False)
onie_year, onie_month, onie_major, onie_minor, onie_release, onie_baudrate = \
o.parse_onie_version('2022.08-5.3.0010-9600', is_base=True)
assert onie_year is None
assert onie_month is None
assert onie_major == '5'
assert onie_minor == '3'
assert onie_release == '0010'
assert onie_baudrate is None
assert o.get_onie_required_version() == o.ONIE_VERSION_REQUIRED
@mock.patch('sonic_platform.component.ONIEUpdater.get_onie_version')
@mock.patch('sonic_platform.component.device_info.get_platform')
def test_onie_updater_is_non_onie_firmware_update_supported(self, mock_platform, mock_version):
mock_platform.return_value = 'x86_64-nvidia_sn5600-r0'
o = ONIEUpdater()
mock_version.return_value = '2022.08-5.3.0010-9600'
assert o.is_non_onie_firmware_update_supported()
mock_version.return_value = '2022.08-5.1.0010-9600'
assert not o.is_non_onie_firmware_update_supported()
mock_platform.return_value = 'x86_64-nvidia_sn2201-r0'
o = ONIEUpdater()
assert o.is_non_onie_firmware_update_supported()
def test_onie_updater_get_onie_version(self):
o = ONIEUpdater()
o._ONIEUpdater__mount_onie_fs = mock.MagicMock()
o._ONIEUpdater__umount_onie_fs = mock.MagicMock()
mock_os_open = mock.mock_open(read_data='')
with mock.patch('sonic_platform.component.open', mock_os_open):
with pytest.raises(RuntimeError):
o.get_onie_version()
o._ONIEUpdater__umount_onie_fs.assert_called_once()
mock_os_open = mock.mock_open(read_data='onie_version')
with mock.patch('sonic_platform.component.open', mock_os_open):
with pytest.raises(RuntimeError):
o.get_onie_version()
mock_os_open = mock.mock_open(read_data='onie_version=2022.08-5.1.0010-9600')
with mock.patch('sonic_platform.component.open', mock_os_open):
assert o.get_onie_version() == '2022.08-5.1.0010-9600'
@mock.patch('sonic_platform.component.subprocess.check_output')
def test_onie_updater_get_onie_firmware_info(self, mock_check_output):
o = ONIEUpdater()
o._ONIEUpdater__mount_onie_fs = mock.MagicMock()
o._ONIEUpdater__umount_onie_fs = mock.MagicMock()
mock_check_output.return_value = 'a'
with pytest.raises(RuntimeError):
o.get_onie_firmware_info('')
o._ONIEUpdater__umount_onie_fs.assert_called_once()
mock_check_output.return_value = 'a=b'
fi = o.get_onie_firmware_info('')
assert fi == {'a':'b'}
mock_check_output.side_effect = subprocess.CalledProcessError(1, None)
with pytest.raises(RuntimeError):
o.get_onie_firmware_info('')
def test_onie_updater_update_firmware(self):
o = ONIEUpdater()
o._ONIEUpdater__stage_update = mock.MagicMock()
o._ONIEUpdater__trigger_update = mock.MagicMock()
o._ONIEUpdater__is_update_staged = mock.MagicMock()
o._ONIEUpdater__unstage_update = mock.MagicMock()
o.update_firmware('')
o._ONIEUpdater__stage_update.assert_called_once()
o._ONIEUpdater__trigger_update.assert_called_once()
o._ONIEUpdater__is_update_staged.assert_not_called()
o._ONIEUpdater__unstage_update.assert_not_called()
o._ONIEUpdater__is_update_staged.return_value = False
o._ONIEUpdater__stage_update.side_effect = RuntimeError('')
with pytest.raises(RuntimeError):
o.update_firmware('')
o._ONIEUpdater__unstage_update.assert_not_called()
o._ONIEUpdater__is_update_staged.return_value = True
with pytest.raises(RuntimeError):
o.update_firmware('')
o._ONIEUpdater__unstage_update.assert_called_once()
@mock.patch('sonic_platform.component.os.path.lexists')
@mock.patch('sonic_platform.component.os.path.exists', mock.MagicMock(return_value=False))
@mock.patch('sonic_platform.component.os.mkdir', mock.MagicMock())
@mock.patch('sonic_platform.component.subprocess.check_call', mock.MagicMock())
@mock.patch('sonic_platform.component.os.symlink', mock.MagicMock())
@mock.patch('sonic_platform.component.check_output_pipe', mock.MagicMock())
def test_onie_updater_mount_onie_fs(self, mock_lexists):
o = ONIEUpdater()
o._ONIEUpdater__umount_onie_fs = mock.MagicMock()
mock_lexists.return_value = False
mp = o._ONIEUpdater__mount_onie_fs()
assert mp == '/mnt/onie-fs'
o._ONIEUpdater__umount_onie_fs.assert_not_called()
mock_lexists.return_value = True
o._ONIEUpdater__mount_onie_fs()
o._ONIEUpdater__umount_onie_fs.assert_called_once()
@mock.patch('sonic_platform.component.os.rmdir')
@mock.patch('sonic_platform.component.os.path.exists', mock.MagicMock(return_value=True))
@mock.patch('sonic_platform.component.subprocess.check_call')
@mock.patch('sonic_platform.component.os.path.ismount', mock.MagicMock(return_value=True))
@mock.patch('sonic_platform.component.os.unlink')
@mock.patch('sonic_platform.component.os.path.islink', mock.MagicMock(return_value=True))
def test_onie_updater_umount_onie_fs(self, mock_unlink, mock_check_call, mock_rmdir):
o = ONIEUpdater()
o._ONIEUpdater__umount_onie_fs()
mock_unlink.assert_called_once()
mock_check_call.assert_called_once()
mock_rmdir.assert_called_once()
@mock.patch('sonic_platform.component.subprocess.check_output')
@mock.patch('sonic_platform.component.subprocess.check_call')
@mock.patch('sonic_platform.component.copyfile', mock.MagicMock())
def test_onie_updater_stage(self, mock_check_call, mock_check_output):
o = ONIEUpdater()
o._ONIEUpdater__stage_update('')
mock_check_call.assert_called_once()
mock_check_call.reset_mock()
o._ONIEUpdater__unstage_update('a.rom')
mock_check_call.assert_called_once()
mock_check_call.reset_mock()
o._ONIEUpdater__trigger_update(True)
mock_check_call.assert_called_with(o.ONIE_FW_UPDATE_CMD_UPDATE, universal_newlines=True)
mock_check_call.reset_mock()
o._ONIEUpdater__trigger_update(False)
mock_check_call.assert_called_with(o.ONIE_FW_UPDATE_CMD_INSTALL, universal_newlines=True)
mock_check_output.return_value = 'invalid/'
assert not o._ONIEUpdater__is_update_staged('')
mock_check_output.return_value = '00-'
assert o._ONIEUpdater__is_update_staged('')
mock_check_call.side_effect = subprocess.CalledProcessError(1, None)
with pytest.raises(RuntimeError):
o._ONIEUpdater__stage_update('')
with pytest.raises(RuntimeError):
o._ONIEUpdater__unstage_update('')
with pytest.raises(RuntimeError):
o._ONIEUpdater__trigger_update(True)
mock_check_output.side_effect = subprocess.CalledProcessError(1, None)
with pytest.raises(RuntimeError):
o._ONIEUpdater__is_update_staged('')
def test_read_generic_file(self):
with pytest.raises(RuntimeError):
Component._read_generic_file('invalid', 1)
content = Component._read_generic_file(os.path.abspath(__file__), 1)
assert content == '#'
def test_check_file_validity(self):
c = ComponentONIE()
assert not c._check_file_validity('invalid')
assert c._check_file_validity(os.path.abspath(__file__))
c.image_ext_name = '.py'
assert c._check_file_validity(os.path.abspath(__file__))
c.image_ext_name = '.txt'
assert not c._check_file_validity(os.path.abspath(__file__))