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 <niteshkumar121411@gmail.com>
Co-authored-by: Paige Patton <64206430+paigerube14@users.noreply.github.com>
This commit is contained in:
Nitesh Singh
2026-06-04 16:38:15 -04:00
committed by GitHub
co-authored by Paige Patton
parent 3b52fc4d5a
commit 53a81e8e90
2 changed files with 82 additions and 0 deletions
@@ -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=""
@@ -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()