diff --git a/hotsos/client.py b/hotsos/client.py index 8f058ebe4..0e6771dad 100755 --- a/hotsos/client.py +++ b/hotsos/client.py @@ -213,10 +213,7 @@ def __init__(self, plugins=None): all will be run. """ self._summary = OutputManager() - if plugins: - self.plugins = plugins - else: - self.plugins = plugintools.PLUGINS.keys() + self.plugins = plugins or plugintools.PLUGINS.keys() def setup_global_env(self): """ State saved here persists across all plugin runs. """ diff --git a/hotsos/core/plugintools.py b/hotsos/core/plugintools.py index d7aa964fb..b7e4b4d0b 100644 --- a/hotsos/core/plugintools.py +++ b/hotsos/core/plugintools.py @@ -7,6 +7,7 @@ from hotsos.core.issues import IssuesManager from hotsos.core.log import log from hotsos.core.ycheck.scenarios import YScenarioChecker +from hotsos.core.ycheck.events import EventDefsPreloader PLUGINS = {} PLUGIN_RUN_ORDER = [] @@ -393,6 +394,7 @@ def run(self): failed_parts = [] # The following are executed as part of each plugin run (but not last). ALWAYS_RUN = {'auto_scenario_check': YScenarioChecker} + ALWAYS_RUN['events_preload'] = EventDefsPreloader for name, always_parts in ALWAYS_RUN.items(): # update current env to reflect actual part being run HotSOSConfig.part_name = name diff --git a/hotsos/core/ycheck/events.py b/hotsos/core/ycheck/events.py index dc5fe23e8..21d7b315d 100644 --- a/hotsos/core/ycheck/events.py +++ b/hotsos/core/ycheck/events.py @@ -1,5 +1,6 @@ import abc from functools import cached_property +from collections import UserDict from hotsos.core.config import HotSOSConfig from hotsos.core.log import log @@ -13,6 +14,7 @@ YHandlerBase, YDefsSection, ) +from hotsos.core.ycheck.engine.properties import search from hotsos.core.ycheck.engine.properties.search import CommonTimestampMatcher @@ -27,6 +29,17 @@ class EventCallbackNotFound(Exception): pass +class EventsSearchRegistryKeyNotFound(Exception): + def __init__(self, key, all_keys): + self.key = key + self.all_keys = all_keys + + def __str__(self): + return ("'{}' not found in event registry. Available keys are:" + "\n - {}". + format(self.key, '\n - '.join(self.all_keys))) + + class EventCallbackMeta(type): def __init__(cls, _name, _mro, members): @@ -225,7 +238,187 @@ def __call__(self): """ Callback method. """ -class EventHandlerBase(YHandlerBase, EventProcessingUtils): +class EventsSearchRegistry(UserDict): + _global_searcher = None + _custom_searcher = None + _global_searcher_results = None + + def __getitem__(self, key): + try: + return super().__getitem__(key) + except KeyError: + raise EventsSearchRegistryKeyNotFound( + key, + list(self.data)) from KeyError + + @classmethod + def global_searcher_results(cls): + if cls._global_searcher is None: + raise Exception("registry global searcher is None") + + if cls._global_searcher_results is not None: + log.debug("using cached global event search results") + return cls._global_searcher_results + + log.debug("fetching global event search results") + cls._global_searcher_results = cls._global_searcher.run() + return cls._global_searcher_results + + @classmethod + def get_global_searcher(cls): + if cls._global_searcher: + log.debug("using existing global searcher (%s)", + cls._global_searcher) + return cls._global_searcher + + cls._custom_searcher = None + constraint = SearchConstraintSearchSince( + ts_matcher_cls=CommonTimestampMatcher) + searcher = FileSearcher(constraint=constraint) + cls._global_searcher = searcher + cls._global_searcher_results = None + log.debug("creating new global searcher (%s)", searcher) + return searcher + + @classmethod + def set_custom_searcher(cls, searcher): + cls._custom_searcher = searcher + + @classmethod + def _reset_searchers(cls): + cls._global_searcher = None + cls._custom_searcher = None + cls._global_searcher_results = None + + def reset(self): + log.debug("resetting events registry") + self._reset_searchers() + self.data = {} + + +class EventsBase(object): + _events_registry = EventsSearchRegistry() + + @property + def registry(self): + return self._events_registry + + @classmethod + def reset_registry(cls): + log.info("resetting events global regsitry") + cls._events_registry.reset() + + def meets_requirements(self, event): + if HotSOSConfig.force_mode: + return True + + if event.requires and not event.requires.result: + log.debug("event '%s' pre-requisites not met - " + "skipping", event.name) + return False + + return True + + def skip_filtered_event(self, event_path): + e_filter = HotSOSConfig.event_filter + if e_filter and event_path != e_filter: + log.info("skipping event %s (filter=%s)", event_path, e_filter) + return True + + return False + + def get_defs(self, group=None): + plugin_defs = YDefsLoader('events').plugin_defs + if not plugin_defs: + return {} + + log.debug("loading event defs (group=%s)", group) + group_defs = plugin_defs + if not group: + return group_defs + + # ensure we only include the events we want + groups = group.split('.') + for i, _group in enumerate(groups): + if i == 0: + group_defs = {_group: group_defs[_group]} + else: + prev = groups[i - 1] + group_defs[prev] = {_group: group_defs[prev][_group]} + + return group_defs + + def load_searches(self, group=None, searcher=None): + if searcher is None: + searcher = self.registry.get_global_searcher() + + log.debug("started loading event (group=%s) searches into searcher " + "(%s)", group, searcher) + + search_props = set() + events = YDefsSection(HotSOSConfig.plugin_name, + self.get_defs(group) or {}) + for prop in events.manager.properties.values(): + for item in prop: + if not issubclass(item['cls'], search.YPropertySearch): + break + + search_props.add(item['path']) + + if len(search_props) > 0: + log.debug("loading searches for %s events", len(search_props)) + for event_search_prop_path in search_props: + branch = None + event = None + # skip plugin name at start and skip ".search" at the end. + for node in event_search_prop_path.split('.')[1:-1]: + if branch is None: + branch = getattr(events, node) + else: + branch = getattr(branch, node) + + # the last one will be the one we want + event = branch + + if self.skip_filtered_event(event.resolve_path): + log.debug("skipping event %s", event.resolve_path) + continue + + allow_constraints = True + if event.input.command: + # don't apply constraints to command outputs + allow_constraints = False + + for path in event.input.paths: + log.debug("loading search for event %s (path=%s, tag=%s)", + event.resolve_path, + path, event.search.unique_search_tag) + # these may be needed later e.g. for sequence lookups + self.registry[event.resolve_path] = {'search': + event.search} + event.search.load_searcher( + searcher, path, + allow_constraints=allow_constraints) + else: + log.debug("no events found - no searches to load") + + log.debug("finished loading event searches into searcher " + "(registry has %s items)", len(self.registry)) + + +class EventDefsPreloader(EventsBase): + + def load_and_run(self): + # Make sure we start with a clean registry + self.registry.reset() + # Pre-load all event searches into a global event searcher + self.load_searches() + # Run the searches so that results are ready when event handlers are + # run. + self.registry.global_searcher_results() + + +class EventHandlerBase(EventsBase, YHandlerBase, EventProcessingUtils): """ Root name used to identify a group of event definitions. Once all the yaml definitions are loaded this defines the level below which events @@ -241,10 +434,23 @@ def __init__(self, *args, searcher=None, **kwargs): done at once. """ super().__init__(*args, **kwargs) - if not searcher: - log.debug("creating searcher for event checker") - searcher = FileSearcher(constraint=SearchConstraintSearchSince( - ts_matcher_cls=CommonTimestampMatcher)) + if searcher is None: + log.debug("no searcher provided - using global searcher") + searcher = self.registry.get_global_searcher() + if len(searcher.catalog) == 0: + # This is mainly intended to be useful for unit tests so that + # they don't have to call it in every single test. + log.debug("global searcher catalog is empty so launching " + "pre-load of all event searches") + # NOTE: this is not re-entrant safe + self.load_searches(group=self.event_group) + else: + # If a searcher is provided we switch over but do not clear global + # searcher. + if self.registry._custom_searcher != searcher: + self.registry.set_custom_searcher(searcher) + + self.load_searches(group=self.event_group, searcher=searcher) self._searcher = searcher self._event_results = None @@ -254,41 +460,20 @@ def searcher(self): return self._searcher @cached_property - def event_definitions(self): + def events(self): """ Load event definitions from yaml. """ - _event_defs = {} - - plugin = YDefsLoader('events').plugin_defs - if not plugin: - return _event_defs - - log.debug("loading defs for subgroup=%s", self.event_group) - ytree = plugin - ypath = self.event_group.split('.') - for i, g in enumerate(ypath): - if i >= len(ypath) - 1: - group_defs = ytree.get(g) - else: - ytree = ytree.get(g) - - group = YDefsSection(self.event_group, group_defs) + group = YDefsSection(HotSOSConfig.plugin_name, + self.get_defs(self.event_group) or {}) log.debug("sections=%s, events=%s", len(list(group.branch_sections)), len(list(group.leaf_sections))) + _events = {} for event in group.leaf_sections: - fullname = "{}.{}.{}".format(HotSOSConfig.plugin_name, - event.parent.name, event.name) - if (HotSOSConfig.event_filter and - fullname != HotSOSConfig.event_filter): - log.info("skipping event %s (filter=%s)", fullname, - HotSOSConfig.event_filter) + if self.skip_filtered_event(event.resolve_path): continue - if (not HotSOSConfig.force_mode and event.requires and not - event.requires.result): - log.error("event '%s' pre-requisites not met - " - "skipping", event.name) + if not self.meets_requirements(event): return {} log.debug("event: %s", event.name) @@ -296,43 +481,29 @@ def event_definitions(self): event.input.command is not None) section_name = event.parent.name - if section_name not in _event_defs: - _event_defs[section_name] = {} - - for path in event.input.paths: - if event.input.command: - # don't apply constraints to command outputs - allow_constraints = False - else: - allow_constraints = True - - event.search.load_searcher(self.searcher, path, - allow_constraints=allow_constraints) + if section_name not in _events: + _events[section_name] = {} - passthrough = bool(event.search.passthrough_results) - emeta = {'passthrough': passthrough, - 'sequence': event.search.sequence_search, - 'tag': event.search.unique_search_tag} - _event_defs[section_name][event.name] = emeta + _events[section_name][event.name] = event.resolve_path - return _event_defs + return _events def load(self): - """ Pre-load event definitions. """ - self.event_definitions + pass @property def final_event_results(self): """ Cache of results in case run() is called again. """ return self._event_results - def run(self, results): + def run(self, results=None): """ Process each event and call respective callback functions when results where found. - - @param results: SearchResultsCollection object. """ + if results is None: + results = self.registry.global_searcher_results() + if self.final_event_results is not None: return self.final_event_results @@ -340,18 +511,20 @@ def run(self, results): raise Exception("need to register at least one callback for " "event handler.") - log.debug("registered callbacks:\n%s", '\n'.join(CALLBACKS.keys())) + log.debug("registered event callbacks:\n%s", '\n'. + join(CALLBACKS.keys())) info = {} - for section_name, section in self.event_definitions.items(): - for event, event_meta in section.items(): - search_tag = event_meta['tag'] + for section_name, section in self.events.items(): + for event, fullname in section.items(): + event_search = self.registry[fullname]['search'] + search_tag = event_search.unique_search_tag seq_def = None - if event_meta['passthrough']: + if bool(event_search.passthrough_results): # this is for implementations that have their own means of # retrieving results. search_results = results else: - seq_def = event_meta['sequence'] + seq_def = event_search.sequence_search if seq_def: search_results = results.find_sequence_sections( seq_def) @@ -361,7 +534,8 @@ def run(self, results): search_results = results.find_by_tag(search_tag) if not search_results: - log.debug("event %s did not yield any results", event) + log.debug("event %s did not yield any results (tag=%s)", + event, search_tag) continue # We want this to throw an exception if the callback is not @@ -409,4 +583,4 @@ def run(self, results): def load_and_run(self): self.load() - return self.run(self.searcher.run()) + return self.run() diff --git a/hotsos/defs/events/openstack/apparmor.yaml b/hotsos/defs/events/openstack/apparmor.yaml index 6c0b54377..80ca3d2f8 100644 --- a/hotsos/defs/events/openstack/apparmor.yaml +++ b/hotsos/defs/events/openstack/apparmor.yaml @@ -10,8 +10,8 @@ denials: # day (\d{1,2}), time ([\d:]+) and key (\S+neutron\S+) separated for # grouping. See AgentApparmorChecks class for more details. nova: - expr: '(\w{3,5})\s+(\d{1,2})\s+([\d:]+)\s+.+apparmor="DENIED".+\s+profile="(\S+nova\S+)"\s+.+' + expr: '(\w{3,5})\s+(\d{1,2})\s+([\d:]+)\s+.+apparmor="DENIED".+\s+profile="(\S+nova\S+)"' hint: apparmor neutron: - expr: '(\w{3,5})\s+(\d{1,2})\s+([\d:]+)\s+.+apparmor="DENIED".+\s+profile="(\S+neutron\S+)"\s+.+' + expr: '(\w{3,5})\s+(\d{1,2})\s+([\d:]+)\s+.+apparmor="DENIED".+\s+profile="(\S+neutron\S+)"' hint: apparmor diff --git a/hotsos/defs/events/openstack/neutron/ml2-routers.yaml b/hotsos/defs/events/openstack/neutron/ml2-routers.yaml index f21a87c1d..499e51403 100644 --- a/hotsos/defs/events/openstack/neutron/ml2-routers.yaml +++ b/hotsos/defs/events/openstack/neutron/ml2-routers.yaml @@ -4,6 +4,6 @@ l3ha: input: command: journalctl options: - args-callback: hotsos.plugin_extensions.openstack.agent.events.NeutronL3HAEventChecks.journalctl_args + args-callback: hotsos.plugin_extensions.openstack.agent.events.NeutronL3HAEventCheckJournalCtl.args # timestamp at start of line will be in journalctl -oshort-iso format expr: '([\d-]+)T([\d:]+)\S+ \S+ Keepalived_vrrp\[\d+\]: (?:VRRP_Instance)?\(VR_(\d+)\) .+ (\S+) STATE' diff --git a/hotsos/defs/events/openstack/nova/external-events.yaml b/hotsos/defs/events/openstack/nova/external-events.yaml index b51dac728..6510424b5 100644 --- a/hotsos/defs/events/openstack/nova/external-events.yaml +++ b/hotsos/defs/events/openstack/nova/external-events.yaml @@ -8,6 +8,6 @@ input: # Supported events - https://docs.openstack.org/api-ref/compute/?expanded=run-events-detail#create-external-events-os-server-external-events events: network-changed: - expr: '[\d-]+ [\d:]+\.\d{3} .+\[instance: (\S+)\].+Received event (network-changed)-(\S+)\s+' + expr: '[\d-]+ [\d:]+\.\d{3} .+\[instance: (\S+)\].+Received event network-changed-(\S+)' network-vif-plugged: - expr: '[\d-]+ [\d:]+\.\d{3} .+\[instance: (\S+)\].+Preparing to wait for external event (network-vif-plugged)-(\S+)\s+' + expr: '[\d-]+ [\d:]+\.\d{3} .+\[instance: (\S+)\].+Preparing to wait for external event network-vif-plugged-(\S+)' diff --git a/hotsos/defs/events/openstack/octavia.yaml b/hotsos/defs/events/openstack/octavia.yaml index 6c272c374..024891ddc 100644 --- a/hotsos/defs/events/openstack/octavia.yaml +++ b/hotsos/defs/events/openstack/octavia.yaml @@ -3,7 +3,7 @@ octavia-health-manager: input: path: 'var/log/octavia/octavia-health-manager.log' amp-missed-heartbeats: - expr: '([\d-]+) ([\d:]+)\.\d{3} .+ Amphora (\S+) health message was processed too slowly:.+' + expr: '([\d-]+) ([\d:]+)\.\d{3} .+ Amphora (\S+) health message was processed too slowly:' hint: 'Amphora' lb-failover-auto: expr: '([\d-]+) ([\d:]+)\.\d{3} .+ Performing failover for amphora:\s+(.+)' diff --git a/hotsos/defs/events/openvswitch/ovn/errors-and-warnings.yaml b/hotsos/defs/events/openvswitch/ovn/errors-and-warnings.yaml index c7b16b07e..309b5c6db 100644 --- a/hotsos/defs/events/openvswitch/ovn/errors-and-warnings.yaml +++ b/hotsos/defs/events/openvswitch/ovn/errors-and-warnings.yaml @@ -3,19 +3,19 @@ ovsdb-server-sb: input: path: 'var/log/ovn/ovsdb-server-sb.log' hint: '(ERR|WARN|EMER)' - expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)\|.+' + expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)' ovsdb-server-nb: input: path: 'var/log/ovn/ovsdb-server-nb.log' hint: '(ERR|WARN|EMER)' - expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)\|.+' + expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)' ovn-northd: input: path: 'var/log/ovn/ovn-northd.log' hint: '(ERR|WARN|EMER)' - expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)\|.+' + expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)' ovn-controller: input: path: 'var/log/ovn/ovn-controller.log' hint: '(ERR|WARN|EMER)' - expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)\|.+' + expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)' diff --git a/hotsos/defs/events/openvswitch/ovs/errors-and-warnings.yaml b/hotsos/defs/events/openvswitch/ovs/errors-and-warnings.yaml index 8497d54db..f98a047c7 100644 --- a/hotsos/defs/events/openvswitch/ovs/errors-and-warnings.yaml +++ b/hotsos/defs/events/openvswitch/ovs/errors-and-warnings.yaml @@ -3,9 +3,9 @@ ovs-vswitchd: input: path: 'var/log/openvswitch/ovs-vswitchd.log' hint: '(ERR|WARN|EMER)' - expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)\|.+' + expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)' ovsdb-server: input: path: 'var/log/openvswitch/ovsdb-server.log' hint: '(ERR|WARN|EMER)' - expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)\|.+' + expr: '([\d-]+)T[\d:]+\.\d+Z.+\|(ERR|ERROR|WARN|EMER)' diff --git a/hotsos/defs/events/openvswitch/ovs/ovs-vswitchd.yaml b/hotsos/defs/events/openvswitch/ovs/ovs-vswitchd.yaml index c7b2647b4..d3b19e0f0 100644 --- a/hotsos/defs/events/openvswitch/ovs/ovs-vswitchd.yaml +++ b/hotsos/defs/events/openvswitch/ovs/ovs-vswitchd.yaml @@ -18,7 +18,5 @@ inactivity-probe: involuntary-context-switches: # we capture date and hour as subgroups expr: '([\d-]+)T(\d+):[\d:]+\.\d+Z.+\|timeval\|WARN\|context switches: 0 voluntary, (\d+) involuntary' - hint: timeval assertion-failures: expr: '([\d-]+)T[\d:]+\.\d+Z.+\|util.+\|EMER\|\S+: assertion ' - hint: assertion diff --git a/hotsos/plugin_extensions/openstack/agent/events.py b/hotsos/plugin_extensions/openstack/agent/events.py index 33c9e4736..fb734fc06 100644 --- a/hotsos/plugin_extensions/openstack/agent/events.py +++ b/hotsos/plugin_extensions/openstack/agent/events.py @@ -347,10 +347,9 @@ def __call__(self, event): return {'transitions': transitions}, 'keepalived' -class NeutronL3HAEventChecks(OpenstackEventHandlerBase): - event_group = 'neutron.ml2-routers' +class NeutronL3HAEventCheckJournalCtl(object): - def journalctl_args(self): + def args(self): """ Args callback for event cli command """ args = [] kwargs = {'unit': 'neutron-l3-agent'} @@ -359,6 +358,10 @@ def journalctl_args(self): return args, kwargs + +class NeutronL3HAEventChecks(OpenstackEventHandlerBase): + event_group = 'neutron.ml2-routers' + def __109_summary_neutron_l3ha(self): return self.final_event_results diff --git a/hotsos/plugin_extensions/openstack/nova_external_events.py b/hotsos/plugin_extensions/openstack/nova_external_events.py index 74d1b052d..c37a64871 100644 --- a/hotsos/plugin_extensions/openstack/nova_external_events.py +++ b/hotsos/plugin_extensions/openstack/nova_external_events.py @@ -36,14 +36,14 @@ def __call__(self, event): s = FileSearcher(constraint=c) for result in event.results: instance_id = result.get(1) - event_id = result.get(3) + event_id = result.get(2) result_path = event.searcher.resolve_source_id(result.source_id) events[event_id] = {'instance_id': instance_id, 'data_source': result_path} for stage in EXT_EVENT_META[event.name]['stages_keys']: - expr = (r".+\[instance: {}\]\s+{}\s.*\s?event\s+{}-{}.? " - ".+". + expr = (r"[\d-]+ [\d:]+\.\d{{3}} .+\[instance: {}\]" + r"\s+{}\s.*\s?event\s+{}-{}.?". format(instance_id, stage, event.name, event_id)) tag = "{}_{}_{}".format(instance_id, event_id, stage) sd = SearchDef(expr, tag, hint=event.name, diff --git a/tests/unit/test_openstack.py b/tests/unit/test_openstack.py index f237a1087..21b5beec8 100644 --- a/tests/unit/test_openstack.py +++ b/tests/unit/test_openstack.py @@ -978,10 +978,10 @@ def test_run_octavia_checks(self): '2022-03-09': 1}} } } + inst = agent.events.OctaviaAgentEventChecks() + inst.load_and_run() + actual = self.part_output_to_actual(inst.output) for section_key in ['amp-missed-heartbeats', 'lb-failovers']: - inst = agent.events.OctaviaAgentEventChecks() - inst.load_and_run() - actual = self.part_output_to_actual(inst.output) self.assertEqual(actual["octavia"][section_key], expected[section_key]) @@ -990,10 +990,10 @@ def test_run_octavia_checks(self): def test_run_apache_checks(self): expected = {'connection-refused': { '2021-10-26': {'127.0.0.1:8981': 3}}} + inst = agent.events.ApacheEventChecks() + inst.load_and_run() + actual = self.part_output_to_actual(inst.output) for section_key in ['connection-refused']: - inst = agent.events.ApacheEventChecks() - inst.load_and_run() - actual = self.part_output_to_actual(inst.output) self.assertEqual(actual['apache'][section_key], expected[section_key]) diff --git a/tests/unit/test_ycheck_events.py b/tests/unit/test_ycheck_events.py index 20a2037e8..8591deb95 100644 --- a/tests/unit/test_ycheck_events.py +++ b/tests/unit/test_ycheck_events.py @@ -51,15 +51,14 @@ """ EVENT_DEF_SIMPLE = r""" -myplugin: - myeventgroup: - input: - path: a/path - myeventsubgroup: - event1: - expr: 'event1' - event2: - expr: 'event2' +myeventgroup: + input: + path: a/path + myeventsubgroup: + event1: + expr: 'event1' + event2: + expr: 'event2' """ # noqa EVENT_DEF_MULTI_SEARCH = r""" @@ -206,7 +205,8 @@ def event_group(self): with self.assertRaises(EventCallbackNotFound): MyEventHandler().load_and_run() - @utils.create_data_root({'events/myplugin/mygroup.yaml': EVENT_DEF_SIMPLE}) + @utils.create_data_root({'events/myplugin/mygroup.yaml': EVENT_DEF_SIMPLE, + 'a/path': 'content'}) def test_events_filter_none(self): HotSOSConfig.plugin_yaml_defs = HotSOSConfig.data_root HotSOSConfig.plugin_name = 'myplugin' @@ -217,22 +217,22 @@ class MyEventHandler(EventHandlerBase): def event_group(self): return 'mygroup' - prefix = 'mygroup.myplugin.myeventgroup.myeventsubgroup' defs = {'myeventsubgroup': { - 'event1': { - 'passthrough': False, - 'sequence': None, - 'tag': prefix + '.event1.search'}, - 'event2': { - 'passthrough': False, - 'sequence': None, - 'tag': prefix + '.event2.search'}}} - self.assertEqual(MyEventHandler().event_definitions, defs) - - @utils.create_data_root({'events/myplugin/mygroup.yaml': EVENT_DEF_SIMPLE}) + 'event1': ('myplugin.mygroup.myeventgroup.myeventsubgroup.' + 'event1'), + 'event2': ('myplugin.mygroup.myeventgroup.myeventsubgroup.' + 'event2')}} + + handler = MyEventHandler() + self.assertEqual(handler.events, defs) + self.assertEqual(len(handler.searcher.catalog), 1) + + @utils.create_data_root({'events/myplugin/mygroup.yaml': EVENT_DEF_SIMPLE, + 'a/path': 'content'}) def test_events_filter_event2(self): HotSOSConfig.plugin_yaml_defs = HotSOSConfig.data_root - HotSOSConfig.event_filter = 'myplugin.myeventsubgroup.event2' + HotSOSConfig.event_filter = ('myplugin.mygroup.myeventgroup.' + 'myeventsubgroup.event2') HotSOSConfig.plugin_name = 'myplugin' class MyEventHandler(EventHandlerBase): @@ -241,15 +241,15 @@ class MyEventHandler(EventHandlerBase): def event_group(self): return 'mygroup' - prefix = 'mygroup.myplugin.myeventgroup.myeventsubgroup' defs = {'myeventsubgroup': { - 'event2': { - 'passthrough': False, - 'sequence': None, - 'tag': prefix + '.event2.search'}}} - self.assertEqual(MyEventHandler().event_definitions, defs) - - @utils.create_data_root({'events/myplugin/mygroup.yaml': EVENT_DEF_SIMPLE}) + 'event2': ('myplugin.mygroup.myeventgroup.myeventsubgroup.' + 'event2')}} + handler = MyEventHandler() + self.assertEqual(handler.events, defs) + self.assertEqual(len(handler.searcher.catalog), 1) + + @utils.create_data_root({'events/myplugin/mygroup.yaml': EVENT_DEF_SIMPLE, + 'a/path': 'content'}) def test_events_filter_nonexistent(self): HotSOSConfig.plugin_yaml_defs = HotSOSConfig.data_root HotSOSConfig.event_filter = 'blahblah' @@ -262,4 +262,6 @@ def event_group(self): return 'mygroup' defs = {} - self.assertEqual(MyEventHandler().event_definitions, defs) + handler = MyEventHandler() + self.assertEqual(handler.events, defs) + self.assertEqual(len(handler.searcher.catalog), 0) diff --git a/tests/unit/utils.py b/tests/unit/utils.py index 1d3efab74..9a7b00da8 100644 --- a/tests/unit/utils.py +++ b/tests/unit/utils.py @@ -12,6 +12,7 @@ from hotsos.core.issues import IssuesManager # disable for stestr otherwise output is much too verbose from hotsos.core.log import log, logging, LoggingManager +from hotsos.core.ycheck.events import EventsBase from hotsos.core.ycheck.scenarios import YScenarioChecker # Must be set prior to other imports @@ -451,6 +452,9 @@ def setUp(self): HotSOSConfig.debug_log_levels['searchkit'] = 'WARNING' LoggingManager().start(level=logging.WARNING) + # Always do this to avoid cross-pollution between tests. + EventsBase.reset_registry() + def _addDuration(self, *args, **kwargs): # For Python >= 3.12 """ Python 3.12 needs subclasses of unittest.TestCase to implement this in order to record times and execute any cleanup actions once