From 72dd400439a9107282a84821eb98ff6b43937954 Mon Sep 17 00:00:00 2001 From: Edward Hope-Morley Date: Thu, 2 May 2024 08:44:02 +0100 Subject: [PATCH] Group all event searches To avoid searching the same file more than once we now gather all searches from all events and executed all searches prior to executing event callbacks so that the results are shared with all and avoiding any repeated searches of the same file. Also removes some extraneous regex from some search patterns. Related-To: #850 --- hotsos/client.py | 5 +- hotsos/core/plugintools.py | 2 + hotsos/core/ycheck/events.py | 300 ++++++++++++++---- hotsos/defs/events/openstack/apparmor.yaml | 4 +- .../events/openstack/neutron/ml2-routers.yaml | 2 +- .../openstack/nova/external-events.yaml | 4 +- hotsos/defs/events/openstack/octavia.yaml | 2 +- .../openvswitch/ovn/errors-and-warnings.yaml | 8 +- .../openvswitch/ovs/errors-and-warnings.yaml | 4 +- .../events/openvswitch/ovs/ovs-vswitchd.yaml | 2 - .../openstack/agent/events.py | 9 +- .../openstack/nova_external_events.py | 6 +- tests/unit/test_openstack.py | 12 +- tests/unit/test_ycheck_events.py | 66 ++-- tests/unit/utils.py | 4 + 15 files changed, 305 insertions(+), 125 deletions(-) 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