diff --git a/.werks/17275.md b/.werks/17275.md new file mode 100644 index 00000000000..410e9c9868c --- /dev/null +++ b/.werks/17275.md @@ -0,0 +1,17 @@ +[//]: # (werk v2) +# Introduce cmk/customer host label + +key | value +---------- | --- +date | 2024-09-05T13:06:17+00:00 +version | 2.4.0b1 +class | feature +edition | cme +component | multisite +level | 1 +compatible | yes + +The MSP edition will now create a `cmk/customer` label for each +host. + +This label can be used to configure filters based on customers. diff --git a/cmk/base/config.py b/cmk/base/config.py index 6acac5592a6..565843d2218 100644 --- a/cmk/base/config.py +++ b/cmk/base/config.py @@ -26,6 +26,7 @@ from enum import Enum from importlib.util import MAGIC_NUMBER as _MAGIC_NUMBER from pathlib import Path +from types import ModuleType from typing import ( Any, AnyStr, @@ -61,7 +62,7 @@ from cmk.utils.hostaddress import HostAddress, HostName, Hosts from cmk.utils.http_proxy_config import http_proxy_config_from_user_setting, HTTPProxyConfig from cmk.utils.ip_lookup import IPStackConfig -from cmk.utils.labels import Labels, LabelSources +from cmk.utils.labels import BuiltinHostLabelsStore, Labels, LabelSources from cmk.utils.legacy_check_api import LegacyCheckDefinition from cmk.utils.log import console from cmk.utils.macros import replace_macros_in_str @@ -140,6 +141,14 @@ from cmk.server_side_calls import v1 as server_side_calls_api from cmk.server_side_calls_backend.config_processing import PreprocessingResult +cme_labels: ModuleType | None +try: + from cmk.utils.cme import ( # type: ignore[import-untyped, no-redef, unused-ignore] + labels as cme_labels, + ) +except ModuleNotFoundError: + cme_labels = None + try: from cmk.base.cee.rrd import RRDObjectConfig except ModuleNotFoundError: @@ -1931,6 +1940,7 @@ def initialize(self) -> ConfigCache: clusters_of=self._clusters_of_cache, nodes_of=self._nodes_cache, all_configured_hosts=list(set(self.hosts_config)), + builtin_host_labels_store=BuiltinHostLabelsStore(), debug_matching_stats=ruleset_matching_stats, ) @@ -3984,12 +3994,11 @@ def reset_config_cache() -> ConfigCache: def _create_config_cache() -> ConfigCache: """create clean config cache""" - cache_class = ( - ConfigCache - if cmk_version.edition(cmk.utils.paths.omd_root) is cmk_version.Edition.CRE - else CEEConfigCache - ) - return cache_class() + if cmk_version.edition(cmk.utils.paths.omd_root) is cmk_version.Edition.CRE: + return ConfigCache() + if cmk_version.edition(cmk.utils.paths.omd_root) is cmk_version.Edition.CME: + return CMEConfigCache() + return CEEConfigCache() # TODO(au): Find a way to retreive the matchtype_information directly from the @@ -4700,3 +4709,13 @@ def get_nested_rules(prefix: str, rulesets: dict[str, list]) -> Mapping[int, str id(service_tag_rules): "service_tag_rules", id(management_bulkwalk_hosts): "management_bulkwalk_hosts", } + + +class CMEConfigCache(CEEConfigCache): + def initialize(self) -> ConfigCache: + super().initialize() + if cme_labels: + self.ruleset_matcher.ruleset_optimizer.set_builtin_host_labels_store( + cme_labels.CMEBuiltinHostLabelsStore() + ) + return self diff --git a/cmk/utils/rulesets/ruleset_matcher.py b/cmk/utils/rulesets/ruleset_matcher.py index de10fc5d1a4..2659053f96b 100644 --- a/cmk/utils/rulesets/ruleset_matcher.py +++ b/cmk/utils/rulesets/ruleset_matcher.py @@ -185,6 +185,7 @@ def __init__( all_configured_hosts: Sequence[HostName], clusters_of: Mapping[HostName, Sequence[HostName]], nodes_of: Mapping[HostName, Sequence[HostName]], + builtin_host_labels_store: BuiltinHostLabelsStore, debug_matching_stats: bool = False, ) -> None: super().__init__() @@ -197,6 +198,7 @@ def __init__( all_configured_hosts, clusters_of, nodes_of, + builtin_host_labels_store, debug_matching_stats, ) self.labels_of_host = self.ruleset_optimizer.labels_of_host @@ -504,6 +506,7 @@ def __init__( all_configured_hosts: Sequence[HostName], clusters_of: Mapping[HostName, Sequence[HostName]], nodes_of: Mapping[HostName, Sequence[HostName]], + builtin_host_labels_store: BuiltinHostLabelsStore, debug_matching_stats: bool = False, ) -> None: super().__init__() @@ -514,6 +517,7 @@ def __init__( self._host_paths = host_paths self._clusters_of = clusters_of self._nodes_of = nodes_of + self._builtin_host_labels_store = builtin_host_labels_store self._all_configured_hosts = all_configured_hosts @@ -547,6 +551,11 @@ def __init__( self._debug_matching_stats = debug_matching_stats self.matching_stats: dict[int, HostRulesetMatchingStats | ServiceRulesetMatchingStats] = {} + def set_builtin_host_labels_store( + self, builtin_host_labels_store: BuiltinHostLabelsStore + ) -> None: + self._builtin_host_labels_store = builtin_host_labels_store + def clear_ruleset_caches(self) -> None: self.__host_ruleset_cache.clear() self.__service_ruleset_cache.clear() @@ -965,7 +974,7 @@ def labels_of_host(self, hostname: HostName) -> Labels: labels.update(self._discovered_labels_of_host(hostname)) labels.update(self._ruleset_labels_of_host(hostname)) labels.update(self._label_manager.explicit_host_labels.get(hostname, {})) - labels.update(RulesetOptimizer._builtin_labels_of_host()) + labels.update(self._builtin_labels_of_host()) return self.__labels_of_host.setdefault(hostname, labels) def label_sources_of_host(self, hostname: HostName) -> LabelSources: @@ -974,7 +983,7 @@ def label_sources_of_host(self, hostname: HostName) -> LabelSources: _get_host_labels()""" labels: LabelSources = {} labels.update({k: "discovered" for k in self._discovered_labels_of_host(hostname).keys()}) - labels.update({k: "discovered" for k in RulesetOptimizer._builtin_labels_of_host()}) + labels.update({k: "discovered" for k in self._builtin_labels_of_host()}) labels.update({k: "ruleset" for k in self._ruleset_labels_of_host(hostname)}) labels.update( { @@ -997,10 +1006,10 @@ def _discovered_labels_of_host(self, hostname: HostName) -> Labels: ) return {l.name: l.value for l in host_labels} - @staticmethod - def _builtin_labels_of_host() -> Labels: + def _builtin_labels_of_host(self) -> Labels: return { - label_id: label["value"] for label_id, label in BuiltinHostLabelsStore().load().items() + label_id: label["value"] + for label_id, label in self._builtin_host_labels_store.load().items() } def labels_of_service(self, hostname: HostName, service_desc: ServiceName) -> Labels: diff --git a/tests/unit/cmk/base/test_config.py b/tests/unit/cmk/base/test_config.py index 9d22114f0da..2b41fb897a8 100644 --- a/tests/unit/cmk/base/test_config.py +++ b/tests/unit/cmk/base/test_config.py @@ -20,6 +20,7 @@ import cmk.ccc.version as cmk_version from cmk.ccc.exceptions import MKGeneralException +from cmk.ccc.version import Edition, edition import cmk.utils.paths from cmk.utils.config_path import VersionedConfigPath @@ -2030,6 +2031,9 @@ def test_host_label_rules_default() -> None: def test_labels(monkeypatch: MonkeyPatch) -> None: + additional_labels = {} + if edition(cmk.utils.paths.omd_root) is Edition.CME: + additional_labels = {"cmk/customer": {"value": "provider", "source": "discovered"}} test_host = HostName("test-host") xyz_host = HostName("xyz") @@ -2056,22 +2060,27 @@ def test_labels(monkeypatch: MonkeyPatch) -> None: ts.add_host(xyz_host) config_cache = ts.apply(monkeypatch) - assert config_cache.labels(xyz_host) == {"cmk/site": "NO_SITE"} + assert config_cache.labels(xyz_host) == { + "cmk/site": "NO_SITE", + } | {k: v["value"] for k, v in additional_labels.items()} assert config_cache.labels(test_host) == { "cmk/site": "NO_SITE", "explicit": "ding", "from-rule": "rule1", "from-rule2": "rule2", - } + } | {k: v["value"] for k, v in additional_labels.items()} assert config_cache.label_sources(test_host) == { "cmk/site": "discovered", "explicit": "explicit", "from-rule": "ruleset", "from-rule2": "ruleset", - } + } | {k: v["source"] for k, v in additional_labels.items()} def test_host_labels_of_host_discovered_labels(monkeypatch: MonkeyPatch, tmp_path: Path) -> None: + additional_labels = {} + if edition(cmk.utils.paths.omd_root) is Edition.CME: + additional_labels = {"cmk/customer": {"value": "provider", "source": "discovered"}} test_host = HostName("test-host") ts = Scenario() ts.add_host(test_host) @@ -2085,11 +2094,11 @@ def test_host_labels_of_host_discovered_labels(monkeypatch: MonkeyPatch, tmp_pat assert config_cache.labels(test_host) == { "cmk/site": "NO_SITE", "äzzzz": "eeeeez", - } + } | {k: v["value"] for k, v in additional_labels.items()} assert config_cache.label_sources(test_host) == { "cmk/site": "discovered", "äzzzz": "discovered", - } + } | {k: v["source"] for k, v in additional_labels.items()} def test_service_label_rules_default() -> None: diff --git a/tests/unit/cmk/base/test_core_config.py b/tests/unit/cmk/base/test_core_config.py index a9ab4438d9e..3618d5a2109 100644 --- a/tests/unit/cmk/base/test_core_config.py +++ b/tests/unit/cmk/base/test_core_config.py @@ -148,6 +148,8 @@ def test_get_host_attributes(monkeypatch: MonkeyPatch) -> None: if cmk_version.edition(cmk.utils.paths.omd_root) is cmk_version.Edition.CME: expected_attrs["_CUSTOMER"] = "provider" + expected_attrs["__LABEL_cmk/customer"] = "provider" + expected_attrs["__LABELSOURCE_cmk/customer"] = "discovered" assert ( config_cache.get_host_attributes( diff --git a/tests/unit/cmk/base/test_core_nagios.py b/tests/unit/cmk/base/test_core_nagios.py index 8a175096d75..e869c46dc2a 100644 --- a/tests/unit/cmk/base/test_core_nagios.py +++ b/tests/unit/cmk/base/test_core_nagios.py @@ -261,6 +261,8 @@ def test_create_nagios_host_spec( if cmk_version.edition(paths.omd_root) is cmk_version.Edition.CME: result = result.copy() result["_CUSTOMER"] = "provider" + result["__LABELSOURCE_cmk/customer"] = "discovered" + result["__LABEL_cmk/customer"] = "provider" ts = Scenario() ts.add_host(HostName("localhost")) diff --git a/tests/unit/cmk/base/test_unit_automations.py b/tests/unit/cmk/base/test_unit_automations.py index 195511666df..fbd5aa7d533 100644 --- a/tests/unit/cmk/base/test_unit_automations.py +++ b/tests/unit/cmk/base/test_unit_automations.py @@ -10,9 +10,11 @@ from tests.testlib.base import Scenario import cmk.ccc.version as cmk_version +from cmk.ccc.version import Edition, edition from cmk.utils import paths from cmk.utils.hostaddress import HostName +from cmk.utils.labels import LabelSource from cmk.utils.rulesets.ruleset_matcher import RuleSpec from cmk.automations.results import AnalyseHostResult, GetServicesLabelsResult @@ -67,6 +69,12 @@ def test_registered_automations() -> None: def test_analyse_host(monkeypatch: MonkeyPatch) -> None: + additional_labels: dict[str, str] = {} + additional_label_sources: dict[str, LabelSource] = {} + if edition(paths.omd_root) is Edition.CME: + additional_labels = {"cmk/customer": "provider"} + additional_label_sources = {"cmk/customer": "discovered"} + automation = automations.AutomationAnalyseHost() ts = Scenario() @@ -81,9 +89,17 @@ def test_analyse_host(monkeypatch: MonkeyPatch) -> None: ) ts.apply(monkeypatch) + label_sources: dict[str, LabelSource] = { + "cmk/site": "discovered", + "explicit": "explicit", + } assert automation.execute(["test-host"]) == AnalyseHostResult( - label_sources={"cmk/site": "discovered", "explicit": "explicit"}, - labels={"cmk/site": "NO_SITE", "explicit": "ding"}, + label_sources=label_sources | additional_label_sources, + labels={ + "cmk/site": "NO_SITE", + "explicit": "ding", + } + | additional_labels, ) diff --git a/tests/unit/cmk/utils/rulesets/test_ruleset_matcher.py b/tests/unit/cmk/utils/rulesets/test_ruleset_matcher.py index c5db2939885..62bfc0d24b3 100644 --- a/tests/unit/cmk/utils/rulesets/test_ruleset_matcher.py +++ b/tests/unit/cmk/utils/rulesets/test_ruleset_matcher.py @@ -14,6 +14,7 @@ from tests.testlib.base import Scenario from cmk.utils.hostaddress import HostName +from cmk.utils.labels import BuiltinHostLabelsStore from cmk.utils.rulesets.ruleset_matcher import ( LabelManager, matches_tag_condition, @@ -151,6 +152,7 @@ def test_ruleset_matcher_get_host_values_labels( all_configured_hosts=[HostName("host1"), HostName("host2")], clusters_of={}, nodes_of={}, + builtin_host_labels_store=BuiltinHostLabelsStore(), ) assert list(matcher.get_host_values(hostname, ruleset=host_label_ruleset)) == expected_result @@ -188,6 +190,7 @@ def test_labels_of_service(monkeypatch: MonkeyPatch) -> None: all_configured_hosts=[test_host, xyz_host], clusters_of={}, nodes_of={}, + builtin_host_labels_store=BuiltinHostLabelsStore(), ) assert not ruleset_matcher.labels_of_service(xyz_host, "CPU load") @@ -220,6 +223,7 @@ def test_labels_of_service_discovered_labels() -> None: all_configured_hosts=[test_host], clusters_of={}, nodes_of={}, + builtin_host_labels_store=BuiltinHostLabelsStore(), ) service_description = "CPU load" @@ -256,6 +260,7 @@ def test_basic_get_host_values() -> None: ], clusters_of={}, nodes_of={}, + builtin_host_labels_store=BuiltinHostLabelsStore(), ) assert not list(matcher.get_host_values(HostName("abc"), ruleset=ruleset)) @@ -296,6 +301,7 @@ def test_basic_get_host_values_subfolders() -> None: ], clusters_of={}, nodes_of={}, + builtin_host_labels_store=BuiltinHostLabelsStore(), ) assert not list(matcher.get_host_values(HostName("xyz"), ruleset=ruleset)) @@ -371,6 +377,7 @@ def test_basic_host_ruleset_get_merged_dict_values() -> None: ], clusters_of={}, nodes_of={}, + builtin_host_labels_store=BuiltinHostLabelsStore(), ) assert not matcher.get_host_merged_dict(HostName("abc"), ruleset=dict_ruleset) @@ -446,6 +453,7 @@ def test_basic_host_ruleset_get_host_bool_value() -> None: ], clusters_of={}, nodes_of={}, + builtin_host_labels_store=BuiltinHostLabelsStore(), ) assert matcher.get_host_bool_value(HostName("abc"), ruleset=binary_ruleset) is False @@ -568,6 +576,7 @@ def test_ruleset_matcher_get_host_values_tags( ], clusters_of={}, nodes_of={}, + builtin_host_labels_store=BuiltinHostLabelsStore(), ) assert list(matcher.get_host_values(hostname, ruleset=tag_ruleset)) == expected_result