diff --git a/hotsos/core/host_helpers/__init__.py b/hotsos/core/host_helpers/__init__.py index 6ce338017..79f8288f9 100644 --- a/hotsos/core/host_helpers/__init__.py +++ b/hotsos/core/host_helpers/__init__.py @@ -1,5 +1,5 @@ from .common import InstallInfoBase -from .cli import ( +from .cli.cli import ( CLIHelper, CLIHelperFile, ) diff --git a/hotsos/core/host_helpers/cli/__init__.py b/hotsos/core/host_helpers/cli/__init__.py new file mode 100644 index 000000000..8b29288b4 --- /dev/null +++ b/hotsos/core/host_helpers/cli/__init__.py @@ -0,0 +1,9 @@ +from .cli import ( + CLIHelper, + CLIHelperFile, +) + +__all__ = [ + CLIHelper.__name__, + CLIHelperFile.__name__, +] diff --git a/hotsos/core/host_helpers/cli.py b/hotsos/core/host_helpers/cli/catalog.py similarity index 84% rename from hotsos/core/host_helpers/cli.py rename to hotsos/core/host_helpers/cli/catalog.py index 3eb7d9fbe..f27f6c38b 100644 --- a/hotsos/core/host_helpers/cli.py +++ b/hotsos/core/host_helpers/cli/catalog.py @@ -1,35 +1,78 @@ -import abc -import datetime import json import os -import pathlib import re import subprocess import tempfile +from collections import UserDict from dataclasses import dataclass, field, fields -from functools import cached_property import yaml from hotsos.core.config import HotSOSConfig -from hotsos.core.host_helpers.common import ( - CmdOutput, - get_ps_axo_flags_available, - HostHelpersBase, - reset_command, - run_post_exec_hooks, - run_pre_exec_hooks, - SourceRunner, -) +from hotsos.core.host_helpers.common import get_ps_axo_flags_available from hotsos.core.host_helpers.exceptions import ( catch_exceptions, CLI_COMMON_EXCEPTIONS, CLIExecError, - CommandNotFound, SourceNotFound, ) from hotsos.core.log import log +@dataclass(frozen=True) +class CmdOutput(): + """ Representation of the output of a command. """ + + # Output value. + value: str + # Optional command source path. + source: str = None + + +def run_pre_exec_hooks(f): + """ pre-exec hooks are run before running __call__ method. + + These hooks are not expected to return anything and are used to manipulate + the instance variables used by the main __call__ method. + """ + def run_pre_exec_hooks_inner(self, *args, **kwargs): + hook = self.hooks.get("pre-exec") + if hook: + # no return expected + hook(*args, **kwargs) + + return f(self, *args, **kwargs) + + return run_pre_exec_hooks_inner + + +def run_post_exec_hooks(f): + """ post-exec hooks are run after running __call__ method and take its + output as input. + """ + def run_post_exec_hooks_inner(self, *args, **kwargs): + out = f(self, *args, **kwargs) + hook = self.hooks.get("post-exec") + if hook: + out = hook(out, *args, **kwargs) + + return out + + return run_post_exec_hooks_inner + + +def reset_command(f): + """ + This should be run by all commands as their last action after all/any hooks + have run. + """ + def reset_command_inner(self, *args, **kwargs): + out = f(self, *args, **kwargs) + self.reset() + return out + + return reset_command_inner + + @dataclass class CmdBase: """ Base class for all command source types. @@ -244,62 +287,6 @@ def __call__(self, *args, **kwargs): return CmdOutput(output.splitlines(keepends=True)) -class JournalctlBase(): - """ Base class for journalctl command implementations. """ - @property - def since_date(self): - """ - Returns a string datetime to be used with journalctl --since. This time - reflects the maximum depth of history we will search in the journal. - - The datetime value returned takes into account config from HotSOSConfig - and has the format "YEAR-MONTH-DAY". It does not specify a time. - """ - current = CLIHelper().date(format="--iso-8601") - ts = datetime.datetime.strptime(current, "%Y-%m-%d") - if HotSOSConfig.use_all_logs: - days = HotSOSConfig.max_logrotate_depth - else: - days = 1 - - ts = ts - datetime.timedelta(days=days) - return ts.strftime("%Y-%m-%d") - - -class JournalctlBinCmd(BinCmd, JournalctlBase): - """ Implements binary journalctl command. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.register_hook("pre-exec", self.format_journalctl_cmd) - - def format_journalctl_cmd(self, **kwargs): - """ Add optional extras to journalctl command. """ - if kwargs.get("unit"): - self.cmd = f"{self.cmd} --unit {kwargs.get('unit')}" - - if kwargs.get("date"): - self.cmd = f"{self.cmd} --since {kwargs.get('date')}" - else: - self.cmd = f"{self.cmd} --since {self.since_date}" - - -class JournalctlBinFileCmd(BinFileCmd, JournalctlBase): - """ Implements file-based journalctl command. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.register_hook("pre-exec", self.preformat_sos_journalctl) - - def preformat_sos_journalctl(self, **kwargs): - self.path = f"journalctl -oshort-iso -D {self.path}" - if kwargs.get("unit"): - self.path = f"{self.path} --unit {kwargs.get('unit')}" - - if kwargs.get("date"): - self.path = f"{self.path} --since {kwargs.get('date')}" - else: - self.path = f"{self.path} --since {self.since_date}" - - class OVSAppCtlBinCmd(BinCmd): """ Implements ovs-appctl binary command. """ def __call__(self, *args, **kwargs): @@ -531,51 +518,12 @@ def cleanup(self, output, **_kwargs): return output -class CLICacheWrapper(): - """ Wrapper for cli cache. """ - def __init__(self, cache_load_f, cache_save_f): - self.load_f = cache_load_f - self.save_f = cache_save_f - - def load(self, key): - return self.load_f(key) - - def save(self, key, value): - return self.save_f(key, value) +class CommandCatalog(UserDict): + """ Catalog of all supported commands. """ - -class CLIHelperBase(HostHelpersBase): - """ Base class for clihelper implementations. """ def __init__(self): - self._command_catalog = None super().__init__() - self.cli_cache = CLICacheWrapper(self.cache_load, self.cache_save) - - @property - def cache_root(self): - """ Cache at plugin level rather than globally. """ - return HotSOSConfig.plugin_tmp_dir - - @property - def cache_type(self): - return 'cli' - - @property - def cache_name(self): - return "commands" - - def cache_load(self, key): - return self.cache.get(key) - - def cache_save(self, key, value): - return self.cache.set(key, value) - - @property - def command_catalog(self): - if self._command_catalog: - return self._command_catalog - - self._command_catalog = { + self.data = { 'apt_config_dump': [BinCmd('apt-config dump'), FileCmd('sos_commands/apt/apt-config_dump')], @@ -765,9 +713,6 @@ def command_catalog(self): 'ip_link': [BinCmd('ip -s -d link'), FileCmd('sos_commands/networking/ip_-s_-d_link')], - 'journalctl': - [JournalctlBinCmd('journalctl -oshort-iso'), - JournalctlBinFileCmd('var/log/journal')], 'ls_lanR_sys_block': [BinCmd('ls -lanR /sys/block/'), FileCmd('sos_commands/block/ls_-lanR_.sys.block')], @@ -917,76 +862,3 @@ def command_catalog(self): [BinCmd('uptime', singleline=True), FileCmd('uptime', singleline=True)], } - return self._command_catalog - - @abc.abstractmethod - def __getattr__(self, cmdname): - """ This is how commands are run. The command is looked up in the - catalog and it's runner object is returned. The caller is expetced to - call() the returned object to execute the command. - - @param cmdname: name of command we want to execute. This must match a - name used to register a handler in the catalog. - @return: SourceRunner object. - """ - - -class CLIHelper(CLIHelperBase): - """ - This is used when we want to have command output as the return value when - a command is executed. - """ - - def __getattr__(self, cmdname): - try: - return SourceRunner(cmdname, self.command_catalog[cmdname], - self.cli_cache) - except KeyError as exc: - raise CommandNotFound(cmdname, exc) from exc - - -class CLIHelperFile(CLIHelperBase): - """ - This is used when we want the return value of a command to be a path to a - file containing the return value of executing that command. - - This will do one of two things; if the command output originates from a - file e.g. a sosreport command output file, it will return the path to that - file. If the command is executed as a binary, its output is written to a - temporary file and the path to that file is returned. - """ - - def __init__(self, *args, delete_temp=True, **kwargs): - super().__init__(*args, **kwargs) - self.delete_temp = delete_temp - self._tmp_file_mtime = None - - def __enter__(self): - return self - - def __exit__(self, *args, **kwargs): - do_delete = (self.delete_temp or - self._tmp_file_mtime == - os.path.getmtime(self.output_file)) - if do_delete: - os.remove(self.output_file) - - # We want exceptions to be raised - return False - - @cached_property - def output_file(self): - path = tempfile.mktemp(dir=HotSOSConfig.plugin_tmp_dir) - pathlib.Path(path).touch() - self._tmp_file_mtime = os.path.getmtime(path) - return path - - def __getattr__(self, cmdname): - try: - ret = SourceRunner(cmdname, self.command_catalog[cmdname], - self.cli_cache, output_file=self.output_file) - return ret - except KeyError as exc: - raise CommandNotFound(cmdname, exc) from exc - - return None diff --git a/hotsos/core/host_helpers/cli/cli.py b/hotsos/core/host_helpers/cli/cli.py new file mode 100644 index 000000000..bee5c9e05 --- /dev/null +++ b/hotsos/core/host_helpers/cli/cli.py @@ -0,0 +1,314 @@ +import abc +import datetime +import json +import os +import pathlib +import pickle +import tempfile +from functools import cached_property + +from hotsos.core.config import HotSOSConfig +from hotsos.core.host_helpers.exceptions import ( + CLIExecError, + SourceNotFound, +) +from hotsos.core.host_helpers.cli.catalog import ( + BinCmd, + BinFileCmd, + CmdOutput, + CommandCatalog, +) +from hotsos.core.host_helpers.common import HostHelpersBase +from hotsos.core.host_helpers.exceptions import CommandNotFound +from hotsos.core.log import log + + +class NullSource(): + """ Exception raised to indicate that a datasource is not available. """ + def __call__(self, *args, **kwargs): + return CmdOutput([]) + + +class JournalctlBase(): + """ Base class for journalctl command implementations. """ + @property + def since_date(self): + """ + Returns a string datetime to be used with journalctl --since. This time + reflects the maximum depth of history we will search in the journal. + + The datetime value returned takes into account config from HotSOSConfig + and has the format "YEAR-MONTH-DAY". It does not specify a time. + """ + current = CLIHelper().date(format="--iso-8601") + ts = datetime.datetime.strptime(current, "%Y-%m-%d") + if HotSOSConfig.use_all_logs: + days = HotSOSConfig.max_logrotate_depth + else: + days = 1 + + ts = ts - datetime.timedelta(days=days) + return ts.strftime("%Y-%m-%d") + + +class JournalctlBinCmd(BinCmd, JournalctlBase): + """ Implements binary journalctl command. """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_hook("pre-exec", self.format_journalctl_cmd) + + def format_journalctl_cmd(self, **kwargs): + """ Add optional extras to journalctl command. """ + if kwargs.get("unit"): + self.cmd = f"{self.cmd} --unit {kwargs.get('unit')}" + + if kwargs.get("date"): + self.cmd = f"{self.cmd} --since {kwargs.get('date')}" + else: + self.cmd = f"{self.cmd} --since {self.since_date}" + + +class JournalctlBinFileCmd(BinFileCmd, JournalctlBase): + """ Implements file-based journalctl command. """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_hook("pre-exec", self.preformat_sos_journalctl) + + def preformat_sos_journalctl(self, **kwargs): + self.path = f"journalctl -oshort-iso -D {self.path}" + if kwargs.get("unit"): + self.path = f"{self.path} --unit {kwargs.get('unit')}" + + if kwargs.get("date"): + self.path = f"{self.path} --since {kwargs.get('date')}" + else: + self.path = f"{self.path} --since {self.since_date}" + + +class SourceRunner(): + """ Manager to control how we execute commands. + + Ensures that we try data sources in a consistent order. + """ + def __init__(self, cmdkey, sources, cache, output_file=None): + """ + @param cmdkey: unique key identifying this command. + @param sources: list of command source implementations. + @param cache: CLICacheWrapper object. + """ + self.cmdkey = cmdkey + self.sources = sources + self.cache = cache + self.output_file = output_file + # Command output can differ between CLIHelper and CLIHelperFile so we + # need to cache them separately. + if output_file: + self.cache_cmdkey = f"{cmdkey}.file" + else: + self.cache_cmdkey = cmdkey + + def bsource(self, *args, **kwargs): + # binary sources only apply if data_root is system root + bin_out = None + for bsource in [s for s in self.sources if s.TYPE == "BIN"]: + cache = False + # NOTE: we currently only support caching commands with no + # args. + if not any([args, kwargs]): + cache = True + out = self.cache.load(self.cache_cmdkey) + if out is not None: + return out + + try: + if self.output_file: + # don't decode if we are going to be saving to a file + kwargs['skip_json_decode'] = True + + bin_out = bsource(*args, **kwargs) + if cache and bin_out is not None: + try: + self.cache.save(self.cache_cmdkey, bin_out) + except pickle.PicklingError as exc: + log.info("unable to cache command '%s' output: %s", + self.cmdkey, exc) + + # if command executed but returned nothing that still counts + # as success. + break + except CLIExecError as exc: + bin_out = CmdOutput(exc.return_value) + + return bin_out + + def fsource(self, *args, **kwargs): + for fsource in [s for s in self.sources if s.TYPE == "FILE"]: + try: + skip_load_contents = False + if self.output_file: + skip_load_contents = True + + return fsource(*args, **kwargs, + skip_load_contents=skip_load_contents) + except CLIExecError as exc: + return CmdOutput(exc.return_value) + except SourceNotFound: + pass + + return None + + def _execute(self, *args, **kwargs): + # always try file sources first + ret = self.fsource(*args, **kwargs) + if ret is not None: + return ret + + if HotSOSConfig.data_root != '/': + return NullSource()() + + return self.bsource(*args, **kwargs) + + def __call__(self, *args, **kwargs): + """ + Execute the command using the appropriate source runner. These can be + binary or file-based depending on whether data root points to / or a + sosreport. File-based are attempted first. + + A command can have more than one source implementation so we must + ensure they all have a chance to run. + """ + out = self._execute(*args, **kwargs) + if self.output_file: + if out.source is not None: + return out.source + + with open(self.output_file, 'w', encoding='utf-8') as fd: + if isinstance(out.value, list): + fd.write(''.join(out.value)) + elif isinstance(out.value, dict): + fd.write(json.dumps(out.value)) + else: + fd.write(out.value) + + return self.output_file + + return out.value + + +class CLICacheWrapper(): + """ Wrapper for cli cache. """ + def __init__(self, cache_load_f, cache_save_f): + self.load_f = cache_load_f + self.save_f = cache_save_f + + def load(self, key): + return self.load_f(key) + + def save(self, key, value): + return self.save_f(key, value) + + +class CLIHelperBase(HostHelpersBase): + """ Base class for clihelper implementations. """ + def __init__(self): + super().__init__() + self.cli_cache = CLICacheWrapper(self.cache_load, self.cache_save) + + @property + def cache_root(self): + """ Cache at plugin level rather than globally. """ + return HotSOSConfig.plugin_tmp_dir + + @property + def cache_type(self): + return 'cli' + + @property + def cache_name(self): + return "commands" + + def cache_load(self, key): + return self.cache.get(key) + + def cache_save(self, key, value): + return self.cache.set(key, value) + + @cached_property + def command_catalog(self): + catalog = CommandCatalog() + catalog.update({'journalctl': + [JournalctlBinCmd('journalctl -oshort-iso'), + JournalctlBinFileCmd('var/log/journal')]}) + return catalog + + @abc.abstractmethod + def __getattr__(self, cmdname): + """ This is how commands are run. The command is looked up in the + catalog and it's runner object is returned. The caller is expetced to + call() the returned object to execute the command. + + @param cmdname: name of command we want to execute. This must match a + name used to register a handler in the catalog. + @return: SourceRunner object. + """ + + +class CLIHelper(CLIHelperBase): + """ + This is used when we want to have command output as the return value when + a command is executed. + """ + + def __getattr__(self, cmdname): + try: + return SourceRunner(cmdname, self.command_catalog[cmdname], + self.cli_cache) + except KeyError as exc: + raise CommandNotFound(cmdname, exc) from exc + + +class CLIHelperFile(CLIHelperBase): + """ + This is used when we want the return value of a command to be a path to a + file containing the return value of executing that command. + + This will do one of two things; if the command output originates from a + file e.g. a sosreport command output file, it will return the path to that + file. If the command is executed as a binary, its output is written to a + temporary file and the path to that file is returned. + """ + + def __init__(self, *args, delete_temp=True, **kwargs): + super().__init__(*args, **kwargs) + self.delete_temp = delete_temp + self._tmp_file_mtime = None + + def __enter__(self): + return self + + def __exit__(self, *args, **kwargs): + do_delete = (self.delete_temp or + self._tmp_file_mtime == + os.path.getmtime(self.output_file)) + if do_delete: + os.remove(self.output_file) + + # We want exceptions to be raised + return False + + @cached_property + def output_file(self): + path = tempfile.mktemp(dir=HotSOSConfig.plugin_tmp_dir) + pathlib.Path(path).touch() + self._tmp_file_mtime = os.path.getmtime(path) + return path + + def __getattr__(self, cmdname): + try: + ret = SourceRunner(cmdname, self.command_catalog[cmdname], + self.cli_cache, output_file=self.output_file) + return ret + except KeyError as exc: + raise CommandNotFound(cmdname, exc) from exc + + return None diff --git a/hotsos/core/host_helpers/common.py b/hotsos/core/host_helpers/common.py index 374f3593b..2f278f804 100644 --- a/hotsos/core/host_helpers/common.py +++ b/hotsos/core/host_helpers/common.py @@ -1,18 +1,12 @@ import abc import glob -import json import os -import pickle import re from functools import cached_property from dataclasses import dataclass, fields from searchkit.utils import MPCache from hotsos.core.config import HotSOSConfig -from hotsos.core.host_helpers.exceptions import ( - CLIExecError, - SourceNotFound, -) from hotsos.core.log import log from hotsos.core.utils import sorted_dict @@ -189,177 +183,6 @@ def summary(self): 'ps': self._process_info} -class NullSource(): - """ Exception raised to indicate that a datasource is not available. """ - def __call__(self, *args, **kwargs): - return CmdOutput([]) - - -@dataclass(frozen=True) -class CmdOutput(): - """ Representation of the output of a command. """ - - # Output value. - value: str - # Optional command source path. - source: str = None - - -class SourceRunner(): - """ Manager to control how we execute commands. - - Ensures that we try data sources in a consistent order. - """ - def __init__(self, cmdkey, sources, cache, output_file=None): - """ - @param cmdkey: unique key identifying this command. - @param sources: list of command source implementations. - @param cache: CLICacheWrapper object. - """ - self.cmdkey = cmdkey - self.sources = sources - self.cache = cache - self.output_file = output_file - # Command output can differ between CLIHelper and CLIHelperFile so we - # need to cache them separately. - if output_file: - self.cache_cmdkey = f"{cmdkey}.file" - else: - self.cache_cmdkey = cmdkey - - def bsource(self, *args, **kwargs): - # binary sources only apply if data_root is system root - bin_out = None - for bsource in [s for s in self.sources if s.TYPE == "BIN"]: - cache = False - # NOTE: we currently only support caching commands with no - # args. - if not any([args, kwargs]): - cache = True - out = self.cache.load(self.cache_cmdkey) - if out is not None: - return out - - try: - if self.output_file: - # don't decode if we are going to be saving to a file - kwargs['skip_json_decode'] = True - - bin_out = bsource(*args, **kwargs) - if cache and bin_out is not None: - try: - self.cache.save(self.cache_cmdkey, bin_out) - except pickle.PicklingError as exc: - log.info("unable to cache command '%s' output: %s", - self.cmdkey, exc) - - # if command executed but returned nothing that still counts - # as success. - break - except CLIExecError as exc: - bin_out = CmdOutput(exc.return_value) - - return bin_out - - def fsource(self, *args, **kwargs): - for fsource in [s for s in self.sources if s.TYPE == "FILE"]: - try: - skip_load_contents = False - if self.output_file: - skip_load_contents = True - - return fsource(*args, **kwargs, - skip_load_contents=skip_load_contents) - except CLIExecError as exc: - return CmdOutput(exc.return_value) - except SourceNotFound: - pass - - return None - - def _execute(self, *args, **kwargs): - # always try file sources first - ret = self.fsource(*args, **kwargs) - if ret is not None: - return ret - - if HotSOSConfig.data_root != '/': - return NullSource()() - - return self.bsource(*args, **kwargs) - - def __call__(self, *args, **kwargs): - """ - Execute the command using the appropriate source runner. These can be - binary or file-based depending on whether data root points to / or a - sosreport. File-based are attempted first. - - A command can have more than one source implementation so we must - ensure they all have a chance to run. - """ - out = self._execute(*args, **kwargs) - if self.output_file: - if out.source is not None: - return out.source - - with open(self.output_file, 'w', encoding='utf-8') as fd: - if isinstance(out.value, list): - fd.write(''.join(out.value)) - elif isinstance(out.value, dict): - fd.write(json.dumps(out.value)) - else: - fd.write(out.value) - - return self.output_file - - return out.value - - -def run_pre_exec_hooks(f): - """ pre-exec hooks are run before running __call__ method. - - These hooks are not expected to return anything and are used to manipulate - the instance variables used by the main __call__ method. - """ - def run_pre_exec_hooks_inner(self, *args, **kwargs): - hook = self.hooks.get("pre-exec") - if hook: - # no return expected - hook(*args, **kwargs) - - return f(self, *args, **kwargs) - - return run_pre_exec_hooks_inner - - -def run_post_exec_hooks(f): - """ post-exec hooks are run after running __call__ method and take its - output as input. - """ - def run_post_exec_hooks_inner(self, *args, **kwargs): - out = f(self, *args, **kwargs) - hook = self.hooks.get("post-exec") - if hook: - out = hook(out, *args, **kwargs) - - return out - - return run_post_exec_hooks_inner - - -def reset_command(f): - """ - This should be run by all commands as their last action after all/any hooks - have run. - """ - def reset_command_inner(self, *args, **kwargs): - out = f(self, *args, **kwargs) - self.reset() - return out - - return reset_command_inner - - def get_ps_axo_flags_available(): path = os.path.join(HotSOSConfig.data_root, "sos_commands/process/ps_axo_flags_state_" diff --git a/hotsos/core/plugins/openstack/common.py b/hotsos/core/plugins/openstack/common.py index 42fcddf4f..7d75568df 100644 --- a/hotsos/core/plugins/openstack/common.py +++ b/hotsos/core/plugins/openstack/common.py @@ -6,7 +6,8 @@ from functools import cached_property from hotsos.core.config import HotSOSConfig -from hotsos.core.host_helpers.cli import CLIHelper, CmdBase +from hotsos.core.host_helpers.cli import CLIHelper +from hotsos.core.host_helpers.cli.catalog import CmdBase from hotsos.core.host_helpers import ( APTPackageHelper, DockerImageHelper, diff --git a/hotsos/core/plugins/storage/ceph/daemon.py b/hotsos/core/plugins/storage/ceph/daemon.py index f0a503cb2..0f66615a0 100644 --- a/hotsos/core/plugins/storage/ceph/daemon.py +++ b/hotsos/core/plugins/storage/ceph/daemon.py @@ -2,7 +2,7 @@ import subprocess from functools import cached_property -from hotsos.core.host_helpers.cli import get_ps_axo_flags_available +from hotsos.core.host_helpers.common import get_ps_axo_flags_available from hotsos.core.host_helpers import ( CLIHelper, CLIHelperFile, diff --git a/tests/unit/host_helpers/test_cli.py b/tests/unit/host_helpers/test_cli.py index 9dbf4ae58..f8502b0f9 100644 --- a/tests/unit/host_helpers/test_cli.py +++ b/tests/unit/host_helpers/test_cli.py @@ -3,7 +3,8 @@ from unittest import mock from hotsos.core.config import HotSOSConfig -from hotsos.core.host_helpers import cli as host_cli +from hotsos.core.host_helpers.cli import cli as host_cli +from hotsos.core.host_helpers.cli import catalog from .. import utils @@ -38,7 +39,7 @@ def test_udevadm_info_dev(self): out = host_cli.CLIHelper().udevadm_info_dev(device='/dev/vdb') self.assertEqual(out, []) - @mock.patch.object(host_cli, 'subprocess') + @mock.patch.object(catalog, 'subprocess') def test_ps(self, mock_subprocess): path = os.path.join(HotSOSConfig.data_root, "ps") with open(path, 'r', encoding='utf-8') as fd: @@ -91,7 +92,7 @@ def fake_run(cmd, *_args, **_kwargs): raise subprocess.CalledProcessError(1, 'ofctl') HotSOSConfig.data_root = '/' - with mock.patch.object(host_cli.subprocess, 'run') as \ + with mock.patch.object(catalog.subprocess, 'run') as \ mock_run: mock_run.side_effect = fake_run diff --git a/tests/unit/storage/test_ceph_osd.py b/tests/unit/storage/test_ceph_osd.py index b855e0abc..c0dc0c6b2 100644 --- a/tests/unit/storage/test_ceph_osd.py +++ b/tests/unit/storage/test_ceph_osd.py @@ -2,6 +2,7 @@ from hotsos.core.config import HotSOSConfig from hotsos.core import host_helpers +from hotsos.core.host_helpers.cli.catalog import CmdOutput from hotsos.core.plugins.storage import ceph from hotsos.core.ycheck.common import GlobalSearcher from hotsos.plugin_extensions.storage import ( @@ -52,20 +53,20 @@ def test_release_name(self): release_name = ceph.common.CephChecks().release_name self.assertEqual(release_name, 'octopus') - @mock.patch('hotsos.core.host_helpers.cli.DateFileCmd.format_date') + @mock.patch('hotsos.core.host_helpers.cli.catalog.DateFileCmd.format_date') def test_release_eol(self, mock_date): # 2030-04-30 - mock_date.return_value = host_helpers.cli.CmdOutput('1903748400') + mock_date.return_value = CmdOutput('1903748400') base = ceph.common.CephChecks() self.assertEqual(base.release_name, 'octopus') self.assertLessEqual(base.days_to_eol, 0) - @mock.patch('hotsos.core.host_helpers.cli.DateFileCmd.format_date') + @mock.patch('hotsos.core.host_helpers.cli.catalog.DateFileCmd.format_date') def test_release_not_eol(self, mock_date): # 2030-01-01 - mock_date.return_value = host_helpers.cli.CmdOutput('1893466800') + mock_date.return_value = CmdOutput('1893466800') base = ceph.common.CephChecks() diff --git a/tests/unit/test_openstack.py b/tests/unit/test_openstack.py index 3e76432e6..d31ea345d 100644 --- a/tests/unit/test_openstack.py +++ b/tests/unit/test_openstack.py @@ -3,6 +3,7 @@ from hotsos.core.config import HotSOSConfig from hotsos.core import host_helpers +from hotsos.core.host_helpers.cli.catalog import CmdOutput import hotsos.core.plugins.openstack as openstack_core import hotsos.core.plugins.openstack.nova as nova_core import hotsos.core.plugins.openstack.neutron as neutron_core @@ -285,20 +286,20 @@ def test_release_name_from_file(self): with mock.patch.object(base, 'installed_pkg_release_names', None): self.assertEqual(base.release_name, 'yoga') - @mock.patch('hotsos.core.host_helpers.cli.DateFileCmd.format_date') + @mock.patch('hotsos.core.host_helpers.cli.catalog.DateFileCmd.format_date') def test_get_release_eol(self, mock_date): # 2030-04-30 - mock_date.return_value = host_helpers.cli.CmdOutput('1903748400') + mock_date.return_value = CmdOutput('1903748400') inst = openstack_core.OpenstackBase() self.assertEqual(inst.release_name, 'ussuri') self.assertLessEqual(inst.days_to_eol, 0) - @mock.patch('hotsos.core.host_helpers.cli.DateFileCmd.format_date') + @mock.patch('hotsos.core.host_helpers.cli.catalog.DateFileCmd.format_date') def test_get_release_not_eol(self, mock_date): # 2030-01-01 - mock_date.return_value = host_helpers.cli.CmdOutput('1893466800') + mock_date.return_value = CmdOutput('1893466800') inst = openstack_core.OpenstackBase() self.assertEqual(inst.release_name, 'ussuri')