From 53a81e8e90906a3bd4809f811f40f1ba2d5579e1 Mon Sep 17 00:00:00 2001 From: Nitesh Singh Date: Fri, 5 Jun 2026 02:08:15 +0530 Subject: [PATCH] feat: Add exec_with_shell_fallback method and fix failing unit tests (#1225) - Add exec_with_shell_fallback method with retry logic and shell fallback - Add unit tests for the new method with proper mocking - All tests now pass as expected Signed-off-by: NITESH SINGH Co-authored-by: Paige Patton <64206430+paigerube14@users.noreply.github.com> --- .../time_actions_scenario_plugin.py | 38 ++++++++++++++++ tests/test_time_actions_scenario_plugin.py | 44 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/krkn/scenario_plugins/time_actions/time_actions_scenario_plugin.py b/krkn/scenario_plugins/time_actions/time_actions_scenario_plugin.py index dd0d61aa..1e5aaf2e 100644 --- a/krkn/scenario_plugins/time_actions/time_actions_scenario_plugin.py +++ b/krkn/scenario_plugins/time_actions/time_actions_scenario_plugin.py @@ -77,6 +77,44 @@ class TimeActionsScenarioPlugin(AbstractScenarioPlugin): break return response + def exec_with_shell_fallback(self, command, pod_name, namespace, container_name, kubecli: KrknKubernetes, max_retries=3): + """ + Execute command in pod with shell fallback on failure. + + Args: + command: Command to execute (string or list) + pod_name: Name of the pod + namespace: Namespace of the pod + container_name: Name of the container + kubecli: Kubernetes client + max_retries: Maximum number of retries + + Returns: + Command output or False on persistent failure + """ + # Try direct execution first + for attempt in range(max_retries): + try: + response = kubecli.exec_cmd_in_pod(command, pod_name, namespace, container_name) + if response and not ("unauthorized" in response.lower() or "authorization" in response.lower()): + return response + except Exception: + pass + + # If direct execution fails, try with shell fallback + try: + shell_command = ["/bin/sh", "-c"] + (command if isinstance(command, list) else [command]) + response = kubecli.exec_cmd_in_pod(shell_command, pod_name, namespace, container_name) + if response and not ("unauthorized" in response.lower() or "authorization" in response.lower()): + return response + except Exception: + pass + + if attempt < max_retries - 1: + time.sleep(1) + + return False + # krkn_lib def get_container_name( self, pod_name, namespace, kubecli: KrknKubernetes, container_name="" diff --git a/tests/test_time_actions_scenario_plugin.py b/tests/test_time_actions_scenario_plugin.py index 650dad4d..a4ac4539 100644 --- a/tests/test_time_actions_scenario_plugin.py +++ b/tests/test_time_actions_scenario_plugin.py @@ -121,6 +121,50 @@ class TestTimeActionsScenarioPlugin(unittest.TestCase): # Assert failure is returned self.assertEqual(result, 1) + @patch('krkn.scenario_plugins.time_actions.time_actions_scenario_plugin.KrknKubernetes') + def test_exec_with_shell_fallback_detects_persistent_shell_error(self, mock_kubecli_class): + """Test that exec_with_shell_fallback returns False when both direct and shell execution fail persistently""" + mock_kubecli = MagicMock() + mock_kubecli_class.return_value = mock_kubecli + mock_kubecli.exec_cmd_in_pod.side_effect = Exception("Command failed") + + result = self.plugin.exec_with_shell_fallback( + "test", "test-pod", "default", "test-container", mock_kubecli + ) + + self.assertFalse(result) + + @patch('krkn.scenario_plugins.time_actions.time_actions_scenario_plugin.KrknKubernetes') + def test_exec_with_shell_fallback_fails_after_max_retries(self, mock_kubecli_class): + """Test that exec_with_shell_fallback returns False after exhausting all retries""" + mock_kubecli = MagicMock() + mock_kubecli_class.return_value = mock_kubecli + mock_kubecli.exec_cmd_in_pod.side_effect = Exception("Command failed") + + result = self.plugin.exec_with_shell_fallback( + "test", "test-pod", "default", "test-container", mock_kubecli, max_retries=2 + ) + + self.assertFalse(result) + + @patch('krkn.scenario_plugins.time_actions.time_actions_scenario_plugin.KrknKubernetes') + def test_exec_with_shell_fallback_retries_on_error(self, mock_kubecli_class): + """Test that exec_with_shell_fallback succeeds after retrying""" + mock_kubecli = MagicMock() + mock_kubecli_class.return_value = mock_kubecli + # First two calls fail, third succeeds + mock_kubecli.exec_cmd_in_pod.side_effect = [ + Exception("First failure"), + Exception("Second failure"), + "success" + ] + + result = self.plugin.exec_with_shell_fallback( + "test", "test-pod", "default", "test-container", mock_kubecli, max_retries=3 + ) + + self.assertEqual(result, "success") + if __name__ == "__main__": unittest.main()