def test_one_nonsnmp_source(self, hostname, ipaddress, mode, config_cache, host_config, source): source = source(hostname, ipaddress, mode=mode) assert source.source_type is SourceType.HOST broker = ParsedSectionsBroker() update_host_sections( broker, make_nodes( config_cache, host_config, ipaddress, mode=mode, sources=[source], ), max_cachefile_age=0, host_config=host_config, fetcher_messages=[ FetcherMessage.from_raw_data( result.OK(source.default_raw_data), Snapshot.null(), source.fetcher_type, ), ], selected_sections=NO_SELECTION, ) assert len(broker) == 1 key = HostKey(hostname, ipaddress, source.source_type) assert key in broker section = broker[key] assert len(section.sections) == 1 assert section.sections[SectionName("section_name_%s" % hostname)] == [["section_content"]]
def test_multiple_sources_from_the_same_host( self, hostname, ipaddress, config_cache, host_config, ): sources = [ DSProgramDataSource( hostname, ipaddress, configurator=DSProgramConfigurator(hostname, ipaddress, template=""), ), TCPDataSource(hostname, ipaddress), ] mhs = make_host_sections( config_cache, host_config, ipaddress, sources=sources, max_cachefile_age=0, selected_raw_sections=None, ) assert len(mhs) == 1 key = HostKey(hostname, ipaddress, SourceType.HOST) assert key in mhs section = mhs[key] assert isinstance(section, AgentHostSections) assert len(section.sections) == 1 # yapf: disable assert (section.sections[SectionName("section_name_%s" % hostname)] == len(sources) * [["section_content"]])
def test_one_nonsnmp_source(self, hostname, ipaddress, mode, config_cache, host_config, source): source = source(hostname, ipaddress, mode=mode) assert source.source_type is SourceType.HOST mhs = MultiHostSections() update_host_sections( mhs, make_nodes( config_cache, host_config, ipaddress, mode=mode, sources=[source], ), max_cachefile_age=0, host_config=host_config, fetcher_messages=[ FetcherMessage.from_raw_data( result.OK(source.default_raw_data), Snapshot.null(), source.fetcher_type, ), ], ) assert len(mhs) == 1 key = HostKey(hostname, ipaddress, source.source_type) assert key in mhs section = mhs[key] assert isinstance(section, AgentHostSections) assert len(section.sections) == 1 assert section.sections[SectionName("section_name_%s" % hostname)] == [["section_content"]]
def test_multiple_sources_from_different_hosts(self, hostname, ipaddress, config_cache, host_config): sources = [ ProgramSource.ds(hostname + "0", ipaddress, template=""), TCPSource(hostname + "1", ipaddress), TCPSource(hostname + "2", ipaddress), ] nodes = make_nodes(config_cache, host_config, ipaddress, sources=sources) host_sections = _collect_host_sections( nodes=nodes, file_cache_max_age=file_cache.MaxAge.none(), fetcher_messages=[ FetcherMessage.from_raw_data( result.OK(source.default_raw_data), Snapshot.null(), source.fetcher_type, ) for _h, _i, sources in nodes for source in sources ], selected_sections=NO_SELECTION, )[0] assert len(host_sections) == 1 key = HostKey(hostname, ipaddress, SourceType.HOST) assert key in host_sections section = host_sections[key] assert len(section.sections) == len(sources) for source in sources: assert section.sections[SectionName( "section_name_%s" % source.hostname)] == [["section_content"]]
def test_update_with_store_and_persisting_raw_data(self, logger, monkeypatch): monkeypatch.setattr(time, "time", lambda c=itertools.count(1000, 50): next(c)) section_store = MockStore( "/dev/null", PersistedSections[AgentRawDataSection]({ SectionName("stored"): (0, 0, [["canned", "section"]]), }), logger=logger, ) raw_data = AgentRawData(b"<<<fresh:persist(10)>>>\nhello section") parser = AgentParser( HostName("testhost"), section_store, check_interval=0, keep_outdated=True, translation={}, encoding_fallback="ascii", simulation=False, logger=logger, ) ahs = parser.parse(raw_data, selection=NO_SELECTION) assert ahs.sections == { SectionName("fresh"): [["hello", "section"]], SectionName("stored"): [["canned", "section"]], } assert ahs.cache_info == { SectionName("stored"): (0, 0), SectionName("fresh"): (1000, -990), } assert ahs.piggybacked_raw_data == {} assert section_store.load() == PersistedSections[AgentRawDataSection]({ SectionName("stored"): (0, 0, [["canned", "section"]]), SectionName("fresh"): (1000, 10, [["hello", "section"]]), })
def test_gather_available_raw_section_names_defaults(backend, mocker): assert snmp_cache.get_oid_from_single_oid_cache(snmp_scan.OID_SYS_DESCR) assert snmp_cache.get_oid_from_single_oid_cache(snmp_scan.OID_SYS_OBJ) assert snmp_scan.gather_available_raw_section_names( [ SNMPScanSection(_.name, _.detect_spec) for _ in config.registered_snmp_sections.values() ], on_error="raise", do_snmp_scan=False, binary_host=False, backend=backend, ) == { SectionName("hr_mem"), SectionName("mgmt_snmp_info"), SectionName("mgmt_snmp_uptime"), SectionName("snmp_info"), SectionName("snmp_os"), SectionName("snmp_uptime"), }
def test_persist_option_and_persisted_sections(self, parser, store, mocker, monkeypatch): monkeypatch.setattr(time, "time", lambda c=itertools.count(1000, 50): next(c)) monkeypatch.setattr( SectionStore, "load", lambda self: PersistedSections[AgentRawDataSection]( { SectionName("persisted"): (42, 69, [["content"]]), } ), ) # Patch IO: monkeypatch.setattr(SectionStore, "store", lambda self, sections: None) raw_data = AgentRawData( b"\n".join( ( b"<<<section:persist(%i)>>>" % (1000 + 50), b"first line", b"second line", ) ) ) ahs = parser.parse(raw_data, selection=NO_SELECTION) assert ahs.sections == { SectionName("section"): [["first", "line"], ["second", "line"]], SectionName("persisted"): [["content"]], } assert ahs.cache_info == { SectionName("section"): (1000, 50), SectionName("persisted"): (42, 27), } assert ahs.piggybacked_raw_data == {} assert store.load() == PersistedSections[AgentRawDataSection]( { SectionName("persisted"): (42, 69, [["content"]]), } )
def test_update_with_persisted_and_store(self): section_store = MockStore(PersistedSections[SNMPRawDataSection]({ SectionName("stored"): (0, 0, [["old"]]), })) _new: SNMPRawDataSection = [["new"]] # For the type checker only raw_data: SNMPRawData = {SectionName("fresh"): _new} parser = SNMPParser( "testhost", section_store, check_intervals={}, keep_outdated=True, logger=logging.getLogger("test"), ) shs = parser.parse(raw_data, selection=NO_SELECTION) assert shs.sections == { SectionName("stored"): [["old"]], SectionName("fresh"): [["new"]], } assert shs.cache_info == {SectionName("stored"): (0, 0)} assert shs.piggybacked_raw_data == {} assert section_store.load() == { SectionName("stored"): (0, 0, [["old"]]), }
def _from_cache_file(self, raw_data: bytes) -> SNMPRawData: return { SectionName(k): v for k, v in ast.literal_eval(raw_data.decode("utf-8")).items() }
def test_parse_sap_hana_diskusage(info, expected_result): section_name = SectionName("sap_hana_diskusage") section_plugin = register.get_section_plugin(section_name) result = section_plugin.parse_function(info) assert result == expected_result
def raw_data(self): table: SNMPTable = [] raw_data: SNMPRawData = {SectionName("X"): table} return raw_data
def load(self) -> PersistedSections[TRawDataSection]: raw_sections_data = _store.load_object_from_file(self.path, default={}) return PersistedSections[TRawDataSection]( {SectionName(k): v for k, v in raw_sections_data.items()})
def _deserialize(data: bytes) -> SNMPRawData: try: return {SectionName(k): v for k, v in json.loads(data.decode("utf8")).items()} except json.JSONDecodeError: raise ValueError(repr(data))
class SNMPFetcher(Fetcher[SNMPRawData]): CPU_SECTIONS_WITHOUT_CPU_IN_NAME = { SectionName("brocade_sys"), SectionName("bvip_util"), } plugin_store: SNMPPluginStore = SNMPPluginStore() def __init__( self, file_cache: SNMPFileCache, *, sections: Dict[SectionName, SectionMeta], on_error: str, missing_sys_description: bool, do_status_data_inventory: bool, section_store_path: Union[Path, str], snmp_config: SNMPHostConfig, ) -> None: super().__init__(file_cache, logging.getLogger("cmk.helper.snmp")) self.sections: Final = sections self.on_error: Final = on_error self.missing_sys_description: Final = missing_sys_description self.do_status_data_inventory: Final = do_status_data_inventory self.snmp_config: Final = snmp_config self._section_store = SectionStore[SNMPRawDataSection]( section_store_path, logger=self._logger, ) self._backend = factory.backend(self.snmp_config, self._logger) @property def disabled_sections(self) -> Set[SectionName]: return {name for name, meta in self.sections.items() if meta.disabled} @property def checking_sections(self) -> Set[SectionName]: return {name for name, meta in self.sections.items() if meta.checking} @property def inventory_sections(self) -> Set[SectionName]: return { name for name, data in self.plugin_store.items() if data.inventory } @classmethod def _from_json(cls, serialized: Dict[str, Any]) -> 'SNMPFetcher': # The SNMPv3 configuration is represented by a tuple of different lengths (see # SNMPCredentials). Since we just deserialized from JSON, we have to convert the # list used by JSON back to a tuple. # SNMPv1/v2 communities are represented by a string: Leave it untouched. if isinstance(serialized["snmp_config"]["credentials"], list): serialized["snmp_config"]["credentials"] = tuple( serialized["snmp_config"]["credentials"]) return cls( file_cache=SNMPFileCache.from_json(serialized.pop("file_cache")), sections={ SectionName(s): SectionMeta.deserialize(m) for s, m in serialized["sections"].items() }, on_error=serialized["on_error"], missing_sys_description=serialized["missing_sys_description"], do_status_data_inventory=serialized["do_status_data_inventory"], section_store_path=serialized["section_store_path"], snmp_config=SNMPHostConfig.deserialize(serialized["snmp_config"]), ) def to_json(self) -> Dict[str, Any]: return { "file_cache": self.file_cache.to_json(), "sections": {str(s): m.serialize() for s, m in self.sections.items()}, "on_error": self.on_error, "missing_sys_description": self.missing_sys_description, "do_status_data_inventory": self.do_status_data_inventory, "section_store_path": str(self._section_store.path), "snmp_config": self.snmp_config.serialize(), } def open(self) -> None: verify_ipaddress(self.snmp_config.ipaddress) def close(self) -> None: pass def _detect(self, *, select_from: Set[SectionName]) -> Set[SectionName]: """Detect the applicable sections for the device in question""" return gather_available_raw_section_names( sections=[(name, self.plugin_store[name].detect_spec) for name in select_from], on_error=self.on_error, missing_sys_description=self.missing_sys_description, backend=self._backend, ) def _use_snmpwalk_cache(self, mode: Mode) -> bool: """Decide whether to load data from the SNMP walk cache The SNMP walk cache applies to individual OIDs that are marked as to-be-cached in the section definition plugins using `OIDCached`. """ return mode in (Mode.DISCOVERY, Mode.CHECKING) def _is_cache_read_enabled(self, mode: Mode) -> bool: """Decide whether to try to read data from cache Fetching for SNMP data is special in that we have to list the sections to fetch in advance, unlike for agent data, where we parse the data and see what we get. For discovery, we must not fetch the pre-configured sections (which are the ones in the cache), but all sections for which the detection spec evaluates to true, which can be many more. """ return mode is Mode.DISCOVERY def _is_cache_write_enabled(self, mode: Mode) -> bool: """Decide whether to write data to cache If we write the fetching result for SNMP, we also "override" the resulting sections for the next call that uses the cache. Since we use the cache for DISCOVERY only, we must only write it if we're dealing with the right sections for discovery. """ return mode is Mode.DISCOVERY def _get_selection(self, mode: Mode) -> Set[SectionName]: """Determine the sections fetched unconditionally (without detection)""" if mode is Mode.CHECKING: return self.checking_sections - self.disabled_sections if mode is Mode.FORCE_SECTIONS: return self.checking_sections return set() def _get_detected_sections(self, mode: Mode) -> Set[SectionName]: """Determine the sections fetched after successful detection""" if mode is Mode.INVENTORY or (mode is Mode.CHECKING and self.do_status_data_inventory): return self.inventory_sections - self.disabled_sections if mode is Mode.DISCOVERY: return set(self.plugin_store) - self.disabled_sections return set() def _fetch_from_io(self, mode: Mode) -> SNMPRawData: """Select the sections we need to fetch and do that Note: There still may be some fetching from cache involved if the fetch interval was overridden by the user. Detection: * Mode.DISCOVERY: In this straight forward case we must determine all applicable sections for the device in question. * Mode.INVENTORY There is no need to try to detect all sections: For the inventory we have a set of sections known to be relevant for inventory plugins, and we can restrict detection to those. * Mode.CHECKING Sections needed for checking are known without detection. If the status data inventory is enabled, we detect from the inventory sections; but not those, which are fetched for checking anyway. """ now = int(time.time()) persisted_sections = (self._section_store.load() if mode is Mode.CHECKING else PersistedSections[SNMPRawDataSection]({})) section_names = self._get_selection(mode) section_names |= self._detect( select_from=self._get_detected_sections(mode) - section_names) walk_cache = snmp_table.WalkCache(self._backend.hostname) if self._use_snmpwalk_cache(mode): walk_cache_msg = "SNMP walk cache is enabled: Use any locally cached information" walk_cache.load( trees=(tree for section_name in section_names for tree in self.plugin_store[section_name].trees), ) else: walk_cache_msg = "SNMP walk cache is disabled" fetched_data: MutableMapping[SectionName, SNMPRawDataSection] = {} for section_name in self._sort_section_names(section_names): try: _from, until, _section = persisted_sections[section_name] if now > until: raise LookupError(section_name) except LookupError: self._logger.debug("%s: Fetching data (%s)", section_name, walk_cache_msg) section = [ snmp_table.get_snmp_table( section_name=section_name, tree=tree, walk_cache=walk_cache, backend=self._backend, ) for tree in self.plugin_store[section_name].trees ] if any(section): fetched_data[section_name] = section walk_cache.save() return fetched_data @classmethod def _sort_section_names( cls, section_names: Iterable[SectionName], ) -> Iterable[SectionName]: # In former Checkmk versions (<=1.4.0) CPU check plugins were # checked before other check plugins like interface checks. # In Checkmk 1.5 the order was random and # interface sections where executed before CPU check plugins. # This lead to high CPU utilization sent by device. Thus we have # to re-order the section names. return sorted( section_names, key=lambda x: (not ('cpu' in str(x) or x in cls. CPU_SECTIONS_WITHOUT_CPU_IN_NAME), x), )
def deserialize(cls, serialized: Mapping[str, Any]) -> "SNMPPluginStore": return cls({ SectionName(k): SNMPPluginStoreItem.deserialize(v) for k, v in serialized["plugin_store"].items() })
def _execute_check_legacy_mode(multi_host_sections: MultiHostSections, hostname: HostName, ipaddress: Optional[HostAddress], service: Service) -> bool: legacy_check_plugin_name = config.legacy_check_plugin_names.get(service.check_plugin_name) if legacy_check_plugin_name is None: _submit_check_result(hostname, service.description, CHECK_NOT_IMPLEMENTED, None) return True check_function = config.check_info[legacy_check_plugin_name].get("check_function") if check_function is None: _submit_check_result(hostname, service.description, CHECK_NOT_IMPLEMENTED, None) return True # Make a bit of context information globally available, so that functions # called by checks know this context. check_api_utils.set_service has # already been called. item_state.set_item_state_prefix(str(service.check_plugin_name), service.item) section_name = legacy_check_plugin_name.split('.')[0] section_content = None mgmt_board_info = config.get_management_board_precedence(section_name, config.check_info) source_type = SourceType.MANAGEMENT if mgmt_board_info == LEGACY_MGMT_ONLY else SourceType.HOST try: section_content = multi_host_sections.get_section_content( HostKey(hostname, ipaddress, source_type), mgmt_board_info, section_name, for_discovery=False, cluster_node_keys=config.get_config_cache().get_clustered_service_node_keys( hostname, source_type, service.description, ip_lookup.lookup_ip_address, ), check_legacy_info=config.check_info, ) # TODO: Move this to a helper function if section_content is None: # No data for this check type return False # Call the actual check function item_state.reset_wrapped_counters() used_params = legacy_determine_check_params(service.parameters) raw_result = check_function(service.item, used_params, section_content) result = sanitize_check_result(raw_result) item_state.raise_counter_wrap() except item_state.MKCounterWrapped as e: # handle check implementations that do not yet support the # handling of wrapped counters via exception on their own. # Do not submit any check result in that case: console.verbose("%-20s PEND - Cannot compute check result: %s\n", ensure_str(service.description), e) # Don't submit to core - we're done. return True except MKTimeout: raise except Exception: if cmk.utils.debug.enabled(): raise result = 3, cmk.base.crash_reporting.create_check_crash_dump( hostname, service.check_plugin_name, { "item": service.item, "params": used_params, "section_content": section_content }, is_manual_check(hostname, service.id()), service.description, ), [] _submit_check_result( hostname, service.description, result, _legacy_determine_cache_info(multi_host_sections, SectionName(section_name)), ) return True
def test_cross_class_comparison_fails(): with pytest.raises(TypeError): _ = CheckPluginName("foo") == SectionName("foo")
def snmp_payload(self): table: SNMPTable = [] return SNMPResultMessage({SectionName("name"): table})
def get_section_content( self, host_key: HostKey, management_board_info: str, check_plugin_name: str, for_discovery: bool, *, cluster_node_keys: Optional[List[HostKey]] = None, check_legacy_info: Dict[str, Dict[str, Any]], ) -> Union[None, ParsedSectionContent, List[ParsedSectionContent]]: """Prepares the section_content construct for a Check_MK check on ANY host The section_content construct is then handed over to the check, inventory or discovery functions for doing their work. If the host is a cluster, the sections from all its nodes is merged together here. Optionally the node info is added to the nodes section content. It handles the whole data and cares about these aspects: a) Extract the section_content for the given check_plugin_name b) Adds node_info to the section_content (if check asks for this) c) Applies the parse function (if check has some) It can return an section_content construct or None when there is no section content for this check available. """ section_name = section_name_of(check_plugin_name) cache_key = (host_key, management_board_info, section_name, for_discovery, bool(cluster_node_keys)) try: return self._section_content_cache[cache_key] except KeyError: pass section_content = self._get_section_content( host_key._replace(source_type=SourceType.MANAGEMENT if management_board_info == LEGACY_MGMT_ONLY else SourceType.HOST), check_plugin_name, SectionName(section_name), for_discovery, cluster_node_keys=cluster_node_keys, check_legacy_info=check_legacy_info, ) # If we found nothing, see if we must check the management board: if (section_content is None and host_key.source_type is SourceType.HOST and management_board_info == LEGACY_HOST_PRECEDENCE): section_content = self._get_section_content( host_key._replace(source_type=SourceType.MANAGEMENT), check_plugin_name, SectionName(section_name), for_discovery, cluster_node_keys=cluster_node_keys, check_legacy_info=check_legacy_info, ) self._section_content_cache[cache_key] = section_content return section_content
def _get_aggregated_result( *, parsed_sections_broker: ParsedSectionsBroker, hostname: HostName, ipaddress: Optional[HostAddress], service: Service, used_params: LegacyCheckParameters, ) -> AggregatedResult: legacy_check_plugin_name = config.legacy_check_plugin_names.get(service.check_plugin_name) if legacy_check_plugin_name is None: return AggregatedResult( submit=True, data_received=True, result=CHECK_NOT_IMPLEMENTED, cache_info=None, ) check_function = config.check_info[legacy_check_plugin_name].get("check_function") if check_function is None: return AggregatedResult( submit=True, data_received=True, result=CHECK_NOT_IMPLEMENTED, cache_info=None, ) section_name = legacy_check_plugin_name.split('.')[0] main_check_info = config.check_info.get(section_name, {}) section_content = None multi_host_sections = _MultiHostSections(parsed_sections_broker) mgmt_board_info = main_check_info.get("management_board") or LEGACY_HOST_PRECEDENCE source_type = SourceType.MANAGEMENT if mgmt_board_info == LEGACY_MGMT_ONLY else SourceType.HOST try: section_content = multi_host_sections.get_section_content( HostKey(hostname, ipaddress, source_type), mgmt_board_info, section_name, for_discovery=False, cluster_node_keys=config.get_config_cache().get_clustered_service_node_keys( hostname, source_type, service.description, ), check_legacy_info=config.check_info, ) if section_content is None: # No data for this check type return AggregatedResult( submit=False, data_received=False, result=RECEIVED_NO_DATA, cache_info=None, ) # Call the actual check function item_state.reset_wrapped_counters() raw_result = check_function(service.item, used_params, section_content) result = _sanitize_check_result(raw_result) item_state.raise_counter_wrap() except item_state.MKCounterWrapped as exc: # handle check implementations that do not yet support the # handling of wrapped counters via exception on their own. # Do not submit any check result in that case: return AggregatedResult( submit=False, data_received=True, result=(0, f"Cannot compute check result: {exc}\n", []), cache_info=None, ) except MKTimeout: raise except Exception: if cmk.utils.debug.enabled(): raise result = 3, cmk.base.crash_reporting.create_check_crash_dump( host_name=hostname, service_name=service.description, plugin_name=service.check_plugin_name, plugin_kwargs={ "item": service.item, "params": used_params, "section_content": section_content }, is_manual=service.id() in check_table.get_check_table(hostname, skip_autochecks=True), ), [] return AggregatedResult( submit=True, data_received=True, result=result, cache_info=multi_host_sections.legacy_determine_cache_info(SectionName(section_name)), )
make_sources, ) from cmk.base.data_sources.agent import AgentHostSections from cmk.base.data_sources.host_sections import HostKey, MultiHostSections from cmk.base.data_sources.piggyback import PiggyBackDataSource from cmk.base.data_sources.programs import DSProgramConfigurator, DSProgramDataSource from cmk.base.data_sources.snmp import SNMPDataSource, SNMPHostSections from cmk.base.data_sources.tcp import TCPDataSource _TestSection = collections.namedtuple( "TestSection", "name, parsed_section_name, parse_function, supercedes", ) SECTION_ONE = _TestSection( SectionName("one"), ParsedSectionName("parsed"), lambda x: { "parsed_by": "one", "node": x[0][0] }, [], ) SECTION_TWO = _TestSection( SectionName("two"), ParsedSectionName("parsed"), lambda x: { "parsed_by": "two", "node": x[0][0] },
def create_snmp_section_plugin( *, name: str, detect_spec: SNMPDetectBaseType, fetch: Union[SNMPTree, List[SNMPTree]], parsed_section_name: Optional[str] = None, parse_function: Union[SimpleSNMPParseFunction, SNMPParseFunction, None] = None, host_label_function: Optional[HostLabelFunction] = None, host_label_default_parameters: Optional[Dict] = None, host_label_ruleset_name: Optional[str] = None, host_label_ruleset_type: RuleSetType = "merged", supersedes: Optional[List[str]] = None, module: Optional[str] = None, validate_creation_kwargs: bool = True, ) -> SNMPSectionPlugin: """Return an SNMPSectionPlugin object after validating and converting the arguments one by one For a detailed description of the parameters please refer to the exposed function in the 'register' namespace of the API. """ section_name = SectionName(name) # normalize to List[SNMPTree] tree_list = [fetch] if isinstance(fetch, SNMPTree) else fetch if validate_creation_kwargs: _validate_detect_spec(detect_spec) _validate_fetch_spec(tree_list) if parse_function is not None: needs_bytes = any(oid.encoding == "binary" for tree in tree_list for oid in tree.oids) _validate_parse_function( parse_function, expected_annotation=_create_parse_annotation( needs_bytes=needs_bytes, is_list=isinstance(fetch, list), ), ) if host_label_function is not None: _validate_host_label_kwargs( host_label_function=host_label_function, host_label_default_parameters=host_label_default_parameters, host_label_ruleset_name=host_label_ruleset_name, host_label_ruleset_type=host_label_ruleset_type, ) return SNMPSectionPlugin( name=section_name, parsed_section_name=ParsedSectionName( parsed_section_name if parsed_section_name else str(section_name)), parse_function=_create_snmp_parse_function(parse_function, isinstance(fetch, SNMPTree)), host_label_function=_create_host_label_function(host_label_function), host_label_default_parameters=host_label_default_parameters, host_label_ruleset_name=(None if host_label_ruleset_name is None else RuleSetName(host_label_ruleset_name)), host_label_ruleset_type=host_label_ruleset_type, supersedes=_create_supersedes(section_name, supersedes), detect_spec=detect_spec, trees=tree_list, module=module, )
class TestSectionMarker: def test_options_serialize_options(self): section_header = SectionMarker.from_headerline( b"<<<" + b":".join( ( b"section", b"cached(1,2)", b"encoding(ascii)", b"nostrip()", b"persist(42)", b"sep(124)", ) ) + b">>>" ) assert section_header == SectionMarker.from_headerline(str(section_header).encode("ascii")) def test_options_deserialize_defaults(self): section_header = SectionMarker.from_headerline(b"<<<section>>>") other_header = SectionMarker.from_headerline(str(section_header).encode("ascii")) assert section_header == other_header assert str(section_header) == str(other_header) @pytest.mark.parametrize( "headerline, section_name, section_options", [ ("norris", SectionName("norris"), {}), ("norris:chuck", SectionName("norris"), {"chuck": None}), ( "my_section:sep(0):cached(23,42)", SectionName("my_section"), {"sep": "0", "cached": "23,42"}, ), ("my.section:sep(0):cached(23,42)", None, {}), # invalid section name ("", None, {}), # invalid section name ], ) # yapf: disable def test_options_from_headerline(self, headerline, section_name, section_options): try: SectionMarker.from_headerline( f"<<<{headerline}>>>".encode("ascii") ) == ( # type: ignore[comparison-overlap] section_name, section_options, ) except ValueError: assert section_name is None def test_options_decode_values(self): section_header = SectionMarker.from_headerline( b"<<<" + b":".join( ( b"name", b"cached(1,2)", b"encoding(ascii)", b"nostrip()", b"persist(42)", b"sep(124)", ) ) + b">>>" ) assert section_header.name == SectionName("name") assert section_header.cached == (1, 2) assert section_header.encoding == "ascii" assert section_header.nostrip is True assert section_header.persist == 42 assert section_header.separator == "|" def test_options_decode_defaults(self): section_header = SectionMarker.from_headerline(b"<<<name>>>") assert section_header.name == SectionName("name") assert section_header.cached is None assert section_header.encoding == "utf-8" assert section_header.nostrip is False assert section_header.persist is None assert section_header.separator is None
def test_validate_supersedings_raise_self_superseding(): with pytest.raises(ValueError, match="cannot supersede myself"): section_plugins._validate_supersedings(SectionName("foo"), [SectionName("foo")])
def snmp_raw_data(self): table: SNMPTable = [[[6500337, 11822045]]] return {SectionName("snmp_uptime"): table}
Mode, ) from cmk.base.data_sources.agent import AgentHostSections from cmk.base.data_sources.host_sections import HostKey, MultiHostSections from cmk.base.data_sources.piggyback import PiggybackConfigurator from cmk.base.data_sources.programs import ProgramConfigurator from cmk.base.data_sources.snmp import SNMPConfigurator, SNMPHostSections, CachedSNMPDetector from cmk.base.data_sources.tcp import TCPConfigurator _TestSection = collections.namedtuple( "TestSection", "name, parsed_section_name, parse_function, supersedes", ) SECTION_ONE = _TestSection( SectionName("one"), ParsedSectionName("parsed"), lambda x: { "parsed_by": "one", "node": x[0][0] }, set(), ) SECTION_TWO = _TestSection( SectionName("two"), ParsedSectionName("parsed"), lambda x: { "parsed_by": "two", "node": x[0][0] },
def get_section_content( self, host_key: HostKey, management_board_info: str, check_plugin_name: CheckPluginNameStr, for_discovery: bool, service_description: Optional[ServiceName] = None ) -> FinalSectionContent: """Prepares the section_content construct for a Check_MK check on ANY host The section_content construct is then handed over to the check, inventory or discovery functions for doing their work. If the host is a cluster, the sections from all its nodes is merged together here. Optionally the node info is added to the nodes section content. It handles the whole data and cares about these aspects: a) Extract the section_content for the given check_plugin_name b) Adds node_info to the section_content (if check asks for this) c) Applies the parse function (if check has some) d) Adds extra_sections (if check asks for this) and also applies node_info and extra_section handling to this It can return an section_content construct or None when there is no section content for this check available. """ section_name = section_name_of(check_plugin_name) nodes_of_clustered_service = self._get_nodes_of_clustered_service( host_key.hostname, service_description) cache_key = (host_key, management_board_info, section_name, for_discovery, bool(nodes_of_clustered_service)) try: return self._section_content_cache[cache_key] except KeyError: pass section_content = self._get_section_content( host_key._replace( source_type=SourceType.MANAGEMENT if management_board_info == LEGACY_MGMT_ONLY else SourceType.HOST), check_plugin_name, SectionName(section_name), for_discovery, nodes_of_clustered_service, ) # If we found nothing, see if we must check the management board: if (section_content is None and host_key.source_type is SourceType.HOST and management_board_info == LEGACY_HOST_PRECEDENCE): section_content = self._get_section_content( host_key._replace(source_type=SourceType.MANAGEMENT), check_plugin_name, SectionName(section_name), for_discovery, nodes_of_clustered_service, ) self._section_content_cache[cache_key] = section_content return section_content
def test_get_host_sections_cluster(mode, monkeypatch, mocker): hostname = "testhost" hosts = { "host0": "10.0.0.0", "host1": "10.0.0.1", "host2": "10.0.0.2", } address = "1.2.3.4" tags = {"agent": "no-agent"} section_name = SectionName("test_section") config_cache = make_scenario(hostname, tags).apply(monkeypatch) host_config = config.HostConfig.make_host_config(hostname) def lookup_ip_address(host_config, family=None, for_mgmt_board=False): return hosts[host_config.hostname] def make_piggybacked_sections(hc): if hc.nodes == host_config.nodes: return {section_name: True} return {} def check(_, *args, **kwargs): return AgentHostSections(sections={section_name: [[str(section_name)]]}) monkeypatch.setattr( ip_lookup, "lookup_ip_address", lookup_ip_address, ) monkeypatch.setattr( _data_sources, "_make_piggybacked_sections", make_piggybacked_sections, ) monkeypatch.setattr( ABCChecker, "check", check, ) mocker.patch.object( cmk.utils.piggyback, "remove_source_status_file", autospec=True, ) mocker.patch.object( cmk.utils.piggyback, "_store_status_file_of", autospec=True, ) # Create a cluster host_config.nodes = list(hosts.keys()) mhs = MultiHostSections() update_host_sections( mhs, make_nodes( config_cache, host_config, address, mode=mode, sources=make_checkers(host_config, address, mode=mode), ), max_cachefile_age=host_config.max_cachefile_age, selected_raw_sections=None, host_config=host_config, ) assert len(mhs) == len(hosts) == 3 cmk.utils.piggyback._store_status_file_of.assert_not_called() # type: ignore[attr-defined] assert cmk.utils.piggyback.remove_source_status_file.call_count == 3 # type: ignore[attr-defined] for host, addr in hosts.items(): remove_source_status_file = cmk.utils.piggyback.remove_source_status_file remove_source_status_file.assert_any_call(host) # type: ignore[attr-defined] key = HostKey(host, addr, SourceType.HOST) assert key in mhs section = mhs[key] assert len(section.sections) == 1 assert next(iter(section.sections)) == section_name assert not section.cache_info assert not section.piggybacked_raw_data assert not section.persisted_sections
def raw_data(self, file_cache): if isinstance(file_cache, DefaultAgentFileCache): return AgentRawData(b"<<<check_mk>>>\nagent raw data") assert isinstance(file_cache, SNMPFileCache) table: Sequence[SNMPTable] = [] return {SectionName("X"): table}
def test_get_host_sections_cluster(monkeypatch, mocker): hostname = "testhost" hosts = { "host0": "10.0.0.0", "host1": "10.0.0.1", "host2": "10.0.0.2", } address = "1.2.3.4" tags = {"agent": "no-agent"} section_name = SectionName("test_section") config_cache = make_scenario(hostname, tags).apply(monkeypatch) host_config = config.HostConfig.make_host_config(hostname) def fake_lookup_ip_address(host_config, family=None): return hosts[host_config.hostname] def check(_, *args, **kwargs): return result.OK(AgentHostSections(sections={section_name: [[str(section_name)]]})) monkeypatch.setattr( config, "lookup_ip_address", fake_lookup_ip_address, ) monkeypatch.setattr( Source, "parse", check, ) mocker.patch.object( cmk.utils.piggyback, "remove_source_status_file", autospec=True, ) mocker.patch.object( cmk.utils.piggyback, "_store_status_file_of", autospec=True, ) # Create a cluster host_config.nodes = list(hosts.keys()) nodes = make_nodes( config_cache, host_config, address, sources=make_sources(host_config, address), ) host_sections = _collect_host_sections( nodes=nodes, file_cache_max_age=host_config.max_cachefile_age, fetcher_messages=[ FetcherMessage.from_raw_data( result.OK(source.default_raw_data), Snapshot.null(), source.fetcher_type, ) for _h, _i, sources in nodes for source in sources ], selected_sections=NO_SELECTION, )[0] assert len(host_sections) == len(hosts) == 3 cmk.utils.piggyback._store_status_file_of.assert_not_called() # type: ignore[attr-defined] assert cmk.utils.piggyback.remove_source_status_file.call_count == 3 # type: ignore[attr-defined] for host, addr in hosts.items(): remove_source_status_file = cmk.utils.piggyback.remove_source_status_file remove_source_status_file.assert_any_call(host) # type: ignore[attr-defined] key = HostKey(host, addr, SourceType.HOST) assert key in host_sections section = host_sections[key] assert len(section.sections) == 1 assert next(iter(section.sections)) == section_name assert not section.cache_info assert not section.piggybacked_raw_data