From d835b2cae8abc71958b69362162e6a70c3d7ef63 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 08:48:59 +0200 Subject: Adding upstream version 4.6.0. Signed-off-by: Daniel Baumann --- test/unittests/test_report_collect.py | 588 ++++++++++++++++++++++++++++++++++ 1 file changed, 588 insertions(+) create mode 100644 test/unittests/test_report_collect.py (limited to 'test/unittests/test_report_collect.py') diff --git a/test/unittests/test_report_collect.py b/test/unittests/test_report_collect.py new file mode 100644 index 0000000..faec951 --- /dev/null +++ b/test/unittests/test_report_collect.py @@ -0,0 +1,588 @@ +from subprocess import TimeoutExpired +from crmsh.report import collect, constants +import crmsh.log + +import unittest +from unittest import mock + + +class TestCollect(unittest.TestCase): + + @mock.patch('logging.Logger.warning') + @mock.patch('os.path.isfile') + def test_get_pcmk_log_no_config(self, mock_isfile, mock_warning): + mock_isfile.side_effect = [False, False, False] + res = collect.get_pcmk_log() + self.assertEqual(res, "") + mock_isfile.assert_has_calls([ + mock.call(constants.PCMKCONF), + mock.call("/var/log/pacemaker/pacemaker.log"), + mock.call("/var/log/pacemaker.log") + ]) + mock_warning.assert_called_once_with("No valid pacemaker log file found") + + @mock.patch('logging.Logger.warning') + @mock.patch('crmsh.utils.read_from_file') + @mock.patch('os.path.isfile') + def test_get_pcmk_log(self, mock_isfile, mock_read, mock_warning): + mock_isfile.return_value = True + mock_read.return_value = """ +# has been enabled, those as well). This log is of more use to developers and +# advanced system administrators, and when reporting problems. +PCMK_logfile=/var/log/pacemaker/pacemaker.log + +# Set the permissions on the above log file to owner/group read/write + """ + res = collect.get_pcmk_log() + self.assertEqual(res, "/var/log/pacemaker/pacemaker.log") + mock_isfile.assert_has_calls([ + mock.call(constants.PCMKCONF), + mock.call("/var/log/pacemaker/pacemaker.log") + ]) + mock_read.assert_called_once_with(constants.PCMKCONF) + + @mock.patch('crmsh.report.utils.dump_logset') + @mock.patch('os.path.isfile') + @mock.patch('crmsh.report.collect.get_pcmk_log') + @mock.patch('crmsh.report.collect.get_corosync_log') + def test_collect_ha_logs(self, mock_corosync_log, mock_get_log, mock_isfile, mock_dump): + mock_corosync_log.return_value = "/var/log/cluster/corosync.log" + mock_get_log.return_value = "/var/pacemaker.log" + mock_isfile.side_effect = [True, True] + mock_ctx_inst = mock.Mock(extra_log_list=[]) + + collect.collect_ha_logs(mock_ctx_inst) + + mock_get_log.assert_called_once_with() + mock_isfile.assert_has_calls([ + mock.call(mock_get_log.return_value), + mock.call(mock_corosync_log.return_value) + ]) + mock_dump.assert_has_calls([ + mock.call(mock_ctx_inst, mock_get_log.return_value), + mock.call(mock_ctx_inst, mock_corosync_log.return_value) + ]) + + @mock.patch('logging.Logger.warning') + @mock.patch('os.path.exists') + @mock.patch('crmsh.corosync.conf') + def test_get_corosync_log_not_exist(self, mock_conf, mock_exists, mock_warning): + mock_conf.return_value = "/etc/corosync/corosync.conf" + mock_exists.return_value = False + self.assertEqual(collect.get_corosync_log(), "") + + @mock.patch('crmsh.corosync.get_value') + @mock.patch('os.path.exists') + @mock.patch('crmsh.corosync.conf') + def test_get_corosync_log(self, mock_conf, mock_exists, mock_get_value): + mock_conf.return_value = "/etc/corosync/corosync.conf" + mock_get_value.return_value = "/var/log/cluster/corosync.log" + mock_exists.return_value = True + self.assertEqual(collect.get_corosync_log(), mock_get_value.return_value) + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.utils.get_cmd_output') + @mock.patch('crmsh.report.utils.ts_to_str') + def test_collect_journal_logs(self, mock_ts_to_str, mock_get_cmd_output, + mock_str2file, mock_logger, mock_real_path): + mock_real_path.side_effect = [ + constants.JOURNAL_F, + constants.JOURNAL_PCMK_F, + constants.JOURNAL_COROSYNC_F, + constants.JOURNAL_SBD_F + ] + mock_ctx_inst = mock.Mock(from_time=1234, to_time=5678, work_dir="/opt/work") + mock_ts_to_str.side_effect = ["10.10", "10.12"] + mock_get_cmd_output.side_effect = ["data_default", "data_pacemaker", "data_corosync", "data_sbd"] + collect.collect_journal_logs(mock_ctx_inst) + mock_ts_to_str.assert_has_calls([ + mock.call(mock_ctx_inst.from_time), + mock.call(mock_ctx_inst.to_time) + ]) + cmd_list = [ + 'journalctl -o short-iso-precise --since "10.10" --until "10.12" --no-pager | tail -n +2', + 'journalctl -u pacemaker -o short-iso-precise --since "10.10" --until "10.12" --no-pager | tail -n +2', + 'journalctl -u corosync -o short-iso-precise --since "10.10" --until "10.12" --no-pager | tail -n +2', + 'journalctl -u sbd -o short-iso-precise --since "10.10" --until "10.12" --no-pager | tail -n +2' + ] + mock_get_cmd_output.assert_has_calls([ + mock.call(cmd_list[0]), + mock.call(cmd_list[1]), + mock.call(cmd_list[2]), + mock.call(cmd_list[3]), + ]) + mock_logger.debug2.assert_has_calls([ + mock.call("Collect journal logs since: 10.10 until: 10.12"), + mock.call(f"Running command: {cmd_list[0]}"), + mock.call(f"Running command: {cmd_list[1]}"), + mock.call(f"Running command: {cmd_list[2]}"), + mock.call(f"Running command: {cmd_list[3]}"), + ]) + mock_logger.debug.assert_has_calls([ + mock.call(f"Dump jounal log for default into {constants.JOURNAL_F}"), + mock.call(f"Dump jounal log for pacemaker into {constants.JOURNAL_PCMK_F}"), + mock.call(f"Dump jounal log for corosync into {constants.JOURNAL_COROSYNC_F}"), + mock.call(f"Dump jounal log for sbd into {constants.JOURNAL_SBD_F}") + ]) + + @mock.patch('crmsh.report.collect.ShellUtils') + def test_dump_D_process_empty(self, mock_run): + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_stderr.return_value = (0, None, None) + res = collect.dump_D_process() + self.assertEqual(res, "Dump D-state process stack: 0\n") + + @mock.patch('crmsh.report.collect.ShellUtils') + def test_dump_D_process(self, mock_run): + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_stderr.side_effect = [ + (0, "1000", None), + (0, "data1", None), + (0, "data2", None) + ] + res = collect.dump_D_process() + self.assertEqual(res, "Dump D-state process stack: 1\npid: 1000 comm: data1\ndata2\n\n") + mock_run_inst.get_stdout_stderr.assert_has_calls([ + mock.call("ps aux|awk '$8 ~ /^D/{print $2}'"), + mock.call('cat /proc/1000/comm'), + mock.call('cat /proc/1000/stack') + ]) + + @mock.patch('logging.Logger.debug') + @mock.patch('os.path.exists') + def test_collect_sbd_info_no_config(self, mock_exists, mock_debug): + mock_exists.return_value = False + mock_ctx_inst = mock.Mock() + collect.collect_sbd_info(mock_ctx_inst) + mock_exists.assert_called_once_with(constants.SBDCONF) + mock_debug.assert_called_once_with(f"SBD config file {constants.SBDCONF} does not exist") + + @mock.patch('shutil.which') + @mock.patch('shutil.copy2') + @mock.patch('os.path.exists') + def test_collect_sbd_info_no_cmd(self, mock_exists, mock_copy, mock_which): + mock_exists.return_value = True + mock_which.return_value = False + mock_ctx_inst = mock.Mock(work_dir="/opt") + collect.collect_sbd_info(mock_ctx_inst) + mock_exists.assert_called_once_with(constants.SBDCONF) + mock_copy.assert_called_once_with(constants.SBDCONF, mock_ctx_inst.work_dir) + mock_which.assert_called_once_with("sbd") + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('builtins.open', create=True) + @mock.patch('logging.Logger.debug') + @mock.patch('crmsh.report.utils.get_cmd_output') + @mock.patch('shutil.which') + @mock.patch('shutil.copy2') + @mock.patch('os.path.exists') + def test_collect_sbd_info(self, mock_exists, mock_copy, mock_which, mock_run, mock_debug, mock_open_file, mock_real_path): + mock_real_path.return_value = constants.SBD_F + mock_exists.return_value = True + mock_which.return_value = True + mock_open_write = mock.mock_open() + file_handle = mock_open_write.return_value.__enter__.return_value + mock_open_file.return_value = mock_open_write.return_value + mock_run.return_value = "data" + mock_ctx_inst = mock.Mock(work_dir="/opt") + + collect.collect_sbd_info(mock_ctx_inst) + + mock_exists.assert_called_once_with(constants.SBDCONF) + mock_copy.assert_called_once_with(constants.SBDCONF, mock_ctx_inst.work_dir) + mock_which.assert_called_once_with("sbd") + mock_open_file.assert_called_once_with(f"{mock_ctx_inst.work_dir}/{constants.SBD_F}", "w") + file_handle.write.assert_has_calls([ + mock.call("\n\n#=====[ Command ] ==========================#\n"), + mock.call("# . /etc/sysconfig/sbd;export SBD_DEVICE;sbd dump;sbd list\n"), + mock.call("data") + ]) + mock_debug.assert_called_once_with(f"Dump SBD config file into {constants.SBD_F}") + + @mock.patch('logging.Logger.warning') + @mock.patch('crmsh.report.collect.ShellUtils') + def test_pe_to_dot(self, mock_run, mock_warning): + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_stderr.return_value = (1, None, None) + collect.pe_to_dot("/opt/pe-input-0.bz2") + mock_run_inst.get_stdout_stderr.assert_called_once_with("crm_simulate -D /opt/pe-input-0.dot -x /opt/pe-input-0.bz2") + mock_warning.assert_called_once_with('pe_to_dot: %s -> %s failed', '/opt/pe-input-0.bz2', '/opt/pe-input-0.dot') + + @mock.patch('crmsh.report.utils.find_files_in_timespan') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + def test_collect_pe_inputs_no_found(self, mock_logger, mock_find_files): + mock_ctx_inst = mock.Mock(pe_dir="/opt/pe_dir") + mock_find_files.return_value = [] + collect.collect_pe_inputs(mock_ctx_inst) + mock_find_files.assert_called_once_with(mock_ctx_inst, [mock_ctx_inst.pe_dir]) + mock_logger.debug2.assert_has_calls([ + mock.call(f"Looking for PE files in {mock_ctx_inst.pe_dir}"), + mock.call("No PE file found for the giving time") + ]) + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.pe_to_dot') + @mock.patch('os.symlink') + @mock.patch('crmsh.utils.mkdirp') + @mock.patch('crmsh.report.utils.find_files_in_timespan') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + def test_collect_pe_inputs(self, mock_logger, mock_find_files, mock_mkdir, mock_symlink, mock_to_dot, mock_real_path): + mock_real_path.return_value = "pe_dir" + mock_ctx_inst = mock.Mock(pe_dir="/opt/pe_dir", work_dir="/opt/work_dir", speed_up=False) + mock_find_files.return_value = ["/opt/pe_dir/pe_input1", "/opt/pe_dir/pe_input2"] + + collect.collect_pe_inputs(mock_ctx_inst) + + mock_find_files.assert_called_once_with(mock_ctx_inst, [mock_ctx_inst.pe_dir]) + mock_logger.debug2.assert_has_calls([ + mock.call(f"Looking for PE files in {mock_ctx_inst.pe_dir}"), + mock.call(f"Found 2 PE files in {mock_ctx_inst.pe_dir}"), + ]) + mock_logger.debug.assert_called_once_with(f"Dump PE files into pe_dir") + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.utils.get_cmd_output') + def test_collect_sys_stats(self, mock_run, mock_str2file, mock_logger, mock_real_path): + mock_real_path.return_value = constants.SYSSTATS_F + mock_run.side_effect = [ + "data_hostname", "data_uptime", "data_ps_axf", "data_ps_auxw", + "data_top", "data_ip_addr", "data_ip_link", "data_ip_show", "data_iscsi", + "data_lspci", "data_mount", "data_cpuinfo", TimeoutExpired("df", 5) + ] + mock_ctx_inst = mock.Mock(work_dir="/opt") + collect.collect_sys_stats(mock_ctx_inst) + mock_logger.warning.assert_called_once_with(f"Timeout while running command: df") + mock_run.assert_has_calls([ + mock.call("hostname", timeout=5), + mock.call("uptime", timeout=5), + mock.call("ps axf", timeout=5), + mock.call("ps auxw", timeout=5), + mock.call("top -b -n 1", timeout=5), + mock.call("ip addr", timeout=5), + mock.call("ip -s link", timeout=5), + mock.call("ip n show", timeout=5), + mock.call("lsscsi", timeout=5), + mock.call("lspci", timeout=5), + mock.call("mount", timeout=5), + mock.call("cat /proc/cpuinfo", timeout=5), + mock.call("df", timeout=5) + ]) + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('logging.Logger.debug') + @mock.patch('crmsh.report.utils.get_distro_info') + @mock.patch('crmsh.utils.str2file') + @mock.patch('os.uname') + @mock.patch('crmsh.report.utils.Package') + def test_collect_sys_info(self, mock_package, mock_uname, mock_str2file, mock_get_distro, mock_debug, mock_real_path): + mock_real_path.return_value = constants.SYSINFO_F + mock_package_inst = mock.Mock() + mock_package.return_value = mock_package_inst + mock_package_inst.version = mock.Mock(return_value="version_data\n") + mock_package_inst.verify = mock.Mock(return_value="verify_data\n") + mock_ctx_inst = mock.Mock(speed_up=False, work_dir="/opt/work") + mock_uname.return_value = ("Linux", None, "4.5", None, "x86_64") + mock_get_distro.return_value = "suse" + + collect.collect_sys_info(mock_ctx_inst) + + mock_package.assert_called_once_with(constants.PACKAGES) + mock_str2file.assert_called_once_with('##### System info #####\nPlatform: Linux\nKernel release: 4.5\nArchitecture: x86_64\nDistribution: suse\n\n##### Installed cluster related packages #####\nversion_data\n\n\n##### Verification output of packages #####\nverify_data\n', '/opt/work/sysinfo.txt') + mock_debug.assert_called_once_with(f"Dump packages and platform info into {constants.SYSINFO_F}") + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.dump_configurations') + @mock.patch('crmsh.report.collect.consume_cib_in_workdir') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.collect.dump_runtime_state') + @mock.patch('crmsh.report.collect.ServiceManager') + def test_collect_config_running(self, mock_service, mock_dump_state, mock_write, mock_debug2, mock_cib, mock_dump_config, mock_real_path): + mock_real_path.return_value = "workdir" + mock_service_inst = mock.Mock() + mock_service.return_value = mock_service_inst + mock_service_inst.service_is_active.return_value = True + mock_ctx_inst = mock.Mock(work_dir="/opt/workdir") + collect.collect_config(mock_ctx_inst) + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.dump_configurations') + @mock.patch('crmsh.report.collect.consume_cib_in_workdir') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('shutil.copy2') + @mock.patch('crmsh.report.collect.ServiceManager') + def test_collect_config_stopped(self, mock_service, mock_copy2, mock_write, mock_debug2, mock_cib, mock_dump_config, mock_real_path): + mock_real_path.return_value = "workdir" + mock_service_inst = mock.Mock() + mock_service.return_value = mock_service_inst + mock_service_inst.service_is_active.return_value = False + mock_ctx_inst = mock.Mock(work_dir="/opt/workdir", cib_dir="/var/log/pacemaker/cib") + collect.collect_config(mock_ctx_inst) + + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.collect.sh.cluster_shell') + @mock.patch('os.path.isfile') + def test_consume_cib_in_workdir(self, mock_isfile, mock_run, mock_str2file): + mock_isfile.return_value = True + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_or_raise_error.side_effect = ["data1", "data2"] + collect.consume_cib_in_workdir("/workdir") + mock_isfile.assert_called_once_with(f"/workdir/{constants.CIB_F}") + mock_run_inst.get_stdout_or_raise_error.assert_has_calls([ + mock.call('CIB_file=/workdir/cib.xml crm configure show'), + mock.call('crm_verify -V -x /workdir/cib.xml') + ]) + mock_str2file.assert_has_calls([ + mock.call("data1", f"/workdir/{constants.CONFIGURE_SHOW_F}"), + mock.call("data2", f"/workdir/{constants.CRM_VERIFY_F}") + ]) + + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.report.collect.sh.cluster_shell') + def test_collect_ratraces_return(self, mock_run, mock_logger): + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_rc_stdout_stderr_without_input.return_value = (0, "data", None) + mock_ctx_inst = mock.Mock(node_list=["node1"]) + collect.collect_ratraces(mock_ctx_inst) + mock_logger.debug2.assert_not_called() + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('shutil.copy2') + @mock.patch('crmsh.utils.mkdirp') + @mock.patch('crmsh.report.utils.find_files_in_timespan') + @mock.patch('crmsh.report.collect.sh.cluster_shell') + def test_collect_ratraces(self, mock_run, mock_find, mock_mkdirp, mock_copy, mock_logger, mock_real_path): + mock_real_path.return_value = "/var/log" + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + data = "INFO: Trace for .* is written to /var/log/cluster/pacemaker.log" + mock_run_inst.get_rc_stdout_stderr_without_input.return_value = (0, data, None) + mock_ctx_inst = mock.Mock(node_list=["node1"], work_dir="/opt/work") + mock_find.return_value = ["/var/log/cluster"] + + collect.collect_ratraces(mock_ctx_inst) + + mock_logger.debug2.assert_called_once_with('Looking for RA trace files in "%s"', '/var/log/cluster') + mock_logger.debug.assert_called_once_with(f'Dump RA trace files into {mock_real_path.return_value}') + + @mock.patch('crmsh.report.collect.ShellUtils') + def test_lsof_ocfs2_device(self, mock_run): + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mount_data = """ +/dev/vda3 on /home type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota) +tmpfs on /run/user/0 type tmpfs (rw,nosuid,nodev,relatime,size=169544k,nr_inodes=42386,mode=700,inode64) +/dev/sda7 on /srv/clusterfs type ocfs2 (rw,relatime,heartbeat=non + """ + mock_run_inst.get_stdout_stderr.side_effect = [(0, mount_data, None), (0, "data", None)] + res = collect.lsof_ocfs2_device() + self.assertEqual(res, "\n\n#=====[ Command ] ==========================#\n# lsof /dev/sda7\ndata") + mock_run_inst.get_stdout_stderr.assert_has_calls([ + mock.call("mount"), + mock.call("lsof /dev/sda7") + ]) + + @mock.patch('crmsh.report.utils.get_cmd_output') + @mock.patch('os.path.exists') + @mock.patch('shutil.which') + def test_ocfs2_commands_output(self, mock_which, mock_exists, mock_run): + mock_which.side_effect = [False for i in range(5)] + [True, True] + mock_exists.return_value = False + mock_run.return_value = "data" + res = collect.ocfs2_commands_output() + self.assertEqual(res, "\n\n#===== [ Command ] ==========================#\n# mount\ndata") + + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.collect.ShellUtils') + def test_collect_ocfs2_info_error(self, mock_run, mock_str2file, mock_debug2): + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_stderr.return_value = (1, None, "error") + mock_ctx_inst = mock.Mock(work_dir="/opt/workdir") + collect.collect_ocfs2_info(mock_ctx_inst) + mock_str2file.assert_called_once_with('Failed to run "mounted.ocfs2 -d": error', '/opt/workdir/ocfs2.txt') + + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.collect.ShellUtils') + def test_collect_ocfs2_info_no_found(self, mock_run, mock_str2file, mock_debug2): + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_stderr.return_value = (0, "data", None) + mock_ctx_inst = mock.Mock(work_dir="/opt/workdir") + collect.collect_ocfs2_info(mock_ctx_inst) + mock_str2file.assert_called_once_with('No ocfs2 partitions found', '/opt/workdir/ocfs2.txt') + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.ocfs2_commands_output') + @mock.patch('crmsh.report.collect.lsof_ocfs2_device') + @mock.patch('crmsh.report.collect.dump_D_process') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.collect.ShellUtils') + def test_collect_ocfs2_info(self, mock_run, mock_str2file, mock_debug2, mock_D, mock_lsof, mock_output, mock_real_path): + mock_real_path.return_value = constants.OCFS2_F + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_stderr.return_value = (0, "line1\nline2", None) + mock_D.return_value = "data_D\n" + mock_lsof.return_value = "data_lsof\n" + mock_output.return_value = "data_output\n" + mock_ctx_inst = mock.Mock(work_dir="/opt/workdir") + collect.collect_ocfs2_info(mock_ctx_inst) + mock_str2file.assert_called_once_with('data_D\ndata_lsof\ndata_output\n', '/opt/workdir/ocfs2.txt') + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('logging.Logger.debug') + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.utils.get_cmd_output') + @mock.patch('shutil.which') + def test_collect_dlm_info(self, mock_which, mock_get_output, mock_str2file, mock_debug, mock_real_path): + mock_real_path.return_value = constants.DLM_DUMP_F + mock_which.return_value = True + ls_data = """ +dlm lockspaces +name 08BB5A6A38EE491DBF63627EEB57E558 +id 0x19041a12 + """ + mock_get_output.side_effect = [ls_data, "lockdebug data", "dump data"] + mock_ctx_inst = mock.Mock(work_dir="/opt/work_dir") + collect.collect_dlm_info(mock_ctx_inst) + mock_debug.assert_called_once_with(f"Dump DLM information into {constants.DLM_DUMP_F}") + + @mock.patch('crmsh.report.collect.dump_core_info') + @mock.patch('logging.Logger.warning') + @mock.patch('os.path.basename') + @mock.patch('crmsh.report.utils.find_files_in_timespan') + def test_collect_coredump_info(self, mock_find, mock_basename, mock_warning, mock_dump): + mock_ctx_inst = mock.Mock(cores_dir_list=['/var/lib/pacemaker/cores'], work_dir="/opt/work_dir") + mock_find.return_value = ["/var/lib/pacemaker/cores/core.1"] + mock_basename.return_value = "core.1" + collect.collect_coredump_info(mock_ctx_inst) + mock_dump.assert_called_once_with("/opt/work_dir", mock_find.return_value) + mock_warning.assert_called_once_with(f"Found coredump file: {mock_find.return_value}") + + @mock.patch('crmsh.report.collect.ShellUtils') + def test_find_binary_path_for_core_not_found(self, mock_run): + mock_run().get_stdout_stderr.return_value = (0, "Core not found", None) + res = collect.find_binary_path_for_core("core.1") + self.assertEqual("Cannot find the program path for core core.1", res) + + @mock.patch('crmsh.report.collect.ShellUtils') + def test_find_binary_path_for_core(self, mock_run): + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_stderr.return_value = (0, "Core was generated by `/usr/sbin/crm_mon'", None) + res = collect.find_binary_path_for_core("core.1") + self.assertEqual("Core core.1 was generated by /usr/sbin/crm_mon", res) + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('shutil.which') + def test_dump_core_info_no_gdb(self, mock_which, mock_str2file, mock_logger, mock_real_path): + mock_real_path.return_value = constants.COREDUMP_F + mock_which.return_value = False + collect.dump_core_info("/opt/workdir", ["core.1"]) + mock_logger.warning.assert_called_once_with("Please install gdb to get more info for coredump files") + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.report.collect.logger', spec=crmsh.log.DEBUG2Logger) + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.collect.find_binary_path_for_core') + @mock.patch('shutil.which') + def test_dump_core_info(self, mock_which, mock_find_binary, mock_str2file, mock_debug2, mock_real_path): + mock_real_path.return_value = constants.COREDUMP_F + mock_which.return_value = True + mock_find_binary.return_value = "data" + collect.dump_core_info("/opt/workdir", ["core.1"]) + mock_str2file.assert_called_once_with("data\n\nPlease utilize the gdb and debuginfo packages to obtain more detailed information locally", f"/opt/workdir/{constants.COREDUMP_F}") + mock_debug2(f"Dump coredump info into {constants.COREDUMP_F}") + + @mock.patch('crmsh.utils.str2file') + @mock.patch('pwd.getpwnam') + @mock.patch('os.stat') + @mock.patch('os.path.isdir') + def test_collect_perms_state(self, mock_isdir, mock_stat, mock_getpwnam, mock_str2file): + mock_ctx_inst = mock.Mock( + pcmk_lib_dir="/var/lib/pacemaker", + pe_dir="/var/lib/pacemaker/pe", + cib_dir="/var/lib/pacemaker/cib", + work_dir="/opt/work_dir" + ) + mock_isdir.side_effect = [False, True, True] + mock_stat_inst_pe = mock.Mock(st_uid=1000, st_gid=1000, st_mode=0o750) + mock_stat_inst_cib = mock.Mock(st_uid=1000, st_gid=1000, st_mode=0o750) + mock_stat.side_effect = [mock_stat_inst_pe, mock_stat_inst_cib] + mock_getpwnam_inst_pe = mock.Mock(pw_uid=1000, pw_gid=1000) + mock_getpwnam_inst_cib = mock.Mock(pw_uid=1001, pw_gid=1000) + mock_getpwnam.side_effect = [mock_getpwnam_inst_pe, mock_getpwnam_inst_cib] + + collect.collect_perms_state(mock_ctx_inst) + + data = "##### Check perms for /var/lib/pacemaker: /var/lib/pacemaker is not a directory or does not exist\n##### Check perms for /var/lib/pacemaker/pe: OK\n##### Check perms for /var/lib/pacemaker/cib: Permissions or ownership for /var/lib/pacemaker/cib are incorrect\n" + mock_str2file.assert_called_once_with(data, f"/opt/work_dir/{constants.PERMISSIONS_F}") + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('crmsh.utils.this_node') + @mock.patch('crmsh.utils.get_dc') + @mock.patch('logging.Logger.debug') + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.collect.sh.cluster_shell') + def test_dump_runtime_state(self, mock_run, mock_str2file, mock_debug, mock_get_dc, mock_this_node, mock_real_path): + mock_real_path.side_effect = [ + constants.CRM_MON_F, + constants.CIB_F, + constants.MEMBERSHIP_F, + "workdir" + ] + mock_run_inst = mock.Mock() + mock_run.return_value = mock_run_inst + mock_run_inst.get_stdout_or_raise_error.side_effect = ["crm_mon_data", "cib_data", "crm_node_data"] + mock_get_dc.return_value = "node1" + mock_this_node.return_value = "node1" + collect.dump_runtime_state("/opt/workdir") + mock_debug.assert_has_calls([ + mock.call(f"Dump cluster state into {constants.CRM_MON_F}"), + mock.call(f"Dump CIB contents into {constants.CIB_F}"), + mock.call(f"Dump members of this partition into {constants.MEMBERSHIP_F}"), + mock.call(f"Current DC is node1; Touch file 'DC' in workdir") + ]) + + @mock.patch('shutil.copytree') + @mock.patch('os.path.basename') + @mock.patch('os.path.isdir') + @mock.patch('shutil.copy2') + @mock.patch('os.path.isfile') + @mock.patch('crmsh.corosync.conf') + def test_dump_configurations(self, mock_corosync_conf, mock_isfile, mock_copy2, mock_isdir, mock_basename, mock_copytree): + mock_corosync_conf.return_value = "/etc/corosync/corosync.conf" + mock_isfile.side_effect = [True, True, False, True] + mock_isdir.return_value = True + mock_basename.return_value = "drbd.d" + collect.dump_configurations("/opt/workdir") + + @mock.patch('crmsh.report.utils.real_path') + @mock.patch('logging.Logger.debug') + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.report.utils.get_cmd_output') + @mock.patch('crmsh.report.utils.find_files_in_timespan') + def test_collect_corosync_blackbox(self, mock_find_files, mock_get_cmd_output, mock_str2file, mock_debug, mock_real_path): + mock_real_path.return_value = constants.COROSYNC_RECORDER_F + mock_ctx_inst = mock.Mock(work_dir="/opt/workdir") + mock_find_files.return_value = ["/var/lib/corosync/fdata.1"] + mock_get_cmd_output.return_value = "data" + collect.collect_corosync_blackbox(mock_ctx_inst) + mock_debug.assert_called_once_with(f"Dump corosync blackbox info into {constants.COROSYNC_RECORDER_F}") -- cgit v1.2.3