diff --git a/test/unittests/test_report_core.py b/test/unittests/test_report_core.py index dd6e842387..1cdcb442dc 100644 --- a/test/unittests/test_report_core.py +++ b/test/unittests/test_report_core.py @@ -1,4 +1,6 @@ +import subprocess from crmsh import config +import crmsh.report.sh from crmsh.report import core, constants, utils, collect import crmsh.log @@ -392,7 +394,7 @@ def test_process_results_no_compress(self, mock_analyze, mock_create, mock_move, mock_move.assert_called_once_with(mock_ctx_inst.work_dir, mock_ctx_inst.dest_dir) @mock.patch('crmsh.report.core.finalword') - @mock.patch('crmsh.report.core.sh.cluster_shell') + @mock.patch('crmsh.sh.cluster_shell') @mock.patch('crmsh.report.core.logger', spec=crmsh.log.DEBUG2Logger) @mock.patch('crmsh.report.utils.create_description_template') @mock.patch('crmsh.report.utils.analyze') @@ -438,7 +440,7 @@ def test_collect_logs_and_info(self, mock_pool, mock_cpu_count, mock_getmember, def test_collect_for_nodes(self, mock_start_collector, mock_info, mock_process): mock_ctx_inst = mock.Mock( node_list=["node1", "node2"], - ssh_askpw_node_list=["node2"], + passwordless_shell_for_nodes={"node1": mock.Mock(crmsh.report.sh.Shell)}, ssh_user="" ) mock_process_inst = mock.Mock() @@ -479,73 +481,52 @@ def test_process_arguments(self, mock_dest, mock_node_list): mock_ctx_inst = mock.Mock(from_time=123, to_time=150) core.process_arguments(mock_ctx_inst) - @mock.patch('crmsh.report.core.logger', spec=crmsh.log.DEBUG2Logger) - @mock.patch('crmsh.utils.check_ssh_passwd_need') - @mock.patch('crmsh.report.core.userdir.getuser') - @mock.patch('crmsh.report.core.userdir.get_sudoer') - def test_find_ssh_user_not_found(self, mock_get_sudoer, mock_getuser, mock_check_ssh, mock_logger): - mock_get_sudoer.return_value = "" - mock_getuser.return_value = "user2" - mock_check_ssh.return_value = True - mock_ctx_inst = mock.Mock(ssh_user="", ssh_askpw_node_list=[], node_list=["node1", "node2"], me="node1") - core.find_ssh_user(mock_ctx_inst) - mock_logger.warning.assert_called_once_with(f"passwordless ssh to node(s) ['node2'] does not work") - - @mock.patch('crmsh.report.core.logger', spec=crmsh.log.DEBUG2Logger) - @mock.patch('logging.Logger.warning') - @mock.patch('logging.Logger.debug') - @mock.patch('crmsh.utils.check_ssh_passwd_need') - @mock.patch('crmsh.utils.this_node') - @mock.patch('crmsh.report.core.userdir.getuser') - @mock.patch('crmsh.report.core.userdir.get_sudoer') - def test_find_ssh_user(self, mock_get_sudoer, mock_getuser, mock_this_node, mock_check_ssh, mock_debug, mock_warn, mock_debug2): - mock_get_sudoer.return_value = "user1" - mock_getuser.return_value = "user2" - mock_this_node.return_value = "node1" - mock_check_ssh.return_value = False - mock_ctx_inst = mock.Mock(ssh_user="", ssh_askpw_node_list=[], node_list=["node1", "node2"]) - core.find_ssh_user(mock_ctx_inst) - self.assertEqual("sudo", mock_ctx_inst.sudo) - self.assertEqual("user1", mock_ctx_inst.ssh_user) - @mock.patch('logging.Logger.warning') - @mock.patch('crmsh.report.core.ShellUtils') - def test_start_collector_return(self, mock_sh_utils, mock_warn): - mock_sh_utils_inst = mock.Mock() - mock_sh_utils.return_value = mock_sh_utils_inst - mock_sh_utils_inst.get_stdout_stderr.return_value = (0, '', None) - mock_ctx_inst = mock.Mock(me="node1") - core.start_collector("node1", mock_ctx_inst) - mock_sh_utils_inst.get_stdout_stderr.assert_called_once_with(f"{constants.BIN_COLLECTOR} '{mock_ctx_inst}'") - - @mock.patch('logging.Logger.warning') - @mock.patch('crmsh.report.core.ShellUtils') - @mock.patch('crmsh.report.core.sh.LocalShell') - @mock.patch('crmsh.utils.this_node') - def test_start_collector_warn(self, mock_this_node, mock_sh, mock_sh_utils, mock_warn): - mock_sh_utils_inst = mock.Mock() - mock_sh_utils.return_value = mock_sh_utils_inst - mock_sh_utils_inst.get_stdout = mock.Mock() - mock_sh_inst = mock.Mock() - mock_sh.return_value = mock_sh_inst - mock_sh_inst.get_rc_stdout_stderr.return_value = (1, '', "error") - mock_ctx_inst = mock.Mock(ssh_user='', sudo='') - mock_this_node.return_value = "node2" - core.start_collector("node1", mock_ctx_inst) - mock_warn.assert_called_once_with("error") + @mock.patch('crmsh.sh.ShellUtils.get_stdout') + @mock.patch('ast.literal_eval') + def test_start_collector(self, mock_literal_eval, mock_get_stdout): + mock_shell = mock.Mock(crmsh.report.sh.Shell) + mock_context = mock.Mock( + ssh_user=None, + passwordless_shell_for_nodes={'node1': mock_shell}, + __str__=mock.Mock(return_value='{"foo":"bar"}') + ) + mock_shell.subprocess_run_without_input.return_value = mock.Mock( + returncode=0, + stdout=b'', + stderr=b'', + ) + core.start_collector('node1', mock_context) + mock_shell.subprocess_run_without_input.assert_called_once_with( + """/usr/sbin/crm report __collector '{"foo":"bar"}'""", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + @mock.patch('crmsh.sh.ShellUtils.get_stdout') @mock.patch('ast.literal_eval') - @mock.patch('crmsh.report.core.sh.LocalShell') - @mock.patch('crmsh.report.core.ShellUtils') - @mock.patch('crmsh.utils.this_node') - def test_start_collector(self, mock_this_node, mock_sh_utils, mock_sh, mock_eval): - mock_sh_utils_inst = mock.Mock() - mock_sh_utils.return_value = mock_sh_utils_inst - mock_sh_utils_inst.get_stdout = mock.Mock() - mock_sh_inst = mock.Mock() - mock_sh.return_value = mock_sh_inst - mock_sh_inst.get_rc_stdout_stderr.return_value = (0, f"line1\n{constants.COMPRESS_DATA_FLAG}data", None) - mock_ctx_inst = mock.Mock(ssh_user='', sudo='') - mock_this_node.return_value = "node2" - mock_eval.return_value = "data" - core.start_collector("node1", mock_ctx_inst) + @mock.patch('logging.Logger.warning') + def test_start_collector_warn(self, mock_warning, mock_literal_eval, mock_get_stdout): + mock_shell = mock.Mock(crmsh.report.sh.Shell) + mock_context = mock.Mock( + ssh_user=None, + passwordless_shell_for_nodes={'node1': mock_shell}, + __str__=mock.Mock(return_value='{"foo":"bar"}') + ) + mock_shell.subprocess_run_without_input.return_value = mock.Mock( + returncode=1, + stdout=b'', + stderr=b'asdfgh', + ) + core.start_collector('node1', mock_context) + mock_shell.subprocess_run_without_input.assert_called_once_with( + """/usr/sbin/crm report __collector '{"foo":"bar"}'""", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + mock_warning.assert_called_with( + 'Failed to run collector on %s: %s: %s', + 'node1', 1, 'asdfgh', + ) + mock_literal_eval.assert_not_called() + mock_get_stdout.assert_not_called() diff --git a/test/unittests/test_report_sh.py b/test/unittests/test_report_sh.py new file mode 100644 index 0000000000..26a9e5e3d2 --- /dev/null +++ b/test/unittests/test_report_sh.py @@ -0,0 +1,87 @@ +import unittest +from unittest import mock + +import crmsh.report.sh +import crmsh.sh + +import subprocess + + +class TestFindShell(unittest.TestCase): + def setUp(self) -> None: + self.mock_cluster_shell = mock.Mock(crmsh.sh.ClusterShell) + self.patcher_local_shell = mock.patch('crmsh.report.sh.Shell.local_shell') + self.patcher_try_create_report_shell = mock.patch('crmsh.report.sh.Shell._try_create_report_shell') + self.mock_local_shell = self.patcher_local_shell.start() + self.mock_local_shell.return_value = mock.Mock(crmsh.sh.LocalShell) + self.mock_try_create_report_shell = self.patcher_try_create_report_shell.start() + + def tearDown(self) -> None: + self.patcher_local_shell.stop() + self.patcher_try_create_report_shell.stop() + + def test_cluster_shell_available(self): + self.mock_cluster_shell.can_run_as.return_value = True + self.assertIsInstance( + crmsh.report.sh.Shell.find_shell(self.mock_cluster_shell, 'node1', None), + crmsh.report.sh.ClusterShellAdaptor, + ) + self.assertIsInstance( + crmsh.report.sh.Shell.find_shell(self.mock_cluster_shell, 'node1', 'alice'), + crmsh.report.sh.ClusterShellAdaptor, + ) + + def test_specified_user_work(self): + self.mock_cluster_shell.can_run_as.return_value = False + self.mock_try_create_report_shell.return_value = mock.Mock(crmsh.report.sh.Shell) + ret = crmsh.report.sh.Shell.find_shell(self.mock_cluster_shell, 'node1', 'alice') + self.mock_local_shell.assert_called_once() + self.mock_try_create_report_shell.assert_called_once_with( + self.mock_local_shell.return_value, + 'node1', 'alice', + ) + self.assertIs(ret, self.mock_try_create_report_shell.return_value) + +class TestTryCreateReportShell(unittest.TestCase): + def setUp(self) -> None: + self.mock_local_shell = mock.Mock(crmsh.sh.LocalShell) + self.patcher_ssh_shell = mock.patch('crmsh.sh.SSHShell') + self.mock_ssh_shell = self.patcher_ssh_shell.start().return_value + + def tearDown(self) -> None: + self.patcher_ssh_shell.stop() + + def test_success(self): + self.mock_ssh_shell.can_run_as.return_value = True + self.mock_ssh_shell.subprocess_run_without_input.return_value = mock.Mock(returncode=0) + ret = crmsh.report.sh.Shell._try_create_report_shell(self.mock_local_shell, 'node1', 'alice') + self.mock_ssh_shell.can_run_as.assert_called_once_with('node1', 'alice') + self.mock_ssh_shell.subprocess_run_without_input.assert_called_once_with( + 'node1', 'alice', + 'sudo true', + start_new_session=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + self.assertIsInstance(ret, crmsh.report.sh.Shell) + + def test_failure_no_sudoer(self): + self.mock_ssh_shell.can_run_as.return_value = True + self.mock_ssh_shell.subprocess_run_without_input.return_value = mock.Mock(returncode=1) + ret = crmsh.report.sh.Shell._try_create_report_shell(self.mock_local_shell, 'node1', 'alice') + self.mock_ssh_shell.can_run_as.assert_called_once_with('node1', 'alice') + self.mock_ssh_shell.subprocess_run_without_input.assert_called_once_with( + 'node1', 'alice', + 'sudo true', + start_new_session=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + self.assertIsNone(ret) + + def test_failure_no_ssh(self): + self.mock_ssh_shell.can_run_as.return_value = False + ret = crmsh.report.sh.Shell._try_create_report_shell(self.mock_local_shell, 'node1', 'alice') + self.mock_ssh_shell.can_run_as.assert_called_once_with('node1', 'alice') + self.mock_ssh_shell.subprocess_run_without_input.assert_not_called() + self.assertIsNone(ret)