diff --git a/fwutil/lib.py b/fwutil/lib.py index 95cced330e..865a748d50 100755 --- a/fwutil/lib.py +++ b/fwutil/lib.py @@ -197,6 +197,9 @@ def get_platform(self): def get_chassis(self): return self.__chassis + def is_smart_switch(self): + return self.__chassis.is_smartswitch() + def is_modular_chassis(self): return len(self.module_component_map) > 0 @@ -535,8 +538,8 @@ def __init__(self, root_path=None): os.mkdir(FIRMWARE_AU_STATUS_DIR) self.__root_path = root_path - - self.__pcp = PlatformComponentsParser(self.is_modular_chassis()) + is_modular_chassis = self.is_modular_chassis() and not self.is_smart_switch() + self.__pcp = PlatformComponentsParser(is_modular_chassis) self.__pcp.parse_platform_components(root_path) self.__validate_platform_schema(self.__pcp) @@ -547,6 +550,9 @@ def __diff_keys(self, keys1, keys2): def __validate_component_map(self, section, pdp_map, pcp_map): diff_keys = self.__diff_keys(list(pdp_map.keys()), list(pcp_map.keys())) + if diff_keys and section == self.SECTION_MODULE and self.is_smart_switch(): + return + if diff_keys: raise RuntimeError( "{} names mismatch: keys={}".format( diff --git a/scripts/generate_dump b/scripts/generate_dump index 287bfec0cd..59110b71a3 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -913,6 +913,65 @@ save_sys() { chmod ugo+rw -R $DUMPDIR/$BASE/sys } +save_sys_depth1() { + trap 'handle_error $? $LINENO' ERR + local src="$1" + local dest="$2" + shift 2 + # sys files we do not want to include in dump + local excludes=("$@") + + $MKDIR $V -p "$dest" + + should_skip() { + local base="$1" + for ex in "${excludes[@]}"; do + [[ "$base" == "$ex" ]] && return 0 + done + return 1 + } + + # Copy files directly under src + for f in "$src"/*; do + if [ -f "$f" ]; then + local base="$(basename "$f")" + should_skip "$base" && continue + local target="$dest/$base" + + if $NOOP; then + echo "cat $f > $target 2>/dev/null" + else + cat "$f" > "$target" 2>/dev/null + fi + fi + done + + # Copy sub folders + for d in "$src"/*; do + if [ -d "$d" ]; then + local subdir_dest="$dest/$(basename "$d")" + $MKDIR $V -p "$subdir_dest" + + # copy only files inside sub folder, not sub-sub folders + for f in "$d"/*; do + if [ -f "$f" ]; then + local base="$(basename "$f")" + should_skip "$base" && continue + local target="$subdir_dest/$base" + + if $NOOP; then + echo "cat $f > $target 2>/dev/null" + else + cat "$f" > "$target" 2>/dev/null + fi + fi + done + fi + done + + chmod ugo+rw -R "$dest" +} + ############################################################################### # Given list of pstore directories or files, saves subdirectories and files to tar. # Globals: @@ -1405,6 +1464,12 @@ collect_mellanox() { save_cmd "get_component_versions.py" "component_versions" + local sdk_sysfs_src_path="/sys/module/sx_core/asic0" + local sdk_sysfs_dest_path="$TARDIR/sdk_sysfs/sx_core/asic0" + local excludes_sysfs_files=(rx_los tx_disable module_info module_latched_flag_info power_mode power_mode_policy reinsert reset) + + save_sys_depth1 "$sdk_sysfs_src_path" "$sdk_sysfs_dest_path" "${excludes_sysfs_files[@]}" & + # Save CMIS-host-management related files local cmis_host_mgmt_path="cmis-host-mgmt" @@ -1419,6 +1484,8 @@ collect_mellanox() { fi save_cmd "show interfaces autoneg status" "autoneg.status" + + wait } ############################################################################### diff --git a/tests/fwutil_test.py b/tests/fwutil_test.py index 5dd68348b4..1dc1d27313 100644 --- a/tests/fwutil_test.py +++ b/tests/fwutil_test.py @@ -92,5 +92,154 @@ def test_is_capable_auto_update(self): assert CUProvider.is_capable_auto_update('none') == True assert CUProvider.is_capable_auto_update('def') == True + @patch('fwutil.lib.Platform') + @patch('fwutil.lib.PlatformComponentsParser') + @patch('fwutil.lib.ComponentUpdateProvider._ComponentUpdateProvider__validate_platform_schema') + @patch('os.path.isdir', return_value=True) + def test_is_smart_switch_method(self, mock_isdir, mock_validate, + mock_parser_class, mock_platform_class): + """Test that the is_smart_switch method correctly returns True + when the chassis.is_smartswitch() method returns True.""" + # Setup mock chassis + mock_chassis = MagicMock() + mock_chassis.is_smartswitch.return_value = True + + # Setup mock platform + mock_platform = MagicMock() + mock_platform.get_chassis.return_value = mock_chassis + mock_platform_class.return_value = mock_platform + + # Create ComponentUpdateProvider instance + cup = fwutil_lib.ComponentUpdateProvider() + + # Test is_smart_switch method + assert cup.is_smart_switch() + mock_chassis.is_smartswitch.assert_called_once() + + @patch('fwutil.lib.Platform') + @patch('fwutil.lib.PlatformComponentsParser') + @patch('fwutil.lib.ComponentUpdateProvider._ComponentUpdateProvider__validate_platform_schema') + @patch('os.mkdir') + def test_smartswitch_modular_chassis_parsing(self, mock_mkdir, mock_validate, + mock_parser_class, mock_platform_class): + """Test that SmartSwitch devices with modules are passed as non-modular (False) + to the PlatformComponentsParser constructor.""" + # Setup mock chassis that is SmartSwitch and has modules + mock_chassis = MagicMock() + mock_chassis.is_smartswitch.return_value = True + mock_chassis.get_all_modules.return_value = [MagicMock(), MagicMock()] # 2 modules + + # Setup mock platform + mock_platform = MagicMock() + mock_platform.get_chassis.return_value = mock_chassis + mock_platform_class.return_value = mock_platform + + # Setup mock parser + mock_parser = MagicMock() + mock_parser_class.return_value = mock_parser + + # Create ComponentUpdateProvider instance + fwutil_lib.ComponentUpdateProvider() + + # Verify that PlatformComponentsParser was called with is_modular_chassis=False + # because SmartSwitch should be treated as non-modular for parsing purposes + mock_parser_class.assert_called_once_with(False) + + @patch('fwutil.lib.Platform') + @patch('fwutil.lib.PlatformComponentsParser') + @patch('fwutil.lib.ComponentUpdateProvider._ComponentUpdateProvider__validate_platform_schema') + @patch('os.mkdir') + def test_regular_modular_chassis_parsing(self, mock_mkdir, mock_validate, mock_parser_class, mock_platform_class): + """Test that regular modular chassis is treated as modular for parsing""" + # Setup mock chassis that is not SmartSwitch but has modules + mock_chassis = MagicMock() + mock_chassis.is_smartswitch.return_value = False + mock_chassis.get_all_modules.return_value = [MagicMock(), MagicMock()] # 2 modules + + # Setup mock platform + mock_platform = MagicMock() + mock_platform.get_chassis.return_value = mock_chassis + mock_platform_class.return_value = mock_platform + + # Setup mock parser + mock_parser = MagicMock() + mock_parser_class.return_value = mock_parser + + # Create ComponentUpdateProvider instance + fwutil_lib.ComponentUpdateProvider() + + # Verify that PlatformComponentsParser was called with is_modular_chassis=True + # because regular modular chassis should be treated as modular + mock_parser_class.assert_called_once_with(True) + + @patch('fwutil.lib.Platform') + @patch('fwutil.lib.PlatformComponentsParser') + @patch('fwutil.lib.ComponentUpdateProvider._ComponentUpdateProvider__validate_platform_schema') + @patch('os.mkdir') + def test_smartswitch_module_validation_skip(self, mock_mkdir, mock_validate, + mock_parser_class, mock_platform_class): + """Test that module validation is skipped for SmartSwitch platforms""" + # Setup mock chassis that is SmartSwitch + mock_chassis = MagicMock() + mock_chassis.is_smartswitch.return_value = True + mock_chassis.get_all_modules.return_value = [MagicMock()] # Has modules + + # Setup mock platform + mock_platform = MagicMock() + mock_platform.get_chassis.return_value = mock_chassis + mock_platform_class.return_value = mock_platform + + # Setup mock parser + mock_parser = MagicMock() + mock_parser_class.return_value = mock_parser + + # Create ComponentUpdateProvider instance + cup = fwutil_lib.ComponentUpdateProvider() + + # Test that module validation is skipped for SmartSwitch + # This should not raise an exception even if there are differences + pdp_map = {'module1': {'comp1': MagicMock()}} + pcp_map = {'module2': {'comp2': MagicMock()}} # Different modules + + # Should not raise exception for SmartSwitch module validation + cup._ComponentUpdateProvider__validate_component_map( + cup.SECTION_MODULE, pdp_map, pcp_map + ) + + @patch('fwutil.lib.Platform') + @patch('fwutil.lib.PlatformComponentsParser') + @patch('fwutil.lib.ComponentUpdateProvider._ComponentUpdateProvider__validate_platform_schema') + @patch('os.mkdir') + def test_regular_chassis_module_validation_error(self, mock_mkdir, mock_validate, + mock_parser_class, mock_platform_class): + """Test that module validation raises error for regular modular chassis""" + # Setup mock chassis that is not SmartSwitch but has modules + mock_chassis = MagicMock() + mock_chassis.is_smartswitch.return_value = False + mock_chassis.get_all_modules.return_value = [MagicMock()] # Has modules + + # Setup mock platform + mock_platform = MagicMock() + mock_platform.get_chassis.return_value = mock_chassis + mock_platform_class.return_value = mock_platform + + # Setup mock parser + mock_parser = MagicMock() + mock_parser_class.return_value = mock_parser + + # Create ComponentUpdateProvider instance + cup = fwutil_lib.ComponentUpdateProvider() + + # Test that module validation raises error for regular modular chassis + pdp_map = {'module1': {'comp1': MagicMock()}} + pcp_map = {'module2': {'comp2': MagicMock()}} # Different modules + + # Should raise exception for regular modular chassis + with pytest.raises(RuntimeError) as excinfo: + cup._ComponentUpdateProvider__validate_component_map( + cup.SECTION_MODULE, pdp_map, pcp_map + ) + assert "Module names mismatch" in str(excinfo.value) + def teardown(self): print('TEARDOWN')